@@ -318,13 +318,15 @@ void ContextifyContext::CreatePerIsolateProperties( |
318 | 318 | SetMethod(isolate, target, "makeContext", MakeContext); |
319 | 319 | SetMethod(isolate, target, "isContext", IsContext); |
320 | 320 | SetMethod(isolate, target, "compileFunction", CompileFunction); |
| 321 | + SetMethod(isolate, target, "containsModuleSyntax", ContainsModuleSyntax); |
321 | 322 | } |
322 | 323 |
|
323 | 324 | void ContextifyContext::RegisterExternalReferences( |
324 | 325 | ExternalReferenceRegistry* registry) { |
325 | 326 | registry->Register(MakeContext); |
326 | 327 | registry->Register(IsContext); |
327 | 328 | registry->Register(CompileFunction); |
| 329 | + registry->Register(ContainsModuleSyntax); |
328 | 330 | registry->Register(PropertyGetterCallback); |
329 | 331 | registry->Register(PropertySetterCallback); |
330 | 332 | registry->Register(PropertyDescriptorCallback); |
@@ -1204,33 +1206,18 @@ void ContextifyContext::CompileFunction( |
1204 | 1206 | data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength()); |
1205 | 1207 | } |
1206 | 1208 |
|
1207 | | - // Set host_defined_options |
1208 | 1209 | Local<PrimitiveArray> host_defined_options = |
1209 | | - PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength); |
1210 | | - host_defined_options->Set( |
1211 | | - isolate, loader::HostDefinedOptions::kID, id_symbol); |
1212 | | - |
1213 | | - ScriptOrigin origin(isolate, |
1214 | | - filename, |
1215 | | - line_offset, // line offset |
1216 | | - column_offset, // column offset |
1217 | | - true, // is cross origin |
1218 | | - -1, // script id |
1219 | | - Local<Value>(), // source map URL |
1220 | | - false, // is opaque (?) |
1221 | | - false, // is WASM |
1222 | | - false, // is ES Module |
1223 | | - host_defined_options); |
1224 | | - |
1225 | | - ScriptCompiler::Source source(code, origin, cached_data); |
1226 | | - ScriptCompiler::CompileOptions options; |
1227 | | - if (source.GetCachedData() == nullptr) { |
1228 | | - options = ScriptCompiler::kNoCompileOptions; |
1229 | | - } else { |
1230 | | - options = ScriptCompiler::kConsumeCodeCache; |
1231 | | - } |
| 1210 | + GetHostDefinedOptions(isolate, id_symbol); |
| 1211 | + ScriptCompiler::Source source = |
| 1212 | + GetCommonJSSourceInstance(isolate, |
| 1213 | + code, |
| 1214 | + filename, |
| 1215 | + line_offset, |
| 1216 | + column_offset, |
| 1217 | + host_defined_options, |
| 1218 | + cached_data); |
| 1219 | + ScriptCompiler::CompileOptions options = GetCompileOptions(source); |
1232 | 1220 |
|
1233 | | - TryCatchScope try_catch(env); |
1234 | 1221 | Context::Scope scope(parsing_context); |
1235 | 1222 |
|
1236 | 1223 | // Read context extensions from buffer |
@@ -1255,9 +1242,83 @@ void ContextifyContext::CompileFunction( |
1255 | 1242 | } |
1256 | 1243 | } |
1257 | 1244 |
|
| 1245 | + TryCatchScope try_catch(env); |
| 1246 | + Local<Object> result = CompileFunctionAndCacheResult(env, |
| 1247 | + parsing_context, |
| 1248 | + &source, |
| 1249 | + params, |
| 1250 | + context_extensions, |
| 1251 | + options, |
| 1252 | + produce_cached_data, |
| 1253 | + id_symbol, |
| 1254 | + try_catch); |
| 1255 | + |
| 1256 | + if (try_catch.HasCaught() && !try_catch.HasTerminated()) { |
| 1257 | + try_catch.ReThrow(); |
| 1258 | + return; |
| 1259 | + } |
| 1260 | + |
| 1261 | + if (result.IsEmpty()) { |
| 1262 | + return; |
| 1263 | + } |
| 1264 | + args.GetReturnValue().Set(result); |
| 1265 | +} |
| 1266 | + |
| 1267 | +Local<PrimitiveArray> ContextifyContext::GetHostDefinedOptions( |
| 1268 | + Isolate* isolate, Local<Symbol> id_symbol) { |
| 1269 | + Local<PrimitiveArray> host_defined_options = |
| 1270 | + PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength); |
| 1271 | + host_defined_options->Set( |
| 1272 | + isolate, loader::HostDefinedOptions::kID, id_symbol); |
| 1273 | + return host_defined_options; |
| 1274 | +} |
| 1275 | + |
| 1276 | +ScriptCompiler::Source ContextifyContext::GetCommonJSSourceInstance( |
| 1277 | + Isolate* isolate, |
| 1278 | + Local<String> code, |
| 1279 | + Local<String> filename, |
| 1280 | + int line_offset, |
| 1281 | + int column_offset, |
| 1282 | + Local<PrimitiveArray> host_defined_options, |
| 1283 | + ScriptCompiler::CachedData* cached_data) { |
| 1284 | + ScriptOrigin origin(isolate, |
| 1285 | + filename, |
| 1286 | + line_offset, // line offset |
| 1287 | + column_offset, // column offset |
| 1288 | + true, // is cross origin |
| 1289 | + -1, // script id |
| 1290 | + Local<Value>(), // source map URL |
| 1291 | + false, // is opaque (?) |
| 1292 | + false, // is WASM |
| 1293 | + false, // is ES Module |
| 1294 | + host_defined_options); |
| 1295 | + return ScriptCompiler::Source(code, origin, cached_data); |
| 1296 | +} |
| 1297 | + |
| 1298 | +ScriptCompiler::CompileOptions ContextifyContext::GetCompileOptions( |
| 1299 | + const ScriptCompiler::Source& source) { |
| 1300 | + ScriptCompiler::CompileOptions options; |
| 1301 | + if (source.GetCachedData() != nullptr) { |
| 1302 | + options = ScriptCompiler::kConsumeCodeCache; |
| 1303 | + } else { |
| 1304 | + options = ScriptCompiler::kNoCompileOptions; |
| 1305 | + } |
| 1306 | + return options; |
| 1307 | +} |
| 1308 | + |
| 1309 | +Local<Object> ContextifyContext::CompileFunctionAndCacheResult( |
| 1310 | + Environment* env, |
| 1311 | + Local<Context> parsing_context, |
| 1312 | + ScriptCompiler::Source* source, |
| 1313 | + std::vector<Local<String>> params, |
| 1314 | + std::vector<Local<Object>> context_extensions, |
| 1315 | + ScriptCompiler::CompileOptions options, |
| 1316 | + bool produce_cached_data, |
| 1317 | + Local<Symbol> id_symbol, |
| 1318 | + const TryCatchScope& try_catch) { |
1258 | 1319 | MaybeLocal<Function> maybe_fn = ScriptCompiler::CompileFunction( |
1259 | 1320 | parsing_context, |
1260 | | - &source, |
| 1321 | + source, |
1261 | 1322 | params.size(), |
1262 | 1323 | params.data(), |
1263 | 1324 | context_extensions.size(), |
@@ -1269,24 +1330,26 @@ void ContextifyContext::CompileFunction( |
1269 | 1330 | if (!maybe_fn.ToLocal(&fn)) { |
1270 | 1331 | if (try_catch.HasCaught() && !try_catch.HasTerminated()) { |
1271 | 1332 | errors::DecorateErrorStack(env, try_catch); |
1272 | | - try_catch.ReThrow(); |
| 1333 | + return Object::New(env->isolate()); |
1273 | 1334 | } |
1274 | | - return; |
1275 | 1335 | } |
| 1336 | + |
| 1337 | + Local<Context> context = env->context(); |
1276 | 1338 | if (fn->SetPrivate(context, env->host_defined_option_symbol(), id_symbol) |
1277 | 1339 | .IsNothing()) { |
1278 | | - return; |
| 1340 | + return Object::New(env->isolate()); |
1279 | 1341 | } |
1280 | 1342 |
|
| 1343 | + Isolate* isolate = env->isolate(); |
1281 | 1344 | Local<Object> result = Object::New(isolate); |
1282 | 1345 | if (result->Set(parsing_context, env->function_string(), fn).IsNothing()) |
1283 | | - return; |
| 1346 | + return Object::New(env->isolate()); |
1284 | 1347 | if (result |
1285 | 1348 | ->Set(parsing_context, |
1286 | 1349 | env->source_map_url_string(), |
1287 | 1350 | fn->GetScriptOrigin().SourceMapUrl()) |
1288 | 1351 | .IsNothing()) |
1289 | | - return; |
| 1352 | + return Object::New(env->isolate()); |
1290 | 1353 |
|
1291 | 1354 | std::unique_ptr<ScriptCompiler::CachedData> new_cached_data; |
1292 | 1355 | if (produce_cached_data) { |
@@ -1295,14 +1358,91 @@ void ContextifyContext::CompileFunction( |
1295 | 1358 | if (StoreCodeCacheResult(env, |
1296 | 1359 | result, |
1297 | 1360 | options, |
1298 | | - source, |
| 1361 | + *source, |
1299 | 1362 | produce_cached_data, |
1300 | 1363 | std::move(new_cached_data)) |
1301 | 1364 | .IsNothing()) { |
1302 | | - return; |
| 1365 | + return Object::New(env->isolate()); |
1303 | 1366 | } |
1304 | 1367 |
|
1305 | | - args.GetReturnValue().Set(result); |
| 1368 | + return result; |
| 1369 | +} |
| 1370 | + |
| 1371 | +// When compiling as CommonJS source code that contains ESM syntax, the |
| 1372 | +// following error messages are returned: |
| 1373 | +// - `import` statements: "Cannot use import statement outside a module" |
| 1374 | +// - `export` statements: "Unexpected token 'export'" |
| 1375 | +// - `import.meta` references: "Cannot use 'import.meta' outside a module" |
| 1376 | +// Dynamic `import()` is permitted in CommonJS, so it does not error. |
| 1377 | +// While top-level `await` is not permitted in CommonJS, it returns the same |
| 1378 | +// error message as when `await` is used in a sync function, so we don't use it |
| 1379 | +// as a disambiguation. |
| 1380 | +constexpr std::array<std::string_view, 3> esm_syntax_error_messages = { |
| 1381 | + "Cannot use import statement outside a module", // `import` statements |
| 1382 | + "Unexpected token 'export'", // `export` statements |
| 1383 | + "Cannot use 'import.meta' outside a module"}; // `import.meta` references |
| 1384 | + |
| 1385 | +void ContextifyContext::ContainsModuleSyntax( |
| 1386 | + const FunctionCallbackInfo<Value>& args) { |
| 1387 | + // Argument 1: source code |
| 1388 | + CHECK(args[0]->IsString()); |
| 1389 | + Local<String> code = args[0].As<String>(); |
| 1390 | + |
| 1391 | + // Argument 2: filename |
| 1392 | + Local<String> filename = String::Empty(args.GetIsolate()); |
| 1393 | + if (!args[1]->IsUndefined()) { |
| 1394 | + CHECK(args[1]->IsString()); |
| 1395 | + filename = args[1].As<String>(); |
| 1396 | + } |
| 1397 | + |
| 1398 | + Environment* env = Environment::GetCurrent(args); |
| 1399 | + Isolate* isolate = env->isolate(); |
| 1400 | + Local<Context> context = env->context(); |
| 1401 | + |
| 1402 | + // TODO(geoffreybooth): Centralize this rather than matching the logic in |
| 1403 | + // cjs/loader.js and translators.js |
| 1404 | + Local<String> script_id = String::Concat( |
| 1405 | + isolate, String::NewFromUtf8(isolate, "cjs:").ToLocalChecked(), filename); |
| 1406 | + Local<Symbol> id_symbol = Symbol::New(isolate, script_id); |
| 1407 | + |
| 1408 | + Local<PrimitiveArray> host_defined_options = |
| 1409 | + GetHostDefinedOptions(isolate, id_symbol); |
| 1410 | + ScriptCompiler::Source source = GetCommonJSSourceInstance( |
| 1411 | + isolate, code, filename, 0, 0, host_defined_options, nullptr); |
| 1412 | + ScriptCompiler::CompileOptions options = GetCompileOptions(source); |
| 1413 | + |
| 1414 | + std::vector<Local<String>> params = { |
| 1415 | + String::NewFromUtf8(isolate, "exports").ToLocalChecked(), |
| 1416 | + String::NewFromUtf8(isolate, "require").ToLocalChecked(), |
| 1417 | + String::NewFromUtf8(isolate, "module").ToLocalChecked(), |
| 1418 | + String::NewFromUtf8(isolate, "__filename").ToLocalChecked(), |
| 1419 | + String::NewFromUtf8(isolate, "__dirname").ToLocalChecked()}; |
| 1420 | + |
| 1421 | + TryCatchScope try_catch(env); |
| 1422 | + |
| 1423 | + ContextifyContext::CompileFunctionAndCacheResult(env, |
| 1424 | + context, |
| 1425 | + &source, |
| 1426 | + params, |
| 1427 | + std::vector<Local<Object>>(), |
| 1428 | + options, |
| 1429 | + true, |
| 1430 | + id_symbol, |
| 1431 | + try_catch); |
| 1432 | + |
| 1433 | + bool found_error_message_caused_by_module_syntax = false; |
| 1434 | + if (try_catch.HasCaught() && !try_catch.HasTerminated()) { |
| 1435 | + Utf8Value message_value(env->isolate(), try_catch.Message()->Get()); |
| 1436 | + auto message = message_value.ToStringView(); |
| 1437 | + |
| 1438 | + for (const auto& error_message : esm_syntax_error_messages) { |
| 1439 | + if (message.find(error_message) != std::string_view::npos) { |
| 1440 | + found_error_message_caused_by_module_syntax = true; |
| 1441 | + break; |
| 1442 | + } |
| 1443 | + } |
| 1444 | + } |
| 1445 | + args.GetReturnValue().Set(found_error_message_caused_by_module_syntax); |
1306 | 1446 | } |
1307 | 1447 |
|
1308 | 1448 | static void StartSigintWatchdog(const FunctionCallbackInfo<Value>& args) { |
|