Skip to content

Commit 5d805a1

Browse files
committed
module: correctly detect top-level await in ambiguous contexts
Fixes: #58331
1 parent 4d5ee24 commit 5d805a1

2 files changed

Lines changed: 83 additions & 14 deletions

File tree

src/node_contextify.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,6 +1647,12 @@ static const auto throws_only_in_cjs_error_messages =
16471647
"await is only valid in async functions and "
16481648
"the top level bodies of modules"};
16491649

1650+
static std::vector<std::string_view> maybe_top_level_await_errors = {
1651+
// example: `func(await 1);`
1652+
"missing ) after argument list",
1653+
// example: `if(await 1)`
1654+
"SyntaxError: Unexpected"};
1655+
16501656
// If cached_data is provided, it would be used for the compilation and
16511657
// the on-disk compilation cache from NODE_COMPILE_CACHE (if configured)
16521658
// would be ignored.
@@ -1877,6 +1883,16 @@ bool ShouldRetryAsESM(Realm* realm,
18771883
break;
18781884
}
18791885
}
1886+
1887+
for (const auto& error_message : maybe_top_level_await_errors) {
1888+
if (message_view.find(error_message) != std::string_view::npos) {
1889+
// If the error message is related to top-level await, we can try to
1890+
// compile it as ESM.
1891+
maybe_valid_in_esm = true;
1892+
break;
1893+
}
1894+
}
1895+
18801896
if (!maybe_valid_in_esm) {
18811897
return false;
18821898
}

test/es-module/test-esm-detect-ambiguous.mjs

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -427,22 +427,75 @@ describe('when working with Worker threads', () => {
427427
});
428428
});
429429

430-
describe('cjs & esm ambiguous syntax case', () => {
431-
it('should throw an ambiguous syntax error when using top-level await with require', async () => {
432-
const { stderr, code, signal } = await spawnPromisified(
433-
process.execPath,
434-
[
435-
'--input-type=module',
436-
'--eval',
437-
`await 1;\nconst fs = require('fs');`,
438-
]
439-
);
430+
describe('maybe top-level await syntax errors that are not recognized as top-level await errors', () => {
431+
const expressions = [
432+
// string
433+
{ expression: '""' },
434+
// number
435+
{ expression: '0' },
436+
// boolean
437+
{ expression: 'true' },
438+
// null
439+
{ expression: 'null' },
440+
// undefined
441+
{ expression: 'undefined' },
442+
// object
443+
{ expression: '{}' },
444+
// array
445+
{ expression: '[]' },
446+
// new
447+
{ expression: 'new Date()' },
448+
// identifier
449+
{ initialize: 'const a = 2;', expression: 'a' },
450+
];
451+
it('should not crash the process', async () => {
452+
for (const { expression, initialize } of expressions) {
453+
const wrapperExpressions = [
454+
`function callAwait() {}; callAwait(await ${expression});`,
455+
`if (await ${expression}) {}`,
456+
`{ key: await ${expression} }`,
457+
`[await ${expression}]`,
458+
`(await ${expression})`,
459+
];
460+
for (const wrapperExpression of wrapperExpressions) {
461+
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
462+
'--eval',
463+
`
464+
${initialize || ''}
465+
${wrapperExpression}
466+
`,
467+
]);
468+
469+
strictEqual(stderr, '');
470+
strictEqual(stdout, '');
471+
strictEqual(code, 0);
472+
strictEqual(signal, null);
473+
}
474+
}
475+
});
440476

441-
match(
442-
stderr,
443-
/ReferenceError: Cannot determine intended module format because both require\(\) and top-level await are present\. If the code is intended to be CommonJS, wrap await in an async function\. If the code is intended to be an ES module, replace require\(\) with import\./
444-
);
477+
it('should crash when the expression is not valid', async () => {
478+
let { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
479+
'--eval',
480+
`
481+
function callAwait() {}
482+
callAwait(await "" "");
483+
`,
484+
]);
485+
match(stderr, /SyntaxError: missing \) after argument list/);
486+
strictEqual(stdout, '');
487+
strictEqual(code, 1);
488+
strictEqual(signal, null);
445489

490+
({ code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
491+
'--eval',
492+
`
493+
function callAwait() {}
494+
if (a "") {}
495+
`,
496+
]));
497+
match(stderr, /SyntaxError: Unexpected string/);
498+
strictEqual(stdout, '');
446499
strictEqual(code, 1);
447500
strictEqual(signal, null);
448501
});

0 commit comments

Comments
 (0)