diff --git a/src/node_contextify.cc b/src/node_contextify.cc index c4db3eb076d49b..bc3447109b900e 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -1647,6 +1647,12 @@ static const auto throws_only_in_cjs_error_messages = "await is only valid in async functions and " "the top level bodies of modules"}; +static const auto maybe_top_level_await_errors = + std::array{ + "missing ) after argument list", // example: `func(await 1);` + "SyntaxError: Unexpected" // example: `if(await 1)` + }; + // If cached_data is provided, it would be used for the compilation and // the on-disk compilation cache from NODE_COMPILE_CACHE (if configured) // would be ignored. @@ -1877,6 +1883,16 @@ bool ShouldRetryAsESM(Realm* realm, break; } } + + for (const auto& error_message : maybe_top_level_await_errors) { + if (message_view.find(error_message) != std::string_view::npos) { + // If the error message is related to top-level await, we can try to + // compile it as ESM. + maybe_valid_in_esm = true; + break; + } + } + if (!maybe_valid_in_esm) { return false; } diff --git a/test/es-module/test-esm-tla-syntax-errors-not-recognized-as-tla-error.mjs b/test/es-module/test-esm-tla-syntax-errors-not-recognized-as-tla-error.mjs new file mode 100644 index 00000000000000..a9dc6785c45ecd --- /dev/null +++ b/test/es-module/test-esm-tla-syntax-errors-not-recognized-as-tla-error.mjs @@ -0,0 +1,77 @@ +import { spawnPromisified } from '../common/index.mjs'; +import { describe, it } from 'node:test'; +import { strictEqual, match } from 'node:assert'; + +describe('maybe top-level await syntax errors that are not recognized as top-level await errors', () => { + const expressions = [ + // string + { expression: '""' }, + // number + { expression: '0' }, + // boolean + { expression: 'true' }, + // null + { expression: 'null' }, + // undefined + { expression: 'undefined' }, + // object + { expression: '{}' }, + // array + { expression: '[]' }, + // new + { expression: 'new Date()' }, + // identifier + { initialize: 'const a = 2;', expression: 'a' }, + ]; + it('should not crash the process', async () => { + for (const { expression, initialize } of expressions) { + const wrapperExpressions = [ + `function callAwait() {}; callAwait(await ${expression});`, + `if (await ${expression}) {}`, + `{ key: await ${expression} }`, + `[await ${expression}]`, + `(await ${expression})`, + ]; + for (const wrapperExpression of wrapperExpressions) { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--eval', + ` + ${initialize || ''} + ${wrapperExpression} + `, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + strictEqual(signal, null); + } + } + }); + + it('should crash when the expression is not valid', async () => { + let { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--eval', + ` + function callAwait() {} + callAwait(await "" ""); + `, + ]); + match(stderr, /SyntaxError: missing \) after argument list/); + strictEqual(stdout, ''); + strictEqual(code, 1); + strictEqual(signal, null); + + ({ code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--eval', + ` + function callAwait() {} + if (a "") {} + `, + ])); + match(stderr, /SyntaxError: Unexpected string/); + strictEqual(stdout, ''); + strictEqual(code, 1); + strictEqual(signal, null); + }); +});