Skip to content

Commit 37d9cfc

Browse files
authored
esm: improve error messages for ambiguous module syntax
PR-URL: #60376 Fixes: #60322 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent cbe0233 commit 37d9cfc

File tree

2 files changed

+79
-8
lines changed

2 files changed

+79
-8
lines changed

lib/internal/modules/esm/module_job.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
const {
44
Array,
5+
ArrayPrototypeFind,
56
ArrayPrototypeJoin,
67
ArrayPrototypePush,
7-
ArrayPrototypeSome,
88
FunctionPrototype,
99
ObjectSetPrototypeOf,
1010
PromisePrototypeThen,
@@ -65,8 +65,8 @@ const CJSGlobalLike = [
6565
'__filename',
6666
'__dirname',
6767
];
68-
const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
69-
ArrayPrototypeSome(
68+
const findCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
69+
ArrayPrototypeFind(
7070
CJSGlobalLike,
7171
(globalLike) => errorMessage === `${globalLike} is not defined`,
7272
);
@@ -79,11 +79,28 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
7979
* @returns {void}
8080
*/
8181
const explainCommonJSGlobalLikeNotDefinedError = (e, url, hasTopLevelAwait) => {
82-
if (e?.name === 'ReferenceError' &&
83-
isCommonJSGlobalLikeNotDefinedError(e.message)) {
82+
const notDefinedGlobalLike = e?.name === 'ReferenceError' && findCommonJSGlobalLikeNotDefinedError(e.message);
8483

84+
if (notDefinedGlobalLike) {
8585
if (hasTopLevelAwait) {
86-
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.`;
86+
let advice;
87+
switch (notDefinedGlobalLike) {
88+
case 'require':
89+
advice = 'replace require() with import';
90+
break;
91+
case 'module':
92+
case 'exports':
93+
advice = 'use export instead of module.exports/exports';
94+
break;
95+
case '__filename':
96+
advice = 'use import.meta.filename instead';
97+
break;
98+
case '__dirname':
99+
advice = 'use import.meta.dirname instead';
100+
break;
101+
}
102+
103+
e.message = `Cannot determine intended module format because both '${notDefinedGlobalLike}' 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}.`;
87104
e.code = 'ERR_AMBIGUOUS_MODULE_SYNTAX';
88105
return;
89106
}

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

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

284284
assert.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
assert.strictEqual(stdout, '');
289289
assert.strictEqual(code, 1);
@@ -440,7 +440,61 @@ describe('cjs & esm ambiguous syntax case', () => {
440440

441441
assert.match(
442442
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\./
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+
assert.strictEqual(code, 1);
447+
assert.strictEqual(signal, null);
448+
});
449+
450+
it('should throw an ambiguous syntax error when using top-level await with exports', async () => {
451+
const { stderr, code, signal } = await spawnPromisified(
452+
process.execPath,
453+
[
454+
'--eval',
455+
`exports.foo = 'bar';\nawait 1;`,
456+
]
457+
);
458+
459+
assert.match(
460+
stderr,
461+
/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\./
462+
);
463+
464+
assert.strictEqual(code, 1);
465+
assert.strictEqual(signal, null);
466+
});
467+
468+
it('should throw an ambiguous syntax error when using top-level await with __filename', async () => {
469+
const { stderr, code, signal } = await spawnPromisified(
470+
process.execPath,
471+
[
472+
'--eval',
473+
`console.log(__filename);\nawait 1;`,
474+
]
475+
);
476+
477+
assert.match(
478+
stderr,
479+
/ReferenceError: Cannot determine intended module format because both '__filename' 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 import\.meta\.filename instead\./
480+
);
481+
482+
assert.strictEqual(code, 1);
483+
assert.strictEqual(signal, null);
484+
});
485+
486+
it('should throw an ambiguous syntax error when using top-level await with __dirname', async () => {
487+
const { stderr, code, signal } = await spawnPromisified(
488+
process.execPath,
489+
[
490+
'--eval',
491+
`console.log(__dirname);\nawait 1;`,
492+
]
493+
);
494+
495+
assert.match(
496+
stderr,
497+
/ReferenceError: Cannot determine intended module format because both '__dirname' 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 import\.meta\.dirname instead\./
444498
);
445499

446500
assert.strictEqual(code, 1);

0 commit comments

Comments
 (0)