-
Notifications
You must be signed in to change notification settings - Fork 29.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
repl: add auto‑completion for dynamic import calls
Refs: #33238 Refs: #33282 Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com> PR-URL: #37178 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
- Loading branch information
Showing
4 changed files
with
248 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
const ArrayStream = require('../common/arraystream'); | ||
const fixtures = require('../common/fixtures'); | ||
const assert = require('assert'); | ||
const { builtinModules } = require('module'); | ||
const publicModules = builtinModules.filter( | ||
(lib) => !lib.startsWith('_') && !lib.includes('/'), | ||
); | ||
|
||
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'); | ||
|
||
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 provides built in libs for import() | ||
testMe.complete('import(\'', common.mustCall((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('import(\'', common.mustCall((_, [modules]) => { | ||
assert.strictEqual(data[0].length + 1, modules.length); | ||
assert(modules.includes(newModule) && | ||
!modules.includes(`node:${newModule}`)); | ||
})); | ||
})); | ||
|
||
testMe.complete("import\t( 'n", common.mustCall((error, data) => { | ||
assert.strictEqual(error, null); | ||
assert.strictEqual(data.length, 2); | ||
assert.strictEqual(data[1], 'n'); | ||
const completions = data[0]; | ||
// import(...) completions include `node:` URL modules: | ||
publicModules.forEach((lib, index) => | ||
assert.strictEqual(completions[index], `node:${lib}`)); | ||
assert.strictEqual(completions[publicModules.length], ''); | ||
// There is only one Node.js module that starts with n: | ||
assert.strictEqual(completions[publicModules.length + 1], 'net'); | ||
assert.strictEqual(completions[publicModules.length + 2], ''); | ||
// It's possible to pick up non-core modules too | ||
completions.slice(publicModules.length + 3).forEach((completion) => { | ||
assert.match(completion, /^n/); | ||
}); | ||
})); | ||
|
||
{ | ||
const expected = ['@nodejsscope', '@nodejsscope/']; | ||
// Import calls should handle all types of quotation marks. | ||
for (const quotationMark of ["'", '"', '`']) { | ||
putIn.run(['.clear']); | ||
testMe.complete('import(`@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 = `import(${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('import \t("no_ind', common.mustCall((err, data) => { | ||
assert.strictEqual(err, null); | ||
assert.deepStrictEqual(data, [['no_index', 'no_index/'], 'no_ind']); | ||
})); | ||
} | ||
|
||
// Test tab completion for import() relative to the current directory | ||
{ | ||
putIn.run(['.clear']); | ||
|
||
const cwd = process.cwd(); | ||
process.chdir(__dirname); | ||
|
||
['import(\'.', 'import(".'].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('../')); | ||
})); | ||
}); | ||
|
||
['import(\'..', 'import("..'].forEach((input) => { | ||
testMe.complete(input, common.mustCall((err, data) => { | ||
assert.strictEqual(err, null); | ||
assert.deepStrictEqual(data, [['../'], '..']); | ||
})); | ||
}); | ||
|
||
['./', './test-'].forEach((path) => { | ||
[`import('${path}`, `import("${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.js')); | ||
})); | ||
}); | ||
}); | ||
|
||
['../parallel/', '../parallel/test-'].forEach((path) => { | ||
[`import('${path}`, `import("${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.js')); | ||
})); | ||
}); | ||
}); | ||
|
||
{ | ||
const path = '../fixtures/repl-folder-extensions/f'; | ||
testMe.complete(`import('${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); | ||
} |