diff --git a/lib/internal/modules/cjs/helpers.js b/lib/internal/modules/cjs/helpers.js index 2f62c8623e27be..2181fc2a42cab3 100644 --- a/lib/internal/modules/cjs/helpers.js +++ b/lib/internal/modules/cjs/helpers.js @@ -2,6 +2,7 @@ const { ObjectDefineProperty, + ObjectPrototypeHasOwnProperty, SafeMap, } = primordials; const { @@ -109,55 +110,17 @@ function stripBOM(content) { return content; } -const builtinLibs = [ - 'assert', - 'async_hooks', - 'buffer', - 'child_process', - 'cluster', - 'crypto', - 'dgram', - 'dns', - 'domain', - 'events', - 'fs', - 'http', - 'http2', - 'https', - 'net', - 'os', - 'path', - 'perf_hooks', - 'punycode', - 'querystring', - 'readline', - 'repl', - 'stream', - 'string_decoder', - 'tls', - 'trace_events', - 'tty', - 'url', - 'util', - 'v8', - 'vm', - 'worker_threads', - 'zlib', -]; - -if (internalBinding('config').experimentalWasi) { - builtinLibs.push('wasi'); - builtinLibs.sort(); -} - -if (typeof internalBinding('inspector').open === 'function') { - builtinLibs.push('inspector'); - builtinLibs.sort(); -} - function addBuiltinLibsToObject(object) { // Make built-in modules available directly (loaded lazily). - builtinLibs.forEach((name) => { + const { builtinModules } = require('internal/modules/cjs/loader').Module; + builtinModules.forEach((name) => { + // Neither add underscored modules, nor ones that contain slashes (e.g., + // 'fs/promises') or ones that are already defined. + if (name.startsWith('_') || + name.includes('/') || + ObjectPrototypeHasOwnProperty(object, name)) { + return; + } // Goals of this mechanism are: // - Lazy loading of built-in modules // - Having all built-in modules available as non-enumerable properties @@ -203,7 +166,6 @@ function normalizeReferrerURL(referrer) { module.exports = { addBuiltinLibsToObject, - builtinLibs, loadNativeModule, makeRequireFunction, normalizeReferrerURL, diff --git a/lib/internal/repl/utils.js b/lib/internal/repl/utils.js index 476760a08d5934..160417491fe804 100644 --- a/lib/internal/repl/utils.js +++ b/lib/internal/repl/utils.js @@ -48,6 +48,8 @@ const previewOptions = { showHidden: false }; +const REPL_MODE_STRICT = Symbol('repl-strict'); + // If the error is that we've unexpectedly ended the input, // then let the user try to recover by adding more input. // Note: `e` (the original exception) is not used by the current implementation, @@ -136,8 +138,12 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { let previewCompletionCounter = 0; let completionPreview = null; + let hasCompletions = false; + let wrapped = false; + let escaped = null; + function getPreviewPos() { const displayPos = repl._getDisplayPos(`${repl._prompt}${repl.line}`); const cursorPos = repl.line.length !== repl.cursor ? @@ -146,7 +152,13 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { return { displayPos, cursorPos }; } - const clearPreview = () => { + function isCursorAtInputEnd() { + const { cursorPos, displayPos } = getPreviewPos(); + return cursorPos.rows === displayPos.rows && + cursorPos.cols === displayPos.cols; + } + + const clearPreview = (key) => { if (inputPreview !== null) { const { displayPos, cursorPos } = getPreviewPos(); const rows = displayPos.rows - cursorPos.rows + 1; @@ -179,8 +191,23 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { cursorTo(repl.output, pos.cursorPos.cols); moveCursor(repl.output, 0, -rows); } + if (!key.ctrl && !key.shift) { + if (key.name === 'escape') { + if (escaped === null && key.meta) { + escaped = repl.line; + } + } else if ((key.name === 'return' || key.name === 'enter') && + !key.meta && + escaped !== repl.line && + isCursorAtInputEnd()) { + repl._insertString(completionPreview); + } + } completionPreview = null; } + if (escaped !== repl.line) { + escaped = null; + } }; function showCompletionPreview(line, insertPreview) { @@ -206,6 +233,8 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { return; } + hasCompletions = true; + // If there is a common prefix to all matches, then apply that portion. const completions = rawCompletions.filter((e) => e); const prefix = commonPrefix(completions); @@ -242,6 +271,12 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { }); } + function isInStrictMode(repl) { + return repl.replMode === REPL_MODE_STRICT || process.execArgv + .map((e) => e.toLowerCase().replace(/_/g, '-')) + .includes('--use-strict'); + } + // This returns a code preview for arbitrary input code. function getInputPreview(input, callback) { // For similar reasons as `defaultEval`, wrap expressions starting with a @@ -269,8 +304,11 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { // may be inspected. } else if (preview.exceptionDetails && (result.className === 'EvalError' || - result.className === 'SyntaxError' || - result.className === 'ReferenceError')) { + result.className === 'SyntaxError' || + // Report ReferenceError in case the strict mode is active + // for input that has no completions. + (result.className === 'ReferenceError' && + (hasCompletions || !isInStrictMode(repl))))) { callback(null, null); } else if (result.objectId) { // The writer options might change and have influence on the inspect @@ -316,14 +354,9 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { return; } + hasCompletions = false; + // Add the autocompletion preview. - // TODO(BridgeAR): Trigger the input preview after the completion preview. - // That way it's possible to trigger the input prefix including the - // potential completion suffix. To do so, we also have to change the - // behavior of `enter` and `escape`: - // Enter should automatically add the suffix to the current line as long as - // escape was not pressed. We might even remove the preview in case any - // cursor movement is triggered. const insertPreview = false; showCompletionPreview(repl.line, insertPreview); @@ -397,9 +430,17 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { moveCursor(repl.output, 0, -rows - 1); }; - getInputPreview(line, inputPreviewCallback); + let previewLine = line; + + if (completionPreview !== null && + isCursorAtInputEnd() && + escaped !== repl.line) { + previewLine += completionPreview; + } + + getInputPreview(previewLine, inputPreviewCallback); if (wrapped) { - getInputPreview(line, inputPreviewCallback); + getInputPreview(previewLine, inputPreviewCallback); } wrapped = false; }; @@ -679,6 +720,8 @@ function setupReverseSearch(repl) { } module.exports = { + REPL_MODE_SLOPPY: Symbol('repl-sloppy'), + REPL_MODE_STRICT, isRecoverableError, kStandaloneREPL: Symbol('kStandaloneREPL'), setupPreview, diff --git a/lib/repl.js b/lib/repl.js index 3186e0a31b8c86..bd7b80a91b57ea 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -63,7 +63,6 @@ const { } = primordials; const { - builtinLibs, makeRequireFunction, addBuiltinLibsToObject } = require('internal/modules/cjs/helpers'); @@ -86,6 +85,8 @@ const { } = require('internal/readline/utils'); const { Console } = require('console'); const CJSModule = require('internal/modules/cjs/loader').Module; +const builtinModules = [...CJSModule.builtinModules] + .filter((e) => !e.startsWith('_')); const domain = require('domain'); const debug = require('internal/util/debuglog').debuglog('repl'); const { @@ -103,6 +104,8 @@ const experimentalREPLAwait = require('internal/options').getOptionValue( '--experimental-repl-await' ); const { + REPL_MODE_SLOPPY, + REPL_MODE_STRICT, isRecoverableError, kStandaloneREPL, setupPreview, @@ -156,7 +159,7 @@ module.paths = CJSModule._nodeModulePaths(module.filename); const writer = exports.writer = (obj) => inspect(obj, writer.options); writer.options = { ...inspect.defaultOptions, showProxy: true }; -exports._builtinLibs = builtinLibs; +exports._builtinLibs = builtinModules; function REPLServer(prompt, stream, @@ -363,8 +366,7 @@ function REPLServer(prompt, } while (true) { try { - if (!/^\s*$/.test(code) && - self.replMode === exports.REPL_MODE_STRICT) { + if (self.replMode === exports.REPL_MODE_STRICT && !/^\s*$/.test(code)) { // "void 0" keeps the repl from returning "use strict" as the result // value for statements and declarations that don't return a value. code = `'use strict'; void 0;\n${code}`; @@ -849,7 +851,7 @@ function REPLServer(prompt, self.cursor === 0 && self.line.length === 0) { self.clearLine(); } - clearPreview(); + clearPreview(key); if (!reverseSearch(d, key)) { ttyWrite(d, key); showPreview(); @@ -896,8 +898,8 @@ ObjectSetPrototypeOf(REPLServer, Interface); exports.REPLServer = REPLServer; -exports.REPL_MODE_SLOPPY = Symbol('repl-sloppy'); -exports.REPL_MODE_STRICT = Symbol('repl-strict'); +exports.REPL_MODE_SLOPPY = REPL_MODE_SLOPPY; +exports.REPL_MODE_STRICT = REPL_MODE_STRICT; // Prompt is a string to print on each line for the prompt, // source is a stream to use for I/O, defaulting to stdin/stdout. @@ -1044,7 +1046,7 @@ REPLServer.prototype.turnOffEditorMode = deprecate( 'REPLServer.turnOffEditorMode() is deprecated', 'DEP0078'); -const requireRE = /\brequire\s*\(['"](([\w@./-]+\/)?(?:[\w@./-]*))/; +const requireRE = /\brequire\s*\(\s*['"`](([\w@./-]+\/)?(?:[\w@./-]*))(?![^'"`])$/; const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/; const simpleExpressionRE = /(?:[a-zA-Z_$](?:\w|\$)*\.)*[a-zA-Z_$](?:\w|\$)*\.?$/; @@ -1092,8 +1094,13 @@ REPLServer.prototype.complete = function() { this.completer.apply(this, arguments); }; -// TODO: Native module names should be auto-resolved. -// That improves the auto completion. +function gracefulOperation(fn, args, alternative) { + try { + return fn(...args); + } catch { + return alternative; + } +} // Provide a list of completions for the given leading text. This is // given to the readline interface for handling tab completion. @@ -1115,26 +1122,25 @@ function complete(line, callback) { // REPL commands (e.g. ".break"). let filter; - let match = line.match(/^\s*\.(\w*)$/); - if (match) { + if (/^\s*\.(\w*)$/.test(line)) { completionGroups.push(ObjectKeys(this.commands)); - completeOn = match[1]; - if (match[1].length) { - filter = match[1]; + completeOn = line.match(/^\s*\.(\w*)$/)[1]; + if (completeOn.length) { + filter = completeOn; } completionGroupsLoaded(); - } else if (match = line.match(requireRE)) { + } else if (requireRE.test(line)) { // require('...') - const exts = ObjectKeys(this.context.require.extensions); - const indexRe = new RegExp('^index(?:' + exts.map(regexpEscape).join('|') + - ')$'); + const extensions = ObjectKeys(this.context.require.extensions); + const indexes = extensions.map((extension) => `index${extension}`); + indexes.push('package.json', 'index'); const versionedFileNamesRe = /-\d+\.\d+/; + const match = line.match(requireRE); completeOn = match[1]; const subdir = match[2] || ''; - filter = match[1]; - let dir, files, subfiles, isDirectory; + filter = completeOn; group = []; let paths = []; @@ -1148,41 +1154,34 @@ function complete(line, callback) { paths = module.paths.concat(CJSModule.globalPaths); } - for (let i = 0; i < paths.length; i++) { - dir = path.resolve(paths[i], subdir); - try { - files = fs.readdirSync(dir); - } catch { - continue; - } - for (let f = 0; f < files.length; f++) { - const name = files[f]; - const ext = path.extname(name); - const base = name.slice(0, -ext.length); - if (versionedFileNamesRe.test(base) || name === '.npm') { + for (let dir of paths) { + dir = path.resolve(dir, subdir); + const dirents = gracefulOperation( + fs.readdirSync, + [dir, { withFileTypes: true }], + [] + ); + for (const dirent of dirents) { + if (versionedFileNamesRe.test(dirent.name) || dirent.name === '.npm') { // Exclude versioned names that 'npm' installs. continue; } - const abs = path.resolve(dir, name); - try { - isDirectory = fs.statSync(abs).isDirectory(); - } catch { + const extension = path.extname(dirent.name); + const base = dirent.name.slice(0, -extension.length); + if (!dirent.isDirectory()) { + if (extensions.includes(extension) && (!subdir || base !== 'index')) { + group.push(`${subdir}${base}`); + } continue; } - if (isDirectory) { - group.push(subdir + name + '/'); - try { - subfiles = fs.readdirSync(abs); - } catch { - continue; + group.push(`${subdir}${dirent.name}/`); + const absolute = path.resolve(dir, dirent.name); + const subfiles = gracefulOperation(fs.readdirSync, [absolute], []); + for (const subfile of subfiles) { + if (indexes.includes(subfile)) { + group.push(`${subdir}${dirent.name}`); + break; } - for (let s = 0; s < subfiles.length; s++) { - if (indexRe.test(subfiles[s])) { - group.push(subdir + name); - } - } - } else if (exts.includes(ext) && (!subdir || base !== 'index')) { - group.push(subdir + base); } } } @@ -1195,11 +1194,10 @@ function complete(line, callback) { } completionGroupsLoaded(); - } else if (match = line.match(fsAutoCompleteRE)) { - - let filePath = match[1]; - let fileList; + } else if (fsAutoCompleteRE.test(line)) { filter = ''; + let filePath = line.match(fsAutoCompleteRE)[1]; + let fileList; try { fileList = fs.readdirSync(filePath, { withFileTypes: true }); @@ -1230,7 +1228,7 @@ function complete(line, callback) { // foo<|> # all scope vars with filter 'foo' // foo.<|> # completions for 'foo' with filter '' } else if (line.length === 0 || /\w|\.|\$/.test(line[line.length - 1])) { - match = simpleExpressionRE.exec(line); + const match = simpleExpressionRE.exec(line); if (line.length !== 0 && !match) { completionGroupsLoaded(); return; @@ -1580,10 +1578,6 @@ function defineDefaultCommands(repl) { } } -function regexpEscape(s) { - return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); -} - function Recoverable(err) { this.err = err; } diff --git a/test/fixtures/node_modules/no_index/lib/index.js b/test/fixtures/node_modules/no_index/lib/index.js new file mode 100644 index 00000000000000..4ba52ba2c8df67 --- /dev/null +++ b/test/fixtures/node_modules/no_index/lib/index.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/test/fixtures/node_modules/no_index/package.json b/test/fixtures/node_modules/no_index/package.json new file mode 100644 index 00000000000000..0c6100eadad184 --- /dev/null +++ b/test/fixtures/node_modules/no_index/package.json @@ -0,0 +1,3 @@ +{ + "main": "./lib/index.js" +} diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index e07ca03e807b5d..ea8eee1553219b 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -17,7 +17,6 @@ const expectedModules = new Set([ 'Internal Binding credentials', 'Internal Binding fs', 'Internal Binding fs_dir', - 'Internal Binding inspector', 'Internal Binding module_wrap', 'Internal Binding native_module', 'Internal Binding options', @@ -86,26 +85,28 @@ const expectedModules = new Set([ ]); if (!common.isMainThread) { - expectedModules.add('Internal Binding messaging'); - expectedModules.add('Internal Binding symbols'); - expectedModules.add('Internal Binding worker'); - expectedModules.add('NativeModule _stream_duplex'); - expectedModules.add('NativeModule _stream_passthrough'); - expectedModules.add('NativeModule _stream_readable'); - expectedModules.add('NativeModule _stream_transform'); - expectedModules.add('NativeModule _stream_writable'); - expectedModules.add('NativeModule internal/error-serdes'); - expectedModules.add('NativeModule internal/process/worker_thread_only'); - expectedModules.add('NativeModule internal/streams/buffer_list'); - expectedModules.add('NativeModule internal/streams/destroy'); - expectedModules.add('NativeModule internal/streams/end-of-stream'); - expectedModules.add('NativeModule internal/streams/legacy'); - expectedModules.add('NativeModule internal/streams/pipeline'); - expectedModules.add('NativeModule internal/streams/state'); - expectedModules.add('NativeModule internal/worker'); - expectedModules.add('NativeModule internal/worker/io'); - expectedModules.add('NativeModule stream'); - expectedModules.add('NativeModule worker_threads'); + [ + 'Internal Binding messaging', + 'Internal Binding symbols', + 'Internal Binding worker', + 'NativeModule _stream_duplex', + 'NativeModule _stream_passthrough', + 'NativeModule _stream_readable', + 'NativeModule _stream_transform', + 'NativeModule _stream_writable', + 'NativeModule internal/error-serdes', + 'NativeModule internal/process/worker_thread_only', + 'NativeModule internal/streams/buffer_list', + 'NativeModule internal/streams/destroy', + 'NativeModule internal/streams/end-of-stream', + 'NativeModule internal/streams/legacy', + 'NativeModule internal/streams/pipeline', + 'NativeModule internal/streams/state', + 'NativeModule internal/worker', + 'NativeModule internal/worker/io', + 'NativeModule stream', + 'NativeModule worker_threads', + ].forEach(expectedModules.add.bind(expectedModules)); } if (common.hasIntl) { @@ -115,6 +116,7 @@ if (common.hasIntl) { } if (process.features.inspector) { + expectedModules.add('Internal Binding inspector'); expectedModules.add('NativeModule internal/inspector_async_hook'); expectedModules.add('NativeModule internal/util/inspector'); } diff --git a/test/parallel/test-module-cjs-helpers.js b/test/parallel/test-module-cjs-helpers.js deleted file mode 100644 index 12de65598e54e1..00000000000000 --- a/test/parallel/test-module-cjs-helpers.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; -// Flags: --expose-internals - -require('../common'); -const assert = require('assert'); -const { builtinLibs } = require('internal/modules/cjs/helpers'); - -const expectedLibs = process.features.inspector ? 34 : 33; -assert.strictEqual(builtinLibs.length, expectedLibs); diff --git a/test/parallel/test-repl-history-navigation.js b/test/parallel/test-repl-history-navigation.js index 8eda8e3ceceeef..f34de6a178fe68 100644 --- a/test/parallel/test-repl-history-navigation.js +++ b/test/parallel/test-repl-history-navigation.js @@ -55,6 +55,7 @@ const WORD_RIGHT = { name: 'right', ctrl: true }; const GO_TO_END = { name: 'end' }; const DELETE_WORD_LEFT = { name: 'backspace', ctrl: true }; const SIGINT = { name: 'c', ctrl: true }; +const ESCAPE = { name: 'escape', meta: true }; const prompt = '> '; const WAIT = '€'; @@ -180,8 +181,10 @@ const tests = [ 'veryLongName'.repeat(30), ENTER, `${'\x1B[90m \x1B[39m'.repeat(235)} fun`, + ESCAPE, ENTER, `${' '.repeat(236)} fun`, + ESCAPE, ENTER ], expected: [], @@ -316,6 +319,7 @@ const tests = [ env: { NODE_REPL_HISTORY: defaultHistoryPath }, showEscapeCodes: true, skip: !process.features.inspector, + checkTotal: true, test: [ 'fu', 'n', @@ -329,6 +333,12 @@ const tests = [ BACKSPACE, WORD_LEFT, WORD_RIGHT, + ESCAPE, + ENTER, + UP, + LEFT, + ENTER, + UP, ENTER ], // C = Cursor n forward @@ -377,12 +387,36 @@ const tests = [ '\x1B[0K', '\x1B[7D', '\x1B[10G', ' // n', '\x1B[3G', '\x1B[10G', // 10. Word right. Cleanup '\x1B[0K', '\x1B[3G', '\x1B[7C', ' // n', '\x1B[10G', - '\x1B[0K', - // 11. ENTER + // 11. ESCAPE + '\x1B[0K', ' // n', '\x1B[10G', '\x1B[0K', + // 12. ENTER '\r\n', 'Uncaught ReferenceError: functio is not defined\n', '\x1B[1G', '\x1B[0J', - prompt, '\x1B[3G', '\r\n' + // 13. UP + prompt, '\x1B[3G', '\x1B[1G', '\x1B[0J', + `${prompt}functio`, '\x1B[10G', + ' // n', '\x1B[10G', + ' // n', '\x1B[10G', + // 14. LEFT + '\x1B[0K', '\x1B[1D', + '\x1B[10G', ' // n', '\x1B[9G', '\x1B[10G', + // 15. ENTER + '\x1B[0K', '\x1B[9G', '\x1B[1C', + '\r\n', + 'Uncaught ReferenceError: functio is not defined\n', + '\x1B[1G', '\x1B[0J', + '> ', '\x1B[3G', + // 16. UP + '\x1B[1G', '\x1B[0J', + '> functio', '\x1B[10G', + ' // n', '\x1B[10G', + ' // n', '\x1B[10G', '\x1B[0K', + // 17. ENTER + 'n', '\r\n', + '\x1B[1G', '\x1B[0J', + '... ', '\x1B[5G', + '\r\n' ], clean: true }, diff --git a/test/parallel/test-repl-mode.js b/test/parallel/test-repl-mode.js index df84d86a3c8dc3..d8131d34f93b44 100644 --- a/test/parallel/test-repl-mode.js +++ b/test/parallel/test-repl-mode.js @@ -7,7 +7,8 @@ const repl = require('repl'); const tests = [ testSloppyMode, testStrictMode, - testAutoMode + testAutoMode, + testStrictModeTerminal, ]; tests.forEach(function(test) { @@ -37,6 +38,22 @@ function testStrictMode() { assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> '); } +function testStrictModeTerminal() { + if (!process.features.inspector) { + console.warn('Test skipped: V8 inspector is disabled'); + return; + } + // Verify that ReferenceErrors are reported in strict mode previews. + const cli = initRepl(repl.REPL_MODE_STRICT, { + terminal: true + }); + + cli.input.emit('data', 'xyz '); + assert.ok( + cli.output.accumulator.includes('\n// ReferenceError: xyz is not defined') + ); +} + function testAutoMode() { const cli = initRepl(repl.REPL_MODE_MAGIC); @@ -48,7 +65,7 @@ function testAutoMode() { assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> '); } -function initRepl(mode) { +function initRepl(mode, options) { const input = new Stream(); input.write = input.pause = input.resume = () => {}; input.readable = true; @@ -65,6 +82,7 @@ function initRepl(mode) { output: output, useColors: false, terminal: false, - replMode: mode + replMode: mode, + ...options }); } diff --git a/test/parallel/test-repl-preview.js b/test/parallel/test-repl-preview.js index b8dbbf3f9a993d..3ec873b4c46188 100644 --- a/test/parallel/test-repl-preview.js +++ b/test/parallel/test-repl-preview.js @@ -90,7 +90,11 @@ async function tests(options) { input: 'koo', noPreview: '[Function: koo]', preview: [ - 'k\x1B[90moo\x1B[39m\x1B[9G\x1B[0Ko\x1B[90mo\x1B[39m\x1B[10G\x1B[0Ko', + 'k\x1B[90moo\x1B[39m\x1B[9G', + '\x1B[90m[Function: koo]\x1B[39m\x1B[9G\x1B[1A\x1B[1B\x1B[2K\x1B[1A' + + '\x1B[0Ko\x1B[90mo\x1B[39m\x1B[10G', + '\x1B[90m[Function: koo]\x1B[39m\x1B[10G\x1B[1A\x1B[1B\x1B[2K\x1B[1A' + + '\x1B[0Ko', '\x1B[90m[Function: koo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', '\x1B[36m[Function: koo]\x1B[39m' ] diff --git a/test/parallel/test-repl-strict-mode-previews.js b/test/parallel/test-repl-strict-mode-previews.js new file mode 100644 index 00000000000000..8da4029e186b3c --- /dev/null +++ b/test/parallel/test-repl-strict-mode-previews.js @@ -0,0 +1,46 @@ +// Previews in strict mode should indicate ReferenceErrors. + +'use strict'; + +const common = require('../common'); + +common.skipIfInspectorDisabled(); + +if (process.argv[2] === 'child') { + const stream = require('stream'); + const repl = require('repl'); + class ActionStream extends stream.Stream { + readable = true; + run(data) { + this.emit('data', `${data}`); + this.emit('keypress', '', { ctrl: true, name: 'd' }); + } + resume() {} + pause() {} + } + + repl.start({ + input: new ActionStream(), + output: new stream.Writable({ + write(chunk, _, next) { + console.log(chunk.toString()); + next(); + } + }), + useColors: false, + terminal: true + }).inputStream.run('xyz'); +} else { + const assert = require('assert'); + const { spawnSync } = require('child_process'); + + const result = spawnSync( + process.execPath, + ['--use-strict', `${__filename}`, 'child'] + ); + + assert.match( + result.stdout.toString(), + /\/\/ ReferenceError: xyz is not defined/ + ); +} diff --git a/test/parallel/test-repl-tab-complete.js b/test/parallel/test-repl-tab-complete.js index 6cf689c4b11074..c40485ce3f5eac 100644 --- a/test/parallel/test-repl-tab-complete.js +++ b/test/parallel/test-repl-tab-complete.js @@ -30,6 +30,7 @@ const { const assert = require('assert'); const path = require('path'); const fixtures = require('../common/fixtures'); +const { builtinModules } = require('module'); const hasInspector = process.features.inspector; if (!common.isMainThread) @@ -222,29 +223,52 @@ putIn.run(['.clear']); testMe.complete('require(\'', common.mustCall(function(error, data) { assert.strictEqual(error, null); - repl._builtinLibs.forEach(function(lib) { - assert(data[0].includes(lib), `${lib} not found`); + builtinModules.forEach((lib) => { + if (!lib.startsWith('_')) + assert(data[0].includes(lib), `${lib} not found`); }); })); -testMe.complete('require(\'n', common.mustCall(function(error, data) { +testMe.complete("require\t( 'n", common.mustCall(function(error, data) { assert.strictEqual(error, null); assert.strictEqual(data.length, 2); assert.strictEqual(data[1], 'n'); - assert(data[0].includes('net')); + // There is only one Node.js module that starts with n: + assert.strictEqual(data[0][0], 'net'); + assert.strictEqual(data[0][1], ''); // It's possible to pick up non-core modules too - data[0].forEach(function(completion) { - if (completion) - assert(/^n/.test(completion)); + data[0].slice(2).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']); - testMe.complete('require(\'@nodejs', common.mustCall((err, data) => { + // 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, [expected, '@nodejs']); + assert.deepStrictEqual(data, [['no_index', 'no_index/'], 'no_ind']); })); }