mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
Signed-off-by: Aviv Keller <me@aviv.sh> PR-URL: https://github.com/nodejs/node/pull/64034 Fixes: https://github.com/nodejs/node/issues/64523 Closes: https://github.com/nodejs/node/pull/64534 Fixes: https://github.com/nodejs/node/issues/39387 Closes: https://github.com/nodejs/node/pull/39392 Fixes: https://github.com/nodejs/node/issues/61390 Fixes: https://github.com/nodejs/node/issues/63126 Fixes: https://github.com/nodejs/node/issues/38503 Fixes: https://github.com/nodejs/node/issues/37445 Fixes: https://github.com/nodejs/node/issues/38145 Fixes: https://github.com/nodejs/node/issues/33369 Fixes: https://github.com/nodejs/node/issues/48131 Fixes: https://github.com/nodejs/node/issues/8309 Fixes: https://github.com/nodejs/node/issues/39689 Fixes: https://github.com/nodejs/node/issues/18931 Reviewed-By: Dario Piotrowicz <dario.piotrowicz@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
469 lines
14 KiB
JavaScript
469 lines
14 KiB
JavaScript
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
'use strict';
|
|
|
|
const common = require('../common');
|
|
const { startNewREPLServer, complete } = require('../common/repl');
|
|
const { describe, it } = require('node:test');
|
|
const assert = require('assert');
|
|
|
|
async function expectNoResults(replServer, query) {
|
|
const data = await complete(replServer, query);
|
|
assert.deepStrictEqual(data[0], []);
|
|
}
|
|
|
|
describe('REPL tab completion (core functionality)', () => {
|
|
it('does not break with variable declarations without an initialization', async () => {
|
|
const { replServer } = startNewREPLServer();
|
|
await expectNoResults(replServer, 'let a');
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not break in an object literal', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var inner = {', 'one:1']);
|
|
|
|
await expectNoResults(replServer, 'inner.o');
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'console.lo'),
|
|
[['console.log'], 'console.lo']);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works with optional chaining', async () => {
|
|
const { replServer } = startNewREPLServer();
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'console?.lo'),
|
|
[['console?.log'], 'console?.lo']);
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'console?.zzz'),
|
|
[[], 'console?.zzz']);
|
|
|
|
const data = await complete(replServer, 'console?.');
|
|
assert(data[0].includes('console?.log'));
|
|
assert.strictEqual(data[1], 'console?.');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('returns object completions', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var inner = {', 'one:1']);
|
|
|
|
await run(['};']);
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'inner.o'),
|
|
[['inner.one'], 'inner.o']);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not break in a ternary operator with ()', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var inner = ( true ', '?', '{one: 1} : ']);
|
|
|
|
await expectNoResults(replServer, 'inner.o');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works on literals', async () => {
|
|
const { replServer } = startNewREPLServer();
|
|
|
|
assert.strictEqual((await complete(replServer, '``.a'))[0].includes('``.at'), true);
|
|
assert.strictEqual((await complete(replServer, "''.a"))[0].includes("''.at"), true);
|
|
assert.strictEqual((await complete(replServer, '"".a'))[0].includes('"".at'), true);
|
|
assert.strictEqual((await complete(replServer, '("").a'))[0].includes('("").at'), true);
|
|
assert.strictEqual((await complete(replServer, '[].a'))[0].includes('[].at'), true);
|
|
assert.deepStrictEqual((await complete(replServer, '{}.a'))[0], []);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it("does not return a function's local variable", async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var top = function() {', 'var inner = {one:1};', '}']);
|
|
|
|
await expectNoResults(replServer, 'inner.o');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it("does not return a function's local variable even when the function has parameters", async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run([
|
|
'var top = function(one, two) {',
|
|
'var inner = {',
|
|
' one:1',
|
|
'};',
|
|
]);
|
|
|
|
await expectNoResults(replServer, 'inner.o');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it("does not return a function's local variable" +
|
|
'even if the scope is nested inside an immediately executed function', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run([
|
|
'var top = function() {',
|
|
'(function test () {',
|
|
'var inner = {',
|
|
' one:1',
|
|
'};',
|
|
]);
|
|
|
|
await expectNoResults(replServer, 'inner.o');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it("does not return a function's local variable" +
|
|
'even if the scope is nested inside an immediately executed function' +
|
|
'(the definition has the params and { on a separate line)', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run([
|
|
'var top = function() {',
|
|
'r = function test (',
|
|
' one, two) {',
|
|
'var inner = {',
|
|
' one:1',
|
|
'};',
|
|
]);
|
|
|
|
await expectNoResults(replServer, 'inner.o');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('currently does not work, but should not break (local inner)', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run([
|
|
'var top = function() {',
|
|
'r = function test ()',
|
|
'{',
|
|
'var inner = {',
|
|
' one:1',
|
|
'};',
|
|
]);
|
|
|
|
await expectNoResults(replServer, 'inner.o');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('currently does not work, but should not break (local inner parens next line)', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run([
|
|
'var top = function() {',
|
|
'r = function test (',
|
|
')',
|
|
'{',
|
|
'var inner = {',
|
|
' one:1',
|
|
'};',
|
|
]);
|
|
|
|
await expectNoResults(replServer, 'inner.o');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works on non-Objects', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var str = "test";']);
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'str.len'),
|
|
[['str.length'], 'str.len']);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('should be case-insensitive if member part is lower-case', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };']);
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'foo.b'),
|
|
[['foo.BARbuz', 'foo.barBLA', 'foo.barBar'], 'foo.b']);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('should be case-insensitive if member part is upper-case', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };']);
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'foo.B'),
|
|
[['foo.BARbuz', 'foo.barBLA', 'foo.barBar'], 'foo.B']);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('should not break on spaces', async () => {
|
|
const { replServer } = startNewREPLServer();
|
|
|
|
const data = await complete(replServer, ' ');
|
|
assert.strictEqual(data[1], '');
|
|
assert.ok(data[0].includes('globalThis'));
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it(`should pick up the global "toString" object, and any other properties up the "global" object's prototype chain`, async () => {
|
|
const { replServer } = startNewREPLServer();
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'toSt'),
|
|
[['toString'], 'toSt']);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('should make own properties shadow properties on the prototype', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run([
|
|
'var x = Object.create(null);',
|
|
'x.a = 1;',
|
|
'x.b = 2;',
|
|
'var y = Object.create(x);',
|
|
'y.a = 3;',
|
|
'y.c = 4;',
|
|
]);
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'y.'),
|
|
[['y.b', '', 'y.a', 'y.c'], 'y.']);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works on context properties', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var custom = "test";']);
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'cus'),
|
|
[['CustomEvent', 'custom'], 'cus']);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it("doesn't crash REPL with half-baked proxy objects", async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run([
|
|
'var proxy = new Proxy({}, {ownKeys: () => { throw new Error(); }});',
|
|
]);
|
|
|
|
const data = await complete(replServer, 'proxy.');
|
|
assert(Array.isArray(data));
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not include integer members of an Array', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var ary = [1,2,3];']);
|
|
|
|
const data = await complete(replServer, 'ary.');
|
|
assert.strictEqual(data[0].includes('ary.0'), false);
|
|
assert.strictEqual(data[0].includes('ary.1'), false);
|
|
assert.strictEqual(data[0].includes('ary.2'), false);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not include integer keys in an object', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var obj = {1:"a","1a":"b",a:"b"};']);
|
|
|
|
const data = await complete(replServer, 'obj.');
|
|
assert.strictEqual(data[0].includes('obj.1'), false);
|
|
assert.strictEqual(data[0].includes('obj.1a'), false);
|
|
assert(data[0].includes('obj.a'));
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not try to complete results of non-simple expressions', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['function a() {}']);
|
|
|
|
await expectNoResults(replServer, 'a().b.');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works when prefixed with spaces', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var obj = {1:"a","1a":"b",a:"b"};']);
|
|
|
|
const data = await complete(replServer, ' obj.');
|
|
assert.strictEqual(data[0].includes('obj.1'), false);
|
|
assert.strictEqual(data[0].includes('obj.1a'), false);
|
|
assert(data[0].includes('obj.a'));
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works inside assignments', async () => {
|
|
const { replServer } = startNewREPLServer();
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'var log = console.lo'),
|
|
[['console.log'], 'console.lo']);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not complete identifiers inside an unterminated string literal', async () => {
|
|
// The completion point sits inside the text of an open string, where there
|
|
// is nothing to complete. Regression test: previously the partial path was
|
|
// mistaken for a bare identifier and completed against the global scope.
|
|
const { replServer } = startNewREPLServer();
|
|
|
|
await expectNoResults(replServer, 'const x = "./n');
|
|
await expectNoResults(replServer, "const x = './n");
|
|
await expectNoResults(replServer, 'const x = `./n');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not complete inside fs path strings when blocking is disabled', async () => {
|
|
// With `allowBlockingCompletions` off, `fs.<fn>("…` is just an open string;
|
|
// it must not fall back to completing the partial path as a global.
|
|
const { replServer } = startNewREPLServer({ allowBlockingCompletions: false });
|
|
|
|
await expectNoResults(replServer, 'fs.readFileSync("./n');
|
|
await expectNoResults(replServer, 'fs.promises.readFile("./n');
|
|
await expectNoResults(replServer, 'fs.readFileSync(`./n');
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works for defined commands', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, '.b'),
|
|
[['break'], 'b']);
|
|
|
|
await run(['var obj = {"hello, world!": "some string", "key": 123}']);
|
|
|
|
const data = await complete(replServer, 'obj.');
|
|
assert.strictEqual(data[0].includes('obj.hello, world!'), false);
|
|
assert(data[0].includes('obj.key'));
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not include __defineSetter__ and friends', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run(['var obj = {};']);
|
|
|
|
const data = await complete(replServer, 'obj.');
|
|
assert.strictEqual(data[0].includes('obj.__defineGetter__'), false);
|
|
assert.strictEqual(data[0].includes('obj.__defineSetter__'), false);
|
|
assert.strictEqual(data[0].includes('obj.__lookupGetter__'), false);
|
|
assert.strictEqual(data[0].includes('obj.__lookupSetter__'), false);
|
|
assert.strictEqual(data[0].includes('obj.__proto__'), true);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works with builtin values', async () => {
|
|
const { replServer } = startNewREPLServer();
|
|
|
|
assert.deepStrictEqual(
|
|
await complete(replServer, 'I'),
|
|
[
|
|
[
|
|
'if',
|
|
'import',
|
|
'in',
|
|
'instanceof',
|
|
'',
|
|
'Infinity',
|
|
'Int16Array',
|
|
'Int32Array',
|
|
'Int8Array',
|
|
...(common.hasIntl ? ['Intl'] : []),
|
|
'Iterator',
|
|
'inspector',
|
|
'isFinite',
|
|
'isNaN',
|
|
'',
|
|
'isPrototypeOf',
|
|
],
|
|
'I',
|
|
]);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works with lexically scoped variables', async () => {
|
|
const { replServer, run } = startNewREPLServer();
|
|
|
|
await run([
|
|
'let lexicalLet = true;',
|
|
'const lexicalConst = true;',
|
|
'class lexicalKlass {}',
|
|
]);
|
|
|
|
for (const type of ['Let', 'Const', 'Klass']) {
|
|
const query = `lexical${type[0]}`;
|
|
const expected = [[`lexical${type}`], query];
|
|
assert.deepStrictEqual(await complete(replServer, query), expected);
|
|
}
|
|
|
|
replServer.close();
|
|
});
|
|
});
|