Skip to content

Commit a8dfd58

Browse files
committed
add ContainsModuleSyntax function
1 parent 87b2a2d commit a8dfd58

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

src/node_contextify.cc

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,15 @@ void ContextifyContext::CreatePerIsolateProperties(
318318
SetMethod(isolate, target, "makeContext", MakeContext);
319319
SetMethod(isolate, target, "isContext", IsContext);
320320
SetMethod(isolate, target, "compileFunction", CompileFunction);
321+
SetMethod(isolate, target, "containsModuleSyntax", ContainsModuleSyntax);
321322
}
322323

323324
void ContextifyContext::RegisterExternalReferences(
324325
ExternalReferenceRegistry* registry) {
325326
registry->Register(MakeContext);
326327
registry->Register(IsContext);
327328
registry->Register(CompileFunction);
329+
registry->Register(ContainsModuleSyntax);
328330
registry->Register(PropertyGetterCallback);
329331
registry->Register(PropertySetterCallback);
330332
registry->Register(PropertyDescriptorCallback);
@@ -1339,6 +1341,104 @@ Local<Object> ContextifyContext::CompileFunctionAndCacheResult(
13391341
return result;
13401342
}
13411343

1344+
// These are the error messages thrown due to ESM syntax in a CommonJS module.
1345+
constexpr std::array<std::string_view, 3> esm_syntax_error_messages = {
1346+
// `import` statements return an error with the message:
1347+
"Cannot use import statement outside a module",
1348+
// `export` statements return an error with the message:
1349+
"Unexpected token 'export'",
1350+
// `import.meta` returns an error with the message:
1351+
"Cannot use 'import.meta' outside a module"};
1352+
// Top-level `await` currently returns the same error message as when `await` is
1353+
// used in a sync function, so we don't use it as a disambiguation. Dynamic
1354+
// `import()` is permitted in CommonJS, so we don't use it as a disambiguation.
1355+
1356+
void ContextifyContext::ContainsModuleSyntax(
1357+
const FunctionCallbackInfo<Value>& args) {
1358+
// Argument 1: source code
1359+
CHECK(args[0]->IsString());
1360+
Local<String> code = args[0].As<String>();
1361+
1362+
// Argument 2: filename
1363+
CHECK(args[1]->IsString());
1364+
Local<String> filename = args[1].As<String>();
1365+
1366+
Environment* env = Environment::GetCurrent(args);
1367+
Isolate* isolate = env->isolate();
1368+
Local<Context> context = env->context();
1369+
1370+
// TODO(geoffreybooth): Centralize this rather than matching the logic in
1371+
// cjs/loader.js and translators.js
1372+
Local<Symbol> id_symbol =
1373+
(String::Concat(isolate,
1374+
String::NewFromUtf8(isolate, "cjs:").ToLocalChecked(),
1375+
filename))
1376+
.As<Symbol>();
1377+
1378+
// TODO: Abstract this into a separate function
1379+
// Set host_defined_options
1380+
Local<PrimitiveArray> host_defined_options =
1381+
PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
1382+
host_defined_options->Set(
1383+
isolate, loader::HostDefinedOptions::kID, id_symbol);
1384+
1385+
ScriptOrigin origin(isolate,
1386+
filename,
1387+
0, // line offset
1388+
0, // column offset
1389+
true, // is cross origin
1390+
-1, // script id
1391+
Local<Value>(), // source map URL
1392+
false, // is opaque (?)
1393+
false, // is WASM
1394+
false, // is ES Module
1395+
host_defined_options);
1396+
1397+
ScriptCompiler::CachedData* cached_data = nullptr;
1398+
ScriptCompiler::Source source =
1399+
ScriptCompiler::Source(code, origin, cached_data);
1400+
ScriptCompiler::CompileOptions options;
1401+
if (source.GetCachedData() == nullptr) {
1402+
options = ScriptCompiler::kNoCompileOptions;
1403+
} else {
1404+
options = ScriptCompiler::kConsumeCodeCache;
1405+
}
1406+
// End TODO
1407+
1408+
std::vector<Local<String>> params = {
1409+
String::NewFromUtf8(isolate, "exports").ToLocalChecked(),
1410+
String::NewFromUtf8(isolate, "require").ToLocalChecked(),
1411+
String::NewFromUtf8(isolate, "module").ToLocalChecked(),
1412+
String::NewFromUtf8(isolate, "__filename").ToLocalChecked(),
1413+
String::NewFromUtf8(isolate, "__dirname").ToLocalChecked()};
1414+
1415+
TryCatchScope try_catch(env);
1416+
1417+
ContextifyContext::CompileFunctionAndCacheResult(env,
1418+
context,
1419+
source,
1420+
params,
1421+
std::vector<Local<Object>>(),
1422+
options,
1423+
true,
1424+
id_symbol,
1425+
try_catch);
1426+
1427+
bool found_error_message_caused_by_module_syntax = false;
1428+
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
1429+
Utf8Value message_value(env->isolate(), try_catch.Message()->Get());
1430+
auto message = message_value.ToStringView();
1431+
1432+
for (const auto& error_message : esm_syntax_error_messages) {
1433+
if (message.find(error_message) != std::string_view::npos) {
1434+
found_error_message_caused_by_module_syntax = true;
1435+
break;
1436+
}
1437+
}
1438+
}
1439+
args.GetReturnValue().Set(found_error_message_caused_by_module_syntax);
1440+
}
1441+
13421442
static void StartSigintWatchdog(const FunctionCallbackInfo<Value>& args) {
13431443
int ret = SigintWatchdogHelper::GetInstance()->Start();
13441444
args.GetReturnValue().Set(ret == 0);

src/node_contextify.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ class ContextifyContext : public BaseObject {
9393
bool produce_cached_data,
9494
v8::Local<v8::Symbol> id_symbol,
9595
const errors::TryCatchScope& try_catch);
96+
static void ContainsModuleSyntax(
97+
const v8::FunctionCallbackInfo<v8::Value>& args);
9698
static void WeakCallback(
9799
const v8::WeakCallbackInfo<ContextifyContext>& data);
98100
static void PropertyGetterCallback(

0 commit comments

Comments
 (0)