| // 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 { |
| hijackStderr, |
| restoreStderr |
| } = require('../common/hijackstdio'); |
| const assert = require('assert'); |
| const path = require('path'); |
| const fixtures = require('../common/fixtures'); |
| const { builtinModules } = require('module'); |
| const publicModules = builtinModules.filter((lib) => !lib.startsWith('_')); |
| |
| const hasInspector = process.features.inspector; |
| |
| if (!common.isMainThread) |
| common.skip('process.chdir is not available in Workers'); |
| |
| // We have to change the directory to ../fixtures before requiring repl |
| // in order to make the tests for completion of node_modules work properly |
| // since repl modifies module.paths. |
| process.chdir(fixtures.fixturesDir); |
| |
| const repl = require('repl'); |
| |
| function getNoResultsFunction() { |
| return common.mustSucceed((data) => { |
| assert.deepStrictEqual(data[0], []); |
| }); |
| } |
| |
| const works = [['inner.one'], 'inner.o']; |
| const putIn = new ArrayStream(); |
| const testMe = repl.start({ |
| prompt: '', |
| input: putIn, |
| output: process.stdout, |
| allowBlockingCompletions: true |
| }); |
| |
| // Some errors are passed to the domain, but do not callback |
| testMe._domain.on('error', assert.ifError); |
| |
| // Tab Complete will not break in an object literal |
| putIn.run([ |
| 'var inner = {', |
| 'one:1', |
| ]); |
| testMe.complete('inner.o', getNoResultsFunction()); |
| |
| testMe.complete('console.lo', common.mustCall(function(error, data) { |
| assert.deepStrictEqual(data, [['console.log'], 'console.lo']); |
| })); |
| |
| testMe.complete('console?.lo', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, [['console?.log'], 'console?.lo']); |
| })); |
| |
| testMe.complete('console?.zzz', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, [[], 'console?.zzz']); |
| })); |
| |
| testMe.complete('console?.', common.mustCall((error, data) => { |
| assert(data[0].includes('console?.log')); |
| assert.strictEqual(data[1], 'console?.'); |
| })); |
| |
| // Tab Complete will return globally scoped variables |
| putIn.run(['};']); |
| testMe.complete('inner.o', common.mustCall(function(error, data) { |
| assert.deepStrictEqual(data, works); |
| })); |
| |
| putIn.run(['.clear']); |
| |
| // Tab Complete will not break in an ternary operator with () |
| putIn.run([ |
| 'var inner = ( true ', |
| '?', |
| '{one: 1} : ', |
| ]); |
| testMe.complete('inner.o', getNoResultsFunction()); |
| |
| putIn.run(['.clear']); |
| |
| // Tab Complete will return a simple local variable |
| putIn.run([ |
| 'var top = function() {', |
| 'var inner = {one:1};', |
| ]); |
| testMe.complete('inner.o', getNoResultsFunction()); |
| |
| // When you close the function scope tab complete will not return the |
| // locally scoped variable |
| putIn.run(['};']); |
| testMe.complete('inner.o', getNoResultsFunction()); |
| |
| putIn.run(['.clear']); |
| |
| // Tab Complete will return a complex local variable |
| putIn.run([ |
| 'var top = function() {', |
| 'var inner = {', |
| ' one:1', |
| '};', |
| ]); |
| testMe.complete('inner.o', getNoResultsFunction()); |
| |
| putIn.run(['.clear']); |
| |
| // Tab Complete will return a complex local variable even if the function |
| // has parameters |
| putIn.run([ |
| 'var top = function(one, two) {', |
| 'var inner = {', |
| ' one:1', |
| '};', |
| ]); |
| testMe.complete('inner.o', getNoResultsFunction()); |
| |
| putIn.run(['.clear']); |
| |
| // Tab Complete will return a complex local variable even if the |
| // scope is nested inside an immediately executed function |
| putIn.run([ |
| 'var top = function() {', |
| '(function test () {', |
| 'var inner = {', |
| ' one:1', |
| '};', |
| ]); |
| testMe.complete('inner.o', getNoResultsFunction()); |
| |
| putIn.run(['.clear']); |
| |
| // The definition has the params and { on a separate line. |
| putIn.run([ |
| 'var top = function() {', |
| 'r = function test (', |
| ' one, two) {', |
| 'var inner = {', |
| ' one:1', |
| '};', |
| ]); |
| testMe.complete('inner.o', getNoResultsFunction()); |
| |
| putIn.run(['.clear']); |
| |
| // Currently does not work, but should not break, not the { |
| putIn.run([ |
| 'var top = function() {', |
| 'r = function test ()', |
| '{', |
| 'var inner = {', |
| ' one:1', |
| '};', |
| ]); |
| testMe.complete('inner.o', getNoResultsFunction()); |
| |
| putIn.run(['.clear']); |
| |
| // Currently does not work, but should not break |
| putIn.run([ |
| 'var top = function() {', |
| 'r = function test (', |
| ')', |
| '{', |
| 'var inner = {', |
| ' one:1', |
| '};', |
| ]); |
| testMe.complete('inner.o', getNoResultsFunction()); |
| |
| putIn.run(['.clear']); |
| |
| // Make sure tab completion works on non-Objects |
| putIn.run([ |
| 'var str = "test";', |
| ]); |
| testMe.complete('str.len', common.mustCall(function(error, data) { |
| assert.deepStrictEqual(data, [['str.length'], 'str.len']); |
| })); |
| |
| putIn.run(['.clear']); |
| |
| // Tab completion should be case-insensitive if member part is lower-case |
| putIn.run([ |
| 'var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };', |
| ]); |
| testMe.complete( |
| 'foo.b', |
| common.mustCall(function(error, data) { |
| assert.deepStrictEqual(data, [ |
| ['foo.BARbuz', 'foo.barBLA', 'foo.barBar'], |
| 'foo.b', |
| ]); |
| }) |
| ); |
| |
| putIn.run(['.clear']); |
| |
| // Tab completion should be case-insensitive if member part is upper-case |
| putIn.run([ |
| 'var foo = { barBar: 1, BARbuz: 2, barBLA: 3 };', |
| ]); |
| testMe.complete( |
| 'foo.B', |
| common.mustCall(function(error, data) { |
| assert.deepStrictEqual(data, [ |
| ['foo.BARbuz', 'foo.barBLA', 'foo.barBar'], |
| 'foo.B', |
| ]); |
| }) |
| ); |
| |
| putIn.run(['.clear']); |
| |
| // Tab completion should not break on spaces |
| const spaceTimeout = setTimeout(function() { |
| throw new Error('timeout'); |
| }, 1000); |
| |
| testMe.complete(' ', common.mustSucceed((data) => { |
| assert.strictEqual(data[1], ''); |
| assert.ok(data[0].includes('globalThis')); |
| clearTimeout(spaceTimeout); |
| })); |
| |
| // Tab completion should pick up the global "toString" object, and |
| // any other properties up the "global" object's prototype chain |
| testMe.complete('toSt', common.mustCall(function(error, data) { |
| assert.deepStrictEqual(data, [['toString'], 'toSt']); |
| })); |
| |
| // Own properties should shadow properties on the prototype |
| putIn.run(['.clear']); |
| putIn.run([ |
| 'var x = Object.create(null);', |
| 'x.a = 1;', |
| 'x.b = 2;', |
| 'var y = Object.create(x);', |
| 'y.a = 3;', |
| 'y.c = 4;', |
| ]); |
| testMe.complete('y.', common.mustCall(function(error, data) { |
| assert.deepStrictEqual(data, [['y.b', '', 'y.a', 'y.c'], 'y.']); |
| })); |
| |
| // Tab complete provides built in libs for require() |
| putIn.run(['.clear']); |
| |
| testMe.complete('require(\'', common.mustCall(function(error, data) { |
| assert.strictEqual(error, null); |
| publicModules.forEach((lib) => { |
| assert( |
| data[0].includes(lib) && data[0].includes(`node:${lib}`), |
| `${lib} not found` |
| ); |
| }); |
| const newModule = 'foobar'; |
| assert(!builtinModules.includes(newModule)); |
| repl.builtinModules.push(newModule); |
| testMe.complete('require(\'', common.mustCall((_, [modules]) => { |
| assert.strictEqual(data[0].length + 1, modules.length); |
| assert(modules.includes(newModule)); |
| })); |
| })); |
| |
| testMe.complete("require\t( 'n", common.mustCall(function(error, data) { |
| assert.strictEqual(error, null); |
| assert.strictEqual(data.length, 2); |
| assert.strictEqual(data[1], 'n'); |
| // require(...) completions include `node:`-prefixed modules: |
| let lastIndex = -1; |
| |
| publicModules.forEach((lib, index) => { |
| lastIndex = data[0].indexOf(`node:${lib}`); |
| assert.notStrictEqual(lastIndex, -1); |
| }); |
| assert.strictEqual(data[0][lastIndex + 1], ''); |
| // There is only one Node.js module that starts with n: |
| assert.strictEqual(data[0][lastIndex + 2], 'net'); |
| assert.strictEqual(data[0][lastIndex + 3], ''); |
| // It's possible to pick up non-core modules too |
| data[0].slice(lastIndex + 4).forEach((completion) => { |
| assert.match(completion, /^n/); |
| }); |
| })); |
| |
| { |
| const expected = ['@nodejsscope', '@nodejsscope/']; |
| // Require calls should handle all types of quotation marks. |
| for (const quotationMark of ["'", '"', '`']) { |
| putIn.run(['.clear']); |
| testMe.complete('require(`@nodejs', common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.deepStrictEqual(data, [expected, '@nodejs']); |
| })); |
| |
| putIn.run(['.clear']); |
| // Completions should not be greedy in case the quotation ends. |
| const input = `require(${quotationMark}@nodejsscope${quotationMark}`; |
| testMe.complete(input, common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.deepStrictEqual(data, [[], undefined]); |
| })); |
| } |
| } |
| |
| { |
| putIn.run(['.clear']); |
| // Completions should find modules and handle whitespace after the opening |
| // bracket. |
| testMe.complete('require \t("no_ind', common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.deepStrictEqual(data, [['no_index', 'no_index/'], 'no_ind']); |
| })); |
| } |
| |
| // Test tab completion for require() relative to the current directory |
| { |
| putIn.run(['.clear']); |
| |
| const cwd = process.cwd(); |
| process.chdir(__dirname); |
| |
| ['require(\'.', 'require(".'].forEach((input) => { |
| testMe.complete(input, common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.strictEqual(data.length, 2); |
| assert.strictEqual(data[1], '.'); |
| assert.strictEqual(data[0].length, 2); |
| assert.ok(data[0].includes('./')); |
| assert.ok(data[0].includes('../')); |
| })); |
| }); |
| |
| ['require(\'..', 'require("..'].forEach((input) => { |
| testMe.complete(input, common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.deepStrictEqual(data, [['../'], '..']); |
| })); |
| }); |
| |
| ['./', './test-'].forEach((path) => { |
| [`require('${path}`, `require("${path}`].forEach((input) => { |
| testMe.complete(input, common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.strictEqual(data.length, 2); |
| assert.strictEqual(data[1], path); |
| assert.ok(data[0].includes('./test-repl-tab-complete')); |
| })); |
| }); |
| }); |
| |
| ['../parallel/', '../parallel/test-'].forEach((path) => { |
| [`require('${path}`, `require("${path}`].forEach((input) => { |
| testMe.complete(input, common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.strictEqual(data.length, 2); |
| assert.strictEqual(data[1], path); |
| assert.ok(data[0].includes('../parallel/test-repl-tab-complete')); |
| })); |
| }); |
| }); |
| |
| { |
| const path = '../fixtures/repl-folder-extensions/f'; |
| testMe.complete(`require('${path}`, common.mustSucceed((data) => { |
| assert.strictEqual(data.length, 2); |
| assert.strictEqual(data[1], path); |
| assert.ok(data[0].includes('../fixtures/repl-folder-extensions/foo.js')); |
| })); |
| } |
| |
| process.chdir(cwd); |
| } |
| |
| // Make sure tab completion works on context properties |
| putIn.run(['.clear']); |
| |
| putIn.run([ |
| 'var custom = "test";', |
| ]); |
| testMe.complete('cus', common.mustCall(function(error, data) { |
| assert.deepStrictEqual(data, [['custom'], 'cus']); |
| })); |
| |
| // Make sure tab completion doesn't crash REPL with half-baked proxy objects. |
| // See: https://github.com/nodejs/node/issues/2119 |
| putIn.run(['.clear']); |
| |
| putIn.run([ |
| 'var proxy = new Proxy({}, {ownKeys: () => { throw new Error(); }});', |
| ]); |
| |
| testMe.complete('proxy.', common.mustCall(function(error, data) { |
| assert.strictEqual(error, null); |
| assert(Array.isArray(data)); |
| })); |
| |
| // Make sure tab completion does not include integer members of an Array |
| putIn.run(['.clear']); |
| |
| putIn.run(['var ary = [1,2,3];']); |
| testMe.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); |
| })); |
| |
| // Make sure tab completion does not include integer keys in an object |
| putIn.run(['.clear']); |
| putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']); |
| |
| testMe.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')); |
| })); |
| |
| // Don't try to complete results of non-simple expressions |
| putIn.run(['.clear']); |
| putIn.run(['function a() {}']); |
| |
| testMe.complete('a().b.', getNoResultsFunction()); |
| |
| // Works when prefixed with spaces |
| putIn.run(['.clear']); |
| putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']); |
| |
| testMe.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')); |
| })); |
| |
| // Works inside assignments |
| putIn.run(['.clear']); |
| |
| testMe.complete('var log = console.lo', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, [['console.log'], 'console.lo']); |
| })); |
| |
| // Tab completion for defined commands |
| putIn.run(['.clear']); |
| |
| testMe.complete('.b', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, [['break'], 'b']); |
| })); |
| putIn.run(['.clear']); |
| putIn.run(['var obj = {"hello, world!": "some string", "key": 123}']); |
| testMe.complete('obj.', common.mustCall((error, data) => { |
| assert.strictEqual(data[0].includes('obj.hello, world!'), false); |
| assert(data[0].includes('obj.key')); |
| })); |
| |
| // Make sure tab completion does not include __defineSetter__ and friends. |
| putIn.run(['.clear']); |
| |
| putIn.run(['var obj = {};']); |
| testMe.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); |
| })); |
| |
| // Tab completion for files/directories |
| { |
| putIn.run(['.clear']); |
| process.chdir(__dirname); |
| |
| const readFileSyncs = ['fs.readFileSync("', 'fs.promises.readFileSync("']; |
| if (!common.isWindows) { |
| readFileSyncs.forEach((readFileSync) => { |
| const fixturePath = `${readFileSync}../fixtures/test-repl-tab-completion`; |
| testMe.complete(fixturePath, common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.ok(data[0][0].includes('.hiddenfiles')); |
| assert.ok(data[0][1].includes('hellorandom.txt')); |
| assert.ok(data[0][2].includes('helloworld.js')); |
| })); |
| |
| testMe.complete(`${fixturePath}/hello`, |
| common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.ok(data[0][0].includes('hellorandom.txt')); |
| assert.ok(data[0][1].includes('helloworld.js')); |
| }) |
| ); |
| |
| testMe.complete(`${fixturePath}/.h`, |
| common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.ok(data[0][0].includes('.hiddenfiles')); |
| }) |
| ); |
| |
| testMe.complete(`${readFileSync}./xxxRandom/random`, |
| common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.strictEqual(data[0].length, 0); |
| }) |
| ); |
| |
| const testPath = fixturePath.slice(0, -1); |
| testMe.complete(testPath, common.mustCall((err, data) => { |
| assert.strictEqual(err, null); |
| assert.ok(data[0][0].includes('test-repl-tab-completion')); |
| assert.strictEqual( |
| data[1], |
| path.basename(testPath) |
| ); |
| })); |
| }); |
| } |
| } |
| |
| [ |
| Array, |
| Buffer, |
| |
| Uint8Array, |
| Uint16Array, |
| Uint32Array, |
| |
| Uint8ClampedArray, |
| Int8Array, |
| Int16Array, |
| Int32Array, |
| Float32Array, |
| Float64Array, |
| ].forEach((type) => { |
| putIn.run(['.clear']); |
| |
| if (type === Array) { |
| putIn.run([ |
| 'var ele = [];', |
| 'for (let i = 0; i < 1e6 + 1; i++) ele[i] = 0;', |
| 'ele.biu = 1;', |
| ]); |
| } else if (type === Buffer) { |
| putIn.run(['var ele = Buffer.alloc(1e6 + 1); ele.biu = 1;']); |
| } else { |
| putIn.run([`var ele = new ${type.name}(1e6 + 1); ele.biu = 1;`]); |
| } |
| |
| hijackStderr(common.mustNotCall()); |
| testMe.complete('ele.', common.mustCall((err, data) => { |
| restoreStderr(); |
| assert.ifError(err); |
| |
| const ele = (type === Array) ? |
| [] : |
| (type === Buffer ? |
| Buffer.alloc(0) : |
| new type(0)); |
| |
| assert.strictEqual(data[0].includes('ele.biu'), true); |
| |
| data[0].forEach((key) => { |
| if (!key || key === 'ele.biu') return; |
| assert.notStrictEqual(ele[key.substr(4)], undefined); |
| }); |
| })); |
| }); |
| |
| // check Buffer.prototype.length not crashing. |
| // Refs: https://github.com/nodejs/node/pull/11961 |
| putIn.run(['.clear']); |
| testMe.complete('Buffer.prototype.', common.mustCall()); |
| |
| // Make sure repl gives correct autocomplete on literals |
| testMe.complete('``.a', common.mustCall((err, data) => { |
| assert.strictEqual(data[0].includes('``.at'), true); |
| })); |
| testMe.complete('\'\'.a', common.mustCall((err, data) => { |
| assert.strictEqual(data[0].includes('\'\'.at'), true); |
| })); |
| testMe.complete('"".a', common.mustCall((err, data) => { |
| assert.strictEqual(data[0].includes('"".at'), true); |
| })); |
| testMe.complete('("").a', common.mustCall((err, data) => { |
| assert.strictEqual(data[0].includes('("").at'), true); |
| })); |
| testMe.complete('[].a', common.mustCall((err, data) => { |
| assert.strictEqual(data[0].includes('[].at'), true); |
| })); |
| testMe.complete('{}.a', common.mustCall((err, data) => { |
| assert.deepStrictEqual(data[0], []); |
| })); |
| |
| const testNonGlobal = repl.start({ |
| input: putIn, |
| output: putIn, |
| useGlobal: false |
| }); |
| |
| const builtins = [ |
| [ |
| 'if', |
| 'import', |
| 'in', |
| 'instanceof', |
| '', |
| 'Infinity', |
| 'Int16Array', |
| 'Int32Array', |
| 'Int8Array', |
| ...(common.hasIntl ? ['Intl'] : []), |
| 'inspector', |
| 'isFinite', |
| 'isNaN', |
| '', |
| 'isPrototypeOf', |
| ], |
| 'I', |
| ]; |
| |
| testNonGlobal.complete('I', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, builtins); |
| })); |
| |
| // To test custom completer function. |
| // Sync mode. |
| const customCompletions = 'aaa aa1 aa2 bbb bb1 bb2 bb3 ccc ddd eee'.split(' '); |
| const testCustomCompleterSyncMode = repl.start({ |
| prompt: '', |
| input: putIn, |
| output: putIn, |
| completer: function completer(line) { |
| const hits = customCompletions.filter((c) => c.startsWith(line)); |
| // Show all completions if none found. |
| return [hits.length ? hits : customCompletions, line]; |
| } |
| }); |
| |
| // On empty line should output all the custom completions |
| // without complete anything. |
| testCustomCompleterSyncMode.complete('', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, [ |
| customCompletions, |
| '', |
| ]); |
| })); |
| |
| // On `a` should output `aaa aa1 aa2` and complete until `aa`. |
| testCustomCompleterSyncMode.complete('a', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, [ |
| 'aaa aa1 aa2'.split(' '), |
| 'a', |
| ]); |
| })); |
| |
| // To test custom completer function. |
| // Async mode. |
| const testCustomCompleterAsyncMode = repl.start({ |
| prompt: '', |
| input: putIn, |
| output: putIn, |
| completer: function completer(line, callback) { |
| const hits = customCompletions.filter((c) => c.startsWith(line)); |
| // Show all completions if none found. |
| callback(null, [hits.length ? hits : customCompletions, line]); |
| } |
| }); |
| |
| // On empty line should output all the custom completions |
| // without complete anything. |
| testCustomCompleterAsyncMode.complete('', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, [ |
| customCompletions, |
| '', |
| ]); |
| })); |
| |
| // On `a` should output `aaa aa1 aa2` and complete until `aa`. |
| testCustomCompleterAsyncMode.complete('a', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, [ |
| 'aaa aa1 aa2'.split(' '), |
| 'a', |
| ]); |
| })); |
| |
| // Tab completion in editor mode |
| const editorStream = new ArrayStream(); |
| const editor = repl.start({ |
| stream: editorStream, |
| terminal: true, |
| useColors: false |
| }); |
| |
| editorStream.run(['.clear']); |
| editorStream.run(['.editor']); |
| |
| editor.completer('Uin', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, [['Uint'], 'Uin']); |
| })); |
| |
| editorStream.run(['.clear']); |
| editorStream.run(['.editor']); |
| |
| editor.completer('var log = console.l', common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, [['console.log'], 'console.l']); |
| })); |
| |
| { |
| // Tab completion of lexically scoped variables |
| const stream = new ArrayStream(); |
| const testRepl = repl.start({ stream }); |
| |
| stream.run([` |
| let lexicalLet = true; |
| const lexicalConst = true; |
| class lexicalKlass {} |
| `]); |
| |
| ['Let', 'Const', 'Klass'].forEach((type) => { |
| const query = `lexical${type[0]}`; |
| const expected = hasInspector ? [[`lexical${type}`], query] : |
| [[], `lexical${type[0]}`]; |
| testRepl.complete(query, common.mustCall((error, data) => { |
| assert.deepStrictEqual(data, expected); |
| })); |
| }); |
| } |