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