diff --git a/doc/api/errors.md b/doc/api/errors.md
index 07d439da8e17ed..4175574f1661d1 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -2823,12 +2823,30 @@ A QUIC session failed because version negotiation is required.
### `ERR_REQUIRE_ASYNC_MODULE`
+
+
When trying to `require()` a [ES Module][], the module turns out to be asynchronous.
That is, it contains top-level await.
-To see where the top-level await is, use
-`--experimental-print-required-tla` (this would execute the modules
-before looking for the top-level awaits).
+When uncaught, the flag `--experimental-print-required-tla` prints
+the locations of the top-level awaits in the graph to stderr.
+
+This error has the following additional non-enumerable properties:
+
+* `requireStack` {string\[]} The chain of modules that led to the failing
+ `require()`, starting with the module that required the asynchronous module.
+* `topLevelAwaitLocations` {Object\[]} The locations of the top-level awaits in
+ the graph. Only populated when `--experimental-print-required-tla` is enabled.
+ Each entry has the following properties:
+ * `url` {string} The URL of the module containing the top-level await.
+ * `line` {number} The 1-based line number of the top-level await.
+ * `column` {number} The 1-based column number of the top-level await.
+ * `sourceLine` {string} The source line containing the top-level await.
diff --git a/doc/api/modules.md b/doc/api/modules.md
index 7a22703018eba6..87650979f66508 100644
--- a/doc/api/modules.md
+++ b/doc/api/modules.md
@@ -317,10 +317,9 @@ graph it `import`s contains top-level `await`,
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
load the asynchronous module using [`import()`][].
-If `--experimental-print-required-tla` is enabled, instead of throwing
-`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
-module, try to locate the top-level awaits, and print their location to
-help users fix them.
+If `--experimental-print-required-tla` is enabled and the error is uncaught,
+Node.js will try to locate the top-level `await`s in the `require()`'d module graph
+and print the locations in the stderr.
If support for loading ES modules using `require()` results in unexpected
breakage, it can be disabled using `--no-require-module`.
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 4d502575c9622d..03d27332c894d8 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1719,6 +1719,9 @@ E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parent, locations) {
if (!getOptionValue('--experimental-print-required-tla')) {
message += ' To see where the top-level await comes from, use --experimental-print-required-tla.';
}
+ if (filename) {
+ message += `\nRequired module: ${filename}`;
+ }
if (parent) {
const { getRequireStack } = require('internal/modules/helpers');
const requireStack = getRequireStack(parent);
@@ -1726,13 +1729,26 @@ E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parent, locations) {
message += '\nRequire stack:\n- ' +
ArrayPrototypeJoin(requireStack, '\n- ');
}
- this.requireStack = requireStack;
+ ObjectDefineProperty(this, 'requireStack', {
+ __proto__: null,
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ value: requireStack,
+ });
}
if (locations && locations.length > 0) {
const { urlToFilename } = require('internal/modules/helpers');
const frames = ArrayPrototypeMap(locations, ({ url, line, column, sourceLine }) =>
- `${urlToFilename(url)}:${line}\n\n${sourceLine}\n${StringPrototypeRepeat(' ', column)}^\n`);
+ `${urlToFilename(url)}:${line}\n\n${sourceLine}\n${StringPrototypeRepeat(' ', column - 1)}^\n`);
setArrowMessage(this, ArrayPrototypeJoin(frames, '\n'));
+ ObjectDefineProperty(this, 'topLevelAwaitLocations', {
+ __proto__: null,
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ value: locations,
+ });
}
return message;
}, Error);
diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js
index 1a6622140382ba..f92e87785ce12c 100644
--- a/lib/internal/modules/esm/loader.js
+++ b/lib/internal/modules/esm/loader.js
@@ -291,8 +291,8 @@ class ModuleLoader {
const status = job.module.getStatus();
debug('Module status', job, status);
// hasAsyncGraph is available after module been instantiated.
- if (status >= kInstantiated && job.module.hasAsyncGraph) {
- job.throwAsyncGraphError(parent);
+ if (status >= kInstantiated) {
+ job.throwIfAsyncGraph(parent);
}
if (status === kEvaluated) {
return { wrap: job.module, namespace: job.module.getNamespace() };
@@ -320,9 +320,11 @@ class ModuleLoader {
}
if (status !== kEvaluating) {
assert(status === kUninstantiated, `Unexpected module status ${status}`);
- // A previous require() of the same graph may have bailed out before
- // instantiation because it contains top-level await.
- job.throwIfAsyncGraph(parent);
+ // If we get here, either there's a race where the job is still being instantiated
+ // by an in-flight import(), or the cached module previously encountered an
+ // instantiation error during a prior load (e.g. due to a mismatched import).
+ // TODO(joyeecheung): the current check is too broad. We should attempt to
+ // get the potential instantiation error and throw it.
throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false);
}
let message = `Cannot require() ES Module ${filename} in a cycle.`;
diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js
index ee055c95d6d5c7..5423437710e4a7 100644
--- a/lib/internal/modules/esm/module_job.js
+++ b/lib/internal/modules/esm/module_job.js
@@ -2,6 +2,7 @@
const {
Array,
+ ArrayIsArray,
ArrayPrototypeFind,
ArrayPrototypeJoin,
ArrayPrototypePop,
@@ -37,8 +38,10 @@ const {
kUninstantiated,
} = internalBinding('module_wrap');
const {
+ getPromiseDetails,
privateSymbols: {
entry_point_module_private_symbol,
+ module_source_private_symbol: kModuleSource,
},
} = internalBinding('util');
/**
@@ -135,12 +138,12 @@ const explainCommonJSGlobalLikeNotDefinedError = (e, url, hasTopLevelAwait) => {
* @typedef {object} TopLevelAwaitLocation
* @property {string} url URL of the module containing the top-level await.
* @property {number} line 1-based line number of the top-level await.
- * @property {number} column 0-based column number of the top-level await.
+ * @property {number} column 1-based column number of the top-level await.
* @property {string} sourceLine The source line containing the top-level await.
*/
/**
- * Locate the top-level awaits in the given module by parsing the source with acron.
+ * Locate the top-level awaits in the given module by parsing the source with acorn.
* @param {string} source Module source code.
* @returns {object[]} The acorn AST nodes of the top-level awaits, in source order.
*/
@@ -174,27 +177,62 @@ function findTopLevelAwait(source) {
return found;
}
+/**
+ * Collect the modules that contain top-level await in the linked graph of a job.
+ * @param {ModuleJobBase} root The root of the module graph to search.
+ * @returns {ModuleWrap[]} Modules that contain top-level await.
+ */
+function findModulesWithTopLevelAwait(root) {
+ const found = [];
+ const seen = new SafeSet();
+ const stack = [root];
+ while (stack.length > 0) {
+ const job = ArrayPrototypePop(stack);
+ if (seen.has(job)) { continue; }
+ seen.add(job);
+ if (job.module?.hasTopLevelAwait) {
+ ArrayPrototypePush(found, job.module);
+ }
+ let linked = job.linked;
+ if (isPromise(linked)) {
+ linked = getPromiseDetails(linked)?.[1];
+ }
+ // If `require(esm)` comes from the deprecated async loader hook worker thread,
+ // linked may be pending at this point. In that case, this branch would be skipped -
+ // we just allow lossy reporting of TLA locations in an edge case when a deprecated
+ // feature is used in combination with another experimental flag.
+ if (ArrayIsArray(linked)) {
+ for (let i = 0; i < linked.length; i++) {
+ ArrayPrototypePush(stack, linked[i]);
+ }
+ }
+ }
+ return found;
+}
+
/**
* Locate the top-level awaits in the given modules.
- * @param {ModuleWrap[]} modules Modules that may contain top-level await.
+ * @param {ModuleJobBase} root The root of the module graph to search.
* @returns {TopLevelAwaitLocation[]} The locations of the top-level awaits.
*/
-function getTopLevelAwaitLocations(modules) {
+function getTopLevelAwaitLocations(root) {
+ const modules = findModulesWithTopLevelAwait(root);
const locations = [];
for (let i = 0; i < modules.length; i++) {
const module = modules[i];
- const source = module.source;
+ const source = module[kModuleSource];
if (typeof source !== 'string') { continue; } // Not retained during compilation. Skip.
const found = findTopLevelAwait(source);
if (found.length === 0) { continue; }
- const lines = StringPrototypeSplit(source, '\n');
+ const lines = StringPrototypeSplit(source, /\r?\n/);
for (let j = 0; j < found.length; j++) {
const { start } = found[j].loc;
ArrayPrototypePush(locations, {
__proto__: null,
url: module.url,
line: start.line,
- column: start.column,
+ // Acorn reports 0-based columns, convert them to 1-based to match `line`.
+ column: start.column + 1,
sourceLine: lines[start.line - 1],
});
}
@@ -261,61 +299,18 @@ class ModuleJobBase {
}
/**
- * Collect the modules that contain top-level await in the linked graph of
- * this job. Whether each module contains top-level await is known at
- * compilation, so for a synchronously linked graph this finds asynchronous
- * graphs before instantiation.
- * On the (deprecated) async loader hook worker thread, linking may be asynchronous, in
- * which case the subgraphs that are not synchronously linked are skipped
- * and callers should still consult hasAsyncGraph after instantiation.
- * @returns {ModuleWrap[]}
- */
- findModulesWithTopLevelAwait() {
- const found = [];
- const seen = new SafeSet();
- const stack = [this];
- while (stack.length > 0) {
- const job = ArrayPrototypePop(stack);
- if (seen.has(job)) { continue; }
- seen.add(job);
- if (job.module?.hasTopLevelAwait) {
- ArrayPrototypePush(found, job.module);
- }
- // job.linked is the array of evaluation-phase dependency jobs when the
- // linking is synchronous. Skip it if it's still a promise.
- if (!isPromise(job.linked)) {
- for (let i = 0; i < job.linked.length; i++) {
- ArrayPrototypePush(stack, job.linked[i]);
- }
- }
- }
- return found;
- }
-
- /**
- * Throw the ERR_REQUIRE_ASYNC_MODULE with metadata for a require()'d graph that
- * contains top-level await.
- * @param {Module|undefined} parent CommonJS module that require()'d this, if any.
- * @param {ModuleWrap[]} [modules] Modules with top-level await, when already
- * collected by the caller, to avoid walking the graph again.
- */
- throwAsyncGraphError(parent, modules = this.findModulesWithTopLevelAwait()) {
- const locations = getOptionValue('--experimental-print-required-tla') ? getTopLevelAwaitLocations(modules) : [];
- const filename = urlToFilename(this.url);
- throw new ERR_REQUIRE_ASYNC_MODULE(filename, parent, locations);
- }
-
- /**
- * If the a require()'d graph contains top-level await, collect the source locations
+ * If the require()'d graph contains top-level await, collect the source locations
* of the top-level awaits using source code retained during compilation and throw
- * ERR_REQUIRE_ASYNC_MODULE. This can be run before instantiation is complete.
+ * ERR_REQUIRE_ASYNC_MODULE. The module must be at least instantiated.
* @param {Module|undefined} parent CommonJS module that require()'d this, if any.
*/
throwIfAsyncGraph(parent) {
- const modules = this.findModulesWithTopLevelAwait();
- if (modules.length > 0) {
- this.throwAsyncGraphError(parent, modules);
+ if (!this.module.hasAsyncGraph) {
+ return;
}
+ const locations = getOptionValue('--experimental-print-required-tla') ? getTopLevelAwaitLocations(this) : [];
+ const filename = urlToFilename(this.url);
+ throw new ERR_REQUIRE_ASYNC_MODULE(filename, parent, locations);
}
/**
@@ -537,9 +532,7 @@ class ModuleJob extends ModuleJobBase {
status = this.module.getStatus();
}
if (status === kInstantiated || status === kErrored) {
- if (this.module.hasAsyncGraph) {
- this.throwAsyncGraphError(parent);
- }
+ this.throwIfAsyncGraph(parent);
if (status === kInstantiated) {
setHasStartedUserESMExecution();
const namespace = this.module.evaluateSync();
@@ -547,9 +540,7 @@ class ModuleJob extends ModuleJobBase {
}
throw this.module.getError();
} else if (status === kEvaluating || status === kEvaluated) {
- if (this.module.hasAsyncGraph) {
- this.throwAsyncGraphError(parent);
- }
+ this.throwIfAsyncGraph(parent);
// kEvaluating can show up when this is being used to deal with CJS <-> CJS cycles.
// Allow it for now, since we only need to ban ESM <-> CJS cycles which would be
// detected earlier during the linking phase, though the CJS handling in the ESM
@@ -645,12 +636,11 @@ class ModuleJobSync extends ModuleJobBase {
}
return { __proto__: null, module: this.module };
} else if (status === kInstantiated || status === kUninstantiated) {
- // The require() of this (synchronously linked) module bailed out: either
- // it was rejected for containing top-level await after instantiation
- // (kInstantiated), or its instantiation failed and left it uninstantiated
- // (kUninstantiated, e.g. a missing named export). When it's reached via async
- // run() from import, finish the instantiation and evaluate it asynchronously,
- // re-throwing any instantiation error.
+ // If we get here, the module was initially required and is now being imported.
+ // The require() module failed either because the graph has TLA (kInstantiated),
+ // or instantiation failed (kUninstantiated, e.g. missing named export).
+ // Try finishing the instantiation - if it succeeds, proceed to evaluation,
+ // otherwise the branch below re-throw any instantiation error.
if (status === kUninstantiated) {
this.module.instantiate();
}
@@ -676,9 +666,7 @@ class ModuleJobSync extends ModuleJobBase {
// On the deprecated async loader hook worker thread, dependencies linked by an
// earlier import may not be walkable synchronously, so double-check with
// V8 now that the graph is instantiated.
- if (this.module.hasAsyncGraph) {
- this.throwAsyncGraphError(parent);
- }
+ this.throwIfAsyncGraph(parent);
setHasStartedUserESMExecution();
try {
const namespace = this.module.evaluateSync();
diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js
index 7e453171026757..9e27a2db1ebce8 100644
--- a/lib/internal/modules/esm/utils.js
+++ b/lib/internal/modules/esm/utils.js
@@ -11,6 +11,7 @@ const {
const {
privateSymbols: {
host_defined_option_symbol,
+ module_source_private_symbol: kModuleSource,
},
} = internalBinding('util');
const {
@@ -368,7 +369,7 @@ function compileSourceTextModule(url, source, type, context = kEmptyObject) {
// only serves as a shortcut.
if (wrap.hasTopLevelAwait &&
getOptionValue('--experimental-print-required-tla')) {
- wrap.source = source;
+ wrap[kModuleSource] = source;
}
// Cache the source map for the module if present.
diff --git a/test/common/index.js b/test/common/index.js
index d011f930690ff1..ac0c400581c439 100755
--- a/test/common/index.js
+++ b/test/common/index.js
@@ -943,30 +943,37 @@ function expectRequiredModule(mod, expectation, checkESModule = true) {
assert.deepStrictEqual(clone, { ...expectation });
}
-function expectRequiredTLAError(err) {
- const message = /require\(\) cannot be used on an ESM graph with top-level await/;
- if (typeof err === 'string') {
- assert.match(err, /ERR_REQUIRE_ASYNC_MODULE/);
- assert.match(err, message);
- } else {
- assert.strictEqual(err.code, 'ERR_REQUIRE_ASYNC_MODULE');
- assert.match(err.message, message);
- }
-}
-
// Extract the entries of the rendered "Require stack:" list (each shown as
// "- ") from an error message or a process output string.
-function parseRequireStack(output) {
+function expectRequireStack(output, expected) {
const lines = output.replace(/\r/g, '').split('\n');
const start = lines.indexOf('Require stack:');
if (start === -1) {
- return [];
+ assert.deepStrictEqual([], expected);
+ return;
}
const stack = [];
for (let i = start + 1; i < lines.length && lines[i].startsWith('- '); i++) {
stack.push(lines[i].slice(2));
}
- return stack;
+ assert.deepStrictEqual(stack, expected);
+}
+
+function expectRequiredTLAError(err, stack) {
+ const message = /require\(\) cannot be used on an ESM graph with top-level await/;
+ if (typeof err === 'string') {
+ assert.match(err, /ERR_REQUIRE_ASYNC_MODULE/);
+ assert.match(err, message);
+ if (stack) {
+ expectRequireStack(err, stack);
+ }
+ } else {
+ assert.strictEqual(err.code, 'ERR_REQUIRE_ASYNC_MODULE');
+ assert.match(err.message, message);
+ if (stack) {
+ assert.deepStrictEqual(err.requireStack, stack);
+ }
+ }
}
function sleepSync(ms) {
@@ -1026,7 +1033,7 @@ const common = {
mustSucceed,
nodeProcessAborted,
PIPE,
- parseRequireStack,
+ expectRequireStack,
parseTestMetadata,
platformTimeout,
printSkipMessage,
diff --git a/test/es-module/test-require-module-cached-tla.js b/test/es-module/test-require-module-cached-tla.js
index 5c707269cc2a4f..ee99bfa224977f 100644
--- a/test/es-module/test-require-module-cached-tla.js
+++ b/test/es-module/test-require-module-cached-tla.js
@@ -9,8 +9,7 @@ const assert = require('assert');
assert.throws(() => {
require('../fixtures/es-modules/tla/resolved.mjs');
}, (err) => {
- common.expectRequiredTLAError(err);
- assert.deepStrictEqual(common.parseRequireStack(err.message), [__filename]);
+ common.expectRequiredTLAError(err, [__filename]);
return true;
});
})().then(common.mustCall());
diff --git a/test/es-module/test-require-module-tla-error-metadata.js b/test/es-module/test-require-module-tla-error-metadata.js
new file mode 100644
index 00000000000000..fc93c6efa94716
--- /dev/null
+++ b/test/es-module/test-require-module-tla-error-metadata.js
@@ -0,0 +1,26 @@
+// Flags: --experimental-print-required-tla
+'use strict';
+
+// Tests that ERR_REQUIRE_ASYNC_MODULE carries metadata about the failure
+// when --experimental-print-required-tla is enabled.
+
+require('../common');
+const assert = require('assert');
+const fixtures = require('../common/fixtures');
+
+const entrypoint = fixtures.path('es-modules/tla/require-indented.js');
+const url = fixtures.fileURL('es-modules/tla/tla-indented.mjs').href;
+
+assert.throws(() => {
+ require(entrypoint);
+}, (err) => {
+ assert.strictEqual(err.code, 'ERR_REQUIRE_ASYNC_MODULE');
+ assert.deepStrictEqual(err.requireStack, [entrypoint, __filename]);
+ assert.deepStrictEqual(err.topLevelAwaitLocations, [
+ { __proto__: null, url, line: 4, column: 3, sourceLine: ' await Promise.resolve(ready);' },
+ { __proto__: null, url, line: 7, column: 1, sourceLine: 'for await (const x of [Promise.resolve(1)]) {' },
+ ]);
+ // The properties are not enumerable so they do not show up in the inspected error.
+ assert.deepStrictEqual(Object.keys(err), ['code']);
+ return true;
+});
diff --git a/test/es-module/test-require-module-tla-print.mjs b/test/es-module/test-require-module-tla-error-snapshot.mjs
similarity index 87%
rename from test/es-module/test-require-module-tla-print.mjs
rename to test/es-module/test-require-module-tla-error-snapshot.mjs
index 41b44376fdc3e6..bba882437263c2 100644
--- a/test/es-module/test-require-module-tla-print.mjs
+++ b/test/es-module/test-require-module-tla-error-snapshot.mjs
@@ -1,6 +1,5 @@
-// Tests the output of require(esm) on a graph with top-level await when
-// --experimental-print-required-tla is enabled: the location of the
-// top-level await (with a caret) and the require stack.
+// Snapshot tests for the output of require(esm) on a graph with top-level await when
+// --experimental-print-required-tla is enabled.
import '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
diff --git a/test/es-module/test-require-module-tla-execution.js b/test/es-module/test-require-module-tla-execution.js
index dab795dbdaa406..688691103fdaa9 100644
--- a/test/es-module/test-require-module-tla-execution.js
+++ b/test/es-module/test-require-module-tla-execution.js
@@ -1,7 +1,7 @@
'use strict';
-// Tests that require(esm) with top-level-await throws before execution starts
-// if --experimental-print-required-tla is not enabled.
+// Tests the output of require(esm) with top-level-await when --experimental-print-required-tla
+// is NOT enabled.
const common = require('../common');
const assert = require('assert');
@@ -9,18 +9,14 @@ const { spawnSyncAndExit } = require('../common/child_process');
const fixtures = require('../common/fixtures');
{
- spawnSyncAndExit(process.execPath, [
- fixtures.path('es-modules/tla/require-execution.js'),
- ], {
+ const filename = fixtures.path('es-modules/tla/require-execution.js');
+ spawnSyncAndExit(process.execPath, [ filename ], {
signal: null,
status: 1,
stderr(output) {
+ assert.match(output, /To see where the top-level await comes from, use --experimental-print-required-tla/);
assert.doesNotMatch(output, /I am executed/);
- common.expectRequiredTLAError(output);
- assert.deepStrictEqual(
- common.parseRequireStack(output),
- [fixtures.path('es-modules/tla/require-execution.js')],
- );
+ common.expectRequiredTLAError(output, [filename]);
return true;
},
stdout: '',
diff --git a/test/es-module/test-require-module-tla-nested.js b/test/es-module/test-require-module-tla-nested.js
index 0b73af5ad35235..879e1ceab6bf1b 100644
--- a/test/es-module/test-require-module-tla-nested.js
+++ b/test/es-module/test-require-module-tla-nested.js
@@ -8,7 +8,6 @@ const assert = require('assert');
assert.throws(() => {
require('../fixtures/es-modules/tla/parent.mjs');
}, (err) => {
- common.expectRequiredTLAError(err);
- assert.deepStrictEqual(common.parseRequireStack(err.message), [__filename]);
+ common.expectRequiredTLAError(err, [__filename]);
return true;
});
diff --git a/test/es-module/test-require-module-tla-print-arrow.js b/test/es-module/test-require-module-tla-print-arrow.js
deleted file mode 100644
index 2ba7c165a2bdd0..00000000000000
--- a/test/es-module/test-require-module-tla-print-arrow.js
+++ /dev/null
@@ -1,29 +0,0 @@
-'use strict';
-
-// Tests that require(esm) with --experimental-print-required-tla points the
-// caret at the right column for indented top-level await and for-await-of.
-
-const common = require('../common');
-const assert = require('assert');
-const { spawnSyncAndExit } = require('../common/child_process');
-const fixtures = require('../common/fixtures');
-
-{
- spawnSyncAndExit(process.execPath, [
- '--experimental-print-required-tla',
- fixtures.path('es-modules/tla/require-indented.js'),
- ], {
- signal: null,
- status: 1,
- stderr(output) {
- output = output.replace(/\r/g, '');
- common.expectRequiredTLAError(output);
- // Indented await: the caret is aligned under the await.
- assert(output.includes(' await Promise.resolve(ready);\n ^'), output);
- // for-await-of: the caret points at the for keyword.
- assert(output.includes('for await (const x of [Promise.resolve(1)]) {\n^'), output);
- return true;
- },
- stdout: '',
- });
-}
diff --git a/test/es-module/test-require-module-tla-print-execution.js b/test/es-module/test-require-module-tla-print-execution.js
index 68b4dc5a6557e9..c0191e40959020 100644
--- a/test/es-module/test-require-module-tla-print-execution.js
+++ b/test/es-module/test-require-module-tla-print-execution.js
@@ -10,22 +10,22 @@ const { spawnSyncAndExit } = require('../common/child_process');
const fixtures = require('../common/fixtures');
{
+ const filename = fixtures.path('es-modules/tla/require-execution.js');
spawnSyncAndExit(process.execPath, [
'--experimental-print-required-tla',
- fixtures.path('es-modules/tla/require-execution.js'),
+ filename,
], {
signal: null,
status: 1,
stderr(output) {
output = output.replace(/\r/g, '');
assert.doesNotMatch(output, /I am executed/);
- common.expectRequiredTLAError(output);
+ common.expectRequiredTLAError(output, [filename]);
+ // The immediately required module is shown regardless of the flag.
+ assert.match(output, /Required module: .*tla[/\\]execution\.mjs/);
// The location of the top-level await is shown with a caret.
- assert(output.includes(`${fixtures.path('es-modules/tla/execution.mjs')}:3`), output);
+ assert.match(output, /tla[/\\]execution\.mjs:3/);
assert(output.includes("await Promise.resolve('hi');\n^"), output);
- // The require() chain is shown as a require stack.
- assert.match(output, /Require stack:/);
- assert.match(output, /require-execution\.js/);
return true;
},
stdout: '',
diff --git a/test/es-module/test-require-module-tla-print-nested.js b/test/es-module/test-require-module-tla-print-nested.js
deleted file mode 100644
index 5f48c9b18e6d91..00000000000000
--- a/test/es-module/test-require-module-tla-print-nested.js
+++ /dev/null
@@ -1,31 +0,0 @@
-'use strict';
-
-// Tests that require(esm) attaches the location of a top-level await found in
-// an inner dependency of the graph if --experimental-print-required-tla is
-// enabled. The entry point is a real CommonJS file that require()s the ESM.
-
-const common = require('../common');
-const assert = require('assert');
-const { spawnSyncAndExit } = require('../common/child_process');
-const fixtures = require('../common/fixtures');
-
-{
- spawnSyncAndExit(process.execPath, [
- '--experimental-print-required-tla',
- fixtures.path('es-modules/tla/require-nested.js'),
- ], {
- signal: null,
- status: 1,
- stderr(output) {
- output = output.replace(/\r/g, '');
- common.expectRequiredTLAError(output);
- // The top-level await lives in a transitive dependency (a.mjs).
- assert(output.includes(`${fixtures.path('es-modules/tla/a.mjs')}:3`), output);
- assert(output.includes('await new Promise((resolve) => {\n^'), output);
- assert.match(output, /Require stack:/);
- assert.match(output, /require-nested\.js/);
- return true;
- },
- stdout: '',
- });
-}
diff --git a/test/es-module/test-require-module-tla-print-preload.js b/test/es-module/test-require-module-tla-print-preload.js
deleted file mode 100644
index b53d4e89026c3e..00000000000000
--- a/test/es-module/test-require-module-tla-print-preload.js
+++ /dev/null
@@ -1,32 +0,0 @@
-'use strict';
-
-// Tests that preloading an ESM with top-level await via -r throws before
-// execution starts and attaches the location of the top-level await
-// if --experimental-print-required-tla is enabled.
-
-const common = require('../common');
-const assert = require('assert');
-const { spawnSyncAndExit } = require('../common/child_process');
-const fixtures = require('../common/fixtures');
-
-{
- spawnSyncAndExit(process.execPath, [
- '--experimental-print-required-tla',
- '-r', fixtures.path('es-modules/tla/execution.mjs'),
- '-e', 'console.log("main ran")',
- ], {
- signal: null,
- status: 1,
- stderr(output) {
- output = output.replace(/\r/g, '');
- // The main script should not run because preloading fails first.
- assert.doesNotMatch(output, /main ran/);
- assert.doesNotMatch(output, /I am executed/);
- common.expectRequiredTLAError(output);
- assert(output.includes(`${fixtures.path('es-modules/tla/execution.mjs')}:3`), output);
- assert(output.includes("await Promise.resolve('hi');\n^"), output);
- return true;
- },
- stdout: '',
- });
-}
diff --git a/test/es-module/test-require-module-tla-rejected.js b/test/es-module/test-require-module-tla-rejected.js
index 07e8da183eca64..0922cc74de975b 100644
--- a/test/es-module/test-require-module-tla-rejected.js
+++ b/test/es-module/test-require-module-tla-rejected.js
@@ -8,7 +8,6 @@ const assert = require('assert');
assert.throws(() => {
require('../fixtures/es-modules/tla/rejected.mjs');
}, (err) => {
- common.expectRequiredTLAError(err);
- assert.deepStrictEqual(common.parseRequireStack(err.message), [__filename]);
+ common.expectRequiredTLAError(err, [__filename]);
return true;
});
diff --git a/test/es-module/test-require-module-tla-resolved.js b/test/es-module/test-require-module-tla-resolved.js
index 1545ab446819fa..3e15e97cce303d 100644
--- a/test/es-module/test-require-module-tla-resolved.js
+++ b/test/es-module/test-require-module-tla-resolved.js
@@ -8,7 +8,6 @@ const assert = require('assert');
assert.throws(() => {
require('../fixtures/es-modules/tla/resolved.mjs');
}, (err) => {
- common.expectRequiredTLAError(err);
- assert.deepStrictEqual(common.parseRequireStack(err.message), [__filename]);
+ common.expectRequiredTLAError(err, [__filename]);
return true;
});
diff --git a/test/es-module/test-require-module-tla-unresolved.js b/test/es-module/test-require-module-tla-unresolved.js
index 2226b2ebba436a..c38f4cb2c02b59 100644
--- a/test/es-module/test-require-module-tla-unresolved.js
+++ b/test/es-module/test-require-module-tla-unresolved.js
@@ -8,7 +8,6 @@ const assert = require('assert');
assert.throws(() => {
require('../fixtures/es-modules/tla/unresolved.mjs');
}, (err) => {
- common.expectRequiredTLAError(err);
- assert.deepStrictEqual(common.parseRequireStack(err.message), [__filename]);
+ common.expectRequiredTLAError(err, [__filename]);
return true;
});
diff --git a/test/fixtures/es-modules/tla/preload-main.snapshot b/test/fixtures/es-modules/tla/preload-main.snapshot
index 81279b9e2c017d..b8f0b17bcec0a7 100644
--- a/test/fixtures/es-modules/tla/preload-main.snapshot
+++ b/test/fixtures/es-modules/tla/preload-main.snapshot
@@ -4,12 +4,12 @@ await Promise.resolve('hi');
^
Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead.
+Required module: /test/fixtures/es-modules/tla/execution.mjs
Require stack:
- internal/preload
at
at {
- code: 'ERR_REQUIRE_ASYNC_MODULE',
- requireStack: [ 'internal/preload' ]
+ code: 'ERR_REQUIRE_ASYNC_MODULE'
}
Node.js
diff --git a/test/fixtures/es-modules/tla/require-awaitusing.snapshot b/test/fixtures/es-modules/tla/require-awaitusing.snapshot
index ae2a0b7c690217..fcaa21d8580cac 100644
--- a/test/fixtures/es-modules/tla/require-awaitusing.snapshot
+++ b/test/fixtures/es-modules/tla/require-awaitusing.snapshot
@@ -4,14 +4,12 @@ await using lock = {
^
Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead.
+Required module: /test/fixtures/es-modules/tla/awaitusing.mjs
Require stack:
- /test/fixtures/es-modules/tla/require-awaitusing.js
at
at {
- code: 'ERR_REQUIRE_ASYNC_MODULE',
- requireStack: [
- '/test/fixtures/es-modules/tla/require-awaitusing.js'
- ]
+ code: 'ERR_REQUIRE_ASYNC_MODULE'
}
Node.js
diff --git a/test/fixtures/es-modules/tla/require-execution.snapshot b/test/fixtures/es-modules/tla/require-execution.snapshot
index 65f312c9d9c606..9b3f813d0152ff 100644
--- a/test/fixtures/es-modules/tla/require-execution.snapshot
+++ b/test/fixtures/es-modules/tla/require-execution.snapshot
@@ -4,14 +4,12 @@ await Promise.resolve('hi');
^
Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead.
+Required module: /test/fixtures/es-modules/tla/execution.mjs
Require stack:
- /test/fixtures/es-modules/tla/require-execution.js
at
at {
- code: 'ERR_REQUIRE_ASYNC_MODULE',
- requireStack: [
- '/test/fixtures/es-modules/tla/require-execution.js'
- ]
+ code: 'ERR_REQUIRE_ASYNC_MODULE'
}
Node.js
diff --git a/test/fixtures/es-modules/tla/require-indented.snapshot b/test/fixtures/es-modules/tla/require-indented.snapshot
index 1bc7bf6e46edfd..d3b5f4fc551590 100644
--- a/test/fixtures/es-modules/tla/require-indented.snapshot
+++ b/test/fixtures/es-modules/tla/require-indented.snapshot
@@ -9,14 +9,12 @@ for await (const x of [Promise.resolve(1)]) {
^
Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead.
+Required module: /test/fixtures/es-modules/tla/tla-indented.mjs
Require stack:
- /test/fixtures/es-modules/tla/require-indented.js
at
at {
- code: 'ERR_REQUIRE_ASYNC_MODULE',
- requireStack: [
- '/test/fixtures/es-modules/tla/require-indented.js'
- ]
+ code: 'ERR_REQUIRE_ASYNC_MODULE'
}
Node.js
diff --git a/test/fixtures/es-modules/tla/require-nested.snapshot b/test/fixtures/es-modules/tla/require-nested.snapshot
index bf818b8e939838..fe5ada075fdb39 100644
--- a/test/fixtures/es-modules/tla/require-nested.snapshot
+++ b/test/fixtures/es-modules/tla/require-nested.snapshot
@@ -4,16 +4,13 @@ await new Promise((resolve) => {
^
Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead.
+Required module: /test/fixtures/es-modules/tla/parent.mjs
Require stack:
- /test/fixtures/es-modules/tla/require-nested-dep.js
- /test/fixtures/es-modules/tla/require-nested.js
at
at {
- code: 'ERR_REQUIRE_ASYNC_MODULE',
- requireStack: [
- '/test/fixtures/es-modules/tla/require-nested-dep.js',
- '/test/fixtures/es-modules/tla/require-nested.js'
- ]
+ code: 'ERR_REQUIRE_ASYNC_MODULE'
}
Node.js