Skip to content

Commit

Permalink
repl: processTopLevelAwait fallback error handling
Browse files Browse the repository at this point in the history
PR-URL: #39290
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
ejose19 authored and targos committed Jul 11, 2021

Partially verified

This commit is signed with the committer’s verified signature.
targos’s contribution has been verified via GPG key.
We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
1 parent a101fe6 commit b168ec2
Showing 3 changed files with 68 additions and 3 deletions.
5 changes: 4 additions & 1 deletion lib/internal/repl/await.js
Original file line number Diff line number Diff line change
@@ -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);
}
36 changes: 34 additions & 2 deletions lib/repl.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
}

30 changes: 30 additions & 0 deletions test/parallel/test-repl-top-level-await.js
Original file line number Diff line number Diff line change
@@ -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) {

0 comments on commit b168ec2

Please sign in to comment.