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

PR-URL: https://github.com/nodejs/node/pull/59032 Fixes: https://github.com/nodejs/node/issues/59029 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Matthew Aitken <maitken033380023@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
582 lines
15 KiB
JavaScript
582 lines
15 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 ArrayStream = require('../common/arraystream');
|
|
const { describe, it } = require('node:test');
|
|
const assert = require('assert');
|
|
|
|
const repl = require('repl');
|
|
|
|
function getNoResultsFunction() {
|
|
return common.mustSucceed((data) => {
|
|
assert.deepStrictEqual(data[0], []);
|
|
});
|
|
}
|
|
|
|
function prepareREPL() {
|
|
const input = new ArrayStream();
|
|
const replServer = repl.start({
|
|
prompt: '',
|
|
input,
|
|
output: process.stdout,
|
|
allowBlockingCompletions: true,
|
|
});
|
|
|
|
// Some errors are passed to the domain, but do not callback
|
|
replServer._domain.on('error', assert.ifError);
|
|
|
|
return { replServer, input };
|
|
}
|
|
|
|
describe('REPL tab completion (core functionality)', () => {
|
|
it('does not break with variable declarations without an initialization', () => {
|
|
const { replServer } = prepareREPL();
|
|
replServer.complete('let a', getNoResultsFunction());
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not break in an object literal', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var inner = {', 'one:1']);
|
|
|
|
replServer.complete('inner.o', getNoResultsFunction());
|
|
|
|
replServer.complete(
|
|
'console.lo',
|
|
common.mustCall(function(_error, data) {
|
|
assert.deepStrictEqual(data, [['console.log'], 'console.lo']);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works with optional chaining', () => {
|
|
const { replServer } = prepareREPL();
|
|
|
|
replServer.complete(
|
|
'console?.lo',
|
|
common.mustCall((_error, data) => {
|
|
assert.deepStrictEqual(data, [['console?.log'], 'console?.lo']);
|
|
})
|
|
);
|
|
|
|
replServer.complete(
|
|
'console?.zzz',
|
|
common.mustCall((_error, data) => {
|
|
assert.deepStrictEqual(data, [[], 'console?.zzz']);
|
|
})
|
|
);
|
|
|
|
replServer.complete(
|
|
'console?.',
|
|
common.mustCall((_error, data) => {
|
|
assert(data[0].includes('console?.log'));
|
|
assert.strictEqual(data[1], 'console?.');
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('returns object completions', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var inner = {', 'one:1']);
|
|
|
|
input.run(['};']);
|
|
|
|
replServer.complete(
|
|
'inner.o',
|
|
common.mustCall(function(_error, data) {
|
|
assert.deepStrictEqual(data, [['inner.one'], 'inner.o']);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not break in a ternary operator with ()', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var inner = ( true ', '?', '{one: 1} : ']);
|
|
|
|
replServer.complete('inner.o', getNoResultsFunction());
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works on literals', () => {
|
|
const { replServer } = prepareREPL();
|
|
|
|
replServer.complete(
|
|
'``.a',
|
|
common.mustCall((err, data) => {
|
|
assert.strictEqual(data[0].includes('``.at'), true);
|
|
})
|
|
);
|
|
replServer.complete(
|
|
"''.a",
|
|
common.mustCall((err, data) => {
|
|
assert.strictEqual(data[0].includes("''.at"), true);
|
|
})
|
|
);
|
|
replServer.complete(
|
|
'"".a',
|
|
common.mustCall((err, data) => {
|
|
assert.strictEqual(data[0].includes('"".at'), true);
|
|
})
|
|
);
|
|
replServer.complete(
|
|
'("").a',
|
|
common.mustCall((err, data) => {
|
|
assert.strictEqual(data[0].includes('("").at'), true);
|
|
})
|
|
);
|
|
replServer.complete(
|
|
'[].a',
|
|
common.mustCall((err, data) => {
|
|
assert.strictEqual(data[0].includes('[].at'), true);
|
|
})
|
|
);
|
|
replServer.complete(
|
|
'{}.a',
|
|
common.mustCall((err, data) => {
|
|
assert.deepStrictEqual(data[0], []);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it("does not return a function's local variable", () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var top = function() {', 'var inner = {one:1};', '}']);
|
|
|
|
replServer.complete('inner.o', getNoResultsFunction());
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it("does not return a function's local variable even when the function has parameters", () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run([
|
|
'var top = function(one, two) {',
|
|
'var inner = {',
|
|
' one:1',
|
|
'};',
|
|
]);
|
|
|
|
replServer.complete('inner.o', getNoResultsFunction());
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it("does not return a function's local variable" +
|
|
'even if the scope is nested inside an immediately executed function', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run([
|
|
'var top = function() {',
|
|
'(function test () {',
|
|
'var inner = {',
|
|
' one:1',
|
|
'};',
|
|
]);
|
|
|
|
replServer.complete('inner.o', getNoResultsFunction());
|
|
|
|
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)', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run([
|
|
'var top = function() {',
|
|
'r = function test (',
|
|
' one, two) {',
|
|
'var inner = {',
|
|
' one:1',
|
|
'};',
|
|
]);
|
|
|
|
replServer.complete('inner.o', getNoResultsFunction());
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('currently does not work, but should not break (local inner)', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run([
|
|
'var top = function() {',
|
|
'r = function test ()',
|
|
'{',
|
|
'var inner = {',
|
|
' one:1',
|
|
'};',
|
|
]);
|
|
|
|
replServer.complete('inner.o', getNoResultsFunction());
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('currently does not work, but should not break (local inner parens next line)', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run([
|
|
'var top = function() {',
|
|
'r = function test (',
|
|
')',
|
|
'{',
|
|
'var inner = {',
|
|
' one:1',
|
|
'};',
|
|
]);
|
|
|
|
replServer.complete('inner.o', getNoResultsFunction());
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works on non-Objects', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var str = "test";']);
|
|
|
|
replServer.complete(
|
|
'str.len',
|
|
common.mustCall(function(_error, data) {
|
|
assert.deepStrictEqual(data, [['str.length'], 'str.len']);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('should be case-insensitive if member part is lower-case', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };']);
|
|
|
|
replServer.complete(
|
|
'foo.b',
|
|
common.mustCall(function(_error, data) {
|
|
assert.deepStrictEqual(data, [
|
|
['foo.BARbuz', 'foo.barBLA', 'foo.barBar'],
|
|
'foo.b',
|
|
]);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('should be case-insensitive if member part is upper-case', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };']);
|
|
|
|
replServer.complete(
|
|
'foo.B',
|
|
common.mustCall(function(_error, data) {
|
|
assert.deepStrictEqual(data, [
|
|
['foo.BARbuz', 'foo.barBLA', 'foo.barBar'],
|
|
'foo.B',
|
|
]);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('should not break on spaces', () => {
|
|
const { replServer } = prepareREPL();
|
|
|
|
const spaceTimeout = setTimeout(function() {
|
|
throw new Error('timeout');
|
|
}, 1000);
|
|
|
|
replServer.complete(
|
|
' ',
|
|
common.mustSucceed((data) => {
|
|
assert.strictEqual(data[1], '');
|
|
assert.ok(data[0].includes('globalThis'));
|
|
clearTimeout(spaceTimeout);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it(`should pick up the global "toString" object, and any other properties up the "global" object's prototype chain`, () => {
|
|
const { replServer } = prepareREPL();
|
|
|
|
replServer.complete(
|
|
'toSt',
|
|
common.mustCall(function(_error, data) {
|
|
assert.deepStrictEqual(data, [['toString'], 'toSt']);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('should make own properties shadow properties on the prototype', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run([
|
|
'var x = Object.create(null);',
|
|
'x.a = 1;',
|
|
'x.b = 2;',
|
|
'var y = Object.create(x);',
|
|
'y.a = 3;',
|
|
'y.c = 4;',
|
|
]);
|
|
|
|
replServer.complete(
|
|
'y.',
|
|
common.mustCall(function(_error, data) {
|
|
assert.deepStrictEqual(data, [['y.b', '', 'y.a', 'y.c'], 'y.']);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works on context properties', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var custom = "test";']);
|
|
|
|
replServer.complete(
|
|
'cus',
|
|
common.mustCall(function(_error, data) {
|
|
assert.deepStrictEqual(data, [['CustomEvent', 'custom'], 'cus']);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it("doesn't crash REPL with half-baked proxy objects", () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run([
|
|
'var proxy = new Proxy({}, {ownKeys: () => { throw new Error(); }});',
|
|
]);
|
|
|
|
replServer.complete(
|
|
'proxy.',
|
|
common.mustCall(function(error, data) {
|
|
assert.strictEqual(error, null);
|
|
assert(Array.isArray(data));
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not include integer members of an Array', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var ary = [1,2,3];']);
|
|
|
|
replServer.complete(
|
|
'ary.',
|
|
common.mustCall(function(_error, data) {
|
|
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', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var obj = {1:"a","1a":"b",a:"b"};']);
|
|
|
|
replServer.complete(
|
|
'obj.',
|
|
common.mustCall(function(_error, data) {
|
|
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', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['function a() {}']);
|
|
|
|
replServer.complete('a().b.', getNoResultsFunction());
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works when prefixed with spaces', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var obj = {1:"a","1a":"b",a:"b"};']);
|
|
|
|
replServer.complete(
|
|
' obj.',
|
|
common.mustCall((_error, data) => {
|
|
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', () => {
|
|
const { replServer } = prepareREPL();
|
|
|
|
replServer.complete(
|
|
'var log = console.lo',
|
|
common.mustCall((_error, data) => {
|
|
assert.deepStrictEqual(data, [['console.log'], 'console.lo']);
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('works for defined commands', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
replServer.complete(
|
|
'.b',
|
|
common.mustCall((error, data) => {
|
|
assert.deepStrictEqual(data, [['break'], 'b']);
|
|
})
|
|
);
|
|
|
|
input.run(['var obj = {"hello, world!": "some string", "key": 123}']);
|
|
|
|
replServer.complete(
|
|
'obj.',
|
|
common.mustCall((error, data) => {
|
|
assert.strictEqual(data[0].includes('obj.hello, world!'), false);
|
|
assert(data[0].includes('obj.key'));
|
|
})
|
|
);
|
|
|
|
replServer.close();
|
|
});
|
|
|
|
it('does not include __defineSetter__ and friends', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run(['var obj = {};']);
|
|
|
|
replServer.complete(
|
|
'obj.',
|
|
common.mustCall(function(error, data) {
|
|
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', () => {
|
|
const { replServer } = prepareREPL();
|
|
|
|
replServer.complete(
|
|
'I',
|
|
common.mustCall((error, data) => {
|
|
assert.deepStrictEqual(data, [
|
|
[
|
|
'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', () => {
|
|
const { replServer, input } = prepareREPL();
|
|
|
|
input.run([
|
|
'let lexicalLet = true;',
|
|
'const lexicalConst = true;',
|
|
'class lexicalKlass {}',
|
|
]);
|
|
|
|
['Let', 'Const', 'Klass'].forEach((type) => {
|
|
const query = `lexical${type[0]}`;
|
|
const hasInspector = process.features.inspector;
|
|
const expected = hasInspector ?
|
|
[[`lexical${type}`], query] :
|
|
[[], `lexical${type[0]}`];
|
|
replServer.complete(
|
|
query,
|
|
common.mustCall((error, data) => {
|
|
assert.deepStrictEqual(data, expected);
|
|
})
|
|
);
|
|
});
|
|
|
|
replServer.close();
|
|
});
|
|
});
|