• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22/* A REPL library that you can include in your own code to get a runtime
23 * interface to your program.
24 *
25 *   const repl = require("repl");
26 *   // start repl on stdin
27 *   repl.start("prompt> ");
28 *
29 *   // listen for unix socket connections and start repl on them
30 *   net.createServer(function(socket) {
31 *     repl.start("node via Unix socket> ", socket);
32 *   }).listen("/tmp/node-repl-sock");
33 *
34 *   // listen for TCP socket connections and start repl on them
35 *   net.createServer(function(socket) {
36 *     repl.start("node via TCP socket> ", socket);
37 *   }).listen(5001);
38 *
39 *   // expose foo to repl context
40 *   repl.start("node > ").context.foo = "stdin is fun";
41 */
42
43'use strict';
44
45const {
46  ArrayPrototypeFilter,
47  ArrayPrototypeFindIndex,
48  ArrayPrototypeForEach,
49  ArrayPrototypeIncludes,
50  ArrayPrototypeJoin,
51  ArrayPrototypeMap,
52  ArrayPrototypePop,
53  ArrayPrototypePush,
54  ArrayPrototypePushApply,
55  ArrayPrototypeReverse,
56  ArrayPrototypeShift,
57  ArrayPrototypeSlice,
58  ArrayPrototypeSome,
59  ArrayPrototypeSort,
60  ArrayPrototypeSplice,
61  ArrayPrototypeUnshift,
62  Boolean,
63  Error,
64  FunctionPrototypeBind,
65  MathMaxApply,
66  NumberIsNaN,
67  NumberParseFloat,
68  ObjectAssign,
69  ObjectCreate,
70  ObjectDefineProperty,
71  ObjectGetOwnPropertyDescriptor,
72  ObjectGetOwnPropertyNames,
73  ObjectGetPrototypeOf,
74  ObjectKeys,
75  ObjectSetPrototypeOf,
76  Promise,
77  ReflectApply,
78  RegExp,
79  RegExpPrototypeExec,
80  SafePromiseRace,
81  SafeSet,
82  SafeWeakSet,
83  StringPrototypeCharAt,
84  StringPrototypeCodePointAt,
85  StringPrototypeEndsWith,
86  StringPrototypeIncludes,
87  StringPrototypeRepeat,
88  StringPrototypeSlice,
89  StringPrototypeSplit,
90  StringPrototypeStartsWith,
91  StringPrototypeTrim,
92  StringPrototypeTrimLeft,
93  StringPrototypeToLocaleLowerCase,
94  Symbol,
95  SyntaxError,
96  SyntaxErrorPrototype,
97  globalThis,
98} = primordials;
99
100const { BuiltinModule } = require('internal/bootstrap/loaders');
101const {
102  makeRequireFunction,
103  addBuiltinLibsToObject,
104} = require('internal/modules/cjs/helpers');
105const {
106  isIdentifierStart,
107  isIdentifierChar,
108} = require('internal/deps/acorn/acorn/dist/acorn');
109const {
110  decorateErrorStack,
111  isError,
112  deprecate,
113  SideEffectFreeRegExpPrototypeSymbolReplace,
114  SideEffectFreeRegExpPrototypeSymbolSplit,
115} = require('internal/util');
116const { inspect } = require('internal/util/inspect');
117const vm = require('vm');
118const path = require('path');
119const fs = require('fs');
120const { Interface } = require('readline');
121const {
122  commonPrefix,
123} = require('internal/readline/utils');
124const { Console } = require('console');
125const { shouldColorize } = require('internal/util/colors');
126const CJSModule = require('internal/modules/cjs/loader').Module;
127let _builtinLibs = ArrayPrototypeFilter(
128  CJSModule.builtinModules,
129  (e) => !StringPrototypeStartsWith(e, '_'),
130);
131const nodeSchemeBuiltinLibs = ArrayPrototypeMap(
132  _builtinLibs, (lib) => `node:${lib}`);
133ArrayPrototypeForEach(
134  BuiltinModule.getSchemeOnlyModuleNames(),
135  (lib) => ArrayPrototypePush(nodeSchemeBuiltinLibs, `node:${lib}`),
136);
137const domain = require('domain');
138let debug = require('internal/util/debuglog').debuglog('repl', (fn) => {
139  debug = fn;
140});
141const {
142  codes: {
143    ERR_CANNOT_WATCH_SIGINT,
144    ERR_INVALID_REPL_EVAL_CONFIG,
145    ERR_INVALID_REPL_INPUT,
146    ERR_SCRIPT_EXECUTION_INTERRUPTED,
147  },
148  isErrorStackTraceLimitWritable,
149  overrideStackTrace,
150} = require('internal/errors');
151const { sendInspectorCommand } = require('internal/util/inspector');
152const { getOptionValue } = require('internal/options');
153const {
154  validateFunction,
155  validateObject,
156} = require('internal/validators');
157const experimentalREPLAwait = getOptionValue(
158  '--experimental-repl-await',
159);
160const pendingDeprecation = getOptionValue('--pending-deprecation');
161const {
162  REPL_MODE_SLOPPY,
163  REPL_MODE_STRICT,
164  isRecoverableError,
165  kStandaloneREPL,
166  setupPreview,
167  setupReverseSearch,
168} = require('internal/repl/utils');
169const {
170  constants: {
171    ALL_PROPERTIES,
172    SKIP_SYMBOLS,
173  },
174  getOwnNonIndexProperties,
175} = internalBinding('util');
176const {
177  startSigintWatchdog,
178  stopSigintWatchdog,
179} = internalBinding('contextify');
180
181const history = require('internal/repl/history');
182const {
183  extensionFormatMap,
184  legacyExtensionFormatMap,
185} = require('internal/modules/esm/formats');
186
187let nextREPLResourceNumber = 1;
188// This prevents v8 code cache from getting confused and using a different
189// cache from a resource of the same name
190function getREPLResourceName() {
191  return `REPL${nextREPLResourceNumber++}`;
192}
193
194// Lazy-loaded.
195let processTopLevelAwait;
196
197const globalBuiltins =
198  new SafeSet(vm.runInNewContext('Object.getOwnPropertyNames(globalThis)'));
199
200const parentModule = module;
201const domainSet = new SafeWeakSet();
202
203const kBufferedCommandSymbol = Symbol('bufferedCommand');
204const kContextId = Symbol('contextId');
205
206let addedNewListener = false;
207
208try {
209  // Hack for require.resolve("./relative") to work properly.
210  module.filename = path.resolve('repl');
211} catch {
212  // path.resolve('repl') fails when the current working directory has been
213  // deleted.  Fall back to the directory name of the (absolute) executable
214  // path.  It's not really correct but what are the alternatives?
215  const dirname = path.dirname(process.execPath);
216  module.filename = path.resolve(dirname, 'repl');
217}
218
219// Hack for repl require to work properly with node_modules folders
220module.paths = CJSModule._nodeModulePaths(module.filename);
221
222// This is the default "writer" value, if none is passed in the REPL options,
223// and it can be overridden by custom print functions, such as `probe` or
224// `eyes.js`.
225const writer = (obj) => inspect(obj, writer.options);
226writer.options = { ...inspect.defaultOptions, showProxy: true };
227
228function REPLServer(prompt,
229                    stream,
230                    eval_,
231                    useGlobal,
232                    ignoreUndefined,
233                    replMode) {
234  if (!(this instanceof REPLServer)) {
235    return new REPLServer(prompt,
236                          stream,
237                          eval_,
238                          useGlobal,
239                          ignoreUndefined,
240                          replMode);
241  }
242
243  let options;
244  if (prompt !== null && typeof prompt === 'object') {
245    // An options object was given.
246    options = { ...prompt };
247    stream = options.stream || options.socket;
248    eval_ = options.eval;
249    useGlobal = options.useGlobal;
250    ignoreUndefined = options.ignoreUndefined;
251    prompt = options.prompt;
252    replMode = options.replMode;
253  } else {
254    options = {};
255  }
256
257  if (!options.input && !options.output) {
258    // Legacy API, passing a 'stream'/'socket' option.
259    if (!stream) {
260      // Use stdin and stdout as the default streams if none were given.
261      stream = process;
262    }
263    // We're given a duplex readable/writable Stream, like a `net.Socket`
264    // or a custom object with 2 streams, or the `process` object.
265    options.input = stream.stdin || stream;
266    options.output = stream.stdout || stream;
267  }
268
269  if (options.terminal === undefined) {
270    options.terminal = options.output.isTTY;
271  }
272  options.terminal = !!options.terminal;
273
274  if (options.terminal && options.useColors === undefined) {
275    // If possible, check if stdout supports colors or not.
276    options.useColors = shouldColorize(options.output) || process.env.NODE_DISABLE_COLORS === undefined;
277  }
278
279  // TODO(devsnek): Add a test case for custom eval functions.
280  const preview = options.terminal &&
281    (options.preview !== undefined ? !!options.preview : !eval_);
282
283  ObjectDefineProperty(this, 'inputStream', {
284    __proto__: null,
285    get: pendingDeprecation ?
286      deprecate(() => this.input,
287                'repl.inputStream and repl.outputStream are deprecated. ' +
288                  'Use repl.input and repl.output instead',
289                'DEP0141') :
290      () => this.input,
291    set: pendingDeprecation ?
292      deprecate((val) => this.input = val,
293                'repl.inputStream and repl.outputStream are deprecated. ' +
294                  'Use repl.input and repl.output instead',
295                'DEP0141') :
296      (val) => this.input = val,
297    enumerable: false,
298    configurable: true,
299  });
300  ObjectDefineProperty(this, 'outputStream', {
301    __proto__: null,
302    get: pendingDeprecation ?
303      deprecate(() => this.output,
304                'repl.inputStream and repl.outputStream are deprecated. ' +
305                  'Use repl.input and repl.output instead',
306                'DEP0141') :
307      () => this.output,
308    set: pendingDeprecation ?
309      deprecate((val) => this.output = val,
310                'repl.inputStream and repl.outputStream are deprecated. ' +
311                  'Use repl.input and repl.output instead',
312                'DEP0141') :
313      (val) => this.output = val,
314    enumerable: false,
315    configurable: true,
316  });
317
318  this.allowBlockingCompletions = !!options.allowBlockingCompletions;
319  this.useColors = !!options.useColors;
320  this._domain = options.domain || domain.create();
321  this.useGlobal = !!useGlobal;
322  this.ignoreUndefined = !!ignoreUndefined;
323  this.replMode = replMode || module.exports.REPL_MODE_SLOPPY;
324  this.underscoreAssigned = false;
325  this.last = undefined;
326  this.underscoreErrAssigned = false;
327  this.lastError = undefined;
328  this.breakEvalOnSigint = !!options.breakEvalOnSigint;
329  this.editorMode = false;
330  // Context id for use with the inspector protocol.
331  this[kContextId] = undefined;
332
333  if (this.breakEvalOnSigint && eval_) {
334    // Allowing this would not reflect user expectations.
335    // breakEvalOnSigint affects only the behavior of the default eval().
336    throw new ERR_INVALID_REPL_EVAL_CONFIG();
337  }
338
339  if (options[kStandaloneREPL]) {
340    // It is possible to introspect the running REPL accessing this variable
341    // from inside the REPL. This is useful for anyone working on the REPL.
342    module.exports.repl = this;
343  } else if (!addedNewListener) {
344    // Add this listener only once and use a WeakSet that contains the REPLs
345    // domains. Otherwise we'd have to add a single listener to each REPL
346    // instance and that could trigger the `MaxListenersExceededWarning`.
347    process.prependListener('newListener', (event, listener) => {
348      if (event === 'uncaughtException' &&
349          process.domain &&
350          listener.name !== 'domainUncaughtExceptionClear' &&
351          domainSet.has(process.domain)) {
352        // Throw an error so that the event will not be added and the current
353        // domain takes over. That way the user is notified about the error
354        // and the current code evaluation is stopped, just as any other code
355        // that contains an error.
356        throw new ERR_INVALID_REPL_INPUT(
357          'Listeners for `uncaughtException` cannot be used in the REPL');
358      }
359    });
360    addedNewListener = true;
361  }
362
363  domainSet.add(this._domain);
364
365  const savedRegExMatches = ['', '', '', '', '', '', '', '', '', ''];
366  const sep = '\u0000\u0000\u0000';
367  const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
368                                  `${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
369                                  `${sep}(.*)$`);
370
371  eval_ = eval_ || defaultEval;
372
373  const self = this;
374
375  // Pause taking in new input, and store the keys in a buffer.
376  const pausedBuffer = [];
377  let paused = false;
378  function pause() {
379    paused = true;
380  }
381
382  function unpause() {
383    if (!paused) return;
384    paused = false;
385    let entry;
386    const tmpCompletionEnabled = self.isCompletionEnabled;
387    while ((entry = ArrayPrototypeShift(pausedBuffer)) !== undefined) {
388      const { 0: type, 1: payload, 2: isCompletionEnabled } = entry;
389      switch (type) {
390        case 'key': {
391          const { 0: d, 1: key } = payload;
392          self.isCompletionEnabled = isCompletionEnabled;
393          self._ttyWrite(d, key);
394          break;
395        }
396        case 'close':
397          self.emit('exit');
398          break;
399      }
400      if (paused) {
401        break;
402      }
403    }
404    self.isCompletionEnabled = tmpCompletionEnabled;
405  }
406
407  function defaultEval(code, context, file, cb) {
408    const asyncESM = require('internal/process/esm_loader');
409
410    let result, script, wrappedErr;
411    let err = null;
412    let wrappedCmd = false;
413    let awaitPromise = false;
414    const input = code;
415
416    // It's confusing for `{ a : 1 }` to be interpreted as a block
417    // statement rather than an object literal.  So, we first try
418    // to wrap it in parentheses, so that it will be interpreted as
419    // an expression.  Note that if the above condition changes,
420    // lib/internal/repl/utils.js needs to be changed to match.
421    if (RegExpPrototypeExec(/^\s*{/, code) !== null &&
422        RegExpPrototypeExec(/;\s*$/, code) === null) {
423      code = `(${StringPrototypeTrim(code)})\n`;
424      wrappedCmd = true;
425    }
426
427    // `experimentalREPLAwait` is set to true by default.
428    // Shall be false in case `--no-experimental-repl-await` flag is used.
429    if (experimentalREPLAwait && StringPrototypeIncludes(code, 'await')) {
430      if (processTopLevelAwait === undefined) {
431        ({ processTopLevelAwait } = require('internal/repl/await'));
432      }
433
434      try {
435        const potentialWrappedCode = processTopLevelAwait(code);
436        if (potentialWrappedCode !== null) {
437          code = potentialWrappedCode;
438          wrappedCmd = true;
439          awaitPromise = true;
440        }
441      } catch (e) {
442        let recoverableError = false;
443        if (e.name === 'SyntaxError') {
444          let parentURL;
445          try {
446            const { pathToFileURL } = require('url');
447            // Adding `/repl` prevents dynamic imports from loading relative
448            // to the parent of `process.cwd()`.
449            parentURL = pathToFileURL(path.join(process.cwd(), 'repl')).href;
450          } catch {
451            // Continue regardless of error.
452          }
453
454          // Remove all "await"s and attempt running the script
455          // in order to detect if error is truly non recoverable
456          const fallbackCode = SideEffectFreeRegExpPrototypeSymbolReplace(/\bawait\b/g, code, '');
457          try {
458            vm.createScript(fallbackCode, {
459              filename: file,
460              displayErrors: true,
461              importModuleDynamically: (specifier, _, importAssertions) => {
462                return asyncESM.esmLoader.import(specifier, parentURL,
463                                                 importAssertions);
464              },
465            });
466          } catch (fallbackError) {
467            if (isRecoverableError(fallbackError, fallbackCode)) {
468              recoverableError = true;
469              err = new Recoverable(e);
470            }
471          }
472        }
473        if (!recoverableError) {
474          decorateErrorStack(e);
475          err = e;
476        }
477      }
478    }
479
480    // First, create the Script object to check the syntax
481    if (code === '\n')
482      return cb(null);
483
484    if (err === null) {
485      let parentURL;
486      try {
487        const { pathToFileURL } = require('url');
488        // Adding `/repl` prevents dynamic imports from loading relative
489        // to the parent of `process.cwd()`.
490        parentURL = pathToFileURL(path.join(process.cwd(), 'repl')).href;
491      } catch {
492        // Continue regardless of error.
493      }
494      while (true) {
495        try {
496          if (self.replMode === module.exports.REPL_MODE_STRICT &&
497              RegExpPrototypeExec(/^\s*$/, code) === null) {
498            // "void 0" keeps the repl from returning "use strict" as the result
499            // value for statements and declarations that don't return a value.
500            code = `'use strict'; void 0;\n${code}`;
501          }
502          script = vm.createScript(code, {
503            filename: file,
504            displayErrors: true,
505            importModuleDynamically: (specifier, _, importAssertions) => {
506              return asyncESM.esmLoader.import(specifier, parentURL,
507                                               importAssertions);
508            },
509          });
510        } catch (e) {
511          debug('parse error %j', code, e);
512          if (wrappedCmd) {
513            // Unwrap and try again
514            wrappedCmd = false;
515            awaitPromise = false;
516            code = input;
517            wrappedErr = e;
518            continue;
519          }
520          // Preserve original error for wrapped command
521          const error = wrappedErr || e;
522          if (isRecoverableError(error, code))
523            err = new Recoverable(error);
524          else
525            err = error;
526        }
527        break;
528      }
529    }
530
531    // This will set the values from `savedRegExMatches` to corresponding
532    // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9`
533    RegExpPrototypeExec(regExMatcher,
534                        ArrayPrototypeJoin(savedRegExMatches, sep));
535
536    let finished = false;
537    function finishExecution(err, result) {
538      if (finished) return;
539      finished = true;
540
541      // After executing the current expression, store the values of RegExp
542      // predefined properties back in `savedRegExMatches`
543      for (let idx = 1; idx < savedRegExMatches.length; idx += 1) {
544        savedRegExMatches[idx] = RegExp[`$${idx}`];
545      }
546
547      cb(err, result);
548    }
549
550    if (!err) {
551      // Unset raw mode during evaluation so that Ctrl+C raises a signal.
552      let previouslyInRawMode;
553      if (self.breakEvalOnSigint) {
554        // Start the SIGINT watchdog before entering raw mode so that a very
555        // quick Ctrl+C doesn't lead to aborting the process completely.
556        if (!startSigintWatchdog())
557          throw new ERR_CANNOT_WATCH_SIGINT();
558        previouslyInRawMode = self._setRawMode(false);
559      }
560
561      try {
562        try {
563          const scriptOptions = {
564            displayErrors: false,
565            breakOnSigint: self.breakEvalOnSigint,
566          };
567
568          if (self.useGlobal) {
569            result = script.runInThisContext(scriptOptions);
570          } else {
571            result = script.runInContext(context, scriptOptions);
572          }
573        } finally {
574          if (self.breakEvalOnSigint) {
575            // Reset terminal mode to its previous value.
576            self._setRawMode(previouslyInRawMode);
577
578            // Returns true if there were pending SIGINTs *after* the script
579            // has terminated without being interrupted itself.
580            if (stopSigintWatchdog()) {
581              self.emit('SIGINT');
582            }
583          }
584        }
585      } catch (e) {
586        err = e;
587
588        if (process.domain) {
589          debug('not recoverable, send to domain');
590          process.domain.emit('error', err);
591          process.domain.exit();
592          return;
593        }
594      }
595
596      if (awaitPromise && !err) {
597        let sigintListener;
598        pause();
599        let promise = result;
600        if (self.breakEvalOnSigint) {
601          const interrupt = new Promise((resolve, reject) => {
602            sigintListener = () => {
603              const tmp = Error.stackTraceLimit;
604              if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
605              const err = new ERR_SCRIPT_EXECUTION_INTERRUPTED();
606              if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmp;
607              reject(err);
608            };
609            prioritizedSigintQueue.add(sigintListener);
610          });
611          promise = SafePromiseRace([promise, interrupt]);
612        }
613
614        (async () => {
615          try {
616            const result = (await promise)?.value;
617            finishExecution(null, result);
618          } catch (err) {
619            if (err && process.domain) {
620              debug('not recoverable, send to domain');
621              process.domain.emit('error', err);
622              process.domain.exit();
623              return;
624            }
625            finishExecution(err);
626          } finally {
627            // Remove prioritized SIGINT listener if it was not called.
628            prioritizedSigintQueue.delete(sigintListener);
629            unpause();
630          }
631        })();
632      }
633    }
634
635    if (!awaitPromise || err) {
636      finishExecution(err, result);
637    }
638  }
639
640  self.eval = self._domain.bind(eval_);
641
642  self._domain.on('error', function debugDomainError(e) {
643    debug('domain error');
644    let errStack = '';
645
646    if (typeof e === 'object' && e !== null) {
647      overrideStackTrace.set(e, (error, stackFrames) => {
648        let frames;
649        if (typeof stackFrames === 'object') {
650          // Search from the bottom of the call stack to
651          // find the first frame with a null function name
652          const idx = ArrayPrototypeFindIndex(
653            ArrayPrototypeReverse(stackFrames),
654            (frame) => frame.getFunctionName() === null,
655          );
656          // If found, get rid of it and everything below it
657          frames = ArrayPrototypeSplice(stackFrames, idx + 1);
658        } else {
659          frames = stackFrames;
660        }
661        // FIXME(devsnek): this is inconsistent with the checks
662        // that the real prepareStackTrace dispatch uses in
663        // lib/internal/errors.js.
664        if (typeof Error.prepareStackTrace === 'function') {
665          return Error.prepareStackTrace(error, frames);
666        }
667        ArrayPrototypePush(frames, error);
668        return ArrayPrototypeJoin(ArrayPrototypeReverse(frames), '\n    at ');
669      });
670      decorateErrorStack(e);
671
672      if (e.domainThrown) {
673        delete e.domain;
674        delete e.domainThrown;
675      }
676
677      if (isError(e)) {
678        if (e.stack) {
679          if (e.name === 'SyntaxError') {
680            // Remove stack trace.
681            e.stack = SideEffectFreeRegExpPrototypeSymbolReplace(
682              /^\s+at\s.*\n?/gm,
683              SideEffectFreeRegExpPrototypeSymbolReplace(/^REPL\d+:\d+\r?\n/, e.stack, ''),
684              '');
685            const importErrorStr = 'Cannot use import statement outside a ' +
686              'module';
687            if (StringPrototypeIncludes(e.message, importErrorStr)) {
688              e.message = 'Cannot use import statement inside the Node.js ' +
689                'REPL, alternatively use dynamic import';
690              e.stack = SideEffectFreeRegExpPrototypeSymbolReplace(
691                /SyntaxError:.*\n/,
692                e.stack,
693                `SyntaxError: ${e.message}\n`);
694            }
695          } else if (self.replMode === module.exports.REPL_MODE_STRICT) {
696            e.stack = SideEffectFreeRegExpPrototypeSymbolReplace(
697              /(\s+at\s+REPL\d+:)(\d+)/,
698              e.stack,
699              (_, pre, line) => pre + (line - 1),
700            );
701          }
702        }
703        errStack = self.writer(e);
704
705        // Remove one line error braces to keep the old style in place.
706        if (errStack[0] === '[' && errStack[errStack.length - 1] === ']') {
707          errStack = StringPrototypeSlice(errStack, 1, -1);
708        }
709      }
710    }
711
712    if (!self.underscoreErrAssigned) {
713      self.lastError = e;
714    }
715
716    if (options[kStandaloneREPL] &&
717        process.listenerCount('uncaughtException') !== 0) {
718      process.nextTick(() => {
719        process.emit('uncaughtException', e);
720        self.clearBufferedCommand();
721        self.lines.level = [];
722        self.displayPrompt();
723      });
724    } else {
725      if (errStack === '') {
726        errStack = self.writer(e);
727      }
728      const lines = SideEffectFreeRegExpPrototypeSymbolSplit(/(?<=\n)/, errStack);
729      let matched = false;
730
731      errStack = '';
732      ArrayPrototypeForEach(lines, (line) => {
733        if (!matched &&
734            RegExpPrototypeExec(/^\[?([A-Z][a-z0-9_]*)*Error/, line) !== null) {
735          errStack += writer.options.breakLength >= line.length ?
736            `Uncaught ${line}` :
737            `Uncaught:\n${line}`;
738          matched = true;
739        } else {
740          errStack += line;
741        }
742      });
743      if (!matched) {
744        const ln = lines.length === 1 ? ' ' : ':\n';
745        errStack = `Uncaught${ln}${errStack}`;
746      }
747      // Normalize line endings.
748      errStack += StringPrototypeEndsWith(errStack, '\n') ? '' : '\n';
749      self.output.write(errStack);
750      self.clearBufferedCommand();
751      self.lines.level = [];
752      self.displayPrompt();
753    }
754  });
755
756  self.clearBufferedCommand();
757
758  function completer(text, cb) {
759    ReflectApply(complete, self,
760                 [text, self.editorMode ? self.completeOnEditorMode(cb) : cb]);
761  }
762
763  ReflectApply(Interface, this, [{
764    input: options.input,
765    output: options.output,
766    completer: options.completer || completer,
767    terminal: options.terminal,
768    historySize: options.historySize,
769    prompt,
770  }]);
771
772  self.resetContext();
773
774  this.commands = ObjectCreate(null);
775  defineDefaultCommands(this);
776
777  // Figure out which "writer" function to use
778  self.writer = options.writer || module.exports.writer;
779
780  if (self.writer === writer) {
781    // Conditionally turn on ANSI coloring.
782    writer.options.colors = self.useColors;
783
784    if (options[kStandaloneREPL]) {
785      ObjectDefineProperty(inspect, 'replDefaults', {
786        __proto__: null,
787        get() {
788          return writer.options;
789        },
790        set(options) {
791          validateObject(options, 'options');
792          return ObjectAssign(writer.options, options);
793        },
794        enumerable: true,
795        configurable: true,
796      });
797    }
798  }
799
800  function _parseREPLKeyword(keyword, rest) {
801    const cmd = this.commands[keyword];
802    if (cmd) {
803      ReflectApply(cmd.action, this, [rest]);
804      return true;
805    }
806    return false;
807  }
808
809  self.on('close', function emitExit() {
810    if (paused) {
811      ArrayPrototypePush(pausedBuffer, ['close']);
812      return;
813    }
814    self.emit('exit');
815  });
816
817  let sawSIGINT = false;
818  let sawCtrlD = false;
819  const prioritizedSigintQueue = new SafeSet();
820  self.on('SIGINT', function onSigInt() {
821    if (prioritizedSigintQueue.size > 0) {
822      for (const task of prioritizedSigintQueue) {
823        task();
824      }
825      return;
826    }
827
828    const empty = self.line.length === 0;
829    self.clearLine();
830    _turnOffEditorMode(self);
831
832    const cmd = self[kBufferedCommandSymbol];
833    if (!(cmd && cmd.length > 0) && empty) {
834      if (sawSIGINT) {
835        self.close();
836        sawSIGINT = false;
837        return;
838      }
839      self.output.write(
840        '(To exit, press Ctrl+C again or Ctrl+D or type .exit)\n',
841      );
842      sawSIGINT = true;
843    } else {
844      sawSIGINT = false;
845    }
846
847    self.clearBufferedCommand();
848    self.lines.level = [];
849    self.displayPrompt();
850  });
851
852  self.on('line', function onLine(cmd) {
853    debug('line %j', cmd);
854    cmd = cmd || '';
855    sawSIGINT = false;
856
857    if (self.editorMode) {
858      self[kBufferedCommandSymbol] += cmd + '\n';
859
860      // code alignment
861      const matches = self._sawKeyPress ?
862        RegExpPrototypeExec(/^\s+/, cmd) : null;
863      if (matches) {
864        const prefix = matches[0];
865        self.write(prefix);
866        self.line = prefix;
867        self.cursor = prefix.length;
868      }
869      ReflectApply(_memory, self, [cmd]);
870      return;
871    }
872
873    // Check REPL keywords and empty lines against a trimmed line input.
874    const trimmedCmd = StringPrototypeTrim(cmd);
875
876    // Check to see if a REPL keyword was used. If it returns true,
877    // display next prompt and return.
878    if (trimmedCmd) {
879      if (StringPrototypeCharAt(trimmedCmd, 0) === '.' &&
880          StringPrototypeCharAt(trimmedCmd, 1) !== '.' &&
881          NumberIsNaN(NumberParseFloat(trimmedCmd))) {
882        const matches = RegExpPrototypeExec(/^\.([^\s]+)\s*(.*)$/, trimmedCmd);
883        const keyword = matches && matches[1];
884        const rest = matches && matches[2];
885        if (ReflectApply(_parseREPLKeyword, self, [keyword, rest]) === true) {
886          return;
887        }
888        if (!self[kBufferedCommandSymbol]) {
889          self.output.write('Invalid REPL keyword\n');
890          finish(null);
891          return;
892        }
893      }
894    }
895
896    const evalCmd = self[kBufferedCommandSymbol] + cmd + '\n';
897
898    debug('eval %j', evalCmd);
899    self.eval(evalCmd, self.context, getREPLResourceName(), finish);
900
901    function finish(e, ret) {
902      debug('finish', e, ret);
903      ReflectApply(_memory, self, [cmd]);
904
905      if (e && !self[kBufferedCommandSymbol] &&
906          StringPrototypeStartsWith(StringPrototypeTrim(cmd), 'npm ')) {
907        self.output.write('npm should be run outside of the ' +
908                                'Node.js REPL, in your normal shell.\n' +
909                                '(Press Ctrl+D to exit.)\n');
910        self.displayPrompt();
911        return;
912      }
913
914      // If error was SyntaxError and not JSON.parse error
915      if (e) {
916        if (e instanceof Recoverable && !sawCtrlD) {
917          // Start buffering data like that:
918          // {
919          // ...  x: 1
920          // ... }
921          self[kBufferedCommandSymbol] += cmd + '\n';
922          self.displayPrompt();
923          return;
924        }
925        self._domain.emit('error', e.err || e);
926      }
927
928      // Clear buffer if no SyntaxErrors
929      self.clearBufferedCommand();
930      sawCtrlD = false;
931
932      // If we got any output - print it (if no error)
933      if (!e &&
934          // When an invalid REPL command is used, error message is printed
935          // immediately. We don't have to print anything else. So, only when
936          // the second argument to this function is there, print it.
937          arguments.length === 2 &&
938          (!self.ignoreUndefined || ret !== undefined)) {
939        if (!self.underscoreAssigned) {
940          self.last = ret;
941        }
942        self.output.write(self.writer(ret) + '\n');
943      }
944
945      // Display prompt again (unless we already did by emitting the 'error'
946      // event on the domain instance).
947      if (!e) {
948        self.displayPrompt();
949      }
950    }
951  });
952
953  self.on('SIGCONT', function onSigCont() {
954    if (self.editorMode) {
955      self.output.write(`${self._initialPrompt}.editor\n`);
956      self.output.write(
957        '// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n');
958      self.output.write(`${self[kBufferedCommandSymbol]}\n`);
959      self.prompt(true);
960    } else {
961      self.displayPrompt(true);
962    }
963  });
964
965  const { reverseSearch } = setupReverseSearch(this);
966
967  const {
968    clearPreview,
969    showPreview,
970  } = setupPreview(
971    this,
972    kContextId,
973    kBufferedCommandSymbol,
974    preview,
975  );
976
977  // Wrap readline tty to enable editor mode and pausing.
978  const ttyWrite = FunctionPrototypeBind(self._ttyWrite, self);
979  self._ttyWrite = (d, key) => {
980    key = key || {};
981    if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) {
982      ArrayPrototypePush(pausedBuffer,
983                         ['key', [d, key], self.isCompletionEnabled]);
984      return;
985    }
986    if (!self.editorMode || !self.terminal) {
987      // Before exiting, make sure to clear the line.
988      if (key.ctrl && key.name === 'd' &&
989          self.cursor === 0 && self.line.length === 0) {
990        self.clearLine();
991      }
992      clearPreview(key);
993      if (!reverseSearch(d, key)) {
994        ttyWrite(d, key);
995        const showCompletionPreview = key.name !== 'escape';
996        showPreview(showCompletionPreview);
997      }
998      return;
999    }
1000
1001    // Editor mode
1002    if (key.ctrl && !key.shift) {
1003      switch (key.name) {
1004        // TODO(BridgeAR): There should not be a special mode necessary for full
1005        // multiline support.
1006        case 'd': // End editor mode
1007          _turnOffEditorMode(self);
1008          sawCtrlD = true;
1009          ttyWrite(d, { name: 'return' });
1010          break;
1011        case 'n': // Override next history item
1012        case 'p': // Override previous history item
1013          break;
1014        default:
1015          ttyWrite(d, key);
1016      }
1017    } else {
1018      switch (key.name) {
1019        case 'up':   // Override previous history item
1020        case 'down': // Override next history item
1021          break;
1022        case 'tab':
1023          // Prevent double tab behavior
1024          self._previousKey = null;
1025          ttyWrite(d, key);
1026          break;
1027        default:
1028          ttyWrite(d, key);
1029      }
1030    }
1031  };
1032
1033  self.displayPrompt();
1034}
1035ObjectSetPrototypeOf(REPLServer.prototype, Interface.prototype);
1036ObjectSetPrototypeOf(REPLServer, Interface);
1037
1038// Prompt is a string to print on each line for the prompt,
1039// source is a stream to use for I/O, defaulting to stdin/stdout.
1040function start(prompt, source, eval_, useGlobal, ignoreUndefined, replMode) {
1041  return new REPLServer(
1042    prompt, source, eval_, useGlobal, ignoreUndefined, replMode);
1043}
1044
1045REPLServer.prototype.setupHistory = function setupHistory(historyFile, cb) {
1046  history(this, historyFile, cb);
1047};
1048
1049REPLServer.prototype.clearBufferedCommand = function clearBufferedCommand() {
1050  this[kBufferedCommandSymbol] = '';
1051};
1052
1053REPLServer.prototype.close = function close() {
1054  if (this.terminal && this._flushing && !this._closingOnFlush) {
1055    this._closingOnFlush = true;
1056    this.once('flushHistory', () =>
1057      ReflectApply(Interface.prototype.close, this, []),
1058    );
1059
1060    return;
1061  }
1062  process.nextTick(() =>
1063    ReflectApply(Interface.prototype.close, this, []),
1064  );
1065};
1066
1067REPLServer.prototype.createContext = function() {
1068  let context;
1069  if (this.useGlobal) {
1070    context = globalThis;
1071  } else {
1072    sendInspectorCommand((session) => {
1073      session.post('Runtime.enable');
1074      session.once('Runtime.executionContextCreated', ({ params }) => {
1075        this[kContextId] = params.context.id;
1076      });
1077      context = vm.createContext();
1078      session.post('Runtime.disable');
1079    }, () => {
1080      context = vm.createContext();
1081    });
1082    ArrayPrototypeForEach(ObjectGetOwnPropertyNames(globalThis), (name) => {
1083      // Only set properties that do not already exist as a global builtin.
1084      if (!globalBuiltins.has(name)) {
1085        ObjectDefineProperty(context, name,
1086                             {
1087                               __proto__: null,
1088                               ...ObjectGetOwnPropertyDescriptor(globalThis, name),
1089                             });
1090      }
1091    });
1092    context.global = context;
1093    const _console = new Console(this.output);
1094    ObjectDefineProperty(context, 'console', {
1095      __proto__: null,
1096      configurable: true,
1097      writable: true,
1098      value: _console,
1099    });
1100  }
1101
1102  const replModule = new CJSModule('<repl>');
1103  replModule.paths = CJSModule._resolveLookupPaths('<repl>', parentModule);
1104
1105  ObjectDefineProperty(context, 'module', {
1106    __proto__: null,
1107    configurable: true,
1108    writable: true,
1109    value: replModule,
1110  });
1111  ObjectDefineProperty(context, 'require', {
1112    __proto__: null,
1113    configurable: true,
1114    writable: true,
1115    value: makeRequireFunction(replModule),
1116  });
1117
1118  addBuiltinLibsToObject(context, '<REPL>');
1119
1120  return context;
1121};
1122
1123REPLServer.prototype.resetContext = function() {
1124  this.context = this.createContext();
1125  this.underscoreAssigned = false;
1126  this.underscoreErrAssigned = false;
1127  // TODO(BridgeAR): Deprecate the lines.
1128  this.lines = [];
1129  this.lines.level = [];
1130
1131  ObjectDefineProperty(this.context, '_', {
1132    __proto__: null,
1133    configurable: true,
1134    get: () => this.last,
1135    set: (value) => {
1136      this.last = value;
1137      if (!this.underscoreAssigned) {
1138        this.underscoreAssigned = true;
1139        this.output.write('Expression assignment to _ now disabled.\n');
1140      }
1141    },
1142  });
1143
1144  ObjectDefineProperty(this.context, '_error', {
1145    __proto__: null,
1146    configurable: true,
1147    get: () => this.lastError,
1148    set: (value) => {
1149      this.lastError = value;
1150      if (!this.underscoreErrAssigned) {
1151        this.underscoreErrAssigned = true;
1152        this.output.write(
1153          'Expression assignment to _error now disabled.\n');
1154      }
1155    },
1156  });
1157
1158  // Allow REPL extensions to extend the new context
1159  this.emit('reset', this.context);
1160};
1161
1162REPLServer.prototype.displayPrompt = function(preserveCursor) {
1163  let prompt = this._initialPrompt;
1164  if (this[kBufferedCommandSymbol].length) {
1165    prompt = '...';
1166    const len = this.lines.level.length ? this.lines.level.length - 1 : 0;
1167    const levelInd = StringPrototypeRepeat('..', len);
1168    prompt += levelInd + ' ';
1169  }
1170
1171  // Do not overwrite `_initialPrompt` here
1172  ReflectApply(Interface.prototype.setPrompt, this, [prompt]);
1173  this.prompt(preserveCursor);
1174};
1175
1176// When invoked as an API method, overwrite _initialPrompt
1177REPLServer.prototype.setPrompt = function setPrompt(prompt) {
1178  this._initialPrompt = prompt;
1179  ReflectApply(Interface.prototype.setPrompt, this, [prompt]);
1180};
1181
1182const importRE = /\bimport\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/;
1183const requireRE = /\brequire\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/;
1184const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/;
1185const simpleExpressionRE =
1186    /(?:[\w$'"`[{(](?:\w|\$|['"`\]})])*\??\.)*[a-zA-Z_$](?:\w|\$)*\??\.?$/;
1187const versionedFileNamesRe = /-\d+\.\d+/;
1188
1189function isIdentifier(str) {
1190  if (str === '') {
1191    return false;
1192  }
1193  const first = StringPrototypeCodePointAt(str, 0);
1194  if (!isIdentifierStart(first)) {
1195    return false;
1196  }
1197  const firstLen = first > 0xffff ? 2 : 1;
1198  for (let i = firstLen; i < str.length; i += 1) {
1199    const cp = StringPrototypeCodePointAt(str, i);
1200    if (!isIdentifierChar(cp)) {
1201      return false;
1202    }
1203    if (cp > 0xffff) {
1204      i += 1;
1205    }
1206  }
1207  return true;
1208}
1209
1210function isNotLegacyObjectPrototypeMethod(str) {
1211  return isIdentifier(str) &&
1212    str !== '__defineGetter__' &&
1213    str !== '__defineSetter__' &&
1214    str !== '__lookupGetter__' &&
1215    str !== '__lookupSetter__';
1216}
1217
1218function filteredOwnPropertyNames(obj) {
1219  if (!obj) return [];
1220  // `Object.prototype` is the only non-contrived object that fulfills
1221  // `Object.getPrototypeOf(X) === null &&
1222  //  Object.getPrototypeOf(Object.getPrototypeOf(X.constructor)) === X`.
1223  let isObjectPrototype = false;
1224  if (ObjectGetPrototypeOf(obj) === null) {
1225    const ctorDescriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
1226    if (ctorDescriptor && ctorDescriptor.value) {
1227      const ctorProto = ObjectGetPrototypeOf(ctorDescriptor.value);
1228      isObjectPrototype = ctorProto && ObjectGetPrototypeOf(ctorProto) === obj;
1229    }
1230  }
1231  const filter = ALL_PROPERTIES | SKIP_SYMBOLS;
1232  return ArrayPrototypeFilter(
1233    getOwnNonIndexProperties(obj, filter),
1234    isObjectPrototype ? isNotLegacyObjectPrototypeMethod : isIdentifier);
1235}
1236
1237function getGlobalLexicalScopeNames(contextId) {
1238  return sendInspectorCommand((session) => {
1239    let names = [];
1240    session.post('Runtime.globalLexicalScopeNames', {
1241      executionContextId: contextId,
1242    }, (error, result) => {
1243      if (!error) names = result.names;
1244    });
1245    return names;
1246  }, () => []);
1247}
1248
1249REPLServer.prototype.complete = function() {
1250  ReflectApply(this.completer, this, arguments);
1251};
1252
1253function gracefulReaddir(...args) {
1254  try {
1255    return ReflectApply(fs.readdirSync, null, args);
1256  } catch {
1257    // Continue regardless of error.
1258  }
1259}
1260
1261function completeFSFunctions(match) {
1262  let baseName = '';
1263  let filePath = match[1];
1264  let fileList = gracefulReaddir(filePath, { withFileTypes: true });
1265
1266  if (!fileList) {
1267    baseName = path.basename(filePath);
1268    filePath = path.dirname(filePath);
1269    fileList = gracefulReaddir(filePath, { withFileTypes: true }) || [];
1270  }
1271
1272  const completions = ArrayPrototypeMap(
1273    ArrayPrototypeFilter(
1274      fileList,
1275      (dirent) => StringPrototypeStartsWith(dirent.name, baseName),
1276    ),
1277    (d) => d.name,
1278  );
1279
1280  return [[completions], baseName];
1281}
1282
1283// Provide a list of completions for the given leading text. This is
1284// given to the readline interface for handling tab completion.
1285//
1286// Example:
1287//  complete('let foo = util.')
1288//    -> [['util.print', 'util.debug', 'util.log', 'util.inspect'],
1289//        'util.' ]
1290//
1291// Warning: This eval's code like "foo.bar.baz", so it will run property
1292// getter code.
1293function complete(line, callback) {
1294  // List of completion lists, one for each inheritance "level"
1295  let completionGroups = [];
1296  let completeOn, group;
1297
1298  // Ignore right whitespace. It could change the outcome.
1299  line = StringPrototypeTrimLeft(line);
1300
1301  let filter = '';
1302
1303  let match;
1304  // REPL commands (e.g. ".break").
1305  if ((match = RegExpPrototypeExec(/^\s*\.(\w*)$/, line)) !== null) {
1306    ArrayPrototypePush(completionGroups, ObjectKeys(this.commands));
1307    completeOn = match[1];
1308    if (completeOn.length) {
1309      filter = completeOn;
1310    }
1311  } else if ((match = RegExpPrototypeExec(requireRE, line)) !== null) {
1312    // require('...<Tab>')
1313    completeOn = match[1];
1314    filter = completeOn;
1315    if (this.allowBlockingCompletions) {
1316      const subdir = match[2] || '';
1317      const extensions = ObjectKeys(this.context.require.extensions);
1318      const indexes = ArrayPrototypeMap(extensions,
1319                                        (extension) => `index${extension}`);
1320      ArrayPrototypePush(indexes, 'package.json', 'index');
1321
1322      group = [];
1323      let paths = [];
1324
1325      if (completeOn === '.') {
1326        group = ['./', '../'];
1327      } else if (completeOn === '..') {
1328        group = ['../'];
1329      } else if (RegExpPrototypeExec(/^\.\.?\//, completeOn) !== null) {
1330        paths = [process.cwd()];
1331      } else {
1332        paths = [];
1333        ArrayPrototypePushApply(paths, module.paths);
1334        ArrayPrototypePushApply(paths, CJSModule.globalPaths);
1335      }
1336
1337      ArrayPrototypeForEach(paths, (dir) => {
1338        dir = path.resolve(dir, subdir);
1339        const dirents = gracefulReaddir(dir, { withFileTypes: true }) || [];
1340        ArrayPrototypeForEach(dirents, (dirent) => {
1341          if (RegExpPrototypeExec(versionedFileNamesRe, dirent.name) !== null ||
1342              dirent.name === '.npm') {
1343            // Exclude versioned names that 'npm' installs.
1344            return;
1345          }
1346          const extension = path.extname(dirent.name);
1347          const base = StringPrototypeSlice(dirent.name, 0, -extension.length);
1348          if (!dirent.isDirectory()) {
1349            if (StringPrototypeIncludes(extensions, extension) &&
1350                (!subdir || base !== 'index')) {
1351              ArrayPrototypePush(group, `${subdir}${base}`);
1352            }
1353            return;
1354          }
1355          ArrayPrototypePush(group, `${subdir}${dirent.name}/`);
1356          const absolute = path.resolve(dir, dirent.name);
1357          if (ArrayPrototypeSome(
1358            gracefulReaddir(absolute) || [],
1359            (subfile) => ArrayPrototypeIncludes(indexes, subfile),
1360          )) {
1361            ArrayPrototypePush(group, `${subdir}${dirent.name}`);
1362          }
1363        });
1364      });
1365      if (group.length) {
1366        ArrayPrototypePush(completionGroups, group);
1367      }
1368    }
1369
1370    ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs);
1371  } else if ((match = RegExpPrototypeExec(importRE, line)) !== null) {
1372    // import('...<Tab>')
1373    completeOn = match[1];
1374    filter = completeOn;
1375    if (this.allowBlockingCompletions) {
1376      const subdir = match[2] || '';
1377      // File extensions that can be imported:
1378      const extensions = ObjectKeys(
1379        getOptionValue('--experimental-specifier-resolution') === 'node' ?
1380          legacyExtensionFormatMap :
1381          extensionFormatMap);
1382
1383      // Only used when loading bare module specifiers from `node_modules`:
1384      const indexes = ArrayPrototypeMap(extensions, (ext) => `index${ext}`);
1385      ArrayPrototypePush(indexes, 'package.json');
1386
1387      group = [];
1388      let paths = [];
1389      if (completeOn === '.') {
1390        group = ['./', '../'];
1391      } else if (completeOn === '..') {
1392        group = ['../'];
1393      } else if (RegExpPrototypeExec(/^\.\.?\//, completeOn) !== null) {
1394        paths = [process.cwd()];
1395      } else {
1396        paths = ArrayPrototypeSlice(module.paths);
1397      }
1398
1399      ArrayPrototypeForEach(paths, (dir) => {
1400        dir = path.resolve(dir, subdir);
1401        const isInNodeModules = path.basename(dir) === 'node_modules';
1402        const dirents = gracefulReaddir(dir, { withFileTypes: true }) || [];
1403        ArrayPrototypeForEach(dirents, (dirent) => {
1404          const { name } = dirent;
1405          if (RegExpPrototypeExec(versionedFileNamesRe, name) !== null ||
1406              name === '.npm') {
1407            // Exclude versioned names that 'npm' installs.
1408            return;
1409          }
1410
1411          if (!dirent.isDirectory()) {
1412            const extension = path.extname(name);
1413            if (StringPrototypeIncludes(extensions, extension)) {
1414              ArrayPrototypePush(group, `${subdir}${name}`);
1415            }
1416            return;
1417          }
1418
1419          ArrayPrototypePush(group, `${subdir}${name}/`);
1420          if (!subdir && isInNodeModules) {
1421            const absolute = path.resolve(dir, name);
1422            const subfiles = gracefulReaddir(absolute) || [];
1423            if (ArrayPrototypeSome(subfiles, (subfile) => {
1424              return ArrayPrototypeIncludes(indexes, subfile);
1425            })) {
1426              ArrayPrototypePush(group, `${subdir}${name}`);
1427            }
1428          }
1429        });
1430      });
1431
1432      if (group.length) {
1433        ArrayPrototypePush(completionGroups, group);
1434      }
1435    }
1436
1437    ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs);
1438  } else if ((match = RegExpPrototypeExec(fsAutoCompleteRE, line)) !== null &&
1439             this.allowBlockingCompletions) {
1440    ({ 0: completionGroups, 1: completeOn } = completeFSFunctions(match));
1441  // Handle variable member lookup.
1442  // We support simple chained expressions like the following (no function
1443  // calls, etc.). That is for simplicity and also because we *eval* that
1444  // leading expression so for safety (see WARNING above) don't want to
1445  // eval function calls.
1446  //
1447  //   foo.bar<|>     # completions for 'foo' with filter 'bar'
1448  //   spam.eggs.<|>  # completions for 'spam.eggs' with filter ''
1449  //   foo<|>         # all scope vars with filter 'foo'
1450  //   foo.<|>        # completions for 'foo' with filter ''
1451  } else if (line.length === 0 ||
1452             RegExpPrototypeExec(/\w|\.|\$/, line[line.length - 1]) !== null) {
1453    const { 0: match } = RegExpPrototypeExec(simpleExpressionRE, line) || [''];
1454    if (line.length !== 0 && !match) {
1455      completionGroupsLoaded();
1456      return;
1457    }
1458    let expr = '';
1459    completeOn = match;
1460    if (StringPrototypeEndsWith(line, '.')) {
1461      expr = StringPrototypeSlice(match, 0, -1);
1462    } else if (line.length !== 0) {
1463      const bits = StringPrototypeSplit(match, '.');
1464      filter = ArrayPrototypePop(bits);
1465      expr = ArrayPrototypeJoin(bits, '.');
1466    }
1467
1468    // Resolve expr and get its completions.
1469    if (!expr) {
1470      // Get global vars synchronously
1471      ArrayPrototypePush(completionGroups,
1472                         getGlobalLexicalScopeNames(this[kContextId]));
1473      let contextProto = this.context;
1474      while ((contextProto = ObjectGetPrototypeOf(contextProto)) !== null) {
1475        ArrayPrototypePush(completionGroups,
1476                           filteredOwnPropertyNames(contextProto));
1477      }
1478      const contextOwnNames = filteredOwnPropertyNames(this.context);
1479      if (!this.useGlobal) {
1480        // When the context is not `global`, builtins are not own
1481        // properties of it.
1482        // `globalBuiltins` is a `SafeSet`, not an Array-like.
1483        ArrayPrototypePush(contextOwnNames, ...globalBuiltins);
1484      }
1485      ArrayPrototypePush(completionGroups, contextOwnNames);
1486      if (filter !== '') addCommonWords(completionGroups);
1487      completionGroupsLoaded();
1488      return;
1489    }
1490
1491    let chaining = '.';
1492    if (StringPrototypeEndsWith(expr, '?')) {
1493      expr = StringPrototypeSlice(expr, 0, -1);
1494      chaining = '?.';
1495    }
1496
1497    const memberGroups = [];
1498    const evalExpr = `try { ${expr} } catch {}`;
1499    this.eval(evalExpr, this.context, getREPLResourceName(), (e, obj) => {
1500      try {
1501        let p;
1502        if ((typeof obj === 'object' && obj !== null) ||
1503            typeof obj === 'function') {
1504          memberGroups.push(filteredOwnPropertyNames(obj));
1505          p = ObjectGetPrototypeOf(obj);
1506        } else {
1507          p = obj.constructor ? obj.constructor.prototype : null;
1508        }
1509        // Circular refs possible? Let's guard against that.
1510        let sentinel = 5;
1511        while (p !== null && sentinel-- !== 0) {
1512          memberGroups.push(filteredOwnPropertyNames(p));
1513          p = ObjectGetPrototypeOf(p);
1514        }
1515      } catch {
1516        // Maybe a Proxy object without `getOwnPropertyNames` trap.
1517        // We simply ignore it here, as we don't want to break the
1518        // autocompletion. Fixes the bug
1519        // https://github.com/nodejs/node/issues/2119
1520      }
1521
1522      if (memberGroups.length) {
1523        expr += chaining;
1524        ArrayPrototypeForEach(memberGroups, (group) => {
1525          ArrayPrototypePush(completionGroups,
1526                             ArrayPrototypeMap(group,
1527                                               (member) => `${expr}${member}`));
1528        });
1529        if (filter) {
1530          filter = `${expr}${filter}`;
1531        }
1532      }
1533
1534      completionGroupsLoaded();
1535    });
1536    return;
1537  }
1538
1539  return completionGroupsLoaded();
1540
1541  // Will be called when all completionGroups are in place
1542  // Useful for async autocompletion
1543  function completionGroupsLoaded() {
1544    // Filter, sort (within each group), uniq and merge the completion groups.
1545    if (completionGroups.length && filter) {
1546      const newCompletionGroups = [];
1547      const lowerCaseFilter = StringPrototypeToLocaleLowerCase(filter);
1548      ArrayPrototypeForEach(completionGroups, (group) => {
1549        const filteredGroup = ArrayPrototypeFilter(group, (str) => {
1550          // Filter is always case-insensitive following chromium autocomplete
1551          // behavior.
1552          return StringPrototypeStartsWith(
1553            StringPrototypeToLocaleLowerCase(str),
1554            lowerCaseFilter,
1555          );
1556        });
1557        if (filteredGroup.length) {
1558          ArrayPrototypePush(newCompletionGroups, filteredGroup);
1559        }
1560      });
1561      completionGroups = newCompletionGroups;
1562    }
1563
1564    const completions = [];
1565    // Unique completions across all groups.
1566    const uniqueSet = new SafeSet();
1567    uniqueSet.add('');
1568    // Completion group 0 is the "closest" (least far up the inheritance
1569    // chain) so we put its completions last: to be closest in the REPL.
1570    ArrayPrototypeForEach(completionGroups, (group) => {
1571      ArrayPrototypeSort(group, (a, b) => (b > a ? 1 : -1));
1572      const setSize = uniqueSet.size;
1573      ArrayPrototypeForEach(group, (entry) => {
1574        if (!uniqueSet.has(entry)) {
1575          ArrayPrototypeUnshift(completions, entry);
1576          uniqueSet.add(entry);
1577        }
1578      });
1579      // Add a separator between groups.
1580      if (uniqueSet.size !== setSize) {
1581        ArrayPrototypeUnshift(completions, '');
1582      }
1583    });
1584
1585    // Remove obsolete group entry, if present.
1586    if (completions[0] === '') {
1587      ArrayPrototypeShift(completions);
1588    }
1589
1590    callback(null, [completions, completeOn]);
1591  }
1592}
1593
1594REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => {
1595  if (err) return callback(err);
1596
1597  const { 0: completions, 1: completeOn = '' } = results;
1598  let result = ArrayPrototypeFilter(completions, Boolean);
1599
1600  if (completeOn && result.length !== 0) {
1601    result = [commonPrefix(result)];
1602  }
1603
1604  callback(null, [result, completeOn]);
1605};
1606
1607REPLServer.prototype.defineCommand = function(keyword, cmd) {
1608  if (typeof cmd === 'function') {
1609    cmd = { action: cmd };
1610  } else {
1611    validateFunction(cmd.action, 'cmd.action');
1612  }
1613  this.commands[keyword] = cmd;
1614};
1615
1616// TODO(BridgeAR): This should be replaced with acorn to build an AST. The
1617// language became more complex and using a simple approach like this is not
1618// sufficient anymore.
1619function _memory(cmd) {
1620  const self = this;
1621  self.lines = self.lines || [];
1622  self.lines.level = self.lines.level || [];
1623
1624  // Save the line so I can do magic later
1625  if (cmd) {
1626    const len = self.lines.level.length ? self.lines.level.length - 1 : 0;
1627    ArrayPrototypePush(self.lines, StringPrototypeRepeat('  ', len) + cmd);
1628  } else {
1629    // I don't want to not change the format too much...
1630    ArrayPrototypePush(self.lines, '');
1631  }
1632
1633  if (!cmd) {
1634    self.lines.level = [];
1635    return;
1636  }
1637
1638  // I need to know "depth."
1639  // Because I can not tell the difference between a } that
1640  // closes an object literal and a } that closes a function
1641  const countMatches = (regex, str) => {
1642    let count = 0;
1643    while (RegExpPrototypeExec(regex, str) !== null) count++;
1644    return count;
1645  };
1646
1647  // Going down is { and (   e.g. function() {
1648  // going up is } and )
1649  const dw = countMatches(/[{(]/g, cmd);
1650  const up = countMatches(/[})]/g, cmd);
1651  let depth = dw.length - up.length;
1652
1653  if (depth) {
1654    (function workIt() {
1655      if (depth > 0) {
1656        // Going... down.
1657        // Push the line#, depth count, and if the line is a function.
1658        // Since JS only has functional scope I only need to remove
1659        // "function() {" lines, clearly this will not work for
1660        // "function()
1661        // {" but nothing should break, only tab completion for local
1662        // scope will not work for this function.
1663        ArrayPrototypePush(self.lines.level, {
1664          line: self.lines.length - 1,
1665          depth: depth,
1666        });
1667      } else if (depth < 0) {
1668        // Going... up.
1669        const curr = ArrayPrototypePop(self.lines.level);
1670        if (curr) {
1671          const tmp = curr.depth + depth;
1672          if (tmp < 0) {
1673            // More to go, recurse
1674            depth += curr.depth;
1675            workIt();
1676          } else if (tmp > 0) {
1677            // Remove and push back
1678            curr.depth += depth;
1679            ArrayPrototypePush(self.lines.level, curr);
1680          }
1681        }
1682      }
1683    }());
1684  }
1685}
1686
1687function addCommonWords(completionGroups) {
1688  // Only words which do not yet exist as global property should be added to
1689  // this list.
1690  ArrayPrototypePush(completionGroups, [
1691    'async', 'await', 'break', 'case', 'catch', 'const', 'continue',
1692    'debugger', 'default', 'delete', 'do', 'else', 'export', 'false',
1693    'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'let',
1694    'new', 'null', 'return', 'switch', 'this', 'throw', 'true', 'try',
1695    'typeof', 'var', 'void', 'while', 'with', 'yield',
1696  ]);
1697}
1698
1699function _turnOnEditorMode(repl) {
1700  repl.editorMode = true;
1701  ReflectApply(Interface.prototype.setPrompt, repl, ['']);
1702}
1703
1704function _turnOffEditorMode(repl) {
1705  repl.editorMode = false;
1706  repl.setPrompt(repl._initialPrompt);
1707}
1708
1709function defineDefaultCommands(repl) {
1710  repl.defineCommand('break', {
1711    help: 'Sometimes you get stuck, this gets you out',
1712    action: function() {
1713      this.clearBufferedCommand();
1714      this.displayPrompt();
1715    },
1716  });
1717
1718  let clearMessage;
1719  if (repl.useGlobal) {
1720    clearMessage = 'Alias for .break';
1721  } else {
1722    clearMessage = 'Break, and also clear the local context';
1723  }
1724  repl.defineCommand('clear', {
1725    help: clearMessage,
1726    action: function() {
1727      this.clearBufferedCommand();
1728      if (!this.useGlobal) {
1729        this.output.write('Clearing context...\n');
1730        this.resetContext();
1731      }
1732      this.displayPrompt();
1733    },
1734  });
1735
1736  repl.defineCommand('exit', {
1737    help: 'Exit the REPL',
1738    action: function() {
1739      this.close();
1740    },
1741  });
1742
1743  repl.defineCommand('help', {
1744    help: 'Print this help message',
1745    action: function() {
1746      const names = ArrayPrototypeSort(ObjectKeys(this.commands));
1747      const longestNameLength = MathMaxApply(
1748        ArrayPrototypeMap(names, (name) => name.length),
1749      );
1750      ArrayPrototypeForEach(names, (name) => {
1751        const cmd = this.commands[name];
1752        const spaces =
1753          StringPrototypeRepeat(' ', longestNameLength - name.length + 3);
1754        const line = `.${name}${cmd.help ? spaces + cmd.help : ''}\n`;
1755        this.output.write(line);
1756      });
1757      this.output.write('\nPress Ctrl+C to abort current expression, ' +
1758        'Ctrl+D to exit the REPL\n');
1759      this.displayPrompt();
1760    },
1761  });
1762
1763  repl.defineCommand('save', {
1764    help: 'Save all evaluated commands in this REPL session to a file',
1765    action: function(file) {
1766      try {
1767        fs.writeFileSync(file, ArrayPrototypeJoin(this.lines, '\n'));
1768        this.output.write(`Session saved to: ${file}\n`);
1769      } catch {
1770        this.output.write(`Failed to save: ${file}\n`);
1771      }
1772      this.displayPrompt();
1773    },
1774  });
1775
1776  repl.defineCommand('load', {
1777    help: 'Load JS from a file into the REPL session',
1778    action: function(file) {
1779      try {
1780        const stats = fs.statSync(file);
1781        if (stats && stats.isFile()) {
1782          _turnOnEditorMode(this);
1783          const data = fs.readFileSync(file, 'utf8');
1784          this.write(data);
1785          _turnOffEditorMode(this);
1786          this.write('\n');
1787        } else {
1788          this.output.write(
1789            `Failed to load: ${file} is not a valid file\n`,
1790          );
1791        }
1792      } catch {
1793        this.output.write(`Failed to load: ${file}\n`);
1794      }
1795      this.displayPrompt();
1796    },
1797  });
1798  if (repl.terminal) {
1799    repl.defineCommand('editor', {
1800      help: 'Enter editor mode',
1801      action() {
1802        _turnOnEditorMode(this);
1803        this.output.write(
1804          '// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n');
1805      },
1806    });
1807  }
1808}
1809
1810function Recoverable(err) {
1811  this.err = err;
1812}
1813ObjectSetPrototypeOf(Recoverable.prototype, SyntaxErrorPrototype);
1814ObjectSetPrototypeOf(Recoverable, SyntaxError);
1815
1816module.exports = {
1817  start,
1818  writer,
1819  REPLServer,
1820  REPL_MODE_SLOPPY,
1821  REPL_MODE_STRICT,
1822  Recoverable,
1823};
1824
1825ObjectDefineProperty(module.exports, 'builtinModules', {
1826  __proto__: null,
1827  get: () => _builtinLibs,
1828  set: (val) => _builtinLibs = val,
1829  enumerable: true,
1830  configurable: true,
1831});
1832
1833ObjectDefineProperty(module.exports, '_builtinLibs', {
1834  __proto__: null,
1835  get: pendingDeprecation ? deprecate(
1836    () => _builtinLibs,
1837    'repl._builtinLibs is deprecated. Check module.builtinModules instead',
1838    'DEP0142',
1839  ) : () => _builtinLibs,
1840  set: pendingDeprecation ? deprecate(
1841    (val) => _builtinLibs = val,
1842    'repl._builtinLibs is deprecated. Check module.builtinModules instead',
1843    'DEP0142',
1844  ) : (val) => _builtinLibs = val,
1845  enumerable: false,
1846  configurable: true,
1847});
1848