From d7d648468d31653d82873dd6266497ae98f299ac Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 16 Nov 2020 11:12:25 +0100 Subject: [PATCH] repl: refactor to use more primordials --- lib/internal/repl/await.js | 15 +- lib/internal/repl/history.js | 14 +- lib/internal/repl/utils.js | 72 ++++++--- lib/repl.js | 290 +++++++++++++++++++++-------------- 4 files changed, 242 insertions(+), 149 deletions(-) diff --git a/lib/internal/repl/await.js b/lib/internal/repl/await.js index da6c7a3a17da99..8dc1f5de4c7ee6 100644 --- a/lib/internal/repl/await.js +++ b/lib/internal/repl/await.js @@ -1,6 +1,11 @@ 'use strict'; const { + ArrayFrom, + ArrayPrototypeJoin, + ArrayPrototypePop, + ArrayPrototypePush, + FunctionPrototype, ObjectKeys, } = primordials; @@ -19,7 +24,7 @@ const parser = acorn.Parser.extend( staticClassFeatures ); -const noop = () => {}; +const noop = FunctionPrototype; const visitorsWithoutAncestors = { ClassDeclaration(node, state, c) { if (state.ancestors[state.ancestors.length - 2] === state.body) { @@ -76,18 +81,18 @@ for (const nodeType of ObjectKeys(walk.base)) { visitors[nodeType] = (node, state, c) => { const isNew = node !== state.ancestors[state.ancestors.length - 1]; if (isNew) { - state.ancestors.push(node); + ArrayPrototypePush(state.ancestors, node); } callback(node, state, c); if (isNew) { - state.ancestors.pop(); + ArrayPrototypePop(state.ancestors); } }; } function processTopLevelAwait(src) { const wrapped = `(async () => { ${src} })()`; - const wrappedArray = wrapped.split(''); + const wrappedArray = ArrayFrom(wrapped); let root; try { root = parser.parse(wrapped, { ecmaVersion: 'latest' }); @@ -142,7 +147,7 @@ function processTopLevelAwait(src) { state.append(last.expression, ')'); } - return wrappedArray.join(''); + return ArrayPrototypeJoin(wrappedArray, ''); } module.exports = { diff --git a/lib/internal/repl/history.js b/lib/internal/repl/history.js index 5188af947581b4..74ef94e81070dc 100644 --- a/lib/internal/repl/history.js +++ b/lib/internal/repl/history.js @@ -1,7 +1,11 @@ 'use strict'; const { + ArrayPrototypeJoin, Boolean, + FunctionPrototype, + StringPrototypeSplit, + StringPrototypeTrim, } = primordials; const { Interface } = require('readline'); @@ -13,6 +17,8 @@ let debug = require('internal/util/debuglog').debuglog('repl', (fn) => { }); const { clearTimeout, setTimeout } = require('timers'); +const noop = FunctionPrototype; + // XXX(chrisdickinson): The 15ms debounce value is somewhat arbitrary. // The debounce is to guard against code pasted into the REPL. const kDebounceHistoryMS = 15; @@ -27,7 +33,7 @@ function _writeToOutput(repl, message) { function setupHistory(repl, historyPath, ready) { // Empty string disables persistent history if (typeof historyPath === 'string') - historyPath = historyPath.trim(); + historyPath = StringPrototypeTrim(historyPath); if (historyPath === '') { repl._historyPrev = _replHistoryMessage; @@ -84,7 +90,7 @@ function setupHistory(repl, historyPath, ready) { } if (data) { - repl.history = data.split(/[\n\r]+/, repl.historySize); + repl.history = StringPrototypeSplit(data, /[\n\r]+/, repl.historySize); } else { repl.history = []; } @@ -128,7 +134,7 @@ function setupHistory(repl, historyPath, ready) { return; } writing = true; - const historyData = repl.history.join(os.EOL); + const historyData = ArrayPrototypeJoin(repl.history, os.EOL); fs.write(repl._historyHandle, historyData, 0, 'utf8', onwritten); } @@ -151,7 +157,7 @@ function setupHistory(repl, historyPath, ready) { return; } repl.off('line', online); - fs.close(repl._historyHandle, () => {}); + fs.close(repl._historyHandle, noop); } } diff --git a/lib/internal/repl/utils.js b/lib/internal/repl/utils.js index ac81a08a11451d..e2bda7665a9138 100644 --- a/lib/internal/repl/utils.js +++ b/lib/internal/repl/utils.js @@ -1,8 +1,22 @@ 'use strict'; const { + ArrayPrototypeFilter, + ArrayPrototypeIncludes, + ArrayPrototypeMap, + Boolean, + FunctionPrototypeBind, MathMin, - Set, + RegExpPrototypeTest, + SafeSet, + StringPrototypeEndsWith, + StringPrototypeIndexOf, + StringPrototypeLastIndexOf, + StringPrototypeReplace, + StringPrototypeSlice, + StringPrototypeStartsWith, + StringPrototypeToLowerCase, + StringPrototypeTrim, Symbol, } = primordials; @@ -59,7 +73,9 @@ function isRecoverableError(e, code) { // curly brace with parenthesis. Note: only the open parenthesis is added // here as the point is to test for potentially valid but incomplete // expressions. - if (/^\s*\{/.test(code) && isRecoverableError(e, `(${code}`)) return true; + if (RegExpPrototypeTest(/^\s*\{/, code) && + isRecoverableError(e, `(${code}`)) + return true; let recoverable = false; @@ -99,9 +115,11 @@ function isRecoverableError(e, code) { break; case 'Unterminated string constant': - const token = this.input.slice(this.lastTokStart, this.pos); + const token = StringPrototypeSlice(this.input, + this.lastTokStart, this.pos); // See https://www.ecma-international.org/ecma-262/#sec-line-terminators - if (/\\(?:\r\n?|\n|\u2028|\u2029)$/.test(token)) { + if (RegExpPrototypeTest(/\\(?:\r\n?|\n|\u2028|\u2029)$/, + token)) { recoverable = true; } } @@ -235,7 +253,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { hasCompletions = true; // If there is a common prefix to all matches, then apply that portion. - const completions = rawCompletions.filter((e) => e); + const completions = ArrayPrototypeFilter(rawCompletions, Boolean); const prefix = commonPrefix(completions); // No common prefix found. @@ -243,7 +261,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { return; } - const suffix = prefix.slice(completeOn.length); + const suffix = StringPrototypeSlice(prefix, completeOn.length); if (insertPreview) { repl._insertString(suffix); @@ -271,16 +289,22 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { } function isInStrictMode(repl) { - return repl.replMode === REPL_MODE_STRICT || process.execArgv - .map((e) => e.toLowerCase().replace(/_/g, '-')) - .includes('--use-strict'); + return repl.replMode === REPL_MODE_STRICT || ArrayPrototypeIncludes( + ArrayPrototypeMap(process.execArgv, + (e) => StringPrototypeReplace( + StringPrototypeToLowerCase(e), + /_/g, + '-' + )), + '--use-strict'); } // This returns a code preview for arbitrary input code. function getInputPreview(input, callback) { // For similar reasons as `defaultEval`, wrap expressions starting with a // curly brace with parenthesis. - if (input.startsWith('{') && !input.endsWith(';') && !wrapped) { + if (StringPrototypeStartsWith(input, '{') && + !StringPrototypeEndsWith(input, ';') && !wrapped) { input = `(${input})`; wrapped = true; } @@ -346,7 +370,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { return; } - const line = repl.line.trim(); + const line = StringPrototypeTrim(repl.line); // Do not preview in case the line only contains whitespace. if (line === '') { @@ -412,9 +436,9 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { // Line breaks are very rare and probably only occur in case of error // messages with line breaks. - const lineBreakPos = inspected.indexOf('\n'); + const lineBreakPos = StringPrototypeIndexOf(inspected, '\n'); if (lineBreakPos !== -1) { - inspected = `${inspected.slice(0, lineBreakPos)}`; + inspected = `${StringPrototypeSlice(inspected, 0, lineBreakPos)}`; } const result = repl.useColors ? @@ -452,7 +476,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { // Refresh prints the whole screen again and the preview will be removed // during that procedure. Print the preview again. This also makes sure // the preview is always correct after resizing the terminal window. - const originalRefresh = repl._refreshLine.bind(repl); + const originalRefresh = FunctionPrototypeBind(repl._refreshLine, repl); repl._refreshLine = () => { inputPreview = null; originalRefresh(); @@ -462,7 +486,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { let insertCompletionPreview = true; // Insert the longest common suffix of the current input in case the user // moves to the right while already being at the current input end. - const originalMoveCursor = repl._moveCursor.bind(repl); + const originalMoveCursor = FunctionPrototypeBind(repl._moveCursor, repl); repl._moveCursor = (dx) => { const currentCursor = repl.cursor; originalMoveCursor(dx); @@ -476,7 +500,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { // This is the only function that interferes with the completion insertion. // Monkey patch it to prevent inserting the completion when it shouldn't be. - const originalClearLine = repl.clearLine.bind(repl); + const originalClearLine = FunctionPrototypeBind(repl.clearLine, repl); repl.clearLine = () => { insertCompletionPreview = false; originalClearLine(); @@ -492,7 +516,7 @@ function setupReverseSearch(repl) { return { reverseSearch() { return false; } }; } - const alreadyMatched = new Set(); + const alreadyMatched = new SafeSet(); const labels = { r: 'bck-i-search: ', s: 'fwd-i-search: ' @@ -556,9 +580,9 @@ function setupReverseSearch(repl) { if (cursor === -1) { cursor = entry.length; } - cursor = entry.lastIndexOf(input, cursor - 1); + cursor = StringPrototypeLastIndexOf(entry, input, cursor - 1); } else { - cursor = entry.indexOf(input, cursor + 1); + cursor = StringPrototypeIndexOf(entry, input, cursor + 1); } // Match not found. if (cursor === -1) { @@ -566,8 +590,8 @@ function setupReverseSearch(repl) { // Match found. } else { if (repl.useColors) { - const start = entry.slice(0, cursor); - const end = entry.slice(cursor + input.length); + const start = StringPrototypeSlice(entry, 0, cursor); + const end = StringPrototypeSlice(entry, cursor + input.length); entry = `${start}\x1B[4m${input}\x1B[24m${end}`; } print(entry, `${labels[dir]}${input}_`, cursor); @@ -610,7 +634,7 @@ function setupReverseSearch(repl) { // tick end instead of after each operation. let rows = 0; if (lastMatch !== -1) { - const line = repl.history[lastMatch].slice(0, lastCursor); + const line = StringPrototypeSlice(repl.history[lastMatch], 0, lastCursor); rows = repl._getDisplayPos(`${repl.getPrompt()}${line}`).rows; cursorTo(repl.output, promptPos.cols); } else if (isInReverseSearch && repl.line !== '') { @@ -632,7 +656,7 @@ function setupReverseSearch(repl) { // To know exactly how many rows we have to move the cursor back we need the // cursor rows, the output rows and the input rows. const prompt = repl.getPrompt(); - const cursorLine = `${prompt}${outputLine.slice(0, cursor)}`; + const cursorLine = prompt + StringPrototypeSlice(outputLine, 0, cursor); const cursorPos = repl._getDisplayPos(cursorLine); const outputPos = repl._getDisplayPos(`${prompt}${outputLine}`); const inputPos = repl._getDisplayPos(inputLine); @@ -690,7 +714,7 @@ function setupReverseSearch(repl) { search(); } else if (key.name === 'backspace' || (key.ctrl && (key.name === 'h' || key.name === 'w'))) { - reset(input.slice(0, input.length - 1)); + reset(StringPrototypeSlice(input, 0, input.length - 1)); search(); // Special handle + c and escape. Those should only cancel the // reverse search. The original line is visible afterwards again. diff --git a/lib/repl.js b/lib/repl.js index 78c256d60b5559..3b95bd7f29c85f 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -43,7 +43,22 @@ 'use strict'; const { + ArrayPrototypeConcat, + ArrayPrototypeFilter, + ArrayPrototypeFindIndex, + ArrayPrototypeIncludes, + ArrayPrototypeJoin, + ArrayPrototypeMap, + ArrayPrototypePop, + ArrayPrototypePush, + ArrayPrototypeReverse, + ArrayPrototypeShift, + ArrayPrototypeSort, + ArrayPrototypeSplice, + ArrayPrototypeUnshift, + Boolean, Error, + FunctionPrototypeBind, MathMax, NumberIsNaN, NumberParseFloat, @@ -56,16 +71,30 @@ const { ObjectKeys, ObjectSetPrototypeOf, Promise, + PromisePrototypeFinally, + PromisePrototypeThen, PromiseRace, + ReflectApply, RegExp, - Set, + RegExpPrototypeExec, + RegExpPrototypeTest, + SafeSet, + SafeWeakSet, StringPrototypeCharAt, + StringPrototypeCodePointAt, + StringPrototypeEndsWith, StringPrototypeIncludes, StringPrototypeMatch, + StringPrototypeRepeat, + StringPrototypeReplace, + StringPrototypeSlice, + StringPrototypeSplit, + StringPrototypeStartsWith, + StringPrototypeTrim, + StringPrototypeTrimLeft, Symbol, SyntaxError, SyntaxErrorPrototype, - WeakSet, } = primordials; const { @@ -91,8 +120,10 @@ const { } = require('internal/readline/utils'); const { Console } = require('console'); const CJSModule = require('internal/modules/cjs/loader').Module; -let _builtinLibs = [...CJSModule.builtinModules] - .filter((e) => !e.startsWith('_') && !e.includes('/')); +let _builtinLibs = ArrayPrototypeFilter( + CJSModule.builtinModules, + (e) => !StringPrototypeStartsWith(e, '_') && !StringPrototypeIncludes(e, '/') +); const domain = require('domain'); let debug = require('internal/util/debuglog').debuglog('repl', (fn) => { debug = fn; @@ -145,10 +176,10 @@ function getREPLResourceName() { let processTopLevelAwait; const globalBuiltins = - new Set(vm.runInNewContext('Object.getOwnPropertyNames(globalThis)')); + new SafeSet(vm.runInNewContext('Object.getOwnPropertyNames(globalThis)')); const parentModule = module; -const domainSet = new WeakSet(); +const domainSet = new SafeWeakSet(); const kBufferedCommandSymbol = Symbol('bufferedCommand'); const kContextId = Symbol('contextId'); @@ -335,7 +366,7 @@ function REPLServer(prompt, paused = false; let entry; const tmpCompletionEnabled = self.isCompletionEnabled; - while (entry = pausedBuffer.shift()) { + while (entry = ArrayPrototypeShift(pausedBuffer)) { const [type, payload, isCompletionEnabled] = entry; switch (type) { case 'key': { @@ -369,12 +400,13 @@ function REPLServer(prompt, // 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 (/^\s*{/.test(code) && !/;\s*$/.test(code)) { - code = `(${code.trim()})\n`; + if (RegExpPrototypeTest(/^\s*{/, code) && + !RegExpPrototypeTest(/;\s*$/, code)) { + code = `(${StringPrototypeTrim(code)})\n`; wrappedCmd = true; } - if (experimentalREPLAwait && code.includes('await')) { + if (experimentalREPLAwait && StringPrototypeIncludes(code, 'await')) { if (processTopLevelAwait === undefined) { ({ processTopLevelAwait } = require('internal/repl/await')); } @@ -402,7 +434,7 @@ function REPLServer(prompt, while (true) { try { if (self.replMode === module.exports.REPL_MODE_STRICT && - !/^\s*$/.test(code)) { + !RegExpPrototypeTest(/^\s*$/, code)) { // "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}`; @@ -436,7 +468,8 @@ function REPLServer(prompt, // This will set the values from `savedRegExMatches` to corresponding // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9` - regExMatcher.test(savedRegExMatches.join(sep)); + RegExpPrototypeTest(regExMatcher, + ArrayPrototypeJoin(savedRegExMatches, sep)); let finished = false; function finishExecution(err, result) { @@ -516,7 +549,7 @@ function REPLServer(prompt, promise = PromiseRace([promise, interrupt]); } - promise.then((result) => { + PromisePrototypeFinally(PromisePrototypeThen(promise, (result) => { finishExecution(null, result); }, (err) => { if (err && process.domain) { @@ -526,7 +559,7 @@ function REPLServer(prompt, return; } finishExecution(err); - }).finally(() => { + }), () => { // Remove prioritized SIGINT listener if it was not called. prioritizedSigintQueue.delete(sigintListener); unpause(); @@ -551,11 +584,12 @@ function REPLServer(prompt, if (typeof stackFrames === 'object') { // Search from the bottom of the call stack to // find the first frame with a null function name - const idx = stackFrames - .reverse() - .findIndex((frame) => frame.getFunctionName() === null); + const idx = ArrayPrototypeFindIndex( + ArrayPrototypeReverse(stackFrames), + (frame) => frame.getFunctionName() === null + ); // If found, get rid of it and everything below it - frames = stackFrames.splice(idx + 1); + frames = ArrayPrototypeSplice(stackFrames, idx + 1); } else { frames = stackFrames; } @@ -565,8 +599,8 @@ function REPLServer(prompt, if (typeof Error.prepareStackTrace === 'function') { return Error.prepareStackTrace(error, frames); } - frames.push(error); - return frames.reverse().join('\n at '); + ArrayPrototypePush(frames, error); + return ArrayPrototypeJoin(ArrayPrototypeReverse(frames), '\n at '); }); decorateErrorStack(e); @@ -579,27 +613,31 @@ function REPLServer(prompt, if (e.stack) { if (e.name === 'SyntaxError') { // Remove stack trace. - e.stack = e.stack - .replace(/^REPL\d+:\d+\r?\n/, '') - .replace(/^\s+at\s.*\n?/gm, ''); + e.stack = StringPrototypeReplace(StringPrototypeReplace(e.stack, + /^REPL\d+:\d+\r?\n/, ''), + /^\s+at\s.*\n?/gm, ''); 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'; - e.stack = e.stack.replace(/SyntaxError:.*\n/, - `SyntaxError: ${e.message}\n`); + e.stack = StringPrototypeReplace(e.stack, + /SyntaxError:.*\n/, + `SyntaxError: ${e.message}\n`); } } else if (self.replMode === module.exports.REPL_MODE_STRICT) { - e.stack = e.stack.replace(/(\s+at\s+REPL\d+:)(\d+)/, - (_, pre, line) => pre + (line - 1)); + e.stack = StringPrototypeReplace( + e.stack, + /(\s+at\s+REPL\d+:)(\d+)/, + (_, pre, line) => pre + (line - 1) + ); } } errStack = self.writer(e); // Remove one line error braces to keep the old style in place. if (errStack[errStack.length - 1] === ']') { - errStack = errStack.slice(1, -1); + errStack = StringPrototypeSlice(errStack, 1, -1); } } } @@ -620,12 +658,13 @@ function REPLServer(prompt, if (errStack === '') { errStack = self.writer(e); } - const lines = errStack.split(/(?<=\n)/); + const lines = StringPrototypeSplit(errStack, /(?<=\n)/); let matched = false; errStack = ''; for (const line of lines) { - if (!matched && /^\[?([A-Z][a-z0-9_]*)*Error/.test(line)) { + if (!matched && + RegExpPrototypeTest(/^\[?([A-Z][a-z0-9_]*)*Error/, line)) { errStack += writer.options.breakLength >= line.length ? `Uncaught ${line}` : `Uncaught:\n${line}`; @@ -639,7 +678,7 @@ function REPLServer(prompt, errStack = `Uncaught${ln}${errStack}`; } // Normalize line endings. - errStack += errStack.endsWith('\n') ? '' : '\n'; + errStack += StringPrototypeEndsWith(errStack, '\n') ? '' : '\n'; self.output.write(errStack); self.clearBufferedCommand(); self.lines.level = []; @@ -650,18 +689,18 @@ function REPLServer(prompt, self.clearBufferedCommand(); function completer(text, cb) { - complete.call(self, text, self.editorMode ? - self.completeOnEditorMode(cb) : cb); + ReflectApply(complete, self, + [text, self.editorMode ? self.completeOnEditorMode(cb) : cb]); } - Interface.call(this, { + ReflectApply(Interface, this, [{ input: options.input, output: options.output, completer: options.completer || completer, terminal: options.terminal, historySize: options.historySize, prompt - }); + }]); self.resetContext(); @@ -695,7 +734,7 @@ function REPLServer(prompt, function _parseREPLKeyword(keyword, rest) { const cmd = this.commands[keyword]; if (cmd) { - cmd.action.call(this, rest); + ReflectApply(cmd.action, this, [rest]); return true; } return false; @@ -703,7 +742,7 @@ function REPLServer(prompt, self.on('close', function emitExit() { if (paused) { - pausedBuffer.push(['close']); + ArrayPrototypePush(pausedBuffer, ['close']); return; } self.emit('exit'); @@ -711,7 +750,7 @@ function REPLServer(prompt, let sawSIGINT = false; let sawCtrlD = false; - const prioritizedSigintQueue = new Set(); + const prioritizedSigintQueue = new SafeSet(); self.on('SIGINT', function onSigInt() { if (prioritizedSigintQueue.size > 0) { for (const task of prioritizedSigintQueue) { @@ -753,19 +792,20 @@ function REPLServer(prompt, self[kBufferedCommandSymbol] += cmd + '\n'; // code alignment - const matches = self._sawKeyPress ? cmd.match(/^\s+/) : null; + const matches = self._sawKeyPress ? + StringPrototypeMatch(cmd, /^\s+/) : null; if (matches) { const prefix = matches[0]; self.write(prefix); self.line = prefix; self.cursor = prefix.length; } - _memory.call(self, cmd); + ReflectApply(_memory, self, [cmd]); return; } // Check REPL keywords and empty lines against a trimmed line input. - const trimmedCmd = cmd.trim(); + const trimmedCmd = StringPrototypeTrim(cmd); // Check to see if a REPL keyword was used. If it returns true, // display next prompt and return. @@ -776,7 +816,7 @@ function REPLServer(prompt, const matches = StringPrototypeMatch(trimmedCmd, /^\.([^\s]+)\s*(.*)$/); const keyword = matches && matches[1]; const rest = matches && matches[2]; - if (_parseREPLKeyword.call(self, keyword, rest) === true) { + if (ReflectApply(_parseREPLKeyword, self, [keyword, rest]) === true) { return; } if (!self[kBufferedCommandSymbol]) { @@ -794,9 +834,10 @@ function REPLServer(prompt, function finish(e, ret) { debug('finish', e, ret); - _memory.call(self, cmd); + ReflectApply(_memory, self, [cmd]); - if (e && !self[kBufferedCommandSymbol] && cmd.trim().startsWith('npm ')) { + 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'); @@ -865,11 +906,12 @@ function REPLServer(prompt, ); // Wrap readline tty to enable editor mode and pausing. - const ttyWrite = self._ttyWrite.bind(self); + const ttyWrite = FunctionPrototypeBind(self._ttyWrite, self); self._ttyWrite = (d, key) => { key = key || {}; if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) { - pausedBuffer.push(['key', [d, key], self.isCompletionEnabled]); + ArrayPrototypePush(pausedBuffer, + ['key', [d, key], self.isCompletionEnabled]); return; } if (!self.editorMode || !self.terminal) { @@ -942,13 +984,13 @@ REPLServer.prototype.close = function close() { if (this.terminal && this._flushing && !this._closingOnFlush) { this._closingOnFlush = true; this.once('flushHistory', () => - Interface.prototype.close.call(this) + ReflectApply(Interface.prototype.close, this, []) ); return; } process.nextTick(() => - Interface.prototype.close.call(this) + ReflectApply(Interface.prototype.close, this, []) ); }; @@ -1044,19 +1086,19 @@ REPLServer.prototype.displayPrompt = function(preserveCursor) { if (this[kBufferedCommandSymbol].length) { prompt = '...'; const len = this.lines.level.length ? this.lines.level.length - 1 : 0; - const levelInd = '..'.repeat(len); + const levelInd = StringPrototypeRepeat('..', len); prompt += levelInd + ' '; } // Do not overwrite `_initialPrompt` here - Interface.prototype.setPrompt.call(this, prompt); + 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; - Interface.prototype.setPrompt.call(this, prompt); + ReflectApply(Interface.prototype.setPrompt, this, [prompt]); }; const requireRE = /\brequire\s*\(\s*['"`](([\w@./-]+\/)?(?:[\w@./-]*))(?![^'"`])$/; @@ -1068,13 +1110,13 @@ function isIdentifier(str) { if (str === '') { return false; } - const first = str.codePointAt(0); + const first = StringPrototypeCodePointAt(str, 0); if (!isIdentifierStart(first)) { return false; } const firstLen = first > 0xffff ? 2 : 1; for (let i = firstLen; i < str.length; i += 1) { - const cp = str.codePointAt(i); + const cp = StringPrototypeCodePointAt(str, i); if (!isIdentifierChar(cp)) { return false; } @@ -1088,7 +1130,8 @@ function isIdentifier(str) { function filteredOwnPropertyNames(obj) { if (!obj) return []; const filter = ALL_PROPERTIES | SKIP_SYMBOLS; - return getOwnNonIndexProperties(obj, filter).filter(isIdentifier); + return ArrayPrototypeFilter(getOwnNonIndexProperties(obj, filter), + isIdentifier); } function getGlobalLexicalScopeNames(contextId) { @@ -1104,7 +1147,7 @@ function getGlobalLexicalScopeNames(contextId) { } REPLServer.prototype.complete = function() { - this.completer.apply(this, arguments); + ReflectApply(this.completer, this, arguments); }; function gracefulReaddir(...args) { @@ -1115,7 +1158,7 @@ function gracefulReaddir(...args) { function completeFSFunctions(line) { let baseName = ''; - let filePath = line.match(fsAutoCompleteRE)[1]; + let filePath = StringPrototypeMatch(line, fsAutoCompleteRE)[1]; let fileList = gracefulReaddir(filePath, { withFileTypes: true }); if (!fileList) { @@ -1124,9 +1167,13 @@ function completeFSFunctions(line) { fileList = gracefulReaddir(filePath, { withFileTypes: true }) || []; } - const completions = fileList - .filter((dirent) => dirent.name.startsWith(baseName)) - .map((d) => d.name); + const completions = ArrayPrototypeMap( + ArrayPrototypeFilter( + fileList, + (dirent) => StringPrototypeStartsWith(dirent.name, baseName) + ), + (d) => d.name + ); return [[completions], baseName]; } @@ -1147,24 +1194,25 @@ function complete(line, callback) { let completeOn, group; // Ignore right whitespace. It could change the outcome. - line = line.trimLeft(); + line = StringPrototypeTrimLeft(line); // REPL commands (e.g. ".break"). let filter = ''; - if (/^\s*\.(\w*)$/.test(line)) { - completionGroups.push(ObjectKeys(this.commands)); - completeOn = line.match(/^\s*\.(\w*)$/)[1]; + if (RegExpPrototypeTest(/^\s*\.(\w*)$/, line)) { + ArrayPrototypePush(completionGroups, ObjectKeys(this.commands)); + completeOn = StringPrototypeMatch(line, /^\s*\.(\w*)$/)[1]; if (completeOn.length) { filter = completeOn; } - } else if (requireRE.test(line)) { + } else if (RegExpPrototypeTest(requireRE, line)) { // require('...') const extensions = ObjectKeys(this.context.require.extensions); - const indexes = extensions.map((extension) => `index${extension}`); - indexes.push('package.json', 'index'); + const indexes = ArrayPrototypeMap(extensions, + (extension) => `index${extension}`); + ArrayPrototypePush(indexes, 'package.json', 'index'); const versionedFileNamesRe = /-\d+\.\d+/; - const match = line.match(requireRE); + const match = StringPrototypeMatch(line, requireRE); completeOn = match[1]; const subdir = match[2] || ''; filter = completeOn; @@ -1175,47 +1223,49 @@ function complete(line, callback) { group = ['./', '../']; } else if (completeOn === '..') { group = ['../']; - } else if (/^\.\.?\//.test(completeOn)) { + } else if (RegExpPrototypeTest(/^\.\.?\//, completeOn)) { paths = [process.cwd()]; } else { - paths = module.paths.concat(CJSModule.globalPaths); + paths = ArrayPrototypeConcat(module.paths, CJSModule.globalPaths); } for (let dir of paths) { dir = path.resolve(dir, subdir); const dirents = gracefulReaddir(dir, { withFileTypes: true }) || []; for (const dirent of dirents) { - if (versionedFileNamesRe.test(dirent.name) || dirent.name === '.npm') { + if (RegExpPrototypeTest(versionedFileNamesRe, dirent.name) || + dirent.name === '.npm') { // Exclude versioned names that 'npm' installs. continue; } const extension = path.extname(dirent.name); - const base = dirent.name.slice(0, -extension.length); + const base = StringPrototypeSlice(dirent.name, 0, -extension.length); if (!dirent.isDirectory()) { - if (extensions.includes(extension) && (!subdir || base !== 'index')) { - group.push(`${subdir}${base}`); + if (StringPrototypeIncludes(extensions, extension) && + (!subdir || base !== 'index')) { + ArrayPrototypePush(group, `${subdir}${base}`); } continue; } - group.push(`${subdir}${dirent.name}/`); + ArrayPrototypePush(group, `${subdir}${dirent.name}/`); const absolute = path.resolve(dir, dirent.name); const subfiles = gracefulReaddir(absolute) || []; for (const subfile of subfiles) { - if (indexes.includes(subfile)) { - group.push(`${subdir}${dirent.name}`); + if (ArrayPrototypeIncludes(indexes, subfile)) { + ArrayPrototypePush(group, `${subdir}${dirent.name}`); break; } } } } if (group.length) { - completionGroups.push(group); + ArrayPrototypePush(completionGroups, group); } if (!subdir) { - completionGroups.push(_builtinLibs); + ArrayPrototypePush(completionGroups, _builtinLibs); } - } else if (fsAutoCompleteRE.test(line)) { + } else if (RegExpPrototypeTest(fsAutoCompleteRE, line)) { [completionGroups, completeOn] = completeFSFunctions(line); // Handle variable member lookup. // We support simple chained expressions like the following (no function @@ -1227,45 +1277,48 @@ function complete(line, callback) { // spam.eggs.<|> # completions for 'spam.eggs' with filter '' // foo<|> # all scope vars with filter 'foo' // foo.<|> # completions for 'foo' with filter '' - } else if (line.length === 0 || /\w|\.|\$/.test(line[line.length - 1])) { - const [match] = simpleExpressionRE.exec(line) || ['']; + } else if (line.length === 0 || + RegExpPrototypeTest(/\w|\.|\$/, line[line.length - 1])) { + const [match] = RegExpPrototypeExec(simpleExpressionRE, line) || ['']; if (line.length !== 0 && !match) { completionGroupsLoaded(); return; } let expr = ''; completeOn = match; - if (line.endsWith('.')) { - expr = match.slice(0, -1); + if (StringPrototypeEndsWith(line, '.')) { + expr = StringPrototypeSlice(match, 0, -1); } else if (line.length !== 0) { - const bits = match.split('.'); - filter = bits.pop(); - expr = bits.join('.'); + const bits = StringPrototypeSplit(match, '.'); + filter = ArrayPrototypePop(bits); + expr = ArrayPrototypeJoin(bits, '.'); } // Resolve expr and get its completions. if (!expr) { // Get global vars synchronously - completionGroups.push(getGlobalLexicalScopeNames(this[kContextId])); + ArrayPrototypePush(completionGroups, + getGlobalLexicalScopeNames(this[kContextId])); let contextProto = this.context; while (contextProto = ObjectGetPrototypeOf(contextProto)) { - completionGroups.push(filteredOwnPropertyNames(contextProto)); + ArrayPrototypePush(completionGroups, + filteredOwnPropertyNames(contextProto)); } const contextOwnNames = filteredOwnPropertyNames(this.context); if (!this.useGlobal) { // When the context is not `global`, builtins are not own // properties of it. - contextOwnNames.push(...globalBuiltins); + ArrayPrototypePush(contextOwnNames, ...globalBuiltins); } - completionGroups.push(contextOwnNames); + ArrayPrototypePush(completionGroups, contextOwnNames); if (filter !== '') addCommonWords(completionGroups); completionGroupsLoaded(); return; } let chaining = '.'; - if (expr.endsWith('?')) { - expr = expr.slice(0, -1); + if (StringPrototypeEndsWith(expr, '?')) { + expr = StringPrototypeSlice(expr, 0, -1); chaining = '?.'; } @@ -1297,7 +1350,9 @@ function complete(line, callback) { if (memberGroups.length) { expr += chaining; for (const group of memberGroups) { - completionGroups.push(group.map((member) => `${expr}${member}`)); + ArrayPrototypePush(completionGroups, + ArrayPrototypeMap(group, + (member) => `${expr}${member}`)); } if (filter) { filter = `${expr}${filter}`; @@ -1318,9 +1373,12 @@ function complete(line, callback) { if (completionGroups.length && filter) { const newCompletionGroups = []; for (const group of completionGroups) { - const filteredGroup = group.filter((str) => str.startsWith(filter)); + const filteredGroup = ArrayPrototypeFilter( + group, + (str) => StringPrototypeStartsWith(str, filter) + ); if (filteredGroup.length) { - newCompletionGroups.push(filteredGroup); + ArrayPrototypePush(newCompletionGroups, filteredGroup); } } completionGroups = newCompletionGroups; @@ -1328,27 +1386,27 @@ function complete(line, callback) { const completions = []; // Unique completions across all groups. - const uniqueSet = new Set(['']); + const uniqueSet = new SafeSet(['']); // Completion group 0 is the "closest" (least far up the inheritance // chain) so we put its completions last: to be closest in the REPL. for (const group of completionGroups) { - group.sort((a, b) => (b > a ? 1 : -1)); + ArrayPrototypeSort(group, (a, b) => (b > a ? 1 : -1)); const setSize = uniqueSet.size; for (const entry of group) { if (!uniqueSet.has(entry)) { - completions.unshift(entry); + ArrayPrototypeUnshift(completions, entry); uniqueSet.add(entry); } } // Add a separator between groups. if (uniqueSet.size !== setSize) { - completions.unshift(''); + ArrayPrototypeUnshift(completions, ''); } } // Remove obsolete group entry, if present. if (completions[0] === '') { - completions.shift(); + ArrayPrototypeShift(completions); } callback(null, [completions, completeOn]); @@ -1359,7 +1417,7 @@ REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => { if (err) return callback(err); const [completions, completeOn = ''] = results; - let result = completions.filter((v) => v); + let result = ArrayPrototypeFilter(completions, Boolean); if (completeOn && result.length !== 0) { result = [commonPrefix(result)]; @@ -1388,10 +1446,10 @@ function _memory(cmd) { // Save the line so I can do magic later if (cmd) { const len = self.lines.level.length ? self.lines.level.length - 1 : 0; - self.lines.push(' '.repeat(len) + cmd); + ArrayPrototypePush(self.lines, StringPrototypeRepeat(' ', len) + cmd); } else { // I don't want to not change the format too much... - self.lines.push(''); + ArrayPrototypePush(self.lines, ''); } if (!cmd) { @@ -1405,8 +1463,8 @@ function _memory(cmd) { // Going down is { and ( e.g. function() { // going up is } and ) - let dw = cmd.match(/[{(]/g); - let up = cmd.match(/[})]/g); + let dw = StringPrototypeMatch(cmd, /[{(]/g); + let up = StringPrototypeMatch(cmd, /[})]/g); up = up ? up.length : 0; dw = dw ? dw.length : 0; let depth = dw - up; @@ -1421,13 +1479,13 @@ function _memory(cmd) { // "function() // {" but nothing should break, only tab completion for local // scope will not work for this function. - self.lines.level.push({ + ArrayPrototypePush(self.lines.level, { line: self.lines.length - 1, depth: depth }); } else if (depth < 0) { // Going... up. - const curr = self.lines.level.pop(); + const curr = ArrayPrototypePop(self.lines.level); if (curr) { const tmp = curr.depth + depth; if (tmp < 0) { @@ -1437,7 +1495,7 @@ function _memory(cmd) { } else if (tmp > 0) { // Remove and push back curr.depth += depth; - self.lines.level.push(curr); + ArrayPrototypePush(self.lines.level, curr); } } } @@ -1448,7 +1506,7 @@ function _memory(cmd) { function addCommonWords(completionGroups) { // Only words which do not yet exist as global property should be added to // this list. - completionGroups.push([ + ArrayPrototypePush(completionGroups, [ 'async', 'await', 'break', 'case', 'catch', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'else', 'export', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'let', @@ -1459,7 +1517,7 @@ function addCommonWords(completionGroups) { function _turnOnEditorMode(repl) { repl.editorMode = true; - Interface.prototype.setPrompt.call(repl, ''); + ReflectApply(Interface.prototype.setPrompt, repl, ['']); } function _turnOffEditorMode(repl) { @@ -1504,15 +1562,15 @@ function defineDefaultCommands(repl) { repl.defineCommand('help', { help: 'Print this help message', action: function() { - const names = ObjectKeys(this.commands).sort(); - const longestNameLength = names.reduce( - (max, name) => MathMax(max, name.length), - 0 + const names = ArrayPrototypeSort(ObjectKeys(this.commands)); + const longestNameLength = MathMax( + ...ArrayPrototypeMap(names, (name) => name.length) ); for (let n = 0; n < names.length; n++) { const name = names[n]; const cmd = this.commands[name]; - const spaces = ' '.repeat(longestNameLength - name.length + 3); + const spaces = + StringPrototypeRepeat(' ', longestNameLength - name.length + 3); const line = `.${name}${cmd.help ? spaces + cmd.help : ''}\n`; this.output.write(line); } @@ -1526,7 +1584,7 @@ function defineDefaultCommands(repl) { help: 'Save all evaluated commands in this REPL session to a file', action: function(file) { try { - fs.writeFileSync(file, this.lines.join('\n')); + fs.writeFileSync(file, ArrayPrototypeJoin(this.lines, '\n')); this.output.write(`Session saved to: ${file}\n`); } catch { this.output.write(`Failed to save: ${file}\n`);