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>
  • Loading branch information
ejose19 authored and targos committed Jul 11, 2021
1 parent a101fe6 commit b168ec2
Show file tree
Hide file tree
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
Expand Up @@ -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);
}
Expand Down
36 changes: 34 additions & 2 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const {
ReflectApply,
RegExp,
RegExpPrototypeExec,
RegExpPrototypeSymbolReplace,
RegExpPrototypeTest,
SafeSet,
SafeWeakSet,
Expand Down Expand Up @@ -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;
}
}
}

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

0 comments on commit b168ec2

Please sign in to comment.