lib: disable REPL completion on proxies and getters
the REPL completion logic evaluates code, this is generally ok but it can be problematic when there are objects with nested properties since the code evaluation would trigger their potential getters (e.g. the `obj.foo.b` line would trigger the getter of `foo`), the changes here disable the completion logic when proxies and getters are involved thus making sure that code evaluation does not take place when it can potentially (and surprisingly to the user) trigger side effectful behaviors PR-URL: https://github.com/nodejs/node/pull/57909 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
This commit is contained in:
parent
919ef7cae8
commit
1093f38c43
51
lib/repl.js
51
lib/repl.js
@ -96,6 +96,10 @@ const {
|
|||||||
globalThis,
|
globalThis,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isProxy,
|
||||||
|
} = require('internal/util/types');
|
||||||
|
|
||||||
const { BuiltinModule } = require('internal/bootstrap/realm');
|
const { BuiltinModule } = require('internal/bootstrap/realm');
|
||||||
const {
|
const {
|
||||||
makeRequireFunction,
|
makeRequireFunction,
|
||||||
@ -1328,8 +1332,10 @@ function completeFSFunctions(match) {
|
|||||||
// -> [['util.print', 'util.debug', 'util.log', 'util.inspect'],
|
// -> [['util.print', 'util.debug', 'util.log', 'util.inspect'],
|
||||||
// 'util.' ]
|
// 'util.' ]
|
||||||
//
|
//
|
||||||
// Warning: This eval's code like "foo.bar.baz", so it will run property
|
// Warning: This evals code like "foo.bar.baz", so it could run property
|
||||||
// getter code.
|
// getter code. To avoid potential triggering side-effects with getters the completion
|
||||||
|
// logic is skipped when getters or proxies are involved in the expression.
|
||||||
|
// (see: https://github.com/nodejs/node/issues/57829).
|
||||||
function complete(line, callback) {
|
function complete(line, callback) {
|
||||||
// List of completion lists, one for each inheritance "level"
|
// List of completion lists, one for each inheritance "level"
|
||||||
let completionGroups = [];
|
let completionGroups = [];
|
||||||
@ -1525,6 +1531,17 @@ function complete(line, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return includesProxiesOrGetters(
|
||||||
|
StringPrototypeSplit(match, '.'),
|
||||||
|
this.eval,
|
||||||
|
this.context,
|
||||||
|
(includes) => {
|
||||||
|
if (includes) {
|
||||||
|
// The expression involves proxies or getters, meaning that it
|
||||||
|
// can trigger side-effectful behaviors, so bail out
|
||||||
|
return completionGroupsLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
let chaining = '.';
|
let chaining = '.';
|
||||||
if (StringPrototypeEndsWith(expr, '?')) {
|
if (StringPrototypeEndsWith(expr, '?')) {
|
||||||
expr = StringPrototypeSlice(expr, 0, -1);
|
expr = StringPrototypeSlice(expr, 0, -1);
|
||||||
@ -1568,7 +1585,7 @@ function complete(line, callback) {
|
|||||||
|
|
||||||
completionGroupsLoaded();
|
completionGroupsLoaded();
|
||||||
});
|
});
|
||||||
return;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return completionGroupsLoaded();
|
return completionGroupsLoaded();
|
||||||
@ -1626,6 +1643,34 @@ function complete(line, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function includesProxiesOrGetters(exprSegments, evalFn, context, callback, currentExpr = '', idx = 0) {
|
||||||
|
const currentSegment = exprSegments[idx];
|
||||||
|
currentExpr += `${currentExpr.length === 0 ? '' : '.'}${currentSegment}`;
|
||||||
|
evalFn(`try { ${currentExpr} } catch { }`, context, getREPLResourceName(), (_, currentObj) => {
|
||||||
|
if (typeof currentObj !== 'object' || currentObj === null) {
|
||||||
|
return callback(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isProxy(currentObj)) {
|
||||||
|
return callback(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextIdx = idx + 1;
|
||||||
|
|
||||||
|
if (nextIdx >= exprSegments.length) {
|
||||||
|
return callback(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextSegmentProp = ObjectGetOwnPropertyDescriptor(currentObj, exprSegments[nextIdx]);
|
||||||
|
const nextSegmentPropHasGetter = typeof nextSegmentProp?.get === 'function';
|
||||||
|
if (nextSegmentPropHasGetter) {
|
||||||
|
return callback(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return includesProxiesOrGetters(exprSegments, evalFn, context, callback, currentExpr, nextIdx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => {
|
REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => {
|
||||||
if (err) return callback(err);
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
119
test/parallel/test-repl-completion-on-getters-disabled.js
Normal file
119
test/parallel/test-repl-completion-on-getters-disabled.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('node:assert');
|
||||||
|
const { describe, test } = require('node:test');
|
||||||
|
|
||||||
|
const ArrayStream = require('../common/arraystream');
|
||||||
|
|
||||||
|
const repl = require('node:repl');
|
||||||
|
|
||||||
|
function runCompletionTests(replInit, tests) {
|
||||||
|
const stream = new ArrayStream();
|
||||||
|
const testRepl = repl.start({ stream });
|
||||||
|
|
||||||
|
// Some errors are passed to the domain
|
||||||
|
testRepl._domain.on('error', assert.ifError);
|
||||||
|
|
||||||
|
testRepl.write(replInit);
|
||||||
|
testRepl.write('\n');
|
||||||
|
|
||||||
|
tests.forEach(([query, expectedCompletions]) => {
|
||||||
|
testRepl.complete(query, common.mustCall((error, data) => {
|
||||||
|
const actualCompletions = data[0];
|
||||||
|
if (expectedCompletions.length === 0) {
|
||||||
|
assert.deepStrictEqual(actualCompletions, []);
|
||||||
|
} else {
|
||||||
|
expectedCompletions.forEach((expectedCompletion) =>
|
||||||
|
assert(actualCompletions.includes(expectedCompletion), `completion '${expectedCompletion}' not found`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('REPL completion in relation of getters', () => {
|
||||||
|
describe('standard behavior without proxies/getters', () => {
|
||||||
|
test('completion of nested properties of an undeclared objects', () => {
|
||||||
|
runCompletionTests('', [
|
||||||
|
['nonExisting.', []],
|
||||||
|
['nonExisting.f', []],
|
||||||
|
['nonExisting.foo', []],
|
||||||
|
['nonExisting.foo.', []],
|
||||||
|
['nonExisting.foo.bar.b', []],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('completion of nested properties on plain objects', () => {
|
||||||
|
runCompletionTests('const plainObj = { foo: { bar: { baz: {} } } };', [
|
||||||
|
['plainObj.', ['plainObj.foo']],
|
||||||
|
['plainObj.f', ['plainObj.foo']],
|
||||||
|
['plainObj.foo', ['plainObj.foo']],
|
||||||
|
['plainObj.foo.', ['plainObj.foo.bar']],
|
||||||
|
['plainObj.foo.bar.b', ['plainObj.foo.bar.baz']],
|
||||||
|
['plainObj.fooBar.', []],
|
||||||
|
['plainObj.fooBar.baz', []],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('completions on an object with getters', () => {
|
||||||
|
test(`completions are generated for properties that don't trigger getters`, () => {
|
||||||
|
runCompletionTests(
|
||||||
|
`
|
||||||
|
const objWithGetters = {
|
||||||
|
foo: { bar: { baz: {} }, get gBar() { return { baz: {} } } },
|
||||||
|
get gFoo() { return { bar: { baz: {} } }; }
|
||||||
|
};
|
||||||
|
`, [
|
||||||
|
['objWithGetters.', ['objWithGetters.foo']],
|
||||||
|
['objWithGetters.f', ['objWithGetters.foo']],
|
||||||
|
['objWithGetters.foo', ['objWithGetters.foo']],
|
||||||
|
['objWithGetters.foo.', ['objWithGetters.foo.bar']],
|
||||||
|
['objWithGetters.foo.bar.b', ['objWithGetters.foo.bar.baz']],
|
||||||
|
['objWithGetters.gFo', ['objWithGetters.gFoo']],
|
||||||
|
['objWithGetters.foo.gB', ['objWithGetters.foo.gBar']],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no completions are generated for properties that trigger getters', () => {
|
||||||
|
runCompletionTests(
|
||||||
|
`
|
||||||
|
const objWithGetters = {
|
||||||
|
foo: { bar: { baz: {} }, get gBar() { return { baz: {} } } },
|
||||||
|
get gFoo() { return { bar: { baz: {} } }; }
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
['objWithGetters.gFoo.', []],
|
||||||
|
['objWithGetters.gFoo.b', []],
|
||||||
|
['objWithGetters.gFoo.bar.b', []],
|
||||||
|
['objWithGetters.foo.gBar.', []],
|
||||||
|
['objWithGetters.foo.gBar.b', []],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('completions on proxies', () => {
|
||||||
|
test('no completions are generated for a proxy object', () => {
|
||||||
|
runCompletionTests('const proxyObj = new Proxy({ foo: { bar: { baz: {} } } }, {});', [
|
||||||
|
['proxyObj.', []],
|
||||||
|
['proxyObj.f', []],
|
||||||
|
['proxyObj.foo', []],
|
||||||
|
['proxyObj.foo.', []],
|
||||||
|
['proxyObj.foo.bar.b', []],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no completions are generated for a proxy present in a standard object', () => {
|
||||||
|
runCompletionTests(
|
||||||
|
'const objWithProxy = { foo: { bar: new Proxy({ baz: {} }, {}) } };', [
|
||||||
|
['objWithProxy.', ['objWithProxy.foo']],
|
||||||
|
['objWithProxy.foo', ['objWithProxy.foo']],
|
||||||
|
['objWithProxy.foo.', ['objWithProxy.foo.bar']],
|
||||||
|
['objWithProxy.foo.b', ['objWithProxy.foo.bar']],
|
||||||
|
['objWithProxy.foo.bar.', []],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -27,6 +27,13 @@ const fs = require('fs');
|
|||||||
const tmpdir = require('../common/tmpdir');
|
const tmpdir = require('../common/tmpdir');
|
||||||
tmpdir.refresh();
|
tmpdir.refresh();
|
||||||
|
|
||||||
|
// TODO: the following async IIFE and the completePromise function are necessary because
|
||||||
|
// the reply tests are all run against the same repl instance (testMe) and thus coordination
|
||||||
|
// needs to be in place for the tests not to interfere with each other, this is really
|
||||||
|
// not ideal, the tests in this file should be refactored so that each use its own isolated
|
||||||
|
// repl instance, making sure that no special coordination needs to be in place for them
|
||||||
|
// and also allowing the tests to all be run in parallel
|
||||||
|
(async () => {
|
||||||
const repl = require('repl');
|
const repl = require('repl');
|
||||||
|
|
||||||
const works = [['inner.one'], 'inner.o'];
|
const works = [['inner.one'], 'inner.o'];
|
||||||
@ -41,6 +48,15 @@ testMe._domain.on('error', function(reason) {
|
|||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function completePromise(query, callback) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
testMe.complete(query, (...args) => {
|
||||||
|
callback(...args);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const testFile = [
|
const testFile = [
|
||||||
'let inner = (function() {',
|
'let inner = (function() {',
|
||||||
' return {one:1};',
|
' return {one:1};',
|
||||||
@ -59,7 +75,7 @@ assert.strictEqual(fs.readFileSync(saveFileName, 'utf8'),
|
|||||||
testFile.join('\n'));
|
testFile.join('\n'));
|
||||||
|
|
||||||
// Make sure that the REPL data is "correct".
|
// Make sure that the REPL data is "correct".
|
||||||
testMe.complete('inner.o', common.mustSucceed((data) => {
|
await completePromise('inner.o', common.mustSucceed((data) => {
|
||||||
assert.deepStrictEqual(data, works);
|
assert.deepStrictEqual(data, works);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -75,7 +91,7 @@ putIn.run([`.load ${saveFileName}`]);
|
|||||||
assert.strictEqual(testMe.line, '');
|
assert.strictEqual(testMe.line, '');
|
||||||
|
|
||||||
// Make sure that the REPL data is "correct".
|
// Make sure that the REPL data is "correct".
|
||||||
testMe.complete('inner.o', common.mustSucceed((data) => {
|
await completePromise('inner.o', common.mustSucceed((data) => {
|
||||||
assert.deepStrictEqual(data, works);
|
assert.deepStrictEqual(data, works);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -162,3 +178,4 @@ putIn.write = common.mustCall(function(data) {
|
|||||||
putIn.write = () => {};
|
putIn.write = () => {};
|
||||||
});
|
});
|
||||||
putIn.run(['.load']);
|
putIn.run(['.load']);
|
||||||
|
})().then(common.mustCall());
|
||||||
|
@ -53,6 +53,13 @@ function getNoResultsFunction() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: the following async IIFE and the completePromise function are necessary because
|
||||||
|
// the reply tests are all run against the same repl instance (testMe) and thus coordination
|
||||||
|
// needs to be in place for the tests not to interfere with each other, this is really
|
||||||
|
// not ideal, the tests in this file should be refactored so that each use its own isolated
|
||||||
|
// repl instance, making sure that no special coordination needs to be in place for them
|
||||||
|
// and also allowing the tests to all be run in parallel
|
||||||
|
(async () => {
|
||||||
const works = [['inner.one'], 'inner.o'];
|
const works = [['inner.one'], 'inner.o'];
|
||||||
const putIn = new ArrayStream();
|
const putIn = new ArrayStream();
|
||||||
const testMe = repl.start({
|
const testMe = repl.start({
|
||||||
@ -62,6 +69,15 @@ const testMe = repl.start({
|
|||||||
allowBlockingCompletions: true
|
allowBlockingCompletions: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function completePromise(query, callback) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
testMe.complete(query, (...args) => {
|
||||||
|
callback(...args);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Some errors are passed to the domain, but do not callback
|
// Some errors are passed to the domain, but do not callback
|
||||||
testMe._domain.on('error', assert.ifError);
|
testMe._domain.on('error', assert.ifError);
|
||||||
|
|
||||||
@ -70,28 +86,28 @@ putIn.run([
|
|||||||
'var inner = {',
|
'var inner = {',
|
||||||
'one:1',
|
'one:1',
|
||||||
]);
|
]);
|
||||||
testMe.complete('inner.o', getNoResultsFunction());
|
await completePromise('inner.o', getNoResultsFunction());
|
||||||
|
|
||||||
testMe.complete('console.lo', common.mustCall(function(error, data) {
|
await completePromise('console.lo', common.mustCall(function(error, data) {
|
||||||
assert.deepStrictEqual(data, [['console.log'], 'console.lo']);
|
assert.deepStrictEqual(data, [['console.log'], 'console.lo']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
testMe.complete('console?.lo', common.mustCall((error, data) => {
|
await completePromise('console?.lo', common.mustCall((error, data) => {
|
||||||
assert.deepStrictEqual(data, [['console?.log'], 'console?.lo']);
|
assert.deepStrictEqual(data, [['console?.log'], 'console?.lo']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
testMe.complete('console?.zzz', common.mustCall((error, data) => {
|
await completePromise('console?.zzz', common.mustCall((error, data) => {
|
||||||
assert.deepStrictEqual(data, [[], 'console?.zzz']);
|
assert.deepStrictEqual(data, [[], 'console?.zzz']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
testMe.complete('console?.', common.mustCall((error, data) => {
|
await completePromise('console?.', common.mustCall((error, data) => {
|
||||||
assert(data[0].includes('console?.log'));
|
assert(data[0].includes('console?.log'));
|
||||||
assert.strictEqual(data[1], 'console?.');
|
assert.strictEqual(data[1], 'console?.');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Tab Complete will return globally scoped variables
|
// Tab Complete will return globally scoped variables
|
||||||
putIn.run(['};']);
|
putIn.run(['};']);
|
||||||
testMe.complete('inner.o', common.mustCall(function(error, data) {
|
await completePromise('inner.o', common.mustCall(function(error, data) {
|
||||||
assert.deepStrictEqual(data, works);
|
assert.deepStrictEqual(data, works);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -103,7 +119,7 @@ putIn.run([
|
|||||||
'?',
|
'?',
|
||||||
'{one: 1} : ',
|
'{one: 1} : ',
|
||||||
]);
|
]);
|
||||||
testMe.complete('inner.o', getNoResultsFunction());
|
await completePromise('inner.o', getNoResultsFunction());
|
||||||
|
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
@ -112,12 +128,12 @@ putIn.run([
|
|||||||
'var top = function() {',
|
'var top = function() {',
|
||||||
'var inner = {one:1};',
|
'var inner = {one:1};',
|
||||||
]);
|
]);
|
||||||
testMe.complete('inner.o', getNoResultsFunction());
|
await completePromise('inner.o', getNoResultsFunction());
|
||||||
|
|
||||||
// When you close the function scope tab complete will not return the
|
// When you close the function scope tab complete will not return the
|
||||||
// locally scoped variable
|
// locally scoped variable
|
||||||
putIn.run(['};']);
|
putIn.run(['};']);
|
||||||
testMe.complete('inner.o', getNoResultsFunction());
|
await completePromise('inner.o', getNoResultsFunction());
|
||||||
|
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
@ -128,7 +144,7 @@ putIn.run([
|
|||||||
' one:1',
|
' one:1',
|
||||||
'};',
|
'};',
|
||||||
]);
|
]);
|
||||||
testMe.complete('inner.o', getNoResultsFunction());
|
await completePromise('inner.o', getNoResultsFunction());
|
||||||
|
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
@ -140,7 +156,7 @@ putIn.run([
|
|||||||
' one:1',
|
' one:1',
|
||||||
'};',
|
'};',
|
||||||
]);
|
]);
|
||||||
testMe.complete('inner.o', getNoResultsFunction());
|
await completePromise('inner.o', getNoResultsFunction());
|
||||||
|
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
@ -153,7 +169,7 @@ putIn.run([
|
|||||||
' one:1',
|
' one:1',
|
||||||
'};',
|
'};',
|
||||||
]);
|
]);
|
||||||
testMe.complete('inner.o', getNoResultsFunction());
|
await completePromise('inner.o', getNoResultsFunction());
|
||||||
|
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
@ -166,7 +182,7 @@ putIn.run([
|
|||||||
' one:1',
|
' one:1',
|
||||||
'};',
|
'};',
|
||||||
]);
|
]);
|
||||||
testMe.complete('inner.o', getNoResultsFunction());
|
await completePromise('inner.o', getNoResultsFunction());
|
||||||
|
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
@ -179,7 +195,7 @@ putIn.run([
|
|||||||
' one:1',
|
' one:1',
|
||||||
'};',
|
'};',
|
||||||
]);
|
]);
|
||||||
testMe.complete('inner.o', getNoResultsFunction());
|
await completePromise('inner.o', getNoResultsFunction());
|
||||||
|
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
@ -193,7 +209,7 @@ putIn.run([
|
|||||||
' one:1',
|
' one:1',
|
||||||
'};',
|
'};',
|
||||||
]);
|
]);
|
||||||
testMe.complete('inner.o', getNoResultsFunction());
|
await completePromise('inner.o', getNoResultsFunction());
|
||||||
|
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
@ -201,7 +217,8 @@ putIn.run(['.clear']);
|
|||||||
putIn.run([
|
putIn.run([
|
||||||
'var str = "test";',
|
'var str = "test";',
|
||||||
]);
|
]);
|
||||||
testMe.complete('str.len', common.mustCall(function(error, data) {
|
// TODO
|
||||||
|
await completePromise('str.len', common.mustCall(function(error, data) {
|
||||||
assert.deepStrictEqual(data, [['str.length'], 'str.len']);
|
assert.deepStrictEqual(data, [['str.length'], 'str.len']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -211,7 +228,7 @@ putIn.run(['.clear']);
|
|||||||
putIn.run([
|
putIn.run([
|
||||||
'var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };',
|
'var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };',
|
||||||
]);
|
]);
|
||||||
testMe.complete(
|
await completePromise(
|
||||||
'foo.b',
|
'foo.b',
|
||||||
common.mustCall(function(error, data) {
|
common.mustCall(function(error, data) {
|
||||||
assert.deepStrictEqual(data, [
|
assert.deepStrictEqual(data, [
|
||||||
@ -227,7 +244,7 @@ putIn.run(['.clear']);
|
|||||||
putIn.run([
|
putIn.run([
|
||||||
'var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };',
|
'var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };',
|
||||||
]);
|
]);
|
||||||
testMe.complete(
|
await completePromise(
|
||||||
'foo.B',
|
'foo.B',
|
||||||
common.mustCall(function(error, data) {
|
common.mustCall(function(error, data) {
|
||||||
assert.deepStrictEqual(data, [
|
assert.deepStrictEqual(data, [
|
||||||
@ -244,7 +261,7 @@ const spaceTimeout = setTimeout(function() {
|
|||||||
throw new Error('timeout');
|
throw new Error('timeout');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
testMe.complete(' ', common.mustSucceed((data) => {
|
await completePromise(' ', common.mustSucceed((data) => {
|
||||||
assert.strictEqual(data[1], '');
|
assert.strictEqual(data[1], '');
|
||||||
assert.ok(data[0].includes('globalThis'));
|
assert.ok(data[0].includes('globalThis'));
|
||||||
clearTimeout(spaceTimeout);
|
clearTimeout(spaceTimeout);
|
||||||
@ -252,7 +269,7 @@ testMe.complete(' ', common.mustSucceed((data) => {
|
|||||||
|
|
||||||
// Tab completion should pick up the global "toString" object, and
|
// Tab completion should pick up the global "toString" object, and
|
||||||
// any other properties up the "global" object's prototype chain
|
// any other properties up the "global" object's prototype chain
|
||||||
testMe.complete('toSt', common.mustCall(function(error, data) {
|
await completePromise('toSt', common.mustCall(function(error, data) {
|
||||||
assert.deepStrictEqual(data, [['toString'], 'toSt']);
|
assert.deepStrictEqual(data, [['toString'], 'toSt']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -266,14 +283,14 @@ putIn.run([
|
|||||||
'y.a = 3;',
|
'y.a = 3;',
|
||||||
'y.c = 4;',
|
'y.c = 4;',
|
||||||
]);
|
]);
|
||||||
testMe.complete('y.', common.mustCall(function(error, data) {
|
await completePromise('y.', common.mustCall(function(error, data) {
|
||||||
assert.deepStrictEqual(data, [['y.b', '', 'y.a', 'y.c'], 'y.']);
|
assert.deepStrictEqual(data, [['y.b', '', 'y.a', 'y.c'], 'y.']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Tab complete provides built in libs for require()
|
// Tab complete provides built in libs for require()
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
testMe.complete('require(\'', common.mustCall(function(error, data) {
|
await completePromise('require(\'', common.mustCall(async function(error, data) {
|
||||||
assert.strictEqual(error, null);
|
assert.strictEqual(error, null);
|
||||||
publicModules.forEach((lib) => {
|
publicModules.forEach((lib) => {
|
||||||
assert(
|
assert(
|
||||||
@ -284,13 +301,13 @@ testMe.complete('require(\'', common.mustCall(function(error, data) {
|
|||||||
const newModule = 'foobar';
|
const newModule = 'foobar';
|
||||||
assert(!builtinModules.includes(newModule));
|
assert(!builtinModules.includes(newModule));
|
||||||
repl.builtinModules.push(newModule);
|
repl.builtinModules.push(newModule);
|
||||||
testMe.complete('require(\'', common.mustCall((_, [modules]) => {
|
await completePromise('require(\'', common.mustCall((_, [modules]) => {
|
||||||
assert.strictEqual(data[0].length + 1, modules.length);
|
assert.strictEqual(data[0].length + 1, modules.length);
|
||||||
assert(modules.includes(newModule));
|
assert(modules.includes(newModule));
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
testMe.complete("require\t( 'n", common.mustCall(function(error, data) {
|
await completePromise("require\t( 'n", common.mustCall(function(error, data) {
|
||||||
assert.strictEqual(error, null);
|
assert.strictEqual(error, null);
|
||||||
assert.strictEqual(data.length, 2);
|
assert.strictEqual(data.length, 2);
|
||||||
assert.strictEqual(data[1], 'n');
|
assert.strictEqual(data[1], 'n');
|
||||||
@ -316,7 +333,7 @@ testMe.complete("require\t( 'n", common.mustCall(function(error, data) {
|
|||||||
// Require calls should handle all types of quotation marks.
|
// Require calls should handle all types of quotation marks.
|
||||||
for (const quotationMark of ["'", '"', '`']) {
|
for (const quotationMark of ["'", '"', '`']) {
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
testMe.complete('require(`@nodejs', common.mustCall((err, data) => {
|
await completePromise('require(`@nodejs', common.mustCall((err, data) => {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
assert.deepStrictEqual(data, [expected, '@nodejs']);
|
assert.deepStrictEqual(data, [expected, '@nodejs']);
|
||||||
}));
|
}));
|
||||||
@ -406,7 +423,7 @@ putIn.run(['.clear']);
|
|||||||
putIn.run([
|
putIn.run([
|
||||||
'var custom = "test";',
|
'var custom = "test";',
|
||||||
]);
|
]);
|
||||||
testMe.complete('cus', common.mustCall(function(error, data) {
|
await completePromise('cus', common.mustCall(function(error, data) {
|
||||||
assert.deepStrictEqual(data, [['CustomEvent', 'custom'], 'cus']);
|
assert.deepStrictEqual(data, [['CustomEvent', 'custom'], 'cus']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -418,7 +435,7 @@ putIn.run([
|
|||||||
'var proxy = new Proxy({}, {ownKeys: () => { throw new Error(); }});',
|
'var proxy = new Proxy({}, {ownKeys: () => { throw new Error(); }});',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
testMe.complete('proxy.', common.mustCall(function(error, data) {
|
await completePromise('proxy.', common.mustCall(function(error, data) {
|
||||||
assert.strictEqual(error, null);
|
assert.strictEqual(error, null);
|
||||||
assert(Array.isArray(data));
|
assert(Array.isArray(data));
|
||||||
}));
|
}));
|
||||||
@ -427,7 +444,7 @@ testMe.complete('proxy.', common.mustCall(function(error, data) {
|
|||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
putIn.run(['var ary = [1,2,3];']);
|
putIn.run(['var ary = [1,2,3];']);
|
||||||
testMe.complete('ary.', common.mustCall(function(error, data) {
|
await completePromise('ary.', common.mustCall(function(error, data) {
|
||||||
assert.strictEqual(data[0].includes('ary.0'), false);
|
assert.strictEqual(data[0].includes('ary.0'), false);
|
||||||
assert.strictEqual(data[0].includes('ary.1'), false);
|
assert.strictEqual(data[0].includes('ary.1'), false);
|
||||||
assert.strictEqual(data[0].includes('ary.2'), false);
|
assert.strictEqual(data[0].includes('ary.2'), false);
|
||||||
@ -437,7 +454,7 @@ testMe.complete('ary.', common.mustCall(function(error, data) {
|
|||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']);
|
putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']);
|
||||||
|
|
||||||
testMe.complete('obj.', common.mustCall(function(error, data) {
|
await completePromise('obj.', common.mustCall(function(error, data) {
|
||||||
assert.strictEqual(data[0].includes('obj.1'), false);
|
assert.strictEqual(data[0].includes('obj.1'), false);
|
||||||
assert.strictEqual(data[0].includes('obj.1a'), false);
|
assert.strictEqual(data[0].includes('obj.1a'), false);
|
||||||
assert(data[0].includes('obj.a'));
|
assert(data[0].includes('obj.a'));
|
||||||
@ -447,13 +464,13 @@ testMe.complete('obj.', common.mustCall(function(error, data) {
|
|||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
putIn.run(['function a() {}']);
|
putIn.run(['function a() {}']);
|
||||||
|
|
||||||
testMe.complete('a().b.', getNoResultsFunction());
|
await completePromise('a().b.', getNoResultsFunction());
|
||||||
|
|
||||||
// Works when prefixed with spaces
|
// Works when prefixed with spaces
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']);
|
putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']);
|
||||||
|
|
||||||
testMe.complete(' obj.', common.mustCall((error, data) => {
|
await completePromise(' obj.', common.mustCall((error, data) => {
|
||||||
assert.strictEqual(data[0].includes('obj.1'), false);
|
assert.strictEqual(data[0].includes('obj.1'), false);
|
||||||
assert.strictEqual(data[0].includes('obj.1a'), false);
|
assert.strictEqual(data[0].includes('obj.1a'), false);
|
||||||
assert(data[0].includes('obj.a'));
|
assert(data[0].includes('obj.a'));
|
||||||
@ -462,19 +479,19 @@ testMe.complete(' obj.', common.mustCall((error, data) => {
|
|||||||
// Works inside assignments
|
// Works inside assignments
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
testMe.complete('var log = console.lo', common.mustCall((error, data) => {
|
await completePromise('var log = console.lo', common.mustCall((error, data) => {
|
||||||
assert.deepStrictEqual(data, [['console.log'], 'console.lo']);
|
assert.deepStrictEqual(data, [['console.log'], 'console.lo']);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Tab completion for defined commands
|
// Tab completion for defined commands
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
testMe.complete('.b', common.mustCall((error, data) => {
|
await completePromise('.b', common.mustCall((error, data) => {
|
||||||
assert.deepStrictEqual(data, [['break'], 'b']);
|
assert.deepStrictEqual(data, [['break'], 'b']);
|
||||||
}));
|
}));
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
putIn.run(['var obj = {"hello, world!": "some string", "key": 123}']);
|
putIn.run(['var obj = {"hello, world!": "some string", "key": 123}']);
|
||||||
testMe.complete('obj.', common.mustCall((error, data) => {
|
await completePromise('obj.', common.mustCall((error, data) => {
|
||||||
assert.strictEqual(data[0].includes('obj.hello, world!'), false);
|
assert.strictEqual(data[0].includes('obj.hello, world!'), false);
|
||||||
assert(data[0].includes('obj.key'));
|
assert(data[0].includes('obj.key'));
|
||||||
}));
|
}));
|
||||||
@ -483,7 +500,7 @@ testMe.complete('obj.', common.mustCall((error, data) => {
|
|||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
putIn.run(['var obj = {};']);
|
putIn.run(['var obj = {};']);
|
||||||
testMe.complete('obj.', common.mustCall(function(error, data) {
|
await completePromise('obj.', common.mustCall(function(error, data) {
|
||||||
assert.strictEqual(data[0].includes('obj.__defineGetter__'), false);
|
assert.strictEqual(data[0].includes('obj.__defineGetter__'), false);
|
||||||
assert.strictEqual(data[0].includes('obj.__defineSetter__'), false);
|
assert.strictEqual(data[0].includes('obj.__defineSetter__'), false);
|
||||||
assert.strictEqual(data[0].includes('obj.__lookupGetter__'), false);
|
assert.strictEqual(data[0].includes('obj.__lookupGetter__'), false);
|
||||||
@ -542,7 +559,7 @@ testMe.complete('obj.', common.mustCall(function(error, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[
|
for (const type of [
|
||||||
Array,
|
Array,
|
||||||
Buffer,
|
Buffer,
|
||||||
|
|
||||||
@ -556,7 +573,7 @@ testMe.complete('obj.', common.mustCall(function(error, data) {
|
|||||||
Int32Array,
|
Int32Array,
|
||||||
Float32Array,
|
Float32Array,
|
||||||
Float64Array,
|
Float64Array,
|
||||||
].forEach((type) => {
|
]) {
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
|
|
||||||
if (type === Array) {
|
if (type === Array) {
|
||||||
@ -572,7 +589,7 @@ testMe.complete('obj.', common.mustCall(function(error, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hijackStderr(common.mustNotCall());
|
hijackStderr(common.mustNotCall());
|
||||||
testMe.complete('ele.', common.mustCall((err, data) => {
|
await completePromise('ele.', common.mustCall((err, data) => {
|
||||||
restoreStderr();
|
restoreStderr();
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
|
||||||
@ -589,33 +606,37 @@ testMe.complete('obj.', common.mustCall(function(error, data) {
|
|||||||
assert.notStrictEqual(ele[key.slice(4)], undefined);
|
assert.notStrictEqual(ele[key.slice(4)], undefined);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
};
|
||||||
|
|
||||||
// check Buffer.prototype.length not crashing.
|
// check Buffer.prototype.length not crashing.
|
||||||
// Refs: https://github.com/nodejs/node/pull/11961
|
// Refs: https://github.com/nodejs/node/pull/11961
|
||||||
putIn.run(['.clear']);
|
putIn.run(['.clear']);
|
||||||
testMe.complete('Buffer.prototype.', common.mustCall());
|
await completePromise('Buffer.prototype.', common.mustCall());
|
||||||
|
|
||||||
// Make sure repl gives correct autocomplete on literals
|
// Make sure repl gives correct autocomplete on literals
|
||||||
testMe.complete('``.a', common.mustCall((err, data) => {
|
await completePromise('``.a', common.mustCall((err, data) => {
|
||||||
assert.strictEqual(data[0].includes('``.at'), true);
|
assert.strictEqual(data[0].includes('``.at'), true);
|
||||||
}));
|
}));
|
||||||
testMe.complete('\'\'.a', common.mustCall((err, data) => {
|
await completePromise('\'\'.a', common.mustCall((err, data) => {
|
||||||
assert.strictEqual(data[0].includes('\'\'.at'), true);
|
assert.strictEqual(data[0].includes('\'\'.at'), true);
|
||||||
}));
|
}));
|
||||||
testMe.complete('"".a', common.mustCall((err, data) => {
|
await completePromise('"".a', common.mustCall((err, data) => {
|
||||||
assert.strictEqual(data[0].includes('"".at'), true);
|
assert.strictEqual(data[0].includes('"".at'), true);
|
||||||
}));
|
}));
|
||||||
testMe.complete('("").a', common.mustCall((err, data) => {
|
await completePromise('("").a', common.mustCall((err, data) => {
|
||||||
assert.strictEqual(data[0].includes('("").at'), true);
|
assert.strictEqual(data[0].includes('("").at'), true);
|
||||||
}));
|
}));
|
||||||
testMe.complete('[].a', common.mustCall((err, data) => {
|
await completePromise('[].a', common.mustCall((err, data) => {
|
||||||
assert.strictEqual(data[0].includes('[].at'), true);
|
assert.strictEqual(data[0].includes('[].at'), true);
|
||||||
}));
|
}));
|
||||||
testMe.complete('{}.a', common.mustCall((err, data) => {
|
await completePromise('{}.a', common.mustCall((err, data) => {
|
||||||
assert.deepStrictEqual(data[0], []);
|
assert.deepStrictEqual(data[0], []);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
})().then(common.mustCall());
|
||||||
|
|
||||||
|
const putIn = new ArrayStream();
|
||||||
|
|
||||||
const testNonGlobal = repl.start({
|
const testNonGlobal = repl.start({
|
||||||
input: putIn,
|
input: putIn,
|
||||||
output: putIn,
|
output: putIn,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user