@@ -1398,6 +1398,25 @@ constexpr std::array<std::string_view, 3> esm_syntax_error_messages = {
13981398 " Unexpected token 'export'" , // `export` statements
13991399 " Cannot use 'import.meta' outside a module" }; // `import.meta` references
14001400
1401+ // Another class of error messages that we need to check for are syntax errors
1402+ // where the syntax throws when parsed as CommonJS but succeeds when parsed as
1403+ // ESM. So far, the cases we've found are:
1404+ // - CommonJS module variables (`module`, `exports`, `require`, `__filename`,
1405+ // `__dirname`): if the user writes code such as `const module =` in the top
1406+ // level of a CommonJS module, it will throw a syntax error; but the same
1407+ // code is valid in ESM.
1408+ // - Top-level `await`: if the user writes `await` at the top level of a
1409+ // CommonJS module, it will throw a syntax error; but the same code is valid
1410+ // in ESM.
1411+ constexpr std::array<std::string_view, 6 > throws_only_in_cjs_error_messages = {
1412+ " Identifier 'module' has already been declared" ,
1413+ " Identifier 'exports' has already been declared" ,
1414+ " Identifier 'require' has already been declared" ,
1415+ " Identifier '__filename' has already been declared" ,
1416+ " Identifier '__dirname' has already been declared" ,
1417+ " await is only valid in async functions and "
1418+ " the top level bodies of modules" };
1419+
14011420void ContextifyContext::ContainsModuleSyntax (
14021421 const FunctionCallbackInfo<Value>& args) {
14031422 Environment* env = Environment::GetCurrent (args);
@@ -1470,19 +1489,51 @@ void ContextifyContext::ContainsModuleSyntax(
14701489 id_symbol,
14711490 try_catch);
14721491
1473- bool found_error_message_caused_by_module_syntax = false ;
1492+ bool should_retry_as_esm = false ;
14741493 if (try_catch.HasCaught () && !try_catch.HasTerminated ()) {
14751494 Utf8Value message_value (env->isolate (), try_catch.Message ()->Get ());
14761495 auto message = message_value.ToStringView ();
14771496
14781497 for (const auto & error_message : esm_syntax_error_messages) {
14791498 if (message.find (error_message) != std::string_view::npos) {
1480- found_error_message_caused_by_module_syntax = true ;
1499+ should_retry_as_esm = true ;
1500+ break ;
1501+ }
1502+ }
1503+
1504+ for (const auto & error_message : throws_only_in_cjs_error_messages) {
1505+ if (message.find (error_message) != std::string_view::npos) {
1506+ // Try parsing again where the user's code is wrapped within an async
1507+ // function. If the new parse succeeds, then the error was caused by
1508+ // either a top-level declaration of one of the CommonJS module
1509+ // variables, or a top-level `await`.
1510+ TryCatchScope second_parse_try_catch (env);
1511+ Local<String> wrapped_code = String::Concat (isolate,
1512+ String::NewFromUtf8 (isolate, " (async function() {" ).ToLocalChecked (),
1513+ code);
1514+ wrapped_code = String::Concat (isolate, wrapped_code,
1515+ String::NewFromUtf8 (isolate, " })();" ).ToLocalChecked ());
1516+ ScriptCompiler::Source wrapped_source = GetCommonJSSourceInstance (
1517+ isolate, wrapped_code, filename, 0 , 0 , host_defined_options,
1518+ nullptr );
1519+ ContextifyContext::CompileFunctionAndCacheResult (env,
1520+ context,
1521+ &wrapped_source,
1522+ std::move (params),
1523+ std::vector<Local<Object>>(),
1524+ options,
1525+ true ,
1526+ id_symbol,
1527+ second_parse_try_catch);
1528+ if (!second_parse_try_catch.HasCaught () &&
1529+ !second_parse_try_catch.HasTerminated ()) {
1530+ should_retry_as_esm = true ;
1531+ }
14811532 break ;
14821533 }
14831534 }
14841535 }
1485- args.GetReturnValue ().Set (found_error_message_caused_by_module_syntax );
1536+ args.GetReturnValue ().Set (should_retry_as_esm );
14861537}
14871538
14881539static void StartSigintWatchdog (const FunctionCallbackInfo<Value>& args) {
0 commit comments