Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions doc/api/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,33 @@ to disallow any changes to it.
Corresponds to the `[[RequestedModules]]` field of [Cyclic Module Record][]s in
the ECMAScript specification.

### `sourceTextModule.getHasAsyncGraph()`
Copy link
Member

@joyeecheung joyeecheung Sep 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to be a method? It seems fine to just make it a getter property, and it would be more consistent with other getter properties (e.g. error requires the module to be errored, but it's still a getter property)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires the module to be instantiated, and throws before instantiation.

I think throwing in a getter is not a good pattern. Making it a method indicates that this could incur traversal on the dependency graph, and may throw if this is not instantiated.


<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the module has a dependency graph that contains top-level
`await` expressions, otherwise returns `false`.
Comment on lines +931 to +932
Copy link
Member

@joyeecheung joyeecheung Sep 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Returns `true` if the module has a dependency graph that contains top-level
`await` expressions, otherwise returns `false`.
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
-->

* Type: {boolean}

Indicates whether the module contains any top-level `await` expressions.

This corresponds to the field `[[HasTLA]]` in [Cyclic Module Record][] in the
ECMAScript specification.

### `sourceTextModule.instantiate()`

<!-- YAML
Expand Down
13 changes: 13 additions & 0 deletions lib/internal/vm/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,19 @@ class SourceTextModule extends Module {
return super.error;
}

get hasTopLevelAwait() {
validateThisInternalField(this, kWrap, 'SourceTextModule');
return this[kWrap].hasTopLevelAwait;
}

getHasAsyncGraph() {
validateThisInternalField(this, kWrap, 'SourceTextModule');
if (this[kWrap].getStatus() < kInstantiated) {
throw new ERR_VM_MODULE_STATUS('must be instantiated');
}
return this[kWrap].hasAsyncGraph;
}

createCachedData() {
const { status } = this;
if (status === 'evaluating' ||
Expand Down
67 changes: 67 additions & 0 deletions test/parallel/test-vm-module-gethasasyncgraph.js
Original file line number Diff line number Diff line change
@@ -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.getHasAsyncGraph(), {
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.getHasAsyncGraph(), 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.getHasAsyncGraph(), 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.getHasAsyncGraph(), true);
assert.strictEqual(bar.getHasAsyncGraph(), true);
});
59 changes: 59 additions & 0 deletions test/parallel/test-vm-module-hastoplevelawait.js
Original file line number Diff line number Diff line change
@@ -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);
});
Loading