Skip to content

Commit a3cd540

Browse files
committed
esm: improve error messages for ambiguous module syntax
1 parent f819aec commit a3cd540

File tree

2 files changed

+41
-13
lines changed

2 files changed

+41
-13
lines changed

lib/internal/modules/esm/module_job.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
const {
44
Array,
55
ArrayPrototypeJoin,
6-
ArrayPrototypeSome,
76
FunctionPrototype,
87
ObjectSetPrototypeOf,
98
PromisePrototypeThen,
@@ -61,11 +60,14 @@ const CJSGlobalLike = [
6160
'__filename',
6261
'__dirname',
6362
];
64-
const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
65-
ArrayPrototypeSome(
66-
CJSGlobalLike,
67-
(globalLike) => errorMessage === `${globalLike} is not defined`,
68-
);
63+
const getUndefinedCJSGlobalLike = (errorMessage) => {
64+
for (const globalLike of CJSGlobalLike) {
65+
if (errorMessage === `${globalLike} is not defined`) {
66+
return globalLike;
67+
}
68+
}
69+
return null;
70+
};
6971

7072

7173
/**
@@ -75,11 +77,20 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
7577
* @returns {void}
7678
*/
7779
const explainCommonJSGlobalLikeNotDefinedError = (e, url, hasTopLevelAwait) => {
78-
if (e?.name === 'ReferenceError' &&
79-
isCommonJSGlobalLikeNotDefinedError(e.message)) {
80+
const undefinedGlobal = getUndefinedCJSGlobalLike(e?.message);
81+
if (e?.name === 'ReferenceError' && undefinedGlobal !== null) {
8082

8183
if (hasTopLevelAwait) {
82-
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.`;
84+
let advice = '';
85+
if (undefinedGlobal === 'require') {
86+
advice = 'replace require() with import';
87+
} else if (undefinedGlobal === 'module' || undefinedGlobal === 'exports') {
88+
advice = 'use export instead of module.exports/exports';
89+
} else if (undefinedGlobal === '__filename' || undefinedGlobal === '__dirname') {
90+
advice = 'use import.meta.url instead';
91+
}
92+
93+
e.message = `Cannot determine intended module format because both ${undefinedGlobal} 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, ${advice}.`;
8394
e.code = 'ERR_AMBIGUOUS_MODULE_SYNTAX';
8495
return;
8596
}

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ describe('Module syntax detection', { concurrency: !process.env.TEST_PARALLEL },
283283

284284
match(
285285
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\./
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\./
287287
);
288288
strictEqual(stdout, '');
289289
strictEqual(code, 1);
@@ -432,15 +432,32 @@ describe('cjs & esm ambiguous syntax case', () => {
432432
const { stderr, code, signal } = await spawnPromisified(
433433
process.execPath,
434434
[
435-
'--input-type=module',
436435
'--eval',
437-
`await 1;\nconst fs = require('fs');`,
436+
`const fs = require('fs');\nawait 1;`,
438437
]
439438
);
440439

441440
match(
442441
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\./
442+
/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\./
443+
);
444+
445+
strictEqual(code, 1);
446+
strictEqual(signal, null);
447+
});
448+
449+
it('should throw an ambiguous syntax error when using top-level await with exports', async () => {
450+
const { stderr, code, signal } = await spawnPromisified(
451+
process.execPath,
452+
[
453+
'--eval',
454+
`exports.foo = 'bar';\nawait 1;`,
455+
]
456+
);
457+
458+
match(
459+
stderr,
460+
/ReferenceError: Cannot determine intended module format because both exports 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, use export instead of module\.exports\/exports\./
444461
);
445462

446463
strictEqual(code, 1);

0 commit comments

Comments
 (0)