diff --git a/lib/internal/repl/await.js b/lib/internal/repl/await.js index 3d0caa17650a04..e36fa5bfd735b1 100644 --- a/lib/internal/repl/await.js +++ b/lib/internal/repl/await.js @@ -184,7 +184,10 @@ function processTopLevelAwait(src) { '^\n\n' + RegExpPrototypeSymbolReplace(/ \([^)]+\)/, e.message, ''); // V8 unexpected token errors include the token string. if (StringPrototypeEndsWith(message, 'Unexpected token')) - message += " '" + src[e.pos - wrapPrefix.length] + "'"; + message += " '" + + // Wrapper end may cause acorn to report error position after the source + (src[e.pos - wrapPrefix.length] ?? src[src.length - 1]) + + "'"; // eslint-disable-next-line no-restricted-syntax throw new SyntaxError(message); } diff --git a/lib/repl.js b/lib/repl.js index 74fee4c9434129..c73ad5d9d61ade 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -78,6 +78,7 @@ const { ReflectApply, RegExp, RegExpPrototypeExec, + RegExpPrototypeSymbolReplace, RegExpPrototypeTest, SafeSet, SafeWeakSet, @@ -434,8 +435,39 @@ function REPLServer(prompt, awaitPromise = true; } } catch (e) { - decorateErrorStack(e); - err = e; + let recoverableError = false; + if (e.name === 'SyntaxError') { + let parentURL; + try { + const { pathToFileURL } = require('url'); + // Adding `/repl` prevents dynamic imports from loading relative + // to the parent of `process.cwd()`. + parentURL = pathToFileURL(path.join(process.cwd(), 'repl')).href; + } catch { + } + + // Remove all "await"s and attempt running the script + // in order to detect if error is truly non recoverable + const fallbackCode = RegExpPrototypeSymbolReplace(/\bawait\b/g, code, ''); + try { + vm.createScript(fallbackCode, { + filename: file, + displayErrors: true, + importModuleDynamically: async (specifier) => { + return asyncESM.ESMLoader.import(specifier, parentURL); + } + }); + } catch (fallbackError) { + if (isRecoverableError(fallbackError, fallbackCode)) { + recoverableError = true; + err = new Recoverable(e); + } + } + } + if (!recoverableError) { + decorateErrorStack(e); + err = e; + } } } diff --git a/test/parallel/test-repl-top-level-await.js b/test/parallel/test-repl-top-level-await.js index 88446a3a447f37..1388ce9334c883 100644 --- a/test/parallel/test-repl-top-level-await.js +++ b/test/parallel/test-repl-top-level-await.js @@ -152,6 +152,36 @@ async function ordinaryTests() { 'Unexpected token \'.\'', ], ], + ['for (const x of [1,2,3]) {\nawait x\n}', [ + 'for (const x of [1,2,3]) {\r', + '... await x\r', + '... }\r', + 'undefined', + ]], + ['for (const x of [1,2,3]) {\nawait x;\n}', [ + 'for (const x of [1,2,3]) {\r', + '... await x;\r', + '... }\r', + 'undefined', + ]], + ['for await (const x of [1,2,3]) {\nconsole.log(x)\n}', [ + 'for await (const x of [1,2,3]) {\r', + '... console.log(x)\r', + '... }\r', + '1', + '2', + '3', + 'undefined', + ]], + ['for await (const x of [1,2,3]) {\nconsole.log(x);\n}', [ + 'for await (const x of [1,2,3]) {\r', + '... console.log(x);\r', + '... }\r', + '1', + '2', + '3', + 'undefined', + ]], ]; for (const [input, expected = [`${input}\r`], options = {}] of testCases) {