Skip to content

Commit 8879993

Browse files
ejose19guybedford
authored andcommitted
repl: processTopLevelAwait fallback error handling
PR-URL: nodejs#39290 Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 394fdec commit 8879993

File tree

3 files changed

+68
-3
lines changed

3 files changed

+68
-3
lines changed

lib/internal/repl/await.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ function processTopLevelAwait(src) {
184184
'^\n\n' + RegExpPrototypeSymbolReplace(/ \([^)]+\)/, e.message, '');
185185
// V8 unexpected token errors include the token string.
186186
if (StringPrototypeEndsWith(message, 'Unexpected token'))
187-
message += " '" + src[e.pos - wrapPrefix.length] + "'";
187+
message += " '" +
188+
// Wrapper end may cause acorn to report error position after the source
189+
(src[e.pos - wrapPrefix.length] ?? src[src.length - 1]) +
190+
"'";
188191
// eslint-disable-next-line no-restricted-syntax
189192
throw new SyntaxError(message);
190193
}

lib/repl.js

+34-2
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,
@@ -434,8 +435,39 @@ function REPLServer(prompt,
434435
awaitPromise = true;
435436
}
436437
} catch (e) {
437-
decorateErrorStack(e);
438-
err = e;
438+
let recoverableError = false;
439+
if (e.name === 'SyntaxError') {
440+
let parentURL;
441+
try {
442+
const { pathToFileURL } = require('url');
443+
// Adding `/repl` prevents dynamic imports from loading relative
444+
// to the parent of `process.cwd()`.
445+
parentURL = pathToFileURL(path.join(process.cwd(), 'repl')).href;
446+
} catch {
447+
}
448+
449+
// Remove all "await"s and attempt running the script
450+
// in order to detect if error is truly non recoverable
451+
const fallbackCode = RegExpPrototypeSymbolReplace(/\bawait\b/g, code, '');
452+
try {
453+
vm.createScript(fallbackCode, {
454+
filename: file,
455+
displayErrors: true,
456+
importModuleDynamically: async (specifier) => {
457+
return asyncESM.ESMLoader.import(specifier, parentURL);
458+
}
459+
});
460+
} catch (fallbackError) {
461+
if (isRecoverableError(fallbackError, fallbackCode)) {
462+
recoverableError = true;
463+
err = new Recoverable(e);
464+
}
465+
}
466+
}
467+
if (!recoverableError) {
468+
decorateErrorStack(e);
469+
err = e;
470+
}
439471
}
440472
}
441473

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

+30
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)