From 8241a5aec51548684052821b3539b32cb1baa045 Mon Sep 17 00:00:00 2001 From: mag123c Date: Thu, 23 Oct 2025 21:17:22 +0900 Subject: [PATCH 1/4] esm: improve error messages for ambiguous module syntax --- lib/internal/modules/esm/module_job.js | 29 ++++++++++++++++---- test/es-module/test-esm-detect-ambiguous.mjs | 25 ++++++++++++++--- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 8459ab5f4721dc..0207b6d53377ea 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -2,9 +2,9 @@ const { Array, + ArrayPrototypeFind, ArrayPrototypeJoin, ArrayPrototypePush, - ArrayPrototypeSome, FunctionPrototype, ObjectSetPrototypeOf, PromisePrototypeThen, @@ -65,8 +65,8 @@ const CJSGlobalLike = [ '__filename', '__dirname', ]; -const isCommonJSGlobalLikeNotDefinedError = (errorMessage) => - ArrayPrototypeSome( +const findCommonJSGlobalLikeNotDefinedError = (errorMessage) => + ArrayPrototypeFind( CJSGlobalLike, (globalLike) => errorMessage === `${globalLike} is not defined`, ); @@ -79,11 +79,28 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) => * @returns {void} */ const explainCommonJSGlobalLikeNotDefinedError = (e, url, hasTopLevelAwait) => { - if (e?.name === 'ReferenceError' && - isCommonJSGlobalLikeNotDefinedError(e.message)) { + const notDefinedGlobalLike = e?.name === 'ReferenceError' && findCommonJSGlobalLikeNotDefinedError(e.message); + if (notDefinedGlobalLike) { if (hasTopLevelAwait) { - 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.`; + let advice; + switch (notDefinedGlobalLike) { + case 'require': + advice = 'replace require() with import'; + break; + case 'module': + case 'exports': + advice = 'use export instead of module.exports/exports'; + break; + case '__filename': + advice = 'use import.meta.filename instead'; + break; + case '__dirname': + advice = 'use import.meta.dirname instead'; + break; + } + + 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}.`; e.code = 'ERR_AMBIGUOUS_MODULE_SYNTAX'; return; } diff --git a/test/es-module/test-esm-detect-ambiguous.mjs b/test/es-module/test-esm-detect-ambiguous.mjs index 234e27a947a13f..9a8cb8b54ccd93 100644 --- a/test/es-module/test-esm-detect-ambiguous.mjs +++ b/test/es-module/test-esm-detect-ambiguous.mjs @@ -283,7 +283,7 @@ describe('Module syntax detection', { concurrency: !process.env.TEST_PARALLEL }, assert.match( stderr, - /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\./ + /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\./ ); assert.strictEqual(stdout, ''); assert.strictEqual(code, 1); @@ -432,15 +432,32 @@ describe('cjs & esm ambiguous syntax case', () => { const { stderr, code, signal } = await spawnPromisified( process.execPath, [ - '--input-type=module', '--eval', - `await 1;\nconst fs = require('fs');`, + `const fs = require('fs');\nawait 1;`, ] ); assert.match( stderr, - /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\./ + /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\./ + ); + + strictEqual(code, 1); + strictEqual(signal, null); + }); + + it('should throw an ambiguous syntax error when using top-level await with exports', async () => { + const { stderr, code, signal } = await spawnPromisified( + process.execPath, + [ + '--eval', + `exports.foo = 'bar';\nawait 1;`, + ] + ); + + match( + stderr, + /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\./ ); assert.strictEqual(code, 1); From 63b391cb9b04bcbbcd171525471354ceae4802fd Mon Sep 17 00:00:00 2001 From: mag123c Date: Sat, 25 Oct 2025 10:08:05 +0900 Subject: [PATCH 2/4] esm: use import.meta.filename/dirname instead of import.meta.url --- test/es-module/test-esm-detect-ambiguous.mjs | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/es-module/test-esm-detect-ambiguous.mjs b/test/es-module/test-esm-detect-ambiguous.mjs index 9a8cb8b54ccd93..4d42e552da9998 100644 --- a/test/es-module/test-esm-detect-ambiguous.mjs +++ b/test/es-module/test-esm-detect-ambiguous.mjs @@ -463,4 +463,40 @@ describe('cjs & esm ambiguous syntax case', () => { assert.strictEqual(code, 1); assert.strictEqual(signal, null); }); + + it('should throw an ambiguous syntax error when using top-level await with __filename', async () => { + const { stderr, code, signal } = await spawnPromisified( + process.execPath, + [ + '--eval', + `console.log(__filename);\nawait 1;`, + ] + ); + + match( + stderr, + /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\./ + ); + + strictEqual(code, 1); + strictEqual(signal, null); + }); + + it('should throw an ambiguous syntax error when using top-level await with __dirname', async () => { + const { stderr, code, signal } = await spawnPromisified( + process.execPath, + [ + '--eval', + `console.log(__dirname);\nawait 1;`, + ] + ); + + match( + stderr, + /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\./ + ); + + strictEqual(code, 1); + strictEqual(signal, null); + }); }); From e2efe89d48a97a86d28afef5ddfe7d55336451e5 Mon Sep 17 00:00:00 2001 From: mag123c Date: Tue, 28 Oct 2025 09:54:42 +0900 Subject: [PATCH 3/4] esm: apply code review suggestions - Refactor getUndefinedCJSGlobalLike to use ArrayPrototypeFind - Change if-else chain to switch statement - Add single quotes around global names in error messages - Revert test file to use --input-type=module flag and original order - Update test regex patterns to expect quoted global names --- test/es-module/test-esm-detect-ambiguous.mjs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/es-module/test-esm-detect-ambiguous.mjs b/test/es-module/test-esm-detect-ambiguous.mjs index 4d42e552da9998..d21d2c7c25fd3e 100644 --- a/test/es-module/test-esm-detect-ambiguous.mjs +++ b/test/es-module/test-esm-detect-ambiguous.mjs @@ -283,7 +283,7 @@ describe('Module syntax detection', { concurrency: !process.env.TEST_PARALLEL }, assert.match( stderr, - /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\./ + /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\./ ); assert.strictEqual(stdout, ''); assert.strictEqual(code, 1); @@ -432,14 +432,15 @@ describe('cjs & esm ambiguous syntax case', () => { const { stderr, code, signal } = await spawnPromisified( process.execPath, [ + '--input-type=module', '--eval', - `const fs = require('fs');\nawait 1;`, + `await 1;\nconst fs = require('fs');`, ] ); assert.match( stderr, - /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\./ + /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\./ ); strictEqual(code, 1); @@ -457,7 +458,7 @@ describe('cjs & esm ambiguous syntax case', () => { match( stderr, - /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\./ + /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\./ ); assert.strictEqual(code, 1); @@ -475,7 +476,7 @@ describe('cjs & esm ambiguous syntax case', () => { match( stderr, - /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\./ + /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\./ ); strictEqual(code, 1); @@ -493,7 +494,7 @@ describe('cjs & esm ambiguous syntax case', () => { match( stderr, - /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\./ + /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\./ ); strictEqual(code, 1); From a56707fccfae816ea6b4a9c8022b0c8dfdb67321 Mon Sep 17 00:00:00 2001 From: mag123c Date: Fri, 14 Nov 2025 09:44:05 +0900 Subject: [PATCH 4/4] esm: fix linting errors after rebase Remove unused ArrayPrototypeSome import and fix assert usage. --- lib/internal/modules/esm/module_job.js | 2 +- test/es-module/test-esm-detect-ambiguous.mjs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 0207b6d53377ea..929577c0da6d08 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -80,8 +80,8 @@ const findCommonJSGlobalLikeNotDefinedError = (errorMessage) => */ const explainCommonJSGlobalLikeNotDefinedError = (e, url, hasTopLevelAwait) => { const notDefinedGlobalLike = e?.name === 'ReferenceError' && findCommonJSGlobalLikeNotDefinedError(e.message); - if (notDefinedGlobalLike) { + if (notDefinedGlobalLike) { if (hasTopLevelAwait) { let advice; switch (notDefinedGlobalLike) { diff --git a/test/es-module/test-esm-detect-ambiguous.mjs b/test/es-module/test-esm-detect-ambiguous.mjs index d21d2c7c25fd3e..4b2544c42cfbae 100644 --- a/test/es-module/test-esm-detect-ambiguous.mjs +++ b/test/es-module/test-esm-detect-ambiguous.mjs @@ -443,8 +443,8 @@ describe('cjs & esm ambiguous syntax case', () => { /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\./ ); - strictEqual(code, 1); - strictEqual(signal, null); + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); }); it('should throw an ambiguous syntax error when using top-level await with exports', async () => { @@ -456,7 +456,7 @@ describe('cjs & esm ambiguous syntax case', () => { ] ); - match( + assert.match( stderr, /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\./ ); @@ -474,13 +474,13 @@ describe('cjs & esm ambiguous syntax case', () => { ] ); - match( + assert.match( stderr, /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\./ ); - strictEqual(code, 1); - strictEqual(signal, null); + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); }); it('should throw an ambiguous syntax error when using top-level await with __dirname', async () => { @@ -492,12 +492,12 @@ describe('cjs & esm ambiguous syntax case', () => { ] ); - match( + assert.match( stderr, /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\./ ); - strictEqual(code, 1); - strictEqual(signal, null); + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); }); });