| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent c13eadc commit 88b02cb
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,7 +7,7 @@ const { extname } = require('path'); | |||
| 7 | 7 | const { getOptionValue } = require('internal/options'); | |
| 8 | 8 | ||
| 9 | 9 | const experimentalJsonModules = getOptionValue('--experimental-json-modules'); | |
| 10 | - const experimentalSpeciferResolution = | ||
| 10 | + const experimentalSpecifierResolution = | ||
| 11 | 11 | getOptionValue('--experimental-specifier-resolution'); | |
| 12 | 12 | const experimentalWasmModules = getOptionValue('--experimental-wasm-modules'); | |
| 13 | 13 | const { getPackageType } = require('internal/modules/esm/resolve'); | |
@@ -62,7 +62,7 @@ function defaultGetFormat(url, context, defaultGetFormatUnused) { | |||
| 62 | 62 | format = extensionFormatMap[ext]; | |
| 63 | 63 | } | |
| 64 | 64 | if (!format) { | |
| 65 | - if (experimentalSpeciferResolution === 'node') { | ||
| 65 | + if (experimentalSpecifierResolution === 'node') { | ||
| 66 | 66 | process.emitWarning( | |
| 67 | 67 | 'The Node.js specifier resolution in ESM is experimental.', | |
| 68 | 68 | 'ExperimentalWarning'); | |
@@ -75,4 +75,9 @@ function defaultGetFormat(url, context, defaultGetFormatUnused) { | |||
| 75 | 75 | } | |
| 76 | 76 | return { format: null }; | |
| 77 | 77 | } | |
| 78 | - exports.defaultGetFormat = defaultGetFormat; | ||
| 78 | + | ||
| 79 | + module.exports = { | ||
| 80 | + defaultGetFormat, | ||
| 81 | + extensionFormatMap, | ||
| 82 | + legacyExtensionFormatMap, | ||
| 83 | + }; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -54,6 +54,7 @@ const { | |||
| 54 | 54 | ArrayPrototypePush, | |
| 55 | 55 | ArrayPrototypeReverse, | |
| 56 | 56 | ArrayPrototypeShift, | |
| 57 | + ArrayPrototypeSlice, | ||
| 57 | 58 | ArrayPrototypeSome, | |
| 58 | 59 | ArrayPrototypeSort, | |
| 59 | 60 | ArrayPrototypeSplice, | |
@@ -127,6 +128,8 @@ let _builtinLibs = ArrayPrototypeFilter( | |||
| 127 | 128 | CJSModule.builtinModules, | |
| 128 | 129 | (e) => !StringPrototypeStartsWith(e, '_') && !StringPrototypeIncludes(e, '/') | |
| 129 | 130 | ); | |
| 131 | + const nodeSchemeBuiltinLibs = ArrayPrototypeMap( | ||
| 132 | + _builtinLibs, (lib) => `node:${lib}`); | ||
| 130 | 133 | const domain = require('domain'); | |
| 131 | 134 | let debug = require('internal/util/debuglog').debuglog('repl', (fn) => { | |
| 132 | 135 | debug = fn; | |
@@ -168,6 +171,11 @@ const { | |||
| 168 | 171 | } = internalBinding('contextify'); | |
| 169 | 172 | ||
| 170 | 173 | const history = require('internal/repl/history'); | |
| 174 | + const { | ||
| 175 | + extensionFormatMap, | ||
| 176 | + legacyExtensionFormatMap, | ||
| 177 | + } = require('internal/modules/esm/get_format'); | ||
| 178 | + | ||
| 171 | 179 | let nextREPLResourceNumber = 1; | |
| 172 | 180 | // This prevents v8 code cache from getting confused and using a different | |
| 173 | 181 | // cache from a resource of the same name | |
@@ -1135,10 +1143,12 @@ REPLServer.prototype.turnOffEditorMode = deprecate( | |||
| 1135 | 1143 | 'REPLServer.turnOffEditorMode() is deprecated', | |
| 1136 | 1144 | 'DEP0078'); | |
| 1137 | 1145 | ||
| 1146 | + const importRE = /\bimport\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/; | ||
| 1138 | 1147 | const requireRE = /\brequire\s*\(\s*['"`](([\w@./-]+\/)?(?:[\w@./-]*))(?![^'"`])$/; | |
| 1139 | 1148 | const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/; | |
| 1140 | 1149 | const simpleExpressionRE = | |
| 1141 | 1150 | /(?:[a-zA-Z_$](?:\w|\$)*\??\.)*[a-zA-Z_$](?:\w|\$)*\??\.?$/; | |
| 1151 | + const versionedFileNamesRe = /-\d+\.\d+/; | ||
| 1142 | 1152 | ||
| 1143 | 1153 | function isIdentifier(str) { | |
| 1144 | 1154 | if (str === '') { | |
@@ -1245,7 +1255,6 @@ function complete(line, callback) { | |||
| 1245 | 1255 | const indexes = ArrayPrototypeMap(extensions, | |
| 1246 | 1256 | (extension) => `index${extension}`); | |
| 1247 | 1257 | ArrayPrototypePush(indexes, 'package.json', 'index'); | |
| 1248 | - const versionedFileNamesRe = /-\d+\.\d+/; | ||
| 1249 | 1258 | ||
| 1250 | 1259 | const match = StringPrototypeMatch(line, requireRE); | |
| 1251 | 1260 | completeOn = match[1]; | |
@@ -1299,6 +1308,75 @@ function complete(line, callback) { | |||
| 1299 | 1308 | if (!subdir) { | |
| 1300 | 1309 | ArrayPrototypePush(completionGroups, _builtinLibs); | |
| 1301 | 1310 | } | |
| 1311 | + } else if (RegExpPrototypeTest(importRE, line) && | ||
| 1312 | + this.allowBlockingCompletions) { | ||
| 1313 | + // import('...<Tab>') | ||
| 1314 | + // File extensions that can be imported: | ||
| 1315 | + const extensions = ObjectKeys( | ||
| 1316 | + getOptionValue('--experimental-specifier-resolution') === 'node' ? | ||
| 1317 | + legacyExtensionFormatMap : | ||
| 1318 | + extensionFormatMap); | ||
| 1319 | + | ||
| 1320 | + // Only used when loading bare module specifiers from `node_modules`: | ||
| 1321 | + const indexes = ArrayPrototypeMap(extensions, (ext) => `index${ext}`); | ||
| 1322 | + ArrayPrototypePush(indexes, 'package.json'); | ||
| 1323 | + | ||
| 1324 | + const match = StringPrototypeMatch(line, importRE); | ||
| 1325 | + completeOn = match[1]; | ||
| 1326 | + const subdir = match[2] || ''; | ||
| 1327 | + filter = completeOn; | ||
| 1328 | + group = []; | ||
| 1329 | + let paths = []; | ||
| 1330 | + if (completeOn === '.') { | ||
| 1331 | + group = ['./', '../']; | ||
| 1332 | + } else if (completeOn === '..') { | ||
| 1333 | + group = ['../']; | ||
| 1334 | + } else if (RegExpPrototypeTest(/^\.\.?\//, completeOn)) { | ||
| 1335 | + paths = [process.cwd()]; | ||
| 1336 | + } else { | ||
| 1337 | + paths = ArrayPrototypeSlice(module.paths); | ||
| 1338 | + } | ||
| 1339 | + | ||
| 1340 | + ArrayPrototypeForEach(paths, (dir) => { | ||
| 1341 | + dir = path.resolve(dir, subdir); | ||
| 1342 | + const isInNodeModules = path.basename(dir) === 'node_modules'; | ||
| 1343 | + const dirents = gracefulReaddir(dir, { withFileTypes: true }) || []; | ||
| 1344 | + ArrayPrototypeForEach(dirents, (dirent) => { | ||
| 1345 | + const { name } = dirent; | ||
| 1346 | + if (RegExpPrototypeTest(versionedFileNamesRe, name) || | ||
| 1347 | + name === '.npm') { | ||
| 1348 | + // Exclude versioned names that 'npm' installs. | ||
| 1349 | + return; | ||
| 1350 | + } | ||
| 1351 | + | ||
| 1352 | + if (!dirent.isDirectory()) { | ||
| 1353 | + const extension = path.extname(name); | ||
| 1354 | + if (StringPrototypeIncludes(extensions, extension)) { | ||
| 1355 | + ArrayPrototypePush(group, `${subdir}${name}`); | ||
| 1356 | + } | ||
| 1357 | + return; | ||
| 1358 | + } | ||
| 1359 | + | ||
| 1360 | + ArrayPrototypePush(group, `${subdir}${name}/`); | ||
| 1361 | + if (!subdir && isInNodeModules) { | ||
| 1362 | + const absolute = path.resolve(dir, name); | ||
| 1363 | + const subfiles = gracefulReaddir(absolute) || []; | ||
| 1364 | + if (ArrayPrototypeSome(subfiles, (subfile) => { | ||
| 1365 | + return ArrayPrototypeIncludes(indexes, subfile); | ||
| 1366 | + })) { | ||
| 1367 | + ArrayPrototypePush(group, `${subdir}${name}`); | ||
| 1368 | + } | ||
| 1369 | + } | ||
| 1370 | + }); | ||
| 1371 | + }); | ||
| 1372 | + | ||
| 1373 | + if (group.length) { | ||
| 1374 | + ArrayPrototypePush(completionGroups, group); | ||
| 1375 | + } | ||
| 1376 | + | ||
| 1377 | + if (!subdir) { | ||
| 1378 | + ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs); | ||
| 1379 | + } | ||
| 1302 | 1380 | } else if (RegExpPrototypeTest(fsAutoCompleteRE, line) && | |
| 1303 | 1381 | this.allowBlockingCompletions) { | |
| 1304 | 1382 | ({ 0: completionGroups, 1: completeOn } = completeFSFunctions(line)); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -103,6 +103,9 @@ const tests = [ | |||
| 103 | 103 | yield 'require("./'; | |
| 104 | 104 | yield TABULATION; | |
| 105 | 105 | yield SIGINT; | |
| 106 | + yield 'import("./'; | ||
| 107 | + yield TABULATION; | ||
| 108 | + yield SIGINT; | ||
| 106 | 109 | yield 'Array.proto'; | |
| 107 | 110 | yield RIGHT; | |
| 108 | 111 | yield '.pu'; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,158 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + const ArrayStream = require('../common/arraystream'); | ||
| 5 | + const fixtures = require('../common/fixtures'); | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + const { builtinModules } = require('module'); | ||
| 8 | + const publicModules = builtinModules.filter( | ||
| 9 | + (lib) => !lib.startsWith('_') && !lib.includes('/'), | ||
| 10 | + ); | ||
| 11 | + | ||
| 12 | + if (!common.isMainThread) | ||
| 13 | + common.skip('process.chdir is not available in Workers'); | ||
| 14 | + | ||
| 15 | + // We have to change the directory to ../fixtures before requiring repl | ||
| 16 | + // in order to make the tests for completion of node_modules work properly | ||
| 17 | + // since repl modifies module.paths. | ||
| 18 | + process.chdir(fixtures.fixturesDir); | ||
| 19 | + | ||
| 20 | + const repl = require('repl'); | ||
| 21 | + | ||
| 22 | + const putIn = new ArrayStream(); | ||
| 23 | + const testMe = repl.start({ | ||
| 24 | + prompt: '', | ||
| 25 | + input: putIn, | ||
| 26 | + output: process.stdout, | ||
| 27 | + allowBlockingCompletions: true | ||
| 28 | + }); | ||
| 29 | + | ||
| 30 | + // Some errors are passed to the domain, but do not callback | ||
| 31 | + testMe._domain.on('error', assert.ifError); | ||
| 32 | + | ||
| 33 | + // Tab complete provides built in libs for import() | ||
| 34 | + testMe.complete('import(\'', common.mustCall((error, data) => { | ||
| 35 | + assert.strictEqual(error, null); | ||
| 36 | + publicModules.forEach((lib) => { | ||
| 37 | + assert( | ||
| 38 | + data[0].includes(lib) && data[0].includes(`node:${lib}`), | ||
| 39 | + `${lib} not found`, | ||
| 40 | + ); | ||
| 41 | + }); | ||
| 42 | + const newModule = 'foobar'; | ||
| 43 | + assert(!builtinModules.includes(newModule)); | ||
| 44 | + repl.builtinModules.push(newModule); | ||
| 45 | + testMe.complete('import(\'', common.mustCall((_, [modules]) => { | ||
| 46 | + assert.strictEqual(data[0].length + 1, modules.length); | ||
| 47 | + assert(modules.includes(newModule) && | ||
| 48 | + !modules.includes(`node:${newModule}`)); | ||
| 49 | + })); | ||
| 50 | + })); | ||
| 51 | + | ||
| 52 | + testMe.complete("import\t( 'n", common.mustCall((error, data) => { | ||
| 53 | + assert.strictEqual(error, null); | ||
| 54 | + assert.strictEqual(data.length, 2); | ||
| 55 | + assert.strictEqual(data[1], 'n'); | ||
| 56 | + const completions = data[0]; | ||
| 57 | + // import(...) completions include `node:` URL modules: | ||
| 58 | + publicModules.forEach((lib, index) => | ||
| 59 | + assert.strictEqual(completions[index], `node:${lib}`)); | ||
| 60 | + assert.strictEqual(completions[publicModules.length], ''); | ||
| 61 | + // There is only one Node.js module that starts with n: | ||
| 62 | + assert.strictEqual(completions[publicModules.length + 1], 'net'); | ||
| 63 | + assert.strictEqual(completions[publicModules.length + 2], ''); | ||
| 64 | + // It's possible to pick up non-core modules too | ||
| 65 | + completions.slice(publicModules.length + 3).forEach((completion) => { | ||
| 66 | + assert.match(completion, /^n/); | ||
| 67 | + }); | ||
| 68 | + })); | ||
| 69 | + | ||
| 70 | + { | ||
| 71 | + const expected = ['@nodejsscope', '@nodejsscope/']; | ||
| 72 | + // Import calls should handle all types of quotation marks. | ||
| 73 | + for (const quotationMark of ["'", '"', '`']) { | ||
| 74 | + putIn.run(['.clear']); | ||
| 75 | + testMe.complete('import(`@nodejs', common.mustCall((err, data) => { | ||
| 76 | + assert.strictEqual(err, null); | ||
| 77 | + assert.deepStrictEqual(data, [expected, '@nodejs']); | ||
| 78 | + })); | ||
| 79 | + | ||
| 80 | + putIn.run(['.clear']); | ||
| 81 | + // Completions should not be greedy in case the quotation ends. | ||
| 82 | + const input = `import(${quotationMark}@nodejsscope${quotationMark}`; | ||
| 83 | + testMe.complete(input, common.mustCall((err, data) => { | ||
| 84 | + assert.strictEqual(err, null); | ||
| 85 | + assert.deepStrictEqual(data, [[], undefined]); | ||
| 86 | + })); | ||
| 87 | + } | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + { | ||
| 91 | + putIn.run(['.clear']); | ||
| 92 | + // Completions should find modules and handle whitespace after the opening | ||
| 93 | + // bracket. | ||
| 94 | + testMe.complete('import \t("no_ind', common.mustCall((err, data) => { | ||
| 95 | + assert.strictEqual(err, null); | ||
| 96 | + assert.deepStrictEqual(data, [['no_index', 'no_index/'], 'no_ind']); | ||
| 97 | + })); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + // Test tab completion for import() relative to the current directory | ||
| 101 | + { | ||
| 102 | + putIn.run(['.clear']); | ||
| 103 | + | ||
| 104 | + const cwd = process.cwd(); | ||
| 105 | + process.chdir(__dirname); | ||
| 106 | + | ||
| 107 | + ['import(\'.', 'import(".'].forEach((input) => { | ||
| 108 | + testMe.complete(input, common.mustCall((err, data) => { | ||
| 109 | + assert.strictEqual(err, null); | ||
| 110 | + assert.strictEqual(data.length, 2); | ||
| 111 | + assert.strictEqual(data[1], '.'); | ||
| 112 | + assert.strictEqual(data[0].length, 2); | ||
| 113 | + assert.ok(data[0].includes('./')); | ||
| 114 | + assert.ok(data[0].includes('../')); | ||
| 115 | + })); | ||
| 116 | + }); | ||
| 117 | + | ||
| 118 | + ['import(\'..', 'import("..'].forEach((input) => { | ||
| 119 | + testMe.complete(input, common.mustCall((err, data) => { | ||
| 120 | + assert.strictEqual(err, null); | ||
| 121 | + assert.deepStrictEqual(data, [['../'], '..']); | ||
| 122 | + })); | ||
| 123 | + }); | ||
| 124 | + | ||
| 125 | + ['./', './test-'].forEach((path) => { | ||
| 126 | + [`import('${path}`, `import("${path}`].forEach((input) => { | ||
| 127 | + testMe.complete(input, common.mustCall((err, data) => { | ||
| 128 | + assert.strictEqual(err, null); | ||
| 129 | + assert.strictEqual(data.length, 2); | ||
| 130 | + assert.strictEqual(data[1], path); | ||
| 131 | + assert.ok(data[0].includes('./test-repl-tab-complete.js')); | ||
| 132 | + })); | ||
| 133 | + }); | ||
| 134 | + }); | ||
| 135 | + | ||
| 136 | + ['../parallel/', '../parallel/test-'].forEach((path) => { | ||
| 137 | + [`import('${path}`, `import("${path}`].forEach((input) => { | ||
| 138 | + testMe.complete(input, common.mustCall((err, data) => { | ||
| 139 | + assert.strictEqual(err, null); | ||
| 140 | + assert.strictEqual(data.length, 2); | ||
| 141 | + assert.strictEqual(data[1], path); | ||
| 142 | + assert.ok(data[0].includes('../parallel/test-repl-tab-complete.js')); | ||
| 143 | + })); | ||
| 144 | + }); | ||
| 145 | + }); | ||
| 146 | + | ||
| 147 | + { | ||
| 148 | + const path = '../fixtures/repl-folder-extensions/f'; | ||
| 149 | + testMe.complete(`import('${path}`, common.mustSucceed((data) => { | ||
| 150 | + assert.strictEqual(data.length, 2); | ||
| 151 | + assert.strictEqual(data[1], path); | ||
| 152 | + assert.ok(data[0].includes( | ||
| 153 | + '../fixtures/repl-folder-extensions/foo.js/')); | ||
| 154 | + })); | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + process.chdir(cwd); | ||
| 158 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments