Skip to content

Commit 1e065a0

Browse files
ejose19targos
authored andcommitted
repl: processTopLevelAwait fallback error handling
PR-URL: #39290 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 9e5edf2 commit 1e065a0

File tree

3 files changed

+68
-3
lines changed

3 files changed

+68
-3
lines changed

lib/internal/repl/await.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ function processTopLevelAwait(src) {
119119
'^\n\n' + RegExpPrototypeSymbolReplace(/ \([^)]+\)/, e.message, '');
120120
// V8 unexpected token errors include the token string.
121121
if (StringPrototypeEndsWith(message, 'Unexpected token'))
122-
message += " '" + src[e.pos - wrapPrefix.length] + "'";
122+
message += " '" +
123+
// Wrapper end may cause acorn to report error position after the source
124+
(src[e.pos - wrapPrefix.length] ?? src[src.length - 1]) +
125+
"'";
123126
// eslint-disable-next-line no-restricted-syntax
124127
throw new SyntaxError(message);
125128
}

lib/repl.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ const {
7878
ReflectApply,
7979
RegExp,
8080
RegExpPrototypeExec,
81+
RegExpPrototypeSymbolReplace,
8182
RegExpPrototypeTest,
8283
SafeSet,
8384
SafeWeakSet,
@@ -439,8 +440,39 @@ function REPLServer(prompt,
439440
awaitPromise = true;
440441
}
441442
} catch (e) {
442-
decorateErrorStack(e);
443-
err = e;
443+
let recoverableError = false;
444+
if (e.name === 'SyntaxError') {
445+
let parentURL;
446+
try {
447+
const { pathToFileURL } = require('url');
448+
// Adding `/repl` prevents dynamic imports from loading relative
449+
// to the parent of `process.cwd()`.
450+
parentURL = pathToFileURL(path.join(process.cwd(), 'repl')).href;
451+
} catch {
452+
}
453+
454+
// Remove all "await"s and attempt running the script
455+
// in order to detect if error is truly non recoverable
456+
const fallbackCode = RegExpPrototypeSymbolReplace(/\bawait\b/g, code, '');
457+
try {
458+
vm.createScript(fallbackCode, {
459+
filename: file,
460+
displayErrors: true,
461+
importModuleDynamically: async (specifier) => {
462+
return asyncESM.ESMLoader.import(specifier, parentURL);
463+
}
464+
});
465+
} catch (fallbackError) {
466+
if (isRecoverableError(fallbackError, fallbackCode)) {
467+
recoverableError = true;
468+
err = new Recoverable(e);
469+
}
470+
}
471+
}
472+
if (!recoverableError) {
473+
decorateErrorStack(e);
474+
err = e;
475+
}
444476
}
445477
}
446478

test/parallel/test-repl-top-level-await.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,36 @@ async function ordinaryTests() {
152152
'Unexpected token \'.\'',
153153
],
154154
],
155+
['for (const x of [1,2,3]) {\nawait x\n}', [
156+
'for (const x of [1,2,3]) {\r',
157+
'... await x\r',
158+
'... }\r',
159+
'undefined',
160+
]],
161+
['for (const x of [1,2,3]) {\nawait x;\n}', [
162+
'for (const x of [1,2,3]) {\r',
163+
'... await x;\r',
164+
'... }\r',
165+
'undefined',
166+
]],
167+
['for await (const x of [1,2,3]) {\nconsole.log(x)\n}', [
168+
'for await (const x of [1,2,3]) {\r',
169+
'... console.log(x)\r',
170+
'... }\r',
171+
'1',
172+
'2',
173+
'3',
174+
'undefined',
175+
]],
176+
['for await (const x of [1,2,3]) {\nconsole.log(x);\n}', [
177+
'for await (const x of [1,2,3]) {\r',
178+
'... console.log(x);\r',
179+
'... }\r',
180+
'1',
181+
'2',
182+
'3',
183+
'undefined',
184+
]],
155185
];
156186

157187
for (const [input, expected = [`${input}\r`], options = {}] of testCases) {

0 commit comments

Comments
 (0)