repl: refactor to use more primordials

PR-URL: https://github.com/nodejs/node/pull/36264
Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
Antoine du Hamel 2020-11-16 11:12:25 +01:00 committed by Node.js GitHub Bot
parent c91e608a7d
commit 825029ec59
4 changed files with 242 additions and 149 deletions

View File

@ -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 = {

View File

@ -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);
}
}

View File

@ -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 <ctrl> + c and escape. Those should only cancel the
// reverse search. The original line is visible afterwards again.

View File

@ -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('...<Tab>')
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`);