@@ -364,13 +364,15 @@ void ContextifyContext::CreatePerIsolateProperties(
364
364
SetMethod (isolate, target, " makeContext" , MakeContext);
365
365
SetMethod (isolate, target, " compileFunction" , CompileFunction);
366
366
SetMethod (isolate, target, " containsModuleSyntax" , ContainsModuleSyntax);
367
+ SetMethod (isolate, target, " shouldRetryAsESM" , ShouldRetryAsESM);
367
368
}
368
369
369
370
void ContextifyContext::RegisterExternalReferences (
370
371
ExternalReferenceRegistry* registry) {
371
372
registry->Register (MakeContext);
372
373
registry->Register (CompileFunction);
373
374
registry->Register (ContainsModuleSyntax);
375
+ registry->Register (ShouldRetryAsESM);
374
376
registry->Register (PropertyGetterCallback);
375
377
registry->Register (PropertySetterCallback);
376
378
registry->Register (PropertyDescriptorCallback);
@@ -1447,7 +1449,7 @@ Local<Object> ContextifyContext::CompileFunctionAndCacheResult(
1447
1449
// While top-level `await` is not permitted in CommonJS, it returns the same
1448
1450
// error message as when `await` is used in a sync function, so we don't use it
1449
1451
// as a disambiguation.
1450
- constexpr std::array <std::string_view, 3 > esm_syntax_error_messages = {
1452
+ static std::vector <std::string_view> esm_syntax_error_messages = {
1451
1453
" Cannot use import statement outside a module" , // `import` statements
1452
1454
" Unexpected token 'export'" , // `export` statements
1453
1455
" Cannot use 'import.meta' outside a module" }; // `import.meta` references
@@ -1462,7 +1464,7 @@ constexpr std::array<std::string_view, 3> esm_syntax_error_messages = {
1462
1464
// - Top-level `await`: if the user writes `await` at the top level of a
1463
1465
// CommonJS module, it will throw a syntax error; but the same code is valid
1464
1466
// in ESM.
1465
- constexpr std::array <std::string_view, 6 > throws_only_in_cjs_error_messages = {
1467
+ static std::vector <std::string_view> throws_only_in_cjs_error_messages = {
1466
1468
" Identifier 'module' has already been declared" ,
1467
1469
" Identifier 'exports' has already been declared" ,
1468
1470
" Identifier 'require' has already been declared" ,
@@ -1482,35 +1484,17 @@ void ContextifyContext::ContainsModuleSyntax(
1482
1484
env, " containsModuleSyntax needs at least 1 argument" );
1483
1485
}
1484
1486
1487
+ // Argument 1: source code
1488
+ CHECK (args[0 ]->IsString ());
1489
+ auto code = args[0 ].As <String>();
1490
+
1485
1491
// Argument 2: filename; if undefined, use empty string
1486
1492
Local<String> filename = String::Empty (isolate);
1487
1493
if (!args[1 ]->IsUndefined ()) {
1488
1494
CHECK (args[1 ]->IsString ());
1489
1495
filename = args[1 ].As <String>();
1490
1496
}
1491
1497
1492
- // Argument 1: source code; if undefined, read from filename in argument 2
1493
- Local<String> code;
1494
- if (args[0 ]->IsUndefined ()) {
1495
- CHECK (!filename.IsEmpty ());
1496
- const char * filename_str = Utf8Value (isolate, filename).out ();
1497
- std::string contents;
1498
- int result = ReadFileSync (&contents, filename_str);
1499
- if (result != 0 ) {
1500
- isolate->ThrowException (
1501
- ERR_MODULE_NOT_FOUND (isolate, " Cannot read file %s" , filename_str));
1502
- return ;
1503
- }
1504
- code = String::NewFromUtf8 (isolate,
1505
- contents.c_str (),
1506
- v8::NewStringType::kNormal ,
1507
- contents.length ())
1508
- .ToLocalChecked ();
1509
- } else {
1510
- CHECK (args[0 ]->IsString ());
1511
- code = args[0 ].As <String>();
1512
- }
1513
-
1514
1498
// TODO(geoffreybooth): Centralize this rather than matching the logic in
1515
1499
// cjs/loader.js and translators.js
1516
1500
Local<String> script_id = String::Concat (
@@ -1540,73 +1524,95 @@ void ContextifyContext::ContainsModuleSyntax(
1540
1524
1541
1525
bool should_retry_as_esm = false ;
1542
1526
if (try_catch.HasCaught () && !try_catch.HasTerminated ()) {
1543
- Utf8Value message_value (env->isolate (), try_catch.Message ()->Get ());
1544
- auto message = message_value.ToStringView ();
1527
+ should_retry_as_esm =
1528
+ ContextifyContext::ShouldRetryAsESMInternal (env, code);
1529
+ }
1530
+ args.GetReturnValue ().Set (should_retry_as_esm);
1531
+ }
1545
1532
1546
- for (const auto & error_message : esm_syntax_error_messages) {
1547
- if (message.find (error_message) != std::string_view::npos) {
1548
- should_retry_as_esm = true ;
1549
- break ;
1550
- }
1551
- }
1533
+ void ContextifyContext::ShouldRetryAsESM (
1534
+ const FunctionCallbackInfo<Value>& args) {
1535
+ Environment* env = Environment::GetCurrent (args);
1536
+
1537
+ CHECK_EQ (args.Length (), 1 ); // code
1538
+
1539
+ // Argument 1: source code
1540
+ Local<String> code;
1541
+ CHECK (args[0 ]->IsString ());
1542
+ code = args[0 ].As <String>();
1552
1543
1553
- if (!should_retry_as_esm) {
1554
- for (const auto & error_message : throws_only_in_cjs_error_messages) {
1555
- if (message.find (error_message) != std::string_view::npos) {
1556
- // Try parsing again where the CommonJS wrapper is replaced by an
1557
- // async function wrapper. If the new parse succeeds, then the error
1558
- // was caused by either a top-level declaration of one of the CommonJS
1559
- // module variables, or a top-level `await`.
1560
- TryCatchScope second_parse_try_catch (env);
1561
- code =
1562
- String::Concat (isolate,
1563
- String::NewFromUtf8 (isolate, " (async function() {" )
1564
- .ToLocalChecked (),
1565
- code);
1566
- code = String::Concat (
1567
- isolate,
1568
- code,
1569
- String::NewFromUtf8 (isolate, " })();" ).ToLocalChecked ());
1570
- ScriptCompiler::Source wrapped_source = GetCommonJSSourceInstance (
1571
- isolate, code, filename, 0 , 0 , host_defined_options, nullptr );
1572
- std::ignore = ScriptCompiler::CompileFunction (
1573
- context,
1574
- &wrapped_source,
1575
- params.size (),
1576
- params.data (),
1577
- 0 ,
1578
- nullptr ,
1579
- options,
1580
- v8::ScriptCompiler::NoCacheReason::kNoCacheNoReason );
1581
- if (!second_parse_try_catch.HasTerminated ()) {
1582
- if (second_parse_try_catch.HasCaught ()) {
1583
- // If on the second parse an error is thrown by ESM syntax, then
1584
- // what happened was that the user had top-level `await` or a
1585
- // top-level declaration of one of the CommonJS module variables
1586
- // above the first `import` or `export`.
1587
- Utf8Value second_message_value (
1588
- env->isolate (), second_parse_try_catch.Message ()->Get ());
1589
- auto second_message = second_message_value.ToStringView ();
1590
- for (const auto & error_message : esm_syntax_error_messages) {
1591
- if (second_message.find (error_message) !=
1592
- std::string_view::npos) {
1593
- should_retry_as_esm = true ;
1594
- break ;
1595
- }
1596
- }
1597
- } else {
1598
- // No errors thrown in the second parse, so most likely the error
1599
- // was caused by a top-level `await` or a top-level declaration of
1600
- // one of the CommonJS module variables.
1601
- should_retry_as_esm = true ;
1602
- }
1603
- }
1604
- break ;
1544
+ bool should_retry_as_esm =
1545
+ ContextifyContext::ShouldRetryAsESMInternal (env, code);
1546
+
1547
+ args.GetReturnValue ().Set (should_retry_as_esm);
1548
+ }
1549
+
1550
+ bool ContextifyContext::ShouldRetryAsESMInternal (Environment* env,
1551
+ Local<String> code) {
1552
+ Isolate* isolate = env->isolate ();
1553
+
1554
+ Local<String> script_id =
1555
+ FIXED_ONE_BYTE_STRING (isolate, " [retry_as_esm_check]" );
1556
+ Local<Symbol> id_symbol = Symbol::New (isolate, script_id);
1557
+
1558
+ Local<PrimitiveArray> host_defined_options =
1559
+ GetHostDefinedOptions (isolate, id_symbol);
1560
+ ScriptCompiler::Source source =
1561
+ GetCommonJSSourceInstance (isolate,
1562
+ code,
1563
+ script_id, // filename
1564
+ 0 , // line offset
1565
+ 0 , // column offset
1566
+ host_defined_options,
1567
+ nullptr ); // cached_data
1568
+
1569
+ TryCatchScope try_catch (env);
1570
+ ShouldNotAbortOnUncaughtScope no_abort_scope (env);
1571
+
1572
+ // Try parsing where instead of the CommonJS wrapper we use an async function
1573
+ // wrapper. If the parse succeeds, then any CommonJS parse error for this
1574
+ // module was caused by either a top-level declaration of one of the CommonJS
1575
+ // module variables, or a top-level `await`.
1576
+ code = String::Concat (
1577
+ isolate, FIXED_ONE_BYTE_STRING (isolate, " (async function() {" ), code);
1578
+ code = String::Concat (isolate, code, FIXED_ONE_BYTE_STRING (isolate, " })();" ));
1579
+
1580
+ ScriptCompiler::Source wrapped_source = GetCommonJSSourceInstance (
1581
+ isolate, code, script_id, 0 , 0 , host_defined_options, nullptr );
1582
+
1583
+ Local<Context> context = env->context ();
1584
+ std::vector<Local<String>> params = GetCJSParameters (env->isolate_data ());
1585
+ USE (ScriptCompiler::CompileFunction (
1586
+ context,
1587
+ &wrapped_source,
1588
+ params.size (),
1589
+ params.data (),
1590
+ 0 ,
1591
+ nullptr ,
1592
+ ScriptCompiler::kNoCompileOptions ,
1593
+ v8::ScriptCompiler::NoCacheReason::kNoCacheNoReason ));
1594
+
1595
+ if (!try_catch.HasTerminated ()) {
1596
+ if (try_catch.HasCaught ()) {
1597
+ // If on the second parse an error is thrown by ESM syntax, then
1598
+ // what happened was that the user had top-level `await` or a
1599
+ // top-level declaration of one of the CommonJS module variables
1600
+ // above the first `import` or `export`.
1601
+ Utf8Value message_value (env->isolate (), try_catch.Message ()->Get ());
1602
+ auto message_view = message_value.ToStringView ();
1603
+ for (const auto & error_message : esm_syntax_error_messages) {
1604
+ if (message_view.find (error_message) != std::string_view::npos) {
1605
+ return true ;
1605
1606
}
1606
1607
}
1608
+ } else {
1609
+ // No errors thrown in the second parse, so most likely the error
1610
+ // was caused by a top-level `await` or a top-level declaration of
1611
+ // one of the CommonJS module variables.
1612
+ return true ;
1607
1613
}
1608
1614
}
1609
- args. GetReturnValue (). Set (should_retry_as_esm) ;
1615
+ return false ;
1610
1616
}
1611
1617
1612
1618
static void CompileFunctionForCJSLoader (
@@ -1767,6 +1773,7 @@ static void CreatePerContextProperties(Local<Object> target,
1767
1773
Local<Object> constants = Object::New (env->isolate ());
1768
1774
Local<Object> measure_memory = Object::New (env->isolate ());
1769
1775
Local<Object> memory_execution = Object::New (env->isolate ());
1776
+ Local<Object> syntax_detection_errors = Object::New (env->isolate ());
1770
1777
1771
1778
{
1772
1779
Local<Object> memory_mode = Object::New (env->isolate ());
@@ -1787,6 +1794,25 @@ static void CreatePerContextProperties(Local<Object> target,
1787
1794
1788
1795
READONLY_PROPERTY (constants, " measureMemory" , measure_memory);
1789
1796
1797
+ {
1798
+ Local<Value> esm_syntax_error_messages_array =
1799
+ ToV8Value (context, esm_syntax_error_messages).ToLocalChecked ();
1800
+ READONLY_PROPERTY (syntax_detection_errors,
1801
+ " esmSyntaxErrorMessages" ,
1802
+ esm_syntax_error_messages_array);
1803
+ }
1804
+
1805
+ {
1806
+ Local<Value> throws_only_in_cjs_error_messages_array =
1807
+ ToV8Value (context, throws_only_in_cjs_error_messages).ToLocalChecked ();
1808
+ READONLY_PROPERTY (syntax_detection_errors,
1809
+ " throwsOnlyInCommonJSErrorMessages" ,
1810
+ throws_only_in_cjs_error_messages_array);
1811
+ }
1812
+
1813
+ READONLY_PROPERTY (
1814
+ constants, " syntaxDetectionErrors" , syntax_detection_errors);
1815
+
1790
1816
target->Set (context, env->constants_string (), constants).Check ();
1791
1817
}
1792
1818
0 commit comments