vm: expose hasTopLevelAwait on SourceTextModule

Expose `hasTopLevelAwait` and `hasAsyncGraph` on
`vm.SourceTextModule`.

`hasAsyncGraph` requires the module to be instantiated first.

PR-URL: https://github.com/nodejs/node/pull/59865
Fixes: https://github.com/nodejs/node/issues/59656
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
Chengzhong Wu 2025-09-19 10:12:17 +01:00 committed by GitHub
parent 897932c484
commit 4612c793cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 169 additions and 0 deletions

View File

@ -920,6 +920,36 @@ to disallow any changes to it.
Corresponds to the `[[RequestedModules]]` field of [Cyclic Module Record][]s in
the ECMAScript specification.
### `sourceTextModule.hasAsyncGraph()`
<!-- YAML
added: REPLACEME
-->
* Returns: {boolean}
Iterates over the dependency graph and returns `true` if any module in its
dependencies or this module itself contains top-level `await` expressions,
otherwise returns `false`.
The search may be slow if the graph is big enough.
This requires the module to be instantiated first. If the module is not
instantiated yet, an error will be thrown.
### `sourceTextModule.hasTopLevelAwait()`
<!-- YAML
added: REPLACEME
-->
* Returns: {boolean}
Returns whether the module itself contains any top-level `await` expressions.
This corresponds to the field `[[HasTLA]]` in [Cyclic Module Record][] in the
ECMAScript specification.
### `sourceTextModule.instantiate()`
<!-- YAML

View File

@ -429,6 +429,19 @@ class SourceTextModule extends Module {
return super.error;
}
hasAsyncGraph() {
validateThisInternalField(this, kWrap, 'SourceTextModule');
if (this[kWrap].getStatus() < kInstantiated) {
throw new ERR_VM_MODULE_STATUS('must be instantiated');
}
return this[kWrap].hasAsyncGraph;
}
hasTopLevelAwait() {
validateThisInternalField(this, kWrap, 'SourceTextModule');
return this[kWrap].hasTopLevelAwait;
}
createCachedData() {
const { status } = this;
if (status === 'evaluating' ||

View File

@ -0,0 +1,67 @@
'use strict';
// Flags: --experimental-vm-modules
require('../common');
const assert = require('assert');
const { SourceTextModule } = require('vm');
const test = require('node:test');
test('module is not instantiated yet', () => {
const foo = new SourceTextModule(`
export const foo = 4
export default 5;
`);
assert.throws(() => foo.hasAsyncGraph(), {
code: 'ERR_VM_MODULE_STATUS',
});
});
test('simple module with top-level await', () => {
const foo = new SourceTextModule(`
export const foo = 4
export default 5;
await 0;
`);
foo.linkRequests([]);
foo.instantiate();
assert.strictEqual(foo.hasAsyncGraph(), true);
});
test('simple module with non top-level await', () => {
const foo = new SourceTextModule(`
export const foo = 4
export default 5;
export async function f() {
await 0;
}
`);
foo.linkRequests([]);
foo.instantiate();
assert.strictEqual(foo.hasAsyncGraph(), false);
});
test('module with a dependency containing top-level await', () => {
const foo = new SourceTextModule(`
export const foo = 4
export default 5;
await 0;
`);
foo.linkRequests([]);
const bar = new SourceTextModule(`
export { foo } from 'foo';
`);
bar.linkRequests([foo]);
bar.instantiate();
assert.strictEqual(foo.hasAsyncGraph(), true);
assert.strictEqual(bar.hasAsyncGraph(), true);
});

View File

@ -0,0 +1,59 @@
'use strict';
// Flags: --experimental-vm-modules
require('../common');
const assert = require('assert');
const { SourceTextModule } = require('vm');
const test = require('node:test');
test('simple module', () => {
const foo = new SourceTextModule(`
export const foo = 4
export default 5;
`);
assert.strictEqual(foo.hasTopLevelAwait(), false);
});
test('simple module with top-level await', () => {
const foo = new SourceTextModule(`
export const foo = 4
export default 5;
await 0;
`);
assert.strictEqual(foo.hasTopLevelAwait(), true);
});
test('simple module with non top-level await', () => {
const foo = new SourceTextModule(`
export const foo = 4
export default 5;
export async function f() {
await 0;
}
`);
assert.strictEqual(foo.hasTopLevelAwait(), false);
});
test('module with a dependency containing top-level await', () => {
const foo = new SourceTextModule(`
export const foo = 4
export default 5;
await 0;
`);
foo.linkRequests([]);
const bar = new SourceTextModule(`
export { foo } from 'foo';
`);
bar.linkRequests([foo]);
bar.instantiate();
assert.strictEqual(foo.hasTopLevelAwait(), true);
assert.strictEqual(bar.hasTopLevelAwait(), false);
});