At the last TC39 meeting, a new type of Module Records backed by JavaScript source called Dynamic Module Records was discussed, and it is now at Stage 1. Regardless of whether that proposal makes it all the way into the spec, SourceTextModule is indeed a more descriptive and accurate name for what this class represents. PR-URL: https://github.com/nodejs/node/pull/22007 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Jan Krems <jan.krems@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Yuta Hiroto <hello@hiroppy.me> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: John-David Dalton <john.david.dalton@gmail.com> Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
264 lines
6.7 KiB
JavaScript
264 lines
6.7 KiB
JavaScript
'use strict';
|
|
|
|
// Flags: --experimental-vm-modules
|
|
|
|
const common = require('../common');
|
|
|
|
const assert = require('assert');
|
|
|
|
const { SourceTextModule, createContext } = require('vm');
|
|
|
|
async function expectsRejection(fn, settings) {
|
|
const validateError = common.expectsError(settings);
|
|
// Retain async context.
|
|
const storedError = new Error('Thrown from:');
|
|
try {
|
|
await fn();
|
|
} catch (err) {
|
|
try {
|
|
validateError(err);
|
|
} catch (validationError) {
|
|
console.error(validationError);
|
|
console.error('Original error:');
|
|
console.error(err);
|
|
throw storedError;
|
|
}
|
|
return;
|
|
}
|
|
assert.fail('Missing expected exception');
|
|
}
|
|
|
|
async function createEmptyLinkedModule() {
|
|
const m = new SourceTextModule('');
|
|
await m.link(common.mustNotCall());
|
|
return m;
|
|
}
|
|
|
|
async function checkArgType() {
|
|
common.expectsError(() => {
|
|
new SourceTextModule();
|
|
}, {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
type: TypeError
|
|
});
|
|
|
|
for (const invalidOptions of [
|
|
0, 1, null, true, 'str', () => {}, { url: 0 }, Symbol.iterator
|
|
]) {
|
|
common.expectsError(() => {
|
|
new SourceTextModule('', invalidOptions);
|
|
}, {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
type: TypeError
|
|
});
|
|
}
|
|
|
|
for (const invalidLinker of [
|
|
0, 1, undefined, null, true, 'str', {}, Symbol.iterator
|
|
]) {
|
|
await expectsRejection(async () => {
|
|
const m = new SourceTextModule('');
|
|
await m.link(invalidLinker);
|
|
}, {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
type: TypeError
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check methods/properties can only be used under a specific state.
|
|
async function checkModuleState() {
|
|
await expectsRejection(async () => {
|
|
const m = new SourceTextModule('');
|
|
await m.link(common.mustNotCall());
|
|
assert.strictEqual(m.linkingStatus, 'linked');
|
|
await m.link(common.mustNotCall());
|
|
}, {
|
|
code: 'ERR_VM_MODULE_ALREADY_LINKED'
|
|
});
|
|
|
|
await expectsRejection(async () => {
|
|
const m = new SourceTextModule('');
|
|
m.link(common.mustNotCall());
|
|
assert.strictEqual(m.linkingStatus, 'linking');
|
|
await m.link(common.mustNotCall());
|
|
}, {
|
|
code: 'ERR_VM_MODULE_ALREADY_LINKED'
|
|
});
|
|
|
|
common.expectsError(() => {
|
|
const m = new SourceTextModule('');
|
|
m.instantiate();
|
|
}, {
|
|
code: 'ERR_VM_MODULE_NOT_LINKED'
|
|
});
|
|
|
|
await expectsRejection(async () => {
|
|
const m = new SourceTextModule('import "foo";');
|
|
try {
|
|
await m.link(common.mustCall(() => ({})));
|
|
} catch (err) {
|
|
assert.strictEqual(m.linkingStatus, 'errored');
|
|
m.instantiate();
|
|
}
|
|
assert.fail('Unreachable');
|
|
}, {
|
|
code: 'ERR_VM_MODULE_NOT_LINKED'
|
|
});
|
|
|
|
{
|
|
const m = new SourceTextModule('import "foo";');
|
|
await m.link(common.mustCall(async (specifier, module) => {
|
|
assert.strictEqual(module, m);
|
|
assert.strictEqual(specifier, 'foo');
|
|
assert.strictEqual(m.linkingStatus, 'linking');
|
|
common.expectsError(() => {
|
|
m.instantiate();
|
|
}, {
|
|
code: 'ERR_VM_MODULE_NOT_LINKED'
|
|
});
|
|
return new SourceTextModule('');
|
|
}));
|
|
m.instantiate();
|
|
await m.evaluate();
|
|
}
|
|
|
|
await expectsRejection(async () => {
|
|
const m = new SourceTextModule('');
|
|
await m.evaluate();
|
|
}, {
|
|
code: 'ERR_VM_MODULE_STATUS',
|
|
message: 'Module status must be one of instantiated, evaluated, and errored'
|
|
});
|
|
|
|
await expectsRejection(async () => {
|
|
const m = await createEmptyLinkedModule();
|
|
await m.evaluate();
|
|
}, {
|
|
code: 'ERR_VM_MODULE_STATUS',
|
|
message: 'Module status must be one of instantiated, evaluated, and errored'
|
|
});
|
|
|
|
common.expectsError(() => {
|
|
const m = new SourceTextModule('');
|
|
m.error;
|
|
}, {
|
|
code: 'ERR_VM_MODULE_STATUS',
|
|
message: 'Module status must be errored'
|
|
});
|
|
|
|
await expectsRejection(async () => {
|
|
const m = await createEmptyLinkedModule();
|
|
m.instantiate();
|
|
await m.evaluate();
|
|
m.error;
|
|
}, {
|
|
code: 'ERR_VM_MODULE_STATUS',
|
|
message: 'Module status must be errored'
|
|
});
|
|
|
|
common.expectsError(() => {
|
|
const m = new SourceTextModule('');
|
|
m.namespace;
|
|
}, {
|
|
code: 'ERR_VM_MODULE_STATUS',
|
|
message: 'Module status must not be uninstantiated or instantiating'
|
|
});
|
|
|
|
await expectsRejection(async () => {
|
|
const m = await createEmptyLinkedModule();
|
|
m.namespace;
|
|
}, {
|
|
code: 'ERR_VM_MODULE_STATUS',
|
|
message: 'Module status must not be uninstantiated or instantiating'
|
|
});
|
|
}
|
|
|
|
// Check link() fails when the returned module is not valid.
|
|
async function checkLinking() {
|
|
await expectsRejection(async () => {
|
|
const m = new SourceTextModule('import "foo";');
|
|
try {
|
|
await m.link(common.mustCall(() => ({})));
|
|
} catch (err) {
|
|
assert.strictEqual(m.linkingStatus, 'errored');
|
|
throw err;
|
|
}
|
|
assert.fail('Unreachable');
|
|
}, {
|
|
code: 'ERR_VM_MODULE_NOT_MODULE'
|
|
});
|
|
|
|
await expectsRejection(async () => {
|
|
const c = createContext({ a: 1 });
|
|
const foo = new SourceTextModule('', { context: c });
|
|
await foo.link(common.mustNotCall());
|
|
const bar = new SourceTextModule('import "foo";');
|
|
try {
|
|
await bar.link(common.mustCall(() => foo));
|
|
} catch (err) {
|
|
assert.strictEqual(bar.linkingStatus, 'errored');
|
|
throw err;
|
|
}
|
|
assert.fail('Unreachable');
|
|
}, {
|
|
code: 'ERR_VM_MODULE_DIFFERENT_CONTEXT'
|
|
});
|
|
|
|
await expectsRejection(async () => {
|
|
const erroredModule = new SourceTextModule('import "foo";');
|
|
try {
|
|
await erroredModule.link(common.mustCall(() => ({})));
|
|
} catch (err) {
|
|
// ignored
|
|
} finally {
|
|
assert.strictEqual(erroredModule.linkingStatus, 'errored');
|
|
}
|
|
|
|
const rootModule = new SourceTextModule('import "errored";');
|
|
await rootModule.link(common.mustCall(() => erroredModule));
|
|
}, {
|
|
code: 'ERR_VM_MODULE_LINKING_ERRORED'
|
|
});
|
|
}
|
|
|
|
// Check the JavaScript engine deals with exceptions correctly
|
|
async function checkExecution() {
|
|
await (async () => {
|
|
const m = new SourceTextModule('import { nonexistent } from "module";');
|
|
await m.link(common.mustCall(() => new SourceTextModule('')));
|
|
|
|
// There is no code for this exception since it is thrown by the JavaScript
|
|
// engine.
|
|
assert.throws(() => {
|
|
m.instantiate();
|
|
}, SyntaxError);
|
|
})();
|
|
|
|
await (async () => {
|
|
const m = new SourceTextModule('throw new Error();');
|
|
await m.link(common.mustNotCall());
|
|
m.instantiate();
|
|
const evaluatePromise = m.evaluate();
|
|
await evaluatePromise.catch(() => {});
|
|
assert.strictEqual(m.status, 'errored');
|
|
try {
|
|
await evaluatePromise;
|
|
} catch (err) {
|
|
assert.strictEqual(m.error, err);
|
|
return;
|
|
}
|
|
assert.fail('Missing expected exception');
|
|
})();
|
|
}
|
|
|
|
const finished = common.mustCall();
|
|
|
|
(async function main() {
|
|
await checkArgType();
|
|
await checkModuleState();
|
|
await checkLinking();
|
|
await checkExecution();
|
|
finished();
|
|
})();
|