'use strict'; // Flags: --experimental-vm-modules const common = require('../common'); const assert = require('assert'); const { SourceTextModule, createContext, Module } = require('vm'); async function createEmptyLinkedModule() { const m = new SourceTextModule(''); await m.link(common.mustNotCall()); return m; } async function checkArgType() { assert.throws(() => { new SourceTextModule(); }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); for (const invalidOptions of [ 0, 1, null, true, 'str', () => {}, { identifier: 0 }, Symbol.iterator, { context: null }, { context: 'hucairz' }, { context: {} }, ]) { assert.throws(() => { new SourceTextModule('', invalidOptions); }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); } for (const invalidLinker of [ 0, 1, undefined, null, true, 'str', {}, Symbol.iterator, ]) { await assert.rejects(async () => { const m = new SourceTextModule(''); await m.link(invalidLinker); }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); } } // Check methods/properties can only be used under a specific state. async function checkModuleState() { await assert.rejects(async () => { const m = new SourceTextModule(''); await m.link(common.mustNotCall()); assert.strictEqual(m.status, 'linked'); await m.link(common.mustNotCall()); }, { code: 'ERR_VM_MODULE_ALREADY_LINKED' }); await assert.rejects(async () => { const m = new SourceTextModule(''); m.link(common.mustNotCall()); assert.strictEqual(m.status, 'linking'); await m.link(common.mustNotCall()); }, { code: 'ERR_VM_MODULE_STATUS' }); await assert.rejects(async () => { const m = new SourceTextModule(''); await m.evaluate(); }, { code: 'ERR_VM_MODULE_STATUS', message: 'Module status must be one of linked, evaluated, or errored' }); await assert.rejects(async () => { const m = new SourceTextModule(''); await m.evaluate(false); }, { code: 'ERR_INVALID_ARG_TYPE', message: 'The "options" argument must be of type object. ' + 'Received type boolean (false)' }); assert.throws(() => { const m = new SourceTextModule(''); m.error; // eslint-disable-line no-unused-expressions }, { code: 'ERR_VM_MODULE_STATUS', message: 'Module status must be errored' }); await assert.rejects(async () => { const m = await createEmptyLinkedModule(); await m.evaluate(); m.error; // eslint-disable-line no-unused-expressions }, { code: 'ERR_VM_MODULE_STATUS', message: 'Module status must be errored' }); assert.throws(() => { const m = new SourceTextModule(''); m.namespace; // eslint-disable-line no-unused-expressions }, { code: 'ERR_VM_MODULE_STATUS', message: 'Module status must not be unlinked or linking' }); } // Check link() fails when the returned module is not valid. async function checkLinking() { await assert.rejects(async () => { const m = new SourceTextModule('import "foo";'); try { await m.link(common.mustCall(() => ({}))); } catch (err) { assert.strictEqual(m.status, 'errored'); throw err; } }, { code: 'ERR_VM_MODULE_NOT_MODULE' }); await assert.rejects(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.status, 'errored'); throw err; } }, { code: 'ERR_VM_MODULE_DIFFERENT_CONTEXT' }); const error = new Error(); await assert.rejects(async () => { globalThis.error = error; const erroredModule = new SourceTextModule('throw error;'); await erroredModule.link(common.mustNotCall()); try { await erroredModule.evaluate(); } catch { // ignored } delete globalThis.error; assert.strictEqual(erroredModule.status, 'errored'); const rootModule = new SourceTextModule('import "errored";'); await rootModule.link(common.mustCall(() => erroredModule)); }, { code: 'ERR_VM_MODULE_LINK_FAILURE', cause: error, }); } assert.throws(() => { new SourceTextModule('', { importModuleDynamically: 'hucairz' }); }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', message: 'The "options.importModuleDynamically" property must be of type ' + "function. Received type string ('hucairz')" }); // Check the JavaScript engine deals with exceptions correctly async function checkExecution() { await (async () => { const m = new SourceTextModule('import { nonexistent } from "module";'); // There is no code for this exception since it is thrown by the JavaScript // engine. await assert.rejects(() => { return m.link(common.mustCall(() => new SourceTextModule(''))); }, SyntaxError); })(); await (async () => { const m = new SourceTextModule('throw new Error();'); await m.link(common.mustNotCall()); try { await m.evaluate(); } catch (err) { assert.strictEqual(m.error, err); assert.strictEqual(m.status, 'errored'); return; } assert.fail('Missing expected exception'); })(); } // Check for error thrown when breakOnSigint is not a boolean for evaluate() async function checkInvalidOptionForEvaluate() { await assert.rejects(async () => { const m = new SourceTextModule('export const a = 1; export let b = 2'); await m.evaluate({ breakOnSigint: 'a-string' }); }, { name: 'TypeError', message: 'The "options.breakOnSigint" property must be of type boolean. ' + "Received type string ('a-string')", code: 'ERR_INVALID_ARG_TYPE' }); { ['link', 'evaluate'].forEach(async (method) => { await assert.rejects(async () => { await Module.prototype[method](); }, { code: 'ERR_VM_MODULE_NOT_MODULE', message: /Provided module is not an instance of Module/ }); }); } } function checkInvalidCachedData() { [true, false, 'foo', {}, Array, function() {}].forEach((invalidArg) => { const message = 'The "options.cachedData" property must be an ' + 'instance of Buffer, TypedArray, or DataView.' + common.invalidArgTypeHelper(invalidArg); assert.throws( () => new SourceTextModule('import "foo";', { cachedData: invalidArg }), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', message, } ); }); } function checkGettersErrors() { const expectedError = { code: 'ERR_VM_MODULE_NOT_MODULE', message: /Provided module is not an instance of Module/ }; const getters = ['identifier', 'context', 'namespace', 'status', 'error']; getters.forEach((getter) => { assert.throws(() => { // eslint-disable-next-line no-unused-expressions Module.prototype[getter]; }, expectedError); assert.throws(() => { // eslint-disable-next-line no-unused-expressions SourceTextModule.prototype[getter]; }, expectedError); }); // `dependencySpecifiers` getter is just part of SourceTextModule assert.throws(() => { // eslint-disable-next-line no-unused-expressions SourceTextModule.prototype.dependencySpecifiers; }, expectedError); } const finished = common.mustCall(); (async function main() { await checkArgType(); await checkModuleState(); await checkLinking(); await checkExecution(); await checkInvalidOptionForEvaluate(); checkInvalidCachedData(); checkGettersErrors(); finished(); })().then(common.mustCall());