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