From 67a09cff0b12d912562fd8f22b9139a9703a5668 Mon Sep 17 00:00:00 2001 From: RedYetiDev <38299977+RedYetiDev@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:18:10 -0400 Subject: [PATCH] repl: convert to ES5 class --- lib/repl.js | 1657 +++++++++++++++++++++++++-------------------------- 1 file changed, 820 insertions(+), 837 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index 6e2d8120ad2167..0a7bf8c42b9f6d 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -255,957 +255,958 @@ const toDynamicImport = (codeLine) => { return dynamicImportStatement; }; -function REPLServer(prompt, - stream, - eval_, - useGlobal, - ignoreUndefined, - replMode) { - if (!(this instanceof REPLServer)) { - return new REPLServer(prompt, - stream, - eval_, - useGlobal, - ignoreUndefined, - replMode); - } +class REPLServer extends Interface { + constructor(prompt, stream, eval_, useGlobal, ignoreUndefined, replMode) { + let options; + if (prompt !== null && typeof prompt === 'object') { + // An options object was given. + options = { ...prompt }; + stream = options.stream || options.socket; + eval_ = options.eval; + useGlobal = options.useGlobal; + ignoreUndefined = options.ignoreUndefined; + prompt = options.prompt; + replMode = options.replMode; + } else { + options = {}; + } - let options; - if (prompt !== null && typeof prompt === 'object') { - // An options object was given. - options = { ...prompt }; - stream = options.stream || options.socket; - eval_ = options.eval; - useGlobal = options.useGlobal; - ignoreUndefined = options.ignoreUndefined; - prompt = options.prompt; - replMode = options.replMode; - } else { - options = {}; - } + if (!options.input && !options.output) { + // Legacy API, passing a 'stream'/'socket' option. + if (!stream) { + // Use stdin and stdout as the default streams if none were given. + stream = process; + } + // We're given a duplex readable/writable Stream, like a `net.Socket` + // or a custom object with 2 streams, or the `process` object. + options.input = stream.stdin || stream; + options.output = stream.stdout || stream; + } - if (!options.input && !options.output) { - // Legacy API, passing a 'stream'/'socket' option. - if (!stream) { - // Use stdin and stdout as the default streams if none were given. - stream = process; + if (options.terminal === undefined) { + options.terminal = options.output.isTTY; } - // We're given a duplex readable/writable Stream, like a `net.Socket` - // or a custom object with 2 streams, or the `process` object. - options.input = stream.stdin || stream; - options.output = stream.stdout || stream; - } + options.terminal = !!options.terminal; - if (options.terminal === undefined) { - options.terminal = options.output.isTTY; - } - options.terminal = !!options.terminal; + if (options.terminal && options.useColors === undefined) { + // If possible, check if stdout supports colors or not. + options.useColors = shouldColorize(options.output); + } - if (options.terminal && options.useColors === undefined) { - // If possible, check if stdout supports colors or not. - options.useColors = shouldColorize(options.output); - } + // TODO(devsnek): Add a test case for custom eval functions. + const preview = options.terminal && + (options.preview !== undefined ? !!options.preview : !eval_); - // TODO(devsnek): Add a test case for custom eval functions. - const preview = options.terminal && - (options.preview !== undefined ? !!options.preview : !eval_); - - ObjectDefineProperty(this, 'inputStream', { - __proto__: null, - get: pendingDeprecation ? - deprecate(() => this.input, - 'repl.inputStream and repl.outputStream are deprecated. ' + - 'Use repl.input and repl.output instead', - 'DEP0141') : - () => this.input, - set: pendingDeprecation ? - deprecate((val) => this.input = val, - 'repl.inputStream and repl.outputStream are deprecated. ' + - 'Use repl.input and repl.output instead', - 'DEP0141') : - (val) => this.input = val, - enumerable: false, - configurable: true, - }); - ObjectDefineProperty(this, 'outputStream', { - __proto__: null, - get: pendingDeprecation ? - deprecate(() => this.output, - 'repl.inputStream and repl.outputStream are deprecated. ' + - 'Use repl.input and repl.output instead', - 'DEP0141') : - () => this.output, - set: pendingDeprecation ? - deprecate((val) => this.output = val, - 'repl.inputStream and repl.outputStream are deprecated. ' + - 'Use repl.input and repl.output instead', - 'DEP0141') : - (val) => this.output = val, - enumerable: false, - configurable: true, - }); + function completer(text, cb) { + ReflectApply(complete, self, [text, self.editorMode ? self.completeOnEditorMode(cb) : cb]); + } - this.allowBlockingCompletions = !!options.allowBlockingCompletions; - this.useColors = !!options.useColors; - this._domain = options.domain || domain.create(); - this.useGlobal = !!useGlobal; - this.ignoreUndefined = !!ignoreUndefined; - this.replMode = replMode || module.exports.REPL_MODE_SLOPPY; - this.underscoreAssigned = false; - this.last = undefined; - this.underscoreErrAssigned = false; - this.lastError = undefined; - this.breakEvalOnSigint = !!options.breakEvalOnSigint; - this.editorMode = false; - // Context id for use with the inspector protocol. - this[kContextId] = undefined; - - if (this.breakEvalOnSigint && eval_) { - // Allowing this would not reflect user expectations. - // breakEvalOnSigint affects only the behavior of the default eval(). - throw new ERR_INVALID_REPL_EVAL_CONFIG(); - } + super({ + input: options.input, + output: options.output, + completer: options.completer || completer, + terminal: options.terminal, + historySize: options.historySize, + prompt, + }); + + ObjectDefineProperty(this, 'inputStream', { + __proto__: null, + get: pendingDeprecation ? + deprecate(() => this.input, + 'repl.inputStream and repl.outputStream are deprecated. ' + + 'Use repl.input and repl.output instead', + 'DEP0141') : + () => this.input, + set: pendingDeprecation ? + deprecate((val) => this.input = val, + 'repl.inputStream and repl.outputStream are deprecated. ' + + 'Use repl.input and repl.output instead', + 'DEP0141') : + (val) => this.input = val, + enumerable: false, + configurable: true, + }); + ObjectDefineProperty(this, 'outputStream', { + __proto__: null, + get: pendingDeprecation ? + deprecate(() => this.output, + 'repl.inputStream and repl.outputStream are deprecated. ' + + 'Use repl.input and repl.output instead', + 'DEP0141') : + () => this.output, + set: pendingDeprecation ? + deprecate((val) => this.output = val, + 'repl.inputStream and repl.outputStream are deprecated. ' + + 'Use repl.input and repl.output instead', + 'DEP0141') : + (val) => this.output = val, + enumerable: false, + configurable: true, + }); - if (options[kStandaloneREPL]) { - // It is possible to introspect the running REPL accessing this variable - // from inside the REPL. This is useful for anyone working on the REPL. - module.exports.repl = this; - } else if (!addedNewListener) { - // Add this listener only once and use a WeakSet that contains the REPLs - // domains. Otherwise we'd have to add a single listener to each REPL - // instance and that could trigger the `MaxListenersExceededWarning`. - process.prependListener('newListener', (event, listener) => { - if (event === 'uncaughtException' && + this.allowBlockingCompletions = !!options.allowBlockingCompletions; + this.useColors = !!options.useColors; + this._domain = options.domain || domain.create(); + this.useGlobal = !!useGlobal; + this.ignoreUndefined = !!ignoreUndefined; + this.replMode = replMode || module.exports.REPL_MODE_SLOPPY; + this.underscoreAssigned = false; + this.last = undefined; + this.underscoreErrAssigned = false; + this.lastError = undefined; + this.breakEvalOnSigint = !!options.breakEvalOnSigint; + this.editorMode = false; + // Context id for use with the inspector protocol. + this[kContextId] = undefined; + + if (this.breakEvalOnSigint && eval_) { + // Allowing this would not reflect user expectations. + // breakEvalOnSigint affects only the behavior of the default eval(). + throw new ERR_INVALID_REPL_EVAL_CONFIG(); + } + + if (options[kStandaloneREPL]) { + // It is possible to introspect the running REPL accessing this variable + // from inside the REPL. This is useful for anyone working on the REPL. + module.exports.repl = this; + } else if (!addedNewListener) { + // Add this listener only once and use a WeakSet that contains the REPLs + // domains. Otherwise we'd have to add a single listener to each REPL + // instance and that could trigger the `MaxListenersExceededWarning`. + process.prependListener('newListener', (event, listener) => { + if (event === 'uncaughtException' && process.domain && listener.name !== 'domainUncaughtExceptionClear' && domainSet.has(process.domain)) { - // Throw an error so that the event will not be added and the current - // domain takes over. That way the user is notified about the error - // and the current code evaluation is stopped, just as any other code - // that contains an error. - throw new ERR_INVALID_REPL_INPUT( - 'Listeners for `uncaughtException` cannot be used in the REPL'); - } - }); - addedNewListener = true; - } + // Throw an error so that the event will not be added and the current + // domain takes over. That way the user is notified about the error + // and the current code evaluation is stopped, just as any other code + // that contains an error. + throw new ERR_INVALID_REPL_INPUT( + 'Listeners for `uncaughtException` cannot be used in the REPL'); + } + }); + addedNewListener = true; + } - domainSet.add(this._domain); + domainSet.add(this._domain); - const savedRegExMatches = ['', '', '', '', '', '', '', '', '', '']; - const sep = '\u0000\u0000\u0000'; - const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` + - `${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` + - `${sep}(.*)$`); + const savedRegExMatches = ['', '', '', '', '', '', '', '', '', '']; + const sep = '\u0000\u0000\u0000'; + const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` + + `${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` + + `${sep}(.*)$`); - eval_ = eval_ || defaultEval; + eval_ = eval_ || defaultEval; - const self = this; + const self = this; - // Pause taking in new input, and store the keys in a buffer. - const pausedBuffer = []; - let paused = false; - function pause() { - paused = true; - } + // Pause taking in new input, and store the keys in a buffer. + const pausedBuffer = []; + let paused = false; + function pause() { + paused = true; + } - function unpause() { - if (!paused) return; - paused = false; - let entry; - const tmpCompletionEnabled = self.isCompletionEnabled; - while ((entry = ArrayPrototypeShift(pausedBuffer)) !== undefined) { - const { 0: type, 1: payload, 2: isCompletionEnabled } = entry; - switch (type) { - case 'key': { - const { 0: d, 1: key } = payload; - self.isCompletionEnabled = isCompletionEnabled; - self._ttyWrite(d, key); - break; + function unpause() { + if (!paused) return; + paused = false; + let entry; + const tmpCompletionEnabled = self.isCompletionEnabled; + while ((entry = ArrayPrototypeShift(pausedBuffer)) !== undefined) { + const { 0: type, 1: payload, 2: isCompletionEnabled } = entry; + switch (type) { + case 'key': { + const { 0: d, 1: key } = payload; + self.isCompletionEnabled = isCompletionEnabled; + self._ttyWrite(d, key); + break; + } + case 'close': + self.emit('exit'); + break; } - case 'close': - self.emit('exit'); + if (paused) { break; + } } - if (paused) { - break; - } + self.isCompletionEnabled = tmpCompletionEnabled; } - self.isCompletionEnabled = tmpCompletionEnabled; - } - function defaultEval(code, context, file, cb) { - let result, script, wrappedErr; - let err = null; - let wrappedCmd = false; - let awaitPromise = false; - const input = code; - - // It's confusing for `{ a : 1 }` to be interpreted as a block - // statement rather than an object literal. So, we first try - // to wrap it in parentheses, so that it will be interpreted as - // an expression. Note that if the above condition changes, - // lib/internal/repl/utils.js needs to be changed to match. - if (RegExpPrototypeExec(/^\s*{/, code) !== null && + function defaultEval(code, context, file, cb) { + let result, script, wrappedErr; + let err = null; + let wrappedCmd = false; + let awaitPromise = false; + const input = code; + + // It's confusing for `{ a : 1 }` to be interpreted as a block + // statement rather than an object literal. So, we first try + // to wrap it in parentheses, so that it will be interpreted as + // an expression. Note that if the above condition changes, + // lib/internal/repl/utils.js needs to be changed to match. + if (RegExpPrototypeExec(/^\s*{/, code) !== null && RegExpPrototypeExec(/;\s*$/, code) === null) { - code = `(${StringPrototypeTrim(code)})\n`; - wrappedCmd = true; - } - - const hostDefinedOptionId = Symbol(`eval:${file}`); - let parentURL; - try { - const { pathToFileURL } = require('internal/url'); - // Adding `/repl` prevents dynamic imports from loading relative - // to the parent of `process.cwd()`. - parentURL = pathToFileURL(path.join(process.cwd(), 'repl')).href; - } catch { - // Continue regardless of error. - } - async function importModuleDynamically(specifier, _, importAttributes) { - const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); - return cascadedLoader.import(specifier, parentURL, importAttributes); - } - // `experimentalREPLAwait` is set to true by default. - // Shall be false in case `--no-experimental-repl-await` flag is used. - if (experimentalREPLAwait && StringPrototypeIncludes(code, 'await')) { - if (processTopLevelAwait === undefined) { - ({ processTopLevelAwait } = require('internal/repl/await')); + code = `(${StringPrototypeTrim(code)})\n`; + wrappedCmd = true; } + const hostDefinedOptionId = Symbol(`eval:${file}`); + let parentURL; try { - const potentialWrappedCode = processTopLevelAwait(code); - if (potentialWrappedCode !== null) { - code = potentialWrappedCode; - wrappedCmd = true; - awaitPromise = true; + const { pathToFileURL } = require('internal/url'); + // Adding `/repl` prevents dynamic imports from loading relative + // to the parent of `process.cwd()`. + parentURL = pathToFileURL(path.join(process.cwd(), 'repl')).href; + } catch { + // Continue regardless of error. + } + async function importModuleDynamically(specifier, _, importAttributes) { + const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); + return cascadedLoader.import(specifier, parentURL, importAttributes); + } + // `experimentalREPLAwait` is set to true by default. + // Shall be false in case `--no-experimental-repl-await` flag is used. + if (experimentalREPLAwait && StringPrototypeIncludes(code, 'await')) { + if (processTopLevelAwait === undefined) { + ({ processTopLevelAwait } = require('internal/repl/await')); } - } catch (e) { - let recoverableError = false; - if (e.name === 'SyntaxError') { - // Remove all "await"s and attempt running the script - // in order to detect if error is truly non recoverable - const fallbackCode = SideEffectFreeRegExpPrototypeSymbolReplace(/\bawait\b/g, code, ''); - try { - makeContextifyScript( - fallbackCode, // code - file, // filename, - 0, // lineOffset - 0, // columnOffset, - undefined, // cachedData - false, // produceCachedData - undefined, // parsingContext - hostDefinedOptionId, // hostDefinedOptionId - importModuleDynamically, // importModuleDynamically - ); - } catch (fallbackError) { - if (isRecoverableError(fallbackError, fallbackCode)) { - recoverableError = true; - err = new Recoverable(e); + + try { + const potentialWrappedCode = processTopLevelAwait(code); + if (potentialWrappedCode !== null) { + code = potentialWrappedCode; + wrappedCmd = true; + awaitPromise = true; + } + } catch (e) { + let recoverableError = false; + if (e.name === 'SyntaxError') { + // Remove all "await"s and attempt running the script + // in order to detect if error is truly non recoverable + const fallbackCode = SideEffectFreeRegExpPrototypeSymbolReplace(/\bawait\b/g, code, ''); + try { + makeContextifyScript( + fallbackCode, // code + file, // filename, + 0, // lineOffset + 0, // columnOffset, + undefined, // cachedData + false, // produceCachedData + undefined, // parsingContext + hostDefinedOptionId, // hostDefinedOptionId + importModuleDynamically, + ); + } catch (fallbackError) { + if (isRecoverableError(fallbackError, fallbackCode)) { + recoverableError = true; + err = new Recoverable(e); + } } } - } - if (!recoverableError) { - decorateErrorStack(e); - err = e; + if (!recoverableError) { + decorateErrorStack(e); + err = e; + } } } - } - // First, create the Script object to check the syntax - if (code === '\n') - return cb(null); + // First, create the Script object to check the syntax + if (code === '\n') + return cb(null); - if (err === null) { - while (true) { - try { - if (self.replMode === module.exports.REPL_MODE_STRICT && + if (err === null) { + while (true) { + try { + if (self.replMode === module.exports.REPL_MODE_STRICT && RegExpPrototypeExec(/^\s*$/, code) === null) { - // "void 0" keeps the repl from returning "use strict" as the result - // value for statements and declarations that don't return a value. - code = `'use strict'; void 0;\n${code}`; - } - script = makeContextifyScript( - code, // code - file, // filename, - 0, // lineOffset - 0, // columnOffset, - undefined, // cachedData - false, // produceCachedData - undefined, // parsingContext - hostDefinedOptionId, // hostDefinedOptionId - importModuleDynamically, // importModuleDynamically - ); - } catch (e) { - debug('parse error %j', code, e); - if (wrappedCmd) { - // Unwrap and try again - wrappedCmd = false; - awaitPromise = false; - code = input; - wrappedErr = e; - continue; + // "void 0" keeps the repl from returning "use strict" as the result + // value for statements and declarations that don't return a value. + code = `'use strict'; void 0;\n${code}`; + } + script = makeContextifyScript( + code, // code + file, // filename, + 0, // lineOffset + 0, // columnOffset, + undefined, // cachedData + false, // produceCachedData + undefined, // parsingContext + hostDefinedOptionId, // hostDefinedOptionId + importModuleDynamically, + ); + } catch (e) { + debug('parse error %j', code, e); + if (wrappedCmd) { + // Unwrap and try again + wrappedCmd = false; + awaitPromise = false; + code = input; + wrappedErr = e; + continue; + } + // Preserve original error for wrapped command + const error = wrappedErr || e; + if (isRecoverableError(error, code)) + err = new Recoverable(error); + + else + err = error; } - // Preserve original error for wrapped command - const error = wrappedErr || e; - if (isRecoverableError(error, code)) - err = new Recoverable(error); - else - err = error; + break; } - break; } - } - - // This will set the values from `savedRegExMatches` to corresponding - // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9` - RegExpPrototypeExec(regExMatcher, - ArrayPrototypeJoin(savedRegExMatches, sep)); - let finished = false; - function finishExecution(err, result) { - if (finished) return; - finished = true; + // This will set the values from `savedRegExMatches` to corresponding + // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9` + RegExpPrototypeExec(regExMatcher, + ArrayPrototypeJoin(savedRegExMatches, sep)); - // After executing the current expression, store the values of RegExp - // predefined properties back in `savedRegExMatches` - for (let idx = 1; idx < savedRegExMatches.length; idx += 1) { - savedRegExMatches[idx] = RegExp[`$${idx}`]; - } + let finished = false; + function finishExecution(err, result) { + if (finished) return; + finished = true; - cb(err, result); - } + // After executing the current expression, store the values of RegExp + // predefined properties back in `savedRegExMatches` + for (let idx = 1; idx < savedRegExMatches.length; idx += 1) { + savedRegExMatches[idx] = RegExp[`$${idx}`]; + } - if (!err) { - // Unset raw mode during evaluation so that Ctrl+C raises a signal. - let previouslyInRawMode; - if (self.breakEvalOnSigint) { - // Start the SIGINT watchdog before entering raw mode so that a very - // quick Ctrl+C doesn't lead to aborting the process completely. - if (!startSigintWatchdog()) - throw new ERR_CANNOT_WATCH_SIGINT(); - previouslyInRawMode = self._setRawMode(false); + cb(err, result); } - try { + if (!err) { + // Unset raw mode during evaluation so that Ctrl+C raises a signal. + let previouslyInRawMode; + if (self.breakEvalOnSigint) { + // Start the SIGINT watchdog before entering raw mode so that a very + // quick Ctrl+C doesn't lead to aborting the process completely. + if (!startSigintWatchdog()) + throw new ERR_CANNOT_WATCH_SIGINT(); + previouslyInRawMode = self._setRawMode(false); + } + try { - const scriptOptions = { - displayErrors: false, - breakOnSigint: self.breakEvalOnSigint, - }; + try { + const scriptOptions = { + displayErrors: false, + breakOnSigint: self.breakEvalOnSigint, + }; - if (self.useGlobal) { - result = ReflectApply(runInThisContext, script, [scriptOptions]); - } else { - result = ReflectApply(runInContext, script, [context, scriptOptions]); + if (self.useGlobal) { + result = ReflectApply(runInThisContext, script, [scriptOptions]); + } else { + result = ReflectApply(runInContext, script, [context, scriptOptions]); + } + } finally { + if (self.breakEvalOnSigint) { + // Reset terminal mode to its previous value. + self._setRawMode(previouslyInRawMode); + + // Returns true if there were pending SIGINTs *after* the script + // has terminated without being interrupted itself. + if (stopSigintWatchdog()) { + self.emit('SIGINT'); + } + } } - } finally { - if (self.breakEvalOnSigint) { - // Reset terminal mode to its previous value. - self._setRawMode(previouslyInRawMode); + } catch (e) { + err = e; - // Returns true if there were pending SIGINTs *after* the script - // has terminated without being interrupted itself. - if (stopSigintWatchdog()) { - self.emit('SIGINT'); - } + if (process.domain) { + debug('not recoverable, send to domain'); + process.domain.emit('error', err); + process.domain.exit(); + return; } } - } catch (e) { - err = e; - if (process.domain) { - debug('not recoverable, send to domain'); - process.domain.emit('error', err); - process.domain.exit(); - return; - } - } + if (awaitPromise && !err) { + let sigintListener; + pause(); + let promise = result; + if (self.breakEvalOnSigint) { + const interrupt = new Promise((resolve, reject) => { + sigintListener = () => { + const tmp = MainContextError.stackTraceLimit; + if (isErrorStackTraceLimitWritable()) MainContextError.stackTraceLimit = 0; + const err = new ERR_SCRIPT_EXECUTION_INTERRUPTED(); + if (isErrorStackTraceLimitWritable()) MainContextError.stackTraceLimit = tmp; + reject(err); + }; + prioritizedSigintQueue.add(sigintListener); + }); + promise = SafePromiseRace([promise, interrupt]); + } - if (awaitPromise && !err) { - let sigintListener; - pause(); - let promise = result; - if (self.breakEvalOnSigint) { - const interrupt = new Promise((resolve, reject) => { - sigintListener = () => { - const tmp = MainContextError.stackTraceLimit; - if (isErrorStackTraceLimitWritable()) MainContextError.stackTraceLimit = 0; - const err = new ERR_SCRIPT_EXECUTION_INTERRUPTED(); - if (isErrorStackTraceLimitWritable()) MainContextError.stackTraceLimit = tmp; - reject(err); - }; - prioritizedSigintQueue.add(sigintListener); - }); - promise = SafePromiseRace([promise, interrupt]); + (async () => { + try { + const result = (await promise)?.value; + finishExecution(null, result); + } catch (err) { + if (err && process.domain) { + debug('not recoverable, send to domain'); + process.domain.emit('error', err); + process.domain.exit(); + return; + } + finishExecution(err); + } finally { + // Remove prioritized SIGINT listener if it was not called. + prioritizedSigintQueue.delete(sigintListener); + unpause(); + } + })(); } + } - (async () => { - try { - const result = (await promise)?.value; - finishExecution(null, result); - } catch (err) { - if (err && process.domain) { - debug('not recoverable, send to domain'); - process.domain.emit('error', err); - process.domain.exit(); - return; - } - finishExecution(err); - } finally { - // Remove prioritized SIGINT listener if it was not called. - prioritizedSigintQueue.delete(sigintListener); - unpause(); - } - })(); + if (!awaitPromise || err) { + finishExecution(err, result); } } - if (!awaitPromise || err) { - finishExecution(err, result); - } - } + self.eval = self._domain.bind(eval_); + + self._domain.on('error', function debugDomainError(e) { + debug('domain error'); + let errStack = ''; + + if (typeof e === 'object' && e !== null) { + overrideStackTrace.set(e, (error, stackFrames) => { + let frames; + if (typeof stackFrames === 'object') { + // Search from the bottom of the call stack to + // find the first frame with a null function name + const idx = ArrayPrototypeFindLastIndex( + stackFrames, + (frame) => frame.getFunctionName() === null, + ); + // If found, get rid of it and everything below it + frames = ArrayPrototypeSlice(stackFrames, 0, idx); + } else { + frames = stackFrames; + } + // FIXME(devsnek): this is inconsistent with the checks + // that the real prepareStackTrace dispatch uses in + // lib/internal/errors.js. + if (typeof MainContextError.prepareStackTrace === 'function') { + return MainContextError.prepareStackTrace(error, frames); + } + return ErrorPrepareStackTrace(error, frames); + }); + decorateErrorStack(e); - self.eval = self._domain.bind(eval_); - - self._domain.on('error', function debugDomainError(e) { - debug('domain error'); - let errStack = ''; - - if (typeof e === 'object' && e !== null) { - overrideStackTrace.set(e, (error, stackFrames) => { - let frames; - if (typeof stackFrames === 'object') { - // Search from the bottom of the call stack to - // find the first frame with a null function name - const idx = ArrayPrototypeFindLastIndex( - stackFrames, - (frame) => frame.getFunctionName() === null, - ); - // If found, get rid of it and everything below it - frames = ArrayPrototypeSlice(stackFrames, 0, idx); - } else { - frames = stackFrames; + if (e.domainThrown) { + delete e.domain; + delete e.domainThrown; } - // FIXME(devsnek): this is inconsistent with the checks - // that the real prepareStackTrace dispatch uses in - // lib/internal/errors.js. - if (typeof MainContextError.prepareStackTrace === 'function') { - return MainContextError.prepareStackTrace(error, frames); - } - return ErrorPrepareStackTrace(error, frames); - }); - decorateErrorStack(e); - if (e.domainThrown) { - delete e.domain; - delete e.domainThrown; - } - - if (isError(e)) { - if (e.stack) { - if (e.name === 'SyntaxError') { - // Remove stack trace. - e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( - /^\s+at\s.*\n?/gm, - SideEffectFreeRegExpPrototypeSymbolReplace(/^REPL\d+:\d+\r?\n/, e.stack, ''), - ''); - const importErrorStr = 'Cannot use import statement outside a ' + - 'module'; - if (StringPrototypeIncludes(e.message, importErrorStr)) { - e.message = 'Cannot use import statement inside the Node.js ' + - 'REPL, alternatively use dynamic import: ' + toDynamicImport(ArrayPrototypeAt(self.lines, -1)); + if (isError(e)) { + if (e.stack) { + if (e.name === 'SyntaxError') { + // Remove stack trace. e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( - /SyntaxError:.*\n/, + /^\s+at\s.*\n?/gm, + SideEffectFreeRegExpPrototypeSymbolReplace(/^REPL\d+:\d+\r?\n/, e.stack, ''), + ''); + const importErrorStr = 'Cannot use import statement outside a ' + + 'module'; + if (StringPrototypeIncludes(e.message, importErrorStr)) { + e.message = 'Cannot use import statement inside the Node.js ' + + 'REPL, alternatively use dynamic import: ' + toDynamicImport(ArrayPrototypeAt(self.lines, -1)); + e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( + /SyntaxError:.*\n/, + e.stack, + `SyntaxError: ${e.message}\n`); + } + } else if (self.replMode === module.exports.REPL_MODE_STRICT) { + e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( + /(\s+at\s+REPL\d+:)(\d+)/, e.stack, - `SyntaxError: ${e.message}\n`); + (_, pre, line) => pre + (line - 1), + ); } - } else if (self.replMode === module.exports.REPL_MODE_STRICT) { - e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( - /(\s+at\s+REPL\d+:)(\d+)/, - e.stack, - (_, pre, line) => pre + (line - 1), - ); } - } - errStack = self.writer(e); + errStack = self.writer(e); - // Remove one line error braces to keep the old style in place. - if (errStack[0] === '[' && errStack[errStack.length - 1] === ']') { - errStack = StringPrototypeSlice(errStack, 1, -1); + // Remove one line error braces to keep the old style in place. + if (errStack[0] === '[' && errStack[errStack.length - 1] === ']') { + errStack = StringPrototypeSlice(errStack, 1, -1); + } } } - } - if (!self.underscoreErrAssigned) { - self.lastError = e; - } + if (!self.underscoreErrAssigned) { + self.lastError = e; + } - if (options[kStandaloneREPL] && + if (options[kStandaloneREPL] && process.listenerCount('uncaughtException') !== 0) { - process.nextTick(() => { - process.emit('uncaughtException', e); - self.clearBufferedCommand(); - self.lines.level = []; - self.displayPrompt(); - }); - } else { - if (errStack === '') { - errStack = self.writer(e); - } - const lines = SideEffectFreeRegExpPrototypeSymbolSplit(/(?<=\n)/, errStack); - let matched = false; + process.nextTick(() => { + process.emit('uncaughtException', e); + self.clearBufferedCommand(); + self.lines.level = []; + self.displayPrompt(); + }); + } else { + if (errStack === '') { + errStack = self.writer(e); + } + const lines = SideEffectFreeRegExpPrototypeSymbolSplit(/(?<=\n)/, errStack); + let matched = false; - errStack = ''; - ArrayPrototypeForEach(lines, (line) => { - if (!matched && + errStack = ''; + ArrayPrototypeForEach(lines, (line) => { + if (!matched && RegExpPrototypeExec(/^\[?([A-Z][a-z0-9_]*)*Error/, line) !== null) { - errStack += writer.options.breakLength >= line.length ? - `Uncaught ${line}` : - `Uncaught:\n${line}`; - matched = true; - } else { - errStack += line; + errStack += writer.options.breakLength >= line.length ? + `Uncaught ${line}` : + `Uncaught:\n${line}`; + matched = true; + } else { + errStack += line; + } + }); + if (!matched) { + const ln = lines.length === 1 ? ' ' : ':\n'; + errStack = `Uncaught${ln}${errStack}`; } - }); - if (!matched) { - const ln = lines.length === 1 ? ' ' : ':\n'; - errStack = `Uncaught${ln}${errStack}`; + // Normalize line endings. + errStack += StringPrototypeEndsWith(errStack, '\n') ? '' : '\n'; + self.output.write(errStack); + self.clearBufferedCommand(); + self.lines.level = []; + self.displayPrompt(); } - // Normalize line endings. - errStack += StringPrototypeEndsWith(errStack, '\n') ? '' : '\n'; - self.output.write(errStack); - self.clearBufferedCommand(); - self.lines.level = []; - self.displayPrompt(); - } - }); - - self.clearBufferedCommand(); - - function completer(text, cb) { - ReflectApply(complete, self, - [text, self.editorMode ? self.completeOnEditorMode(cb) : cb]); - } - - ReflectApply(Interface, this, [{ - input: options.input, - output: options.output, - completer: options.completer || completer, - terminal: options.terminal, - historySize: options.historySize, - prompt, - }]); - - self.resetContext(); - - this.commands = { __proto__: null }; - defineDefaultCommands(this); - - // Figure out which "writer" function to use - self.writer = options.writer || module.exports.writer; + }); - if (self.writer === writer) { - // Conditionally turn on ANSI coloring. - writer.options.colors = self.useColors; + self.clearBufferedCommand(); - if (options[kStandaloneREPL]) { - ObjectDefineProperty(inspect, 'replDefaults', { - __proto__: null, - get() { - return writer.options; - }, - set(options) { - validateObject(options, 'options'); - return ObjectAssign(writer.options, options); - }, - enumerable: true, - configurable: true, - }); + self.resetContext(); + + this.commands = { __proto__: null }; + defineDefaultCommands(this); + + // Figure out which "writer" function to use + self.writer = options.writer || module.exports.writer; + + if (self.writer === writer) { + // Conditionally turn on ANSI coloring. + writer.options.colors = self.useColors; + + if (options[kStandaloneREPL]) { + ObjectDefineProperty(inspect, 'replDefaults', { + __proto__: null, + get() { + return writer.options; + }, + set(options) { + validateObject(options, 'options'); + return ObjectAssign(writer.options, options); + }, + enumerable: true, + configurable: true, + }); + } } - } - function _parseREPLKeyword(keyword, rest) { - const cmd = this.commands[keyword]; - if (cmd) { - ReflectApply(cmd.action, this, [rest]); - return true; + function _parseREPLKeyword(keyword, rest) { + const cmd = this.commands[keyword]; + if (cmd) { + ReflectApply(cmd.action, this, [rest]); + return true; + } + return false; } - return false; - } - self.on('close', function emitExit() { - if (paused) { - ArrayPrototypePush(pausedBuffer, ['close']); - return; - } - self.emit('exit'); - }); + self.on('close', function emitExit() { + if (paused) { + ArrayPrototypePush(pausedBuffer, ['close']); + return; + } + self.emit('exit'); + }); - let sawSIGINT = false; - let sawCtrlD = false; - const prioritizedSigintQueue = new SafeSet(); - self.on('SIGINT', function onSigInt() { - if (prioritizedSigintQueue.size > 0) { - for (const task of prioritizedSigintQueue) { - task(); + let sawSIGINT = false; + let sawCtrlD = false; + const prioritizedSigintQueue = new SafeSet(); + self.on('SIGINT', function onSigInt() { + if (prioritizedSigintQueue.size > 0) { + for (const task of prioritizedSigintQueue) { + task(); + } + return; } - return; - } - const empty = self.line.length === 0; - self.clearLine(); - _turnOffEditorMode(self); + const empty = self.line.length === 0; + self.clearLine(); + _turnOffEditorMode(self); - const cmd = self[kBufferedCommandSymbol]; - if (!(cmd && cmd.length > 0) && empty) { - if (sawSIGINT) { - self.close(); + const cmd = self[kBufferedCommandSymbol]; + if (!(cmd && cmd.length > 0) && empty) { + if (sawSIGINT) { + self.close(); + sawSIGINT = false; + return; + } + self.output.write( + '(To exit, press Ctrl+C again or Ctrl+D or type .exit)\n', + ); + sawSIGINT = true; + } else { sawSIGINT = false; - return; } - self.output.write( - '(To exit, press Ctrl+C again or Ctrl+D or type .exit)\n', - ); - sawSIGINT = true; - } else { - sawSIGINT = false; - } - self.clearBufferedCommand(); - self.lines.level = []; - self.displayPrompt(); - }); + self.clearBufferedCommand(); + self.lines.level = []; + self.displayPrompt(); + }); + + self.on('line', function onLine(cmd) { + debug('line %j', cmd); + cmd = cmd || ''; + sawSIGINT = false; - self.on('line', function onLine(cmd) { - debug('line %j', cmd); - cmd = cmd || ''; - sawSIGINT = false; - - if (self.editorMode) { - self[kBufferedCommandSymbol] += cmd + '\n'; - - // code alignment - const matches = self._sawKeyPress && !self[kLoadingSymbol] ? - RegExpPrototypeExec(/^\s+/, cmd) : null; - if (matches) { - const prefix = matches[0]; - self.write(prefix); - self.line = prefix; - self.cursor = prefix.length; + if (self.editorMode) { + self[kBufferedCommandSymbol] += cmd + '\n'; + + // code alignment + const matches = self._sawKeyPress && !self[kLoadingSymbol] ? + RegExpPrototypeExec(/^\s+/, cmd) : null; + if (matches) { + const prefix = matches[0]; + self.write(prefix); + self.line = prefix; + self.cursor = prefix.length; + } + ReflectApply(_memory, self, [cmd]); + return; } - ReflectApply(_memory, self, [cmd]); - return; - } - // Check REPL keywords and empty lines against a trimmed line input. - const trimmedCmd = StringPrototypeTrim(cmd); + // Check REPL keywords and empty lines against a trimmed line input. + const trimmedCmd = StringPrototypeTrim(cmd); - // Check to see if a REPL keyword was used. If it returns true, - // display next prompt and return. - if (trimmedCmd) { - if (StringPrototypeCharAt(trimmedCmd, 0) === '.' && + // Check to see if a REPL keyword was used. If it returns true, + // display next prompt and return. + if (trimmedCmd) { + if (StringPrototypeCharAt(trimmedCmd, 0) === '.' && StringPrototypeCharAt(trimmedCmd, 1) !== '.' && NumberIsNaN(NumberParseFloat(trimmedCmd))) { - const matches = RegExpPrototypeExec(/^\.([^\s]+)\s*(.*)$/, trimmedCmd); - const keyword = matches && matches[1]; - const rest = matches && matches[2]; - if (ReflectApply(_parseREPLKeyword, self, [keyword, rest]) === true) { - return; - } - if (!self[kBufferedCommandSymbol]) { - self.output.write('Invalid REPL keyword\n'); - finish(null); - return; + const matches = RegExpPrototypeExec(/^\.([^\s]+)\s*(.*)$/, trimmedCmd); + const keyword = matches && matches[1]; + const rest = matches && matches[2]; + if (ReflectApply(_parseREPLKeyword, self, [keyword, rest]) === true) { + return; + } + if (!self[kBufferedCommandSymbol]) { + self.output.write('Invalid REPL keyword\n'); + finish(null); + return; + } } } - } - const evalCmd = self[kBufferedCommandSymbol] + cmd + '\n'; + const evalCmd = self[kBufferedCommandSymbol] + cmd + '\n'; - debug('eval %j', evalCmd); - self.eval(evalCmd, self.context, getREPLResourceName(), finish); + debug('eval %j', evalCmd); + self.eval(evalCmd, self.context, getREPLResourceName(), finish); - function finish(e, ret) { - debug('finish', e, ret); - ReflectApply(_memory, self, [cmd]); + function finish(e, ret) { + debug('finish', e, ret); + ReflectApply(_memory, self, [cmd]); - if (e && !self[kBufferedCommandSymbol] && + if (e && !self[kBufferedCommandSymbol] && StringPrototypeStartsWith(StringPrototypeTrim(cmd), 'npm ')) { - self.output.write('npm should be run outside of the ' + - 'Node.js REPL, in your normal shell.\n' + - '(Press Ctrl+D to exit.)\n'); - self.displayPrompt(); - return; - } - - // If error was SyntaxError and not JSON.parse error - if (e) { - if (e instanceof Recoverable && !sawCtrlD) { - // Start buffering data like that: - // { - // ... x: 1 - // ... } - self[kBufferedCommandSymbol] += cmd + '\n'; + self.output.write('npm should be run outside of the ' + + 'Node.js REPL, in your normal shell.\n' + + '(Press Ctrl+D to exit.)\n'); self.displayPrompt(); return; } - self._domain.emit('error', e.err || e); - } - // Clear buffer if no SyntaxErrors - self.clearBufferedCommand(); - sawCtrlD = false; + // If error was SyntaxError and not JSON.parse error + if (e) { + if (e instanceof Recoverable && !sawCtrlD) { + // Start buffering data like that: + // { + // ... x: 1 + // ... } + self[kBufferedCommandSymbol] += cmd + '\n'; + self.displayPrompt(); + return; + } + self._domain.emit('error', e.err || e); + } - // If we got any output - print it (if no error) - if (!e && + // Clear buffer if no SyntaxErrors + self.clearBufferedCommand(); + sawCtrlD = false; + + // If we got any output - print it (if no error) + if (!e && // When an invalid REPL command is used, error message is printed // immediately. We don't have to print anything else. So, only when // the second argument to this function is there, print it. arguments.length === 2 && (!self.ignoreUndefined || ret !== undefined)) { - if (!self.underscoreAssigned) { - self.last = ret; + if (!self.underscoreAssigned) { + self.last = ret; + } + self.output.write(self.writer(ret) + '\n'); } - self.output.write(self.writer(ret) + '\n'); - } - // Display prompt again (unless we already did by emitting the 'error' - // event on the domain instance). - if (!e) { - self.displayPrompt(); + // Display prompt again (unless we already did by emitting the 'error' + // event on the domain instance). + if (!e) { + self.displayPrompt(); + } } - } - }); - - self.on('SIGCONT', function onSigCont() { - if (self.editorMode) { - self.output.write(`${self._initialPrompt}.editor\n`); - self.output.write( - '// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n'); - self.output.write(`${self[kBufferedCommandSymbol]}\n`); - self.prompt(true); - } else { - self.displayPrompt(true); - } - }); + }); - const { reverseSearch } = setupReverseSearch(this); + self.on('SIGCONT', function onSigCont() { + if (self.editorMode) { + self.output.write(`${self._initialPrompt}.editor\n`); + self.output.write( + '// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n'); + self.output.write(`${self[kBufferedCommandSymbol]}\n`); + self.prompt(true); + } else { + self.displayPrompt(true); + } + }); - const { - clearPreview, - showPreview, - } = setupPreview( - this, - kContextId, - kBufferedCommandSymbol, - preview, - ); + const { reverseSearch } = setupReverseSearch(this); - // Wrap readline tty to enable editor mode and pausing. - const ttyWrite = FunctionPrototypeBind(self._ttyWrite, self); - self._ttyWrite = (d, key) => { - key = key || {}; - if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) { - ArrayPrototypePush(pausedBuffer, - ['key', [d, key], self.isCompletionEnabled]); - return; - } - if (!self.editorMode || !self.terminal) { - // Before exiting, make sure to clear the line. - if (key.ctrl && key.name === 'd' && - self.cursor === 0 && self.line.length === 0) { - self.clearLine(); - } - clearPreview(key); - if (!reverseSearch(d, key)) { - ttyWrite(d, key); - const showCompletionPreview = key.name !== 'escape'; - showPreview(showCompletionPreview); - } - return; - } + const { + clearPreview, showPreview, + } = setupPreview( + this, + kContextId, + kBufferedCommandSymbol, + preview, + ); - // Editor mode - if (key.ctrl && !key.shift) { - switch (key.name) { - // TODO(BridgeAR): There should not be a special mode necessary for full - // multiline support. - case 'd': // End editor mode - _turnOffEditorMode(self); - sawCtrlD = true; - ttyWrite(d, { name: 'return' }); - break; - case 'n': // Override next history item - case 'p': // Override previous history item - break; - default: - ttyWrite(d, key); + // Wrap readline tty to enable editor mode and pausing. + const ttyWrite = FunctionPrototypeBind(self._ttyWrite, self); + self._ttyWrite = (d, key) => { + key = key || {}; + if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) { + ArrayPrototypePush(pausedBuffer, + ['key', [d, key], self.isCompletionEnabled]); + return; } - } else { - switch (key.name) { - case 'up': // Override previous history item - case 'down': // Override next history item - break; - case 'tab': - // Prevent double tab behavior - self._previousKey = null; - ttyWrite(d, key); - break; - default: + if (!self.editorMode || !self.terminal) { + // Before exiting, make sure to clear the line. + if (key.ctrl && key.name === 'd' && + self.cursor === 0 && self.line.length === 0) { + self.clearLine(); + } + clearPreview(key); + if (!reverseSearch(d, key)) { ttyWrite(d, key); + const showCompletionPreview = key.name !== 'escape'; + showPreview(showCompletionPreview); + } + return; } - } - }; - self.displayPrompt(); -} -ObjectSetPrototypeOf(REPLServer.prototype, Interface.prototype); -ObjectSetPrototypeOf(REPLServer, Interface); - -// Prompt is a string to print on each line for the prompt, -// source is a stream to use for I/O, defaulting to stdin/stdout. -function start(prompt, source, eval_, useGlobal, ignoreUndefined, replMode) { - return new REPLServer( - prompt, source, eval_, useGlobal, ignoreUndefined, replMode); -} - -REPLServer.prototype.setupHistory = function setupHistory(historyFile, cb) { - history(this, historyFile, cb); -}; + // Editor mode + if (key.ctrl && !key.shift) { + switch (key.name) { + // TODO(BridgeAR): There should not be a special mode necessary for full + // multiline support. + case 'd': // End editor mode + _turnOffEditorMode(self); + sawCtrlD = true; + ttyWrite(d, { name: 'return' }); + break; + case 'n': // Override next history item + case 'p': // Override previous history item + break; + default: + ttyWrite(d, key); + } + } else { + switch (key.name) { + case 'up': // Override previous history item + case 'down': // Override next history item + break; + case 'tab': + // Prevent double tab behavior + self._previousKey = null; + ttyWrite(d, key); + break; + default: + ttyWrite(d, key); + } + } + }; -REPLServer.prototype.clearBufferedCommand = function clearBufferedCommand() { - this[kBufferedCommandSymbol] = ''; -}; + self.displayPrompt(); + } + setupHistory(historyFile, cb) { + history(this, historyFile, cb); + } + clearBufferedCommand() { + this[kBufferedCommandSymbol] = ''; + } + close() { + if (this.terminal && this._flushing && !this._closingOnFlush) { + this._closingOnFlush = true; + this.once('flushHistory', () => ReflectApply(Interface.prototype.close, this, []), + ); -REPLServer.prototype.close = function close() { - if (this.terminal && this._flushing && !this._closingOnFlush) { - this._closingOnFlush = true; - this.once('flushHistory', () => - ReflectApply(Interface.prototype.close, this, []), + return; + } + process.nextTick(() => ReflectApply(Interface.prototype.close, this, []), ); - - return; } - process.nextTick(() => - ReflectApply(Interface.prototype.close, this, []), - ); -}; - -REPLServer.prototype.createContext = function() { - let context; - if (this.useGlobal) { - context = globalThis; - } else { - sendInspectorCommand((session) => { - session.post('Runtime.enable'); - session.once('Runtime.executionContextCreated', ({ params }) => { - this[kContextId] = params.context.id; + createContext() { + let context; + if (this.useGlobal) { + context = globalThis; + } else { + sendInspectorCommand((session) => { + session.post('Runtime.enable'); + session.once('Runtime.executionContextCreated', ({ params }) => { + this[kContextId] = params.context.id; + }); + context = vm.createContext(); + session.post('Runtime.disable'); + }, () => { + context = vm.createContext(); }); - context = vm.createContext(); - session.post('Runtime.disable'); - }, () => { - context = vm.createContext(); - }); - ArrayPrototypeForEach(ObjectGetOwnPropertyNames(globalThis), (name) => { - // Only set properties that do not already exist as a global builtin. - if (!globalBuiltins.has(name)) { - ObjectDefineProperty(context, name, - { - __proto__: null, - ...ObjectGetOwnPropertyDescriptor(globalThis, name), - }); - } + ArrayPrototypeForEach(ObjectGetOwnPropertyNames(globalThis), (name) => { + // Only set properties that do not already exist as a global builtin. + if (!globalBuiltins.has(name)) { + ObjectDefineProperty(context, name, + { + __proto__: null, + ...ObjectGetOwnPropertyDescriptor(globalThis, name), + }); + } + }); + context.global = context; + const _console = new Console(this.output); + ObjectDefineProperty(context, 'console', { + __proto__: null, + configurable: true, + writable: true, + value: _console, + }); + } + + const replModule = new CJSModule(''); + replModule.paths = CJSModule._resolveLookupPaths('', parentModule); + + ObjectDefineProperty(context, 'module', { + __proto__: null, + configurable: true, + writable: true, + value: replModule, }); - context.global = context; - const _console = new Console(this.output); - ObjectDefineProperty(context, 'console', { + ObjectDefineProperty(context, 'require', { __proto__: null, configurable: true, writable: true, - value: _console, + value: makeRequireFunction(replModule), }); - } - const replModule = new CJSModule(''); - replModule.paths = CJSModule._resolveLookupPaths('', parentModule); + addBuiltinLibsToObject(context, ''); - ObjectDefineProperty(context, 'module', { - __proto__: null, - configurable: true, - writable: true, - value: replModule, - }); - ObjectDefineProperty(context, 'require', { - __proto__: null, - configurable: true, - writable: true, - value: makeRequireFunction(replModule), - }); + return context; + } + resetContext() { + this.context = this.createContext(); + this.underscoreAssigned = false; + this.underscoreErrAssigned = false; + // TODO(BridgeAR): Deprecate the lines. + this.lines = []; + this.lines.level = []; + + ObjectDefineProperty(this.context, '_', { + __proto__: null, + configurable: true, + get: () => this.last, + set: (value) => { + this.last = value; + if (!this.underscoreAssigned) { + this.underscoreAssigned = true; + this.output.write('Expression assignment to _ now disabled.\n'); + } + }, + }); - addBuiltinLibsToObject(context, ''); + ObjectDefineProperty(this.context, '_error', { + __proto__: null, + configurable: true, + get: () => this.lastError, + set: (value) => { + this.lastError = value; + if (!this.underscoreErrAssigned) { + this.underscoreErrAssigned = true; + this.output.write( + 'Expression assignment to _error now disabled.\n'); + } + }, + }); - return context; -}; + // Allow REPL extensions to extend the new context + this.emit('reset', this.context); + } + displayPrompt(preserveCursor) { + let prompt = this._initialPrompt; + if (this[kBufferedCommandSymbol].length) { + prompt = '...'; + const len = this.lines.level.length ? this.lines.level.length - 1 : 0; + const levelInd = StringPrototypeRepeat('..', len); + prompt += levelInd + ' '; + } -REPLServer.prototype.resetContext = function() { - this.context = this.createContext(); - this.underscoreAssigned = false; - this.underscoreErrAssigned = false; - // TODO(BridgeAR): Deprecate the lines. - this.lines = []; - this.lines.level = []; - - ObjectDefineProperty(this.context, '_', { - __proto__: null, - configurable: true, - get: () => this.last, - set: (value) => { - this.last = value; - if (!this.underscoreAssigned) { - this.underscoreAssigned = true; - this.output.write('Expression assignment to _ now disabled.\n'); - } - }, - }); + // Do not overwrite `_initialPrompt` here + ReflectApply(Interface.prototype.setPrompt, this, [prompt]); + this.prompt(preserveCursor); + } + // When invoked as an API method, overwrite _initialPrompt + setPrompt(prompt) { + this._initialPrompt = prompt; + ReflectApply(Interface.prototype.setPrompt, this, [prompt]); + } + complete() { + ReflectApply(this.completer, this, arguments); + } + completeOnEditorMode(callback) { + return (err, results) => { + if (err) return callback(err); - ObjectDefineProperty(this.context, '_error', { - __proto__: null, - configurable: true, - get: () => this.lastError, - set: (value) => { - this.lastError = value; - if (!this.underscoreErrAssigned) { - this.underscoreErrAssigned = true; - this.output.write( - 'Expression assignment to _error now disabled.\n'); - } - }, - }); + const { 0: completions, 1: completeOn = '' } = results; + let result = ArrayPrototypeFilter(completions, Boolean); - // Allow REPL extensions to extend the new context - this.emit('reset', this.context); -}; + if (completeOn && result.length !== 0) { + result = [commonPrefix(result)]; + } -REPLServer.prototype.displayPrompt = function(preserveCursor) { - let prompt = this._initialPrompt; - if (this[kBufferedCommandSymbol].length) { - prompt = '...'; - const len = this.lines.level.length ? this.lines.level.length - 1 : 0; - const levelInd = StringPrototypeRepeat('..', len); - prompt += levelInd + ' '; + callback(null, [result, completeOn]); + }; } + defineCommand(keyword, cmd) { + if (typeof cmd === 'function') { + cmd = { action: cmd }; + } else { + validateFunction(cmd.action, 'cmd.action'); + } + this.commands[keyword] = cmd; + } +} - // Do not overwrite `_initialPrompt` here - ReflectApply(Interface.prototype.setPrompt, this, [prompt]); - this.prompt(preserveCursor); -}; - -// When invoked as an API method, overwrite _initialPrompt -REPLServer.prototype.setPrompt = function setPrompt(prompt) { - this._initialPrompt = prompt; - ReflectApply(Interface.prototype.setPrompt, this, [prompt]); -}; +// Prompt is a string to print on each line for the prompt, +// source is a stream to use for I/O, defaulting to stdin/stdout. +function start(prompt, source, eval_, useGlobal, ignoreUndefined, replMode) { + return new REPLServer( + prompt, source, eval_, useGlobal, ignoreUndefined, replMode); +} const importRE = /\bimport\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/; const requireRE = /\brequire\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/; @@ -1274,9 +1275,6 @@ function getGlobalLexicalScopeNames(contextId) { }, () => []); } -REPLServer.prototype.complete = function() { - ReflectApply(this.completer, this, arguments); -}; function gracefulReaddir(...args) { try { @@ -1616,28 +1614,6 @@ function complete(line, callback) { } } -REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => { - if (err) return callback(err); - - const { 0: completions, 1: completeOn = '' } = results; - let result = ArrayPrototypeFilter(completions, Boolean); - - if (completeOn && result.length !== 0) { - result = [commonPrefix(result)]; - } - - callback(null, [result, completeOn]); -}; - -REPLServer.prototype.defineCommand = function(keyword, cmd) { - if (typeof cmd === 'function') { - cmd = { action: cmd }; - } else { - validateFunction(cmd.action, 'cmd.action'); - } - this.commands[keyword] = cmd; -}; - // TODO(BridgeAR): This should be replaced with acorn to build an AST. The // language became more complex and using a simple approach like this is not // sufficient anymore. @@ -1854,10 +1830,17 @@ function Recoverable(err) { ObjectSetPrototypeOf(Recoverable.prototype, SyntaxErrorPrototype); ObjectSetPrototypeOf(Recoverable, SyntaxError); +function createREPLServer(prompt, source, eval_, useGlobal, ignoreUndefined, replMode) { + return new REPLServer(prompt, source, eval_, useGlobal, ignoreUndefined, replMode); +} + +ObjectSetPrototypeOf(createREPLServer.prototype, REPLServer); +ObjectSetPrototypeOf(createREPLServer, REPLServer); + module.exports = { start, writer, - REPLServer, + REPLServer: createREPLServer, REPL_MODE_SLOPPY, REPL_MODE_STRICT, Recoverable,