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