mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00

https://github.com/nodejs/node/pull/57909 introduced the disabling of REPL tab completion on object containing proxies and getters (since such completion triggers code evaluation which can be unexpected/disruptive for the user) the solution in 57909 did not address all possible such cases, the changes here improve on such solution by using acorn and AST analysis to cover most if not all possible cases PR-URL: https://github.com/nodejs/node/pull/58891 Reviewed-By: James M Snell <jasnell@gmail.com>
174 lines
5.9 KiB
JavaScript
174 lines
5.9 KiB
JavaScript
'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(
|
|
`
|
|
function getFooKey() {
|
|
return "foo";
|
|
}
|
|
|
|
const fooKey = "foo";
|
|
|
|
const keys = {
|
|
"foo key": "foo",
|
|
};
|
|
|
|
const objWithGetters = {
|
|
foo: { bar: { baz: { buz: {} } }, get gBar() { return { baz: {} } } },
|
|
get gFoo() { return { bar: { baz: {} } }; }
|
|
};
|
|
`, [
|
|
['objWithGetters.', ['objWithGetters.foo']],
|
|
['objWithGetters.f', ['objWithGetters.foo']],
|
|
['objWithGetters.foo', ['objWithGetters.foo']],
|
|
['objWithGetters["foo"].b', ['objWithGetters["foo"].bar']],
|
|
['objWithGetters.foo.', ['objWithGetters.foo.bar']],
|
|
['objWithGetters.foo.bar.b', ['objWithGetters.foo.bar.baz']],
|
|
['objWithGetters.gFo', ['objWithGetters.gFoo']],
|
|
['objWithGetters.foo.gB', ['objWithGetters.foo.gBar']],
|
|
["objWithGetters.foo['bar'].b", ["objWithGetters.foo['bar'].baz"]],
|
|
["objWithGetters['foo']['bar'].b", ["objWithGetters['foo']['bar'].baz"]],
|
|
["objWithGetters['foo']['bar']['baz'].b", ["objWithGetters['foo']['bar']['baz'].buz"]],
|
|
["objWithGetters[keys['foo key']].b", ["objWithGetters[keys['foo key']].bar"]],
|
|
['objWithGetters[fooKey].b', ['objWithGetters[fooKey].bar']],
|
|
["objWithGetters['f' + 'oo'].b", ["objWithGetters['f' + 'oo'].bar"]],
|
|
['objWithGetters[getFooKey()].b', ['objWithGetters[getFooKey()].bar']],
|
|
]);
|
|
});
|
|
|
|
test('no completions are generated for properties that trigger getters', () => {
|
|
runCompletionTests(
|
|
`
|
|
function getGFooKey() {
|
|
return "g" + "Foo";
|
|
}
|
|
|
|
const gFooKey = "gFoo";
|
|
|
|
const keys = {
|
|
"g-foo key": "gFoo",
|
|
};
|
|
|
|
const objWithGetters = {
|
|
foo: { bar: { baz: {} }, get gBar() { return { baz: {}, get gBuz() { return 5; } } } },
|
|
get gFoo() { return { bar: { baz: {} } }; }
|
|
};
|
|
`,
|
|
[
|
|
['objWithGetters.gFoo.', []],
|
|
['objWithGetters.gFoo.b', []],
|
|
['objWithGetters["gFoo"].b', []],
|
|
['objWithGetters.gFoo.bar.b', []],
|
|
['objWithGetters.foo.gBar.', []],
|
|
['objWithGetters.foo.gBar.b', []],
|
|
["objWithGetters.foo['gBar'].b", []],
|
|
["objWithGetters['foo']['gBar'].b", []],
|
|
["objWithGetters['foo']['gBar']['gBuz'].", []],
|
|
["objWithGetters[keys['g-foo key']].b", []],
|
|
['objWithGetters[gFooKey].b', []],
|
|
["objWithGetters['g' + 'Foo'].b", []],
|
|
['objWithGetters[getGFooKey()].b', []],
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('completions on proxies', () => {
|
|
test('no completions are generated for a proxy object', () => {
|
|
runCompletionTests(
|
|
`
|
|
function getFooKey() {
|
|
return "foo";
|
|
}
|
|
|
|
const fooKey = "foo";
|
|
|
|
const keys = {
|
|
"foo key": "foo",
|
|
};
|
|
|
|
const proxyObj = new Proxy({ foo: { bar: { baz: {} } } }, {});
|
|
`, [
|
|
['proxyObj.', []],
|
|
['proxyObj.f', []],
|
|
['proxyObj.foo', []],
|
|
['proxyObj.foo.', []],
|
|
['proxyObj.["foo"].', []],
|
|
['proxyObj.["f" + "oo"].', []],
|
|
['proxyObj.[fooKey].', []],
|
|
['proxyObj.[getFooKey()].', []],
|
|
['proxyObj.[keys["foo key"]].', []],
|
|
['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.', []],
|
|
['objWithProxy.foo["b" + "ar"].', []],
|
|
]);
|
|
});
|
|
});
|
|
});
|