Skip to content

Commit 6ce6fdb

Browse files
authored
module: update tests for combined ambiguous module syntax error
PR-URL: #55874 Fixes: #55776 Reviewed-By: Yagiz Nizipli <[email protected]>
1 parent d08513d commit 6ce6fdb

File tree

4 files changed

+60
-4
lines changed

4 files changed

+60
-4
lines changed

lib/internal/modules/esm/module_job.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,16 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
7373
* @param {string} url
7474
* @returns {void}
7575
*/
76-
const explainCommonJSGlobalLikeNotDefinedError = (e, url) => {
76+
const explainCommonJSGlobalLikeNotDefinedError = (e, url, hasTopLevelAwait) => {
7777
if (e?.name === 'ReferenceError' &&
7878
isCommonJSGlobalLikeNotDefinedError(e.message)) {
79+
80+
if (hasTopLevelAwait) {
81+
e.message = `Cannot determine intended module format because both require() and top-level await are present. If the code is intended to be CommonJS, wrap await in an async function. If the code is intended to be an ES module, replace require() with import.`;
82+
e.code = 'ERR_AMBIGUOUS_MODULE_SYNTAX';
83+
return;
84+
}
85+
7986
e.message += ' in ES module scope';
8087

8188
if (StringPrototypeStartsWith(e.message, 'require ')) {
@@ -364,7 +371,7 @@ class ModuleJob extends ModuleJobBase {
364371
try {
365372
await this.module.evaluate(timeout, breakOnSigint);
366373
} catch (e) {
367-
explainCommonJSGlobalLikeNotDefinedError(e, this.module.url);
374+
explainCommonJSGlobalLikeNotDefinedError(e, this.module.url, this.module.hasTopLevelAwait());
368375
throw e;
369376
}
370377
return { __proto__: null, module: this.module };
@@ -498,7 +505,7 @@ class ModuleJobSync extends ModuleJobBase {
498505
const namespace = this.module.evaluateSync(filename, parentFilename);
499506
return { __proto__: null, module: this.module, namespace };
500507
} catch (e) {
501-
explainCommonJSGlobalLikeNotDefinedError(e, this.module.url);
508+
explainCommonJSGlobalLikeNotDefinedError(e, this.module.url, this.module.hasTopLevelAwait());
502509
throw e;
503510
}
504511
}

src/module_wrap.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,27 @@ void ModuleWrap::IsGraphAsync(const FunctionCallbackInfo<Value>& args) {
876876
args.GetReturnValue().Set(module->IsGraphAsync());
877877
}
878878

879+
void ModuleWrap::HasTopLevelAwait(const FunctionCallbackInfo<Value>& args) {
880+
Isolate* isolate = args.GetIsolate();
881+
ModuleWrap* obj;
882+
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
883+
884+
Local<Module> module = obj->module_.Get(isolate);
885+
886+
// Check if module is valid
887+
if (module.IsEmpty()) {
888+
args.GetReturnValue().Set(false);
889+
return;
890+
}
891+
892+
// For source text modules, check if the graph is async
893+
// For synthetic modules, it's always false
894+
bool has_top_level_await =
895+
module->IsSourceTextModule() && module->IsGraphAsync();
896+
897+
args.GetReturnValue().Set(has_top_level_await);
898+
}
899+
879900
void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) {
880901
Isolate* isolate = args.GetIsolate();
881902
ModuleWrap* obj;
@@ -1304,6 +1325,8 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data,
13041325
SetProtoMethodNoSideEffect(isolate, tpl, "getNamespace", GetNamespace);
13051326
SetProtoMethodNoSideEffect(isolate, tpl, "getStatus", GetStatus);
13061327
SetProtoMethodNoSideEffect(isolate, tpl, "isGraphAsync", IsGraphAsync);
1328+
SetProtoMethodNoSideEffect(
1329+
isolate, tpl, "hasTopLevelAwait", HasTopLevelAwait);
13071330
SetProtoMethodNoSideEffect(isolate, tpl, "getError", GetError);
13081331
SetConstructorFunction(isolate, target, "ModuleWrap", tpl);
13091332
isolate_data->set_module_wrap_constructor_template(tpl);
@@ -1366,6 +1389,7 @@ void ModuleWrap::RegisterExternalReferences(
13661389
registry->Register(GetStatus);
13671390
registry->Register(GetError);
13681391
registry->Register(IsGraphAsync);
1392+
registry->Register(HasTopLevelAwait);
13691393

13701394
registry->Register(CreateRequiredModuleFacade);
13711395

src/module_wrap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class ModuleWrap : public BaseObject {
6464
void MemoryInfo(MemoryTracker* tracker) const override {
6565
tracker->TrackField("resolve_cache", resolve_cache_);
6666
}
67+
static void HasTopLevelAwait(const v8::FunctionCallbackInfo<v8::Value>& args);
6768

6869
v8::Local<v8::Context> context() const;
6970
v8::Maybe<bool> CheckUnsettledTopLevelAwait();

test/es-module/test-esm-detect-ambiguous.mjs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,10 @@ describe('Module syntax detection', { concurrency: !process.env.TEST_PARALLEL },
281281
'const fs = require("node:fs"); await Promise.resolve();',
282282
]);
283283

284-
match(stderr, /ReferenceError: require is not defined in ES module scope/);
284+
match(
285+
stderr,
286+
/ReferenceError: Cannot determine intended module format because both require\(\) and top-level await are present\. If the code is intended to be CommonJS, wrap await in an async function\. If the code is intended to be an ES module, replace require\(\) with import\./
287+
);
285288
strictEqual(stdout, '');
286289
strictEqual(code, 1);
287290
strictEqual(signal, null);
@@ -423,3 +426,24 @@ describe('when working with Worker threads', () => {
423426
strictEqual(signal, null);
424427
});
425428
});
429+
430+
describe('cjs & esm ambiguous syntax case', () => {
431+
it('should throw an ambiguous syntax error when using top-level await with require', async () => {
432+
const { stderr, code, signal } = await spawnPromisified(
433+
process.execPath,
434+
[
435+
'--input-type=module',
436+
'--eval',
437+
`await 1;\nconst fs = require('fs');`,
438+
]
439+
);
440+
441+
match(
442+
stderr,
443+
/ReferenceError: Cannot determine intended module format because both require\(\) and top-level await are present\. If the code is intended to be CommonJS, wrap await in an async function\. If the code is intended to be an ES module, replace require\(\) with import\./
444+
);
445+
446+
strictEqual(code, 1);
447+
strictEqual(signal, null);
448+
});
449+
});

0 commit comments

Comments
 (0)