Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,19 @@ fixReplRequire(module);
const writer = (obj) => inspect(obj, writer.options);
writer.options = { ...inspect.defaultOptions, showProxy: true };

// Converts static import statement to dynamic import statement
// Converts static import statement to dynamic import statement.
// Returns an empty string if `codeLine` cannot be parsed as a module — this
// happens when the user typed something that triggers V8's "Cannot use import
// statement outside a module" but isn't a complete, parseable import statement
// (e.g. `import` alone in the REPL).
const toDynamicImport = (codeLine) => {
let dynamicImportStatement = '';
const ast = acornParse(codeLine, { __proto__: null, sourceType: 'module', ecmaVersion: 'latest' });
let ast;
try {
ast = acornParse(codeLine, { __proto__: null, sourceType: 'module', ecmaVersion: 'latest' });
} catch {
return '';
}
acornWalk.ancestor(ast, {
ImportDeclaration(node) {
const awaitDynamicImport = `await import(${JSONStringify(node.source.value)});`;
Expand Down Expand Up @@ -1036,8 +1045,12 @@ class REPLServer extends Interface {
const importErrorStr = 'Cannot use import statement outside a ' +
'module';
if (StringPrototypeIncludes(e.message, importErrorStr)) {
e.message = 'Cannot use import statement inside the Node.js ' +
'REPL, alternatively use dynamic import: ' + toDynamicImport(ArrayPrototypeAt(this.lines, -1));
const dynamicImport =
toDynamicImport(ArrayPrototypeAt(this.lines, -1));
e.message = dynamicImport ?
'Cannot use import statement inside the Node.js REPL, ' +
`alternatively use dynamic import: ${dynamicImport}` :
'Cannot use import statement inside the Node.js REPL.';
e.stack = SideEffectFreeRegExpPrototypeSymbolReplace(
/SyntaxError:.*\n/,
e.stack,
Expand Down
31 changes: 31 additions & 0 deletions test/parallel/test-repl-import-statement-no-crash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';
// Regression test for https://github.com/nodejs/node/issues/63551:
// Typing a bare `import` keyword in the REPL used to terminate the process.
//
// V8 throws SyntaxError("Cannot use import statement outside a module"),
// which the REPL enriched with a dynamic-import suggestion via
// `toDynamicImport`. That helper called acorn without a try/catch; on
// incomplete input acorn threw, and the exception escaped the REPL's input
// pipeline to crash the process.
//
// The fix wraps the acorn call in try/catch and falls back to a plain
// error message when the line cannot be parsed as a complete import
// statement. This test asserts that:
// 1. Emitting an incomplete `import` line through the REPL does not
// throw out of the line-handler, and
// 2. The REPL still surfaces a SyntaxError to the user.

require('../common');
const assert = require('assert');
const { startNewREPLServer } = require('../common/repl');

const { replServer, output } = startNewREPLServer();

replServer.emit('line', 'import');
replServer.emit('line', '.exit');

assert.match(output.accumulator, /SyntaxError/);
assert.match(
output.accumulator,
/Cannot use import statement inside the Node\.js REPL\b/,
);