Skip to content

Commit 9b0a95e

Browse files
committed
fix: improve ERR_AMBIGUOUS_MODULE_SYNTAX error message
Dynamically identify the CommonJS global that caused the ambiguity.
1 parent 025ade6 commit 9b0a95e

File tree

2 files changed

+34
-15
lines changed

2 files changed

+34
-15
lines changed

lib/internal/modules/esm/module_job.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,11 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
8080
*/
8181
const explainCommonJSGlobalLikeNotDefinedError = (e, url, hasTopLevelAwait) => {
8282
if (e?.name === 'ReferenceError' &&
83-
isCommonJSGlobalLikeNotDefinedError(e.message)) {
83+
isCommonJSGlobalLikeNotDefinedError(e.message)) {
8484

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+
const globalName = StringPrototypeSplit(e.message, ' ')[0];
87+
e.message = `Cannot determine intended module format because both ${globalName} 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 ${globalName} with import.`;
8788
e.code = 'ERR_AMBIGUOUS_MODULE_SYNTAX';
8889
return;
8990
}
@@ -96,9 +97,9 @@ const explainCommonJSGlobalLikeNotDefinedError = (e, url, hasTopLevelAwait) => {
9697

9798
const packageConfig =
9899
StringPrototypeStartsWith(url, 'file://') &&
99-
RegExpPrototypeExec(/\.js(\?[^#]*)?(#.*)?$/, url) !== null &&
100-
require('internal/modules/package_json_reader')
101-
.getPackageScopeConfig(url);
100+
RegExpPrototypeExec(/\.js(\?[^#]*)?(#.*)?$/, url) !== null &&
101+
require('internal/modules/package_json_reader')
102+
.getPackageScopeConfig(url);
102103
if (packageConfig.type === 'module') {
103104
e.message +=
104105
'\nThis file is being treated as an ES module because it has a ' +
@@ -191,7 +192,7 @@ class ModuleJob extends ModuleJobBase {
191192
* @param {ModuleRequestType} requestType Type of the module request.
192193
*/
193194
constructor(loader, url, importAttributes = { __proto__: null }, moduleOrModulePromise,
194-
phase = kEvaluationPhase, isMain, inspectBrk, requestType) {
195+
phase = kEvaluationPhase, isMain, inspectBrk, requestType) {
195196
super(loader, url, importAttributes, phase, isMain, inspectBrk);
196197

197198
// Expose the promise to the ModuleWrap directly for linking below.
@@ -308,8 +309,8 @@ class ModuleJob extends ModuleJobBase {
308309
// stack trace originates in module_job, not the file itself. A hidden
309310
// symbol with filename could be set in node_errors.cc to facilitate this.
310311
if (!getSourceMapsSupport().enabled &&
311-
StringPrototypeIncludes(e.message,
312-
' does not provide an export named')) {
312+
StringPrototypeIncludes(e.message,
313+
' does not provide an export named')) {
313314
const splitStack = StringPrototypeSplit(e.stack, '\n', 2);
314315
const { 1: childSpecifier, 2: name } = RegExpPrototypeExec(
315316
/module '(.*)' does not provide an export named '(.+)'/,
@@ -336,9 +337,8 @@ class ModuleJob extends ModuleJobBase {
336337
` '${childSpecifier}' is a CommonJS module, which may not support` +
337338
' all module.exports as named exports.\nCommonJS modules can ' +
338339
'always be imported via the default export, for example using:' +
339-
`\n\nimport pkg from '${childSpecifier}';\n${
340-
destructuringAssignment ?
341-
`const ${destructuringAssignment} = pkg;\n` : ''}`;
340+
`\n\nimport pkg from '${childSpecifier}';\n${destructuringAssignment ?
341+
`const ${destructuringAssignment} = pkg;\n` : ''}`;
342342
const newStack = StringPrototypeSplit(e.stack, '\n');
343343
newStack[3] = `SyntaxError: ${e.message}`;
344344
e.stack = ArrayPrototypeJoin(newStack, '\n');
@@ -441,7 +441,7 @@ class ModuleJobSync extends ModuleJobBase {
441441
* first line paused in the debugger (because --inspect-brk is passed).
442442
*/
443443
constructor(loader, url, importAttributes, moduleWrap, phase = kEvaluationPhase, isMain,
444-
inspectBrk, requestType) {
444+
inspectBrk, requestType) {
445445
super(loader, url, importAttributes, phase, isMain, inspectBrk);
446446

447447
this.module = moduleWrap;
@@ -492,7 +492,7 @@ class ModuleJobSync extends ModuleJobBase {
492492
}
493493

494494
assert.fail('Unexpected status of a module that is imported again after being required. ' +
495-
`Status = ${status}`);
495+
`Status = ${status}`);
496496
}
497497

498498
runSync(parent) {

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

Lines changed: 21 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,26 @@ 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 module', async () => {
451+
const { stderr, code, signal } = await spawnPromisified(
452+
process.execPath,
453+
[
454+
'--input-type=module',
455+
'--eval',
456+
`await 1;\nmodule.exports = 1;`,
457+
]
458+
);
459+
460+
assert.match(
461+
stderr,
462+
/ReferenceError: Cannot determine intended module format because both module 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 module with import\./
444463
);
445464

446465
assert.strictEqual(code, 1);

0 commit comments

Comments
 (0)