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