From bc94a2bd29a651d76ca39ebdc68b3f75c6c5a1da Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Thu, 25 Jul 2024 17:07:22 -0700 Subject: [PATCH 1/4] feat(ses): Compartment single argument options --- packages/ses/src/compartment.js | 62 ++++++++++++++++++++++++++++---- packages/ses/src/error/assert.js | 14 ++++++-- packages/ses/types.d.ts | 18 ++++++++-- 3 files changed, 83 insertions(+), 11 deletions(-) diff --git a/packages/ses/src/compartment.js b/packages/ses/src/compartment.js index 270c8203cb..7e7c9d9a01 100644 --- a/packages/ses/src/compartment.js +++ b/packages/ses/src/compartment.js @@ -19,6 +19,7 @@ import { setGlobalObjectMutableProperties, setGlobalObjectEvaluators, } from './global-object.js'; +import { assertEqual } from './error/assert.js'; import { sharedGlobalPropertyNames } from './permits.js'; import { load, loadNow } from './module-load.js'; import { link } from './module-link.js'; @@ -26,6 +27,8 @@ import { getDeferredExports } from './module-proxy.js'; import { compartmentEvaluate } from './compartment-evaluate.js'; import { makeSafeEvaluator } from './make-safe-evaluator.js'; +/** @import {ModuleDescriptor} from '../types.js' */ + // moduleAliases associates every public module exports namespace with its // corresponding compartment and specifier so they can be used to link modules // across compartments. @@ -172,6 +175,55 @@ defineProperties(InertCompartment, { * @returns {Compartment['constructor']} */ +// In order to facilitate migration from the deprecated signature +// of the compartment constructor, +// new Compartent(globals?, modules?, options?) +// to the new signature: +// new Compartment(options?) +// where globals and modules are expressed in the options bag instead of +// positional arguments, this function detects the temporary sigil __options__ +// on the first argument and coerces compartments arguments into a single +// compartments object. +const compartmentOptions = (...args) => { + if (args.length === 0) { + return {}; + } + if ( + args.length === 1 && + typeof args[0] === 'object' && + args[0] !== null && + '__options__' in args[0] + ) { + const { __options__, ...options } = args[0]; + assert( + __options__ === true, + `Compartment constructor only supports true __options__ sigil, got ${__options__}`, + ); + return options; + } else { + const [ + globals = /** @type {Map} */ ({}), + modules = /** @type {Map} */ ({}), + options = {}, + ] = args; + assertEqual( + options.modules, + undefined, + `Compartment constructor must receive either a module map argument or modules option, not both`, + ); + assertEqual( + options.globals, + undefined, + `Compartment constructor must receive either globals argument or option, not both`, + ); + return { + ...options, + globals, + modules, + }; + } +}; + /** @type {MakeCompartmentConstructor} */ export const makeCompartmentConstructor = ( targetMakeCompartmentConstructor, @@ -179,11 +231,7 @@ export const makeCompartmentConstructor = ( markVirtualizedNativeFunction, parentCompartment = undefined, ) => { - function Compartment( - endowmentsOption = {}, - moduleMapOption = {}, - options = {}, - ) { + function Compartment(...args) { if (new.target === undefined) { throw TypeError( "Class constructor Compartment cannot be invoked without 'new'", @@ -195,13 +243,15 @@ export const makeCompartmentConstructor = ( name = '', transforms = [], __shimTransforms__ = [], + globals: endowmentsOption = {}, + modules: moduleMapOption = {}, resolveHook, importHook, importNowHook, moduleMapHook, importMetaHook, __noNamespaceBox__: noNamespaceBox = false, - } = options; + } = compartmentOptions(...args); const globalTransforms = [...transforms, ...__shimTransforms__]; const endowments = { __proto__: null, ...endowmentsOption }; const moduleMap = { __proto__: null, ...moduleMapOption }; diff --git a/packages/ses/src/error/assert.js b/packages/ses/src/error/assert.js index 9106b7ee86..29f6f8d760 100644 --- a/packages/ses/src/error/assert.js +++ b/packages/ses/src/error/assert.js @@ -568,5 +568,15 @@ const assert = makeAssert(); export { assert }; // Internal, to obviate polymorphic dispatch, but may become rigorously -// consistent with @endo/error. -export { makeError, note as annotateError, redactedDetails as X, quote as q }; +// consistent with @endo/error: + +/** @type {AssertionFunctions['equal']} */ +const assertEqual = assert.equal; + +export { + assertEqual, + makeError, + note as annotateError, + redactedDetails as X, + quote as q, +}; diff --git a/packages/ses/types.d.ts b/packages/ses/types.d.ts index 4d40fc8017..ae342bcca4 100644 --- a/packages/ses/types.d.ts +++ b/packages/ses/types.d.ts @@ -102,7 +102,8 @@ export type ModuleDescriptor = | RecordModuleDescriptor | ModuleExportsNamespace | VirtualModuleSource - | PrecompiledModuleSource; + | PrecompiledModuleSource + | string; // Deprecated type aliases: export type PrecompiledStaticModuleInterface = PrecompiledModuleSource; @@ -122,6 +123,10 @@ export type ModuleMapHook = ( ) => ModuleDescriptor | undefined; export type ImportHook = (moduleSpecifier: string) => Promise; export type ImportNowHook = (moduleSpecifier: string) => ModuleDescriptor; +export type ImportMetaHook = ( + moduleSpecifier: string, + importMeta: Object, +) => void; export interface CompartmentOptions { name?: string; @@ -129,8 +134,12 @@ export interface CompartmentOptions { moduleMapHook?: ModuleMapHook; importHook?: ImportHook; importNowHook?: ImportNowHook; + importMetaHook?: ImportMetaHook; resolveHook?: ResolveHook; + globals?: Map; + modules?: Map; __shimTransforms__?: Array; + __noNamespaceBox__?: boolean; } export interface EvaluateOptions { @@ -532,9 +541,12 @@ declare global { * code in a context bound to a new global creates a new compartment. */ export class Compartment { + constructor(options?: CompartmentOptions & { __options__: true }); + + // Deprecated: constructor( - globals?: Object, - moduleMap?: ModuleMap, + globals?: Record | undefined, + modules?: Record, options?: CompartmentOptions, ); From 114896e09ec7bbcfaa250eb7c2499f0e67a4e514 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Fri, 26 Jul 2024 18:06:17 -0700 Subject: [PATCH 2/4] test(ses): Use __options__ in all non-legacy tests --- .../ses/test/compartment-constructor.test.js | 17 +- .../ses/test/compartment-transforms.test.js | 48 +- packages/ses/test/compartment.test.js | 12 +- packages/ses/test/confinement.test.js | 2 +- .../tame-console-unfilteredError.test.js | 10 +- ...ame-console-unsafe-unfilteredError.test.js | 10 +- ...unsafe-unsafeError-unfilteredError.test.js | 10 +- .../tame-console-unsafe-unsafeError.test.js | 10 +- .../test/error/tame-console-unsafe.test.js | 10 +- .../error/tame-console-unsafeError.test.js | 10 +- packages/ses/test/error/tame-console.test.js | 10 +- .../test/error/tame-v8-error-unsafe.test.js | 30 +- .../ses/test/global-lexicals-evaluate.test.js | 32 +- .../ses/test/global-object-mutability.test.js | 2 +- packages/ses/test/import-cjs.test.js | 175 ++--- packages/ses/test/import-gauntlet.test.js | 243 +++---- packages/ses/test/import-hook.test.js | 622 ++++++++---------- packages/ses/test/import-non-esm.test.js | 88 +-- packages/ses/test/import-now-hook.test.js | 533 +++++++-------- packages/ses/test/import-stack-traces.test.js | 13 +- packages/ses/test/import.test.js | 342 +++++----- .../ses/test/lockdown-compartment.test.js | 6 +- packages/ses/test/module-map-hook.test.js | 583 +++++++--------- packages/ses/test/module-map.test.js | 278 +++----- packages/ses/test/ses.test.js | 20 +- 25 files changed, 1387 insertions(+), 1729 deletions(-) diff --git a/packages/ses/test/compartment-constructor.test.js b/packages/ses/test/compartment-constructor.test.js index bea5f60210..e2e670a6aa 100644 --- a/packages/ses/test/compartment-constructor.test.js +++ b/packages/ses/test/compartment-constructor.test.js @@ -35,21 +35,18 @@ test('Compartment class', t => { }); test('Compartment name', t => { - const c = new Compartment({}, {}, { name: 'x' }); + const c = new Compartment({ name: 'x', __options__: true }); t.is(c.name, 'x'); }); test('Compartment name object toString', t => { - const c = new Compartment( - {}, - {}, - { - name: { - toString() { - return 'x'; - }, + const c = new Compartment({ + name: { + toString() { + return 'x'; }, }, - ); + __options__: true, + }); t.is(c.name, 'x'); }); diff --git a/packages/ses/test/compartment-transforms.test.js b/packages/ses/test/compartment-transforms.test.js index 2e18ff7081..bdc0b01e2e 100644 --- a/packages/ses/test/compartment-transforms.test.js +++ b/packages/ses/test/compartment-transforms.test.js @@ -9,7 +9,7 @@ test('transforms apply to evaluated expressions', t => { const transform = source => source.replace(/Farewell/g, 'Hello'); const transforms = [transform]; - const c = new Compartment({}, {}, { transforms }); + const c = new Compartment({ transforms, __options__: true }); const greeting = c.evaluate('"Farewell, World!"'); t.is(greeting, 'Hello, World!'); @@ -20,13 +20,13 @@ test('transforms apply to dynamic eval in compartments', t => { const transform = source => source.replace(/Farewell/g, 'Hello'); const transforms = [transform]; - const c = new Compartment( - { + const c = new Compartment({ + transforms, + globals: { greeting: '"Farewell, World!"', }, - {}, - { transforms }, - ); + __options__: true, + }); const greeting = c.evaluate('(0, eval)(greeting)'); t.is(greeting, 'Hello, World!'); @@ -37,7 +37,7 @@ test('transforms do not apply to dynamic eval in compartments within compartment const transform = source => source.replace(/Farewell/g, 'Hello'); const transforms = [transform]; - const c = new Compartment({}, {}, { transforms }); + const c = new Compartment({ transforms, __options__: true }); const d = c.evaluate('new Compartment()'); const greeting = d.evaluate('"Farewell, World!"'); @@ -52,11 +52,13 @@ test('transforms do not apply to imported modules', async t => { const resolveHook = () => ''; const importHook = () => new ModuleSource('export default "Farewell, World!";'); - const c = new Compartment( - {}, - {}, - { transforms, resolveHook, importHook, __noNamespaceBox__: true }, - ); + const c = new Compartment({ + transforms, + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await c.import('any-string-here'); const { default: greeting } = namespace; @@ -69,7 +71,10 @@ test('__shimTransforms__ apply to evaluated expressions', t => { const transform = source => source.replace(/Farewell/g, 'Hello'); const transforms = [transform]; - const c = new Compartment({}, {}, { __shimTransforms__: transforms }); + const c = new Compartment({ + __shimTransforms__: transforms, + __options__: true, + }); const greeting = c.evaluate('"Farewell, World!"'); t.is(greeting, 'Hello, World!'); @@ -83,16 +88,13 @@ test('__shimTransforms__ do apply to imported modules', async t => { const resolveHook = () => ''; const importHook = () => new ModuleSource('export default "Farewell, World!";'); - const c = new Compartment( - {}, - {}, - { - __shimTransforms__: transforms, - __noNamespaceBox__: true, - resolveHook, - importHook, - }, - ); + const c = new Compartment({ + __shimTransforms__: transforms, + __noNamespaceBox__: true, + __options__: true, + resolveHook, + importHook, + }); const namespace = await c.import('any-string-here'); const { default: greeting } = namespace; diff --git a/packages/ses/test/compartment.test.js b/packages/ses/test/compartment.test.js index d5517f3019..5e20dc0ed2 100644 --- a/packages/ses/test/compartment.test.js +++ b/packages/ses/test/compartment.test.js @@ -44,7 +44,7 @@ test('SES compartment also has compartments', t => { // // }); test('SES compartment has harden', t => { - const c = new Compartment({ a: 123 }); + const c = new Compartment({ globals: { a: 123 }, __options__: true }); const obj = c.evaluate('harden({a})'); t.is(obj.a, 123, 'expected object'); if (!harden.isFake) { @@ -96,11 +96,17 @@ test('main use case', t => { } return power(arg); } - const attenuatedPower = new Compartment({ power }).evaluate(`(${attenuate})`); + const attenuatedPower = new Compartment({ + globals: { power }, + __options__: true, + }).evaluate(`(${attenuate})`); function use(arg) { return power(arg); } - const c = new Compartment({ power: attenuatedPower }); + const c = new Compartment({ + globals: { power: attenuatedPower }, + __options__: true, + }); const user = c.evaluate(`(${use})`); t.is(user(1), 2); t.throws(() => user(-1), { instanceOf: TypeError }); diff --git a/packages/ses/test/confinement.test.js b/packages/ses/test/confinement.test.js index 6dfa913398..7573bd5a3a 100644 --- a/packages/ses/test/confinement.test.js +++ b/packages/ses/test/confinement.test.js @@ -71,7 +71,7 @@ test('confinement evaluation Symbol.unscopables with-statement escape', t => { // by leaving its shadow on the actual global scope. globalThis.flag = 'unsafe'; - const c = new Compartment({ flag: 'safe' }); + const c = new Compartment({ globals: { flag: 'safe' }, __options__: true }); // Known loss of fidility of emulating a proper host: t.throws(() => c.evaluate('Symbol.unscopables = { flag: true };')); diff --git a/packages/ses/test/error/tame-console-unfilteredError.test.js b/packages/ses/test/error/tame-console-unfilteredError.test.js index 0136d72522..c65e853590 100644 --- a/packages/ses/test/error/tame-console-unfilteredError.test.js +++ b/packages/ses/test/error/tame-console-unfilteredError.test.js @@ -21,12 +21,18 @@ test('console', t => { harden(getPrototypeOf(console)); harden(console); - const c1 = new Compartment({ console }); + const c1 = new Compartment({ + globals: { console }, + __options__: true, + }); t.is(console, c1.evaluate('(console)')); const fakeConsole = { log: console.log }; harden(fakeConsole); - const c2 = new Compartment({ console: fakeConsole }); + const c2 = new Compartment({ + globals: { console: fakeConsole }, + __options__: true, + }); t.is(console.log, c2.evaluate('(console.log)')); }); diff --git a/packages/ses/test/error/tame-console-unsafe-unfilteredError.test.js b/packages/ses/test/error/tame-console-unsafe-unfilteredError.test.js index af8b3a001d..6bfadbf6de 100644 --- a/packages/ses/test/error/tame-console-unsafe-unfilteredError.test.js +++ b/packages/ses/test/error/tame-console-unsafe-unfilteredError.test.js @@ -25,12 +25,18 @@ test('console', t => { harden(getPrototypeOf(console)); harden(console); - const c1 = new Compartment({ console }); + const c1 = new Compartment({ + globals: { console }, + __options__: true, + }); t.is(console, c1.evaluate('(console)')); const fakeConsole = { log: console.log }; harden(fakeConsole); - const c2 = new Compartment({ console: fakeConsole }); + const c2 = new Compartment({ + globals: { console: fakeConsole }, + __options__: true, + }); t.is(console.log, c2.evaluate('(console.log)')); }); diff --git a/packages/ses/test/error/tame-console-unsafe-unsafeError-unfilteredError.test.js b/packages/ses/test/error/tame-console-unsafe-unsafeError-unfilteredError.test.js index b02588b3b3..337c040e78 100644 --- a/packages/ses/test/error/tame-console-unsafe-unsafeError-unfilteredError.test.js +++ b/packages/ses/test/error/tame-console-unsafe-unsafeError-unfilteredError.test.js @@ -27,12 +27,18 @@ test('console', t => { harden(getPrototypeOf(console)); harden(console); - const c1 = new Compartment({ console }); + const c1 = new Compartment({ + globals: { console }, + __options__: true, + }); t.is(console, c1.evaluate('(console)')); const fakeConsole = { log: console.log }; harden(fakeConsole); - const c2 = new Compartment({ console: fakeConsole }); + const c2 = new Compartment({ + globals: { console: fakeConsole }, + __options__: true, + }); t.is(console.log, c2.evaluate('(console.log)')); }); diff --git a/packages/ses/test/error/tame-console-unsafe-unsafeError.test.js b/packages/ses/test/error/tame-console-unsafe-unsafeError.test.js index f32cfdf283..de7a0e75ec 100644 --- a/packages/ses/test/error/tame-console-unsafe-unsafeError.test.js +++ b/packages/ses/test/error/tame-console-unsafe-unsafeError.test.js @@ -26,12 +26,18 @@ test('console', t => { harden(getPrototypeOf(console)); harden(console); - const c1 = new Compartment({ console }); + const c1 = new Compartment({ + globals: { console }, + __options__: true, + }); t.is(console, c1.evaluate('(console)')); const fakeConsole = { log: console.log }; harden(fakeConsole); - const c2 = new Compartment({ console: fakeConsole }); + const c2 = new Compartment({ + globals: { console: fakeConsole }, + __options__: true, + }); t.is(console.log, c2.evaluate('(console.log)')); }); diff --git a/packages/ses/test/error/tame-console-unsafe.test.js b/packages/ses/test/error/tame-console-unsafe.test.js index 0565669f18..4bc1f2bc18 100644 --- a/packages/ses/test/error/tame-console-unsafe.test.js +++ b/packages/ses/test/error/tame-console-unsafe.test.js @@ -21,12 +21,18 @@ test('console', t => { harden(getPrototypeOf(console)); harden(console); - const c1 = new Compartment({ console }); + const c1 = new Compartment({ + globals: { console }, + __options__: true, + }); t.is(console, c1.evaluate('(console)')); const fakeConsole = { log: console.log }; harden(fakeConsole); - const c2 = new Compartment({ console: fakeConsole }); + const c2 = new Compartment({ + globals: { console: fakeConsole }, + __options__: true, + }); t.is(console.log, c2.evaluate('(console.log)')); }); diff --git a/packages/ses/test/error/tame-console-unsafeError.test.js b/packages/ses/test/error/tame-console-unsafeError.test.js index e0314362eb..c6ba5b7d97 100644 --- a/packages/ses/test/error/tame-console-unsafeError.test.js +++ b/packages/ses/test/error/tame-console-unsafeError.test.js @@ -22,12 +22,18 @@ test('console', t => { harden(getPrototypeOf(console)); harden(console); - const c1 = new Compartment({ console }); + const c1 = new Compartment({ + globals: { console }, + __options__: true, + }); t.is(console, c1.evaluate('(console)')); const fakeConsole = { log: console.log }; harden(fakeConsole); - const c2 = new Compartment({ console: fakeConsole }); + const c2 = new Compartment({ + globals: { console: fakeConsole }, + __options__: true, + }); t.is(console.log, c2.evaluate('(console.log)')); }); diff --git a/packages/ses/test/error/tame-console.test.js b/packages/ses/test/error/tame-console.test.js index 7f4bc6fcd6..4b9d788895 100644 --- a/packages/ses/test/error/tame-console.test.js +++ b/packages/ses/test/error/tame-console.test.js @@ -21,12 +21,18 @@ test('console', t => { harden(getPrototypeOf(console)); harden(console); - const c1 = new Compartment({ console }); + const c1 = new Compartment({ + globals: { console }, + __options__: true, + }); t.is(console, c1.evaluate('(console)')); const fakeConsole = { log: console.log }; harden(fakeConsole); - const c2 = new Compartment({ console: fakeConsole }); + const c2 = new Compartment({ + globals: { console: fakeConsole }, + __options__: true, + }); t.is(console.log, c2.evaluate('(console.log)')); }); diff --git a/packages/ses/test/error/tame-v8-error-unsafe.test.js b/packages/ses/test/error/tame-v8-error-unsafe.test.js index d4ba403335..6cf7495c9d 100644 --- a/packages/ses/test/error/tame-v8-error-unsafe.test.js +++ b/packages/ses/test/error/tame-v8-error-unsafe.test.js @@ -52,7 +52,10 @@ function simulateDepd() { } test('SES compartment error compatibility - minimal case', t => { - const c1 = new Compartment({ t }); + const c1 = new Compartment({ + globals: { t }, + __options__: true, + }); const result = c1.evaluate(` const obj = {}; Error.stackTraceLimit = 10; @@ -64,7 +67,10 @@ test('SES compartment error compatibility - minimal case', t => { }); test('SES compartment error compatibility - basic: prepareStackTrace accepts assignment', t => { - const c1 = new Compartment({ t }); + const c1 = new Compartment({ + globals: { t }, + __options__: true, + }); const result = c1.evaluate(` const obj = {}; const newPST = (stack) => stack; @@ -77,7 +83,10 @@ test('SES compartment error compatibility - basic: prepareStackTrace accepts ass }); test('SES compartment error compatibility - functional prepareStackTrace', t => { - const c1 = new Compartment({ t }); + const c1 = new Compartment({ + globals: { t }, + __options__: true, + }); const result = c1.evaluate(` const prepareObjectStackTrace = (_, stack) => { t.fail('must not be called'); @@ -91,7 +100,10 @@ test('SES compartment error compatibility - functional prepareStackTrace', t => }); test('SES compartment error compatibility - endow w Error power', t => { - const c1 = new Compartment({ t, Error }); + const c1 = new Compartment({ + globals: { t, Error }, + __options__: true, + }); const result = c1.evaluate(` const obj = { toString: () => 'Pseudo Error', @@ -155,7 +167,10 @@ test('SES compartment error compatibility - endow w Error with locally configura return LocalError; } - const c1 = new Compartment({ t, Error: createLocalError(Error) }); + const c1 = new Compartment({ + globals: { t, Error: createLocalError(Error) }, + __options__: true, + }); const result1 = c1.evaluate(` ${simulateDepd.toString()}; simulateDepd(); @@ -163,7 +178,10 @@ test('SES compartment error compatibility - endow w Error with locally configura t.is(result1, 'getStack'); // assert LocalError is not leaking to Error prototype - const evilC = new Compartment({ t, Error: createLocalError(Error) }); + const evilC = new Compartment({ + globals: { t, Error: createLocalError(Error) }, + __options__: true, + }); evilC.evaluate(` Error.prepareStackTrace = () => { t.fail('prepareStackTrace from evil compartment should not have been called'); diff --git a/packages/ses/test/global-lexicals-evaluate.test.js b/packages/ses/test/global-lexicals-evaluate.test.js index e81e988c68..422fbe2d0b 100644 --- a/packages/ses/test/global-lexicals-evaluate.test.js +++ b/packages/ses/test/global-lexicals-evaluate.test.js @@ -4,9 +4,11 @@ import '../index.js'; test('endowments own properties are mentionable', t => { t.plan(1); - const endowments = { hello: 'World!' }; - const modules = {}; - const compartment = new Compartment(endowments, modules); + const globals = { hello: 'World!' }; + const compartment = new Compartment({ + globals, + __options__: true, + }); const whom = compartment.evaluate('hello'); t.is(whom, 'World!'); @@ -15,9 +17,11 @@ test('endowments own properties are mentionable', t => { test('endowments own properties are enumerable', t => { t.plan(1); - const endowments = { hello: 'World!' }; - const modules = {}; - const compartment = new Compartment(endowments, modules); + const globals = { hello: 'World!' }; + const compartment = new Compartment({ + globals, + __options__: true, + }); const keys = compartment.evaluate('Object.keys(globalThis)'); t.deepEqual(keys, ['hello']); @@ -26,9 +30,11 @@ test('endowments own properties are enumerable', t => { test('endowments prototypically inherited properties are not mentionable', t => { t.plan(1); - const endowments = { __proto__: { hello: 'World!' } }; - const modules = {}; - const compartment = new Compartment(endowments, modules); + const globals = { __proto__: { hello: 'World!' } }; + const compartment = new Compartment({ + globals, + __options__: true, + }); t.throws(() => compartment.evaluate('hello'), { message: /hello is not defined/, @@ -38,9 +44,11 @@ test('endowments prototypically inherited properties are not mentionable', t => test('endowments prototypically inherited properties are not enumerable', t => { t.plan(1); - const endowments = { __proto__: { hello: 'World!' } }; - const modules = {}; - const compartment = new Compartment(endowments, modules); + const globals = { __proto__: { hello: 'World!' } }; + const compartment = new Compartment({ + globals, + __options__: true, + }); const keys = compartment.evaluate('Object.keys(globalThis)'); t.deepEqual(keys, []); diff --git a/packages/ses/test/global-object-mutability.test.js b/packages/ses/test/global-object-mutability.test.js index 86ff9a177f..2443deac17 100644 --- a/packages/ses/test/global-object-mutability.test.js +++ b/packages/ses/test/global-object-mutability.test.js @@ -11,7 +11,7 @@ test('globalObject properties mutable', t => { t.is(c.evaluate('Date()'), 'bogus'); c.evaluate('Compartment = function(opts) { this.extra = "extra" }'); - t.is(c.evaluate('(new Compartment({})).extra'), 'extra'); + t.is(c.evaluate('(new Compartment()).extra'), 'extra'); c.evaluate('Function = function() { this.extra = "extra" }'); t.is(c.evaluate('new Function().extra'), 'extra'); diff --git a/packages/ses/test/import-cjs.test.js b/packages/ses/test/import-cjs.test.js index b80b47e92f..8821331c17 100644 --- a/packages/ses/test/import-cjs.test.js +++ b/packages/ses/test/import-cjs.test.js @@ -86,11 +86,12 @@ test('import a CommonJS module with exports assignment', async t => { ); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const module = compartment.module('.'); const { meaning } = await compartment.import('.'); @@ -111,11 +112,12 @@ test('import a CommonJS module with exports replacement', async t => { ); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const module = compartment.module('.'); const { default: meaning } = await compartment.import('.'); @@ -148,11 +150,12 @@ test('CommonJS module imports CommonJS module by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -184,11 +187,12 @@ test('CommonJS module imports CommonJS module as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./odd'); const { default: odd } = namespace; @@ -221,11 +225,12 @@ test('ESM imports CommonJS module as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -257,11 +262,12 @@ test('ESM imports CommonJS module as star', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -295,11 +301,12 @@ test('ESM imports CommonJS module with replaced exports as star', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -331,11 +338,12 @@ test('ESM imports CommonJS module by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -367,11 +375,12 @@ test('CommonJS module imports ESM as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -405,11 +414,12 @@ test('CommonJS module imports ESM by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -462,11 +472,13 @@ test('cross import ESM and CommonJS modules', async t => { throw Error(`Cannot load module for specifier ${specifier}`); }; - const compartment = new Compartment( - { t }, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + globals: { t }, + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); await compartment.import('./src/main.js'); }); @@ -507,7 +519,12 @@ test('live bindings through through an ESM between CommonJS modules', async t => throw Error(`Cannot load module for specifier ${specifier}`); }; - const compartment = new Compartment({ t }, {}, { resolveHook, importHook }); + const compartment = new Compartment({ + globals: { t }, + resolveHook, + importHook, + __options__: true, + }); await compartment.import('./src/main.js'); }); @@ -540,14 +557,12 @@ test('export name as default from CommonJS module', async t => { throw Error(`Cannot load module for specifier ${specifier}`); }; - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook, - }, - ); + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook, + __options__: true, + }); await compartment.import('./main.js'); }); @@ -581,15 +596,13 @@ test('synchronous loading via importNowHook', async t => { throw Error(`Cannot load module for specifier ${specifier}`); }; - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook: async () => {}, - importNowHook, - }, - ); + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook: async () => {}, + importNowHook, + __options__: true, + }); compartment.importNow('./main.js'); }); @@ -629,15 +642,13 @@ test('importNowHook only called if specifier was not imported before', async t = throw Error(`Cannot load module for specifier ${specifier}`); }; - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook, - importNowHook, - }, - ); + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook, + importNowHook, + __options__: true, + }); // compartment.import would be the more natural here, but all prerequisites // to synchronously finding the module before calling importNowHook should diff --git a/packages/ses/test/import-gauntlet.test.js b/packages/ses/test/import-gauntlet.test.js index 14bdc61dad..1ed87f1118 100644 --- a/packages/ses/test/import-gauntlet.test.js +++ b/packages/ses/test/import-gauntlet.test.js @@ -17,14 +17,11 @@ test('import for side effect', async t => { `, }); - const compartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - }, - ); + const compartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __options__: true, + }); await compartment.import('./main.js'); }); @@ -43,15 +40,12 @@ test('import all from module', async t => { `, }); - const compartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main.js'); @@ -73,15 +67,12 @@ test('import named exports from me', async t => { `, }); - const compartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main.js'); @@ -102,15 +93,12 @@ test('import color from module', async t => { `, }); - const compartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main.js'); @@ -129,15 +117,12 @@ test('import and reexport', async t => { `, }); - const compartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main.js'); @@ -157,15 +142,12 @@ test('import and export all', async t => { `, }); - const compartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main.js'); @@ -188,15 +170,12 @@ test('live binding', async t => { `, }); - const compartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main.js'); @@ -225,14 +204,12 @@ test('live binding through reexporting intermediary', async t => { `, }); - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - }, - ); + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __options__: true, + }); await compartment.import('./main.js'); }); @@ -251,14 +228,12 @@ test('export name as default', async t => { `, }); - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - }, - ); + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __options__: true, + }); await compartment.import('./main.js'); }); @@ -292,14 +267,12 @@ test('export-as with duplicated export name', async t => { `, }); - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - }, - ); + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __options__: true, + }); await compartment.import('./main.js'); }); @@ -321,14 +294,12 @@ test.failing('reexport with implicit default syntax', async t => { `, }); - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - }, - ); + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + __options__: true, + }); await compartment.import('./main.js'); }); @@ -351,24 +322,22 @@ test('importHook returning a RedirectStaticModuleInterface with a specified comp './meaning.js': './alias-target.js', }; - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook: async moduleSpecifier => { - const aliasTarget = aliasRegistry[moduleSpecifier]; - if (aliasTarget !== undefined) { - const record = { - specifier: aliasTarget, - compartment, - }; - return record; - } - return importHook(moduleSpecifier); - }, + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook: async moduleSpecifier => { + const aliasTarget = aliasRegistry[moduleSpecifier]; + if (aliasTarget !== undefined) { + const record = { + specifier: aliasTarget, + compartment, + }; + return record; + } + return importHook(moduleSpecifier); }, - ); + __options__: true, + }); await compartment.import('./main.js'); }); @@ -394,27 +363,25 @@ test('importHook returning a ModuleInstance with a precompiled functor', async t }); const importHook = makeImportHook('https://example.com'); - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook: async moduleSpecifier => { - await null; - if (moduleSpecifier === './precompiled.js') { - const baseRecord = await importHook(moduleSpecifier); - return { - ...baseRecord, - __syncModuleFunctor__: ({ onceVar, liveVar }) => { - onceVar.a(123); - liveVar.b(456); - }, - }; - } - return importHook(moduleSpecifier); - }, + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook: async moduleSpecifier => { + await null; + if (moduleSpecifier === './precompiled.js') { + const baseRecord = await importHook(moduleSpecifier); + return { + ...baseRecord, + __syncModuleFunctor__: ({ onceVar, liveVar }) => { + onceVar.a(123); + liveVar.b(456); + }, + }; + } + return importHook(moduleSpecifier); }, - ); + __options__: true, + }); await compartment.import('./main.js'); }); @@ -429,14 +396,12 @@ test('this in module scope must be undefined', async t => { }); const importHook = makeImportHook('https://example.com'); - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook, - }, - ); + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook, + __options__: true, + }); await compartment.import('./index.js'); }); diff --git a/packages/ses/test/import-hook.test.js b/packages/ses/test/import-hook.test.js index 12b5870008..d66cd1e308 100644 --- a/packages/ses/test/import-hook.test.js +++ b/packages/ses/test/import-hook.test.js @@ -7,84 +7,66 @@ import { ModuleSource } from '@endo/module-source'; import '../index.js'; test('import hook returns module source descriptor with precompiled module source', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - resolveHook: specifier => specifier, - importHook(specifier) { - if (specifier === './index.js') { - return { - source: new ModuleSource('export default 42'), - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment = new Compartment({ + resolveHook: specifier => specifier, + importHook(specifier) { + if (specifier === './index.js') { + return { + source: new ModuleSource('export default 42'), + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const index = await compartment.import('./index.js'); t.is(index.default, 42); }); test('import hook returns module source descriptor with virtual module source', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - resolveHook: specifier => specifier, - importHook(specifier) { - if (specifier === './index.js') { - return { - source: { - imports: [], - exports: ['default'], - execute(env) { - env.default = 42; - }, + const compartment = new Compartment({ + resolveHook: specifier => specifier, + importHook(specifier) { + if (specifier === './index.js') { + return { + source: { + imports: [], + exports: ['default'], + execute(env) { + env.default = 42; }, - }; - } - return undefined; - }, - __noNamespaceBox__: true, + }, + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const index = await compartment.import('./index.js'); t.is(index.default, 42); }); test('import hook returns parent compartment module source descriptor with string reference to parent compartment', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - resolveHook: specifier => specifier, - importHook(specifier) { - if (specifier === './meaning.js') { - return { - source: { - imports: [], - exports: ['default'], - execute(env) { - env.default = 42; - }, + const parent = new Compartment({ + resolveHook: specifier => specifier, + importHook(specifier) { + if (specifier === './meaning.js') { + return { + source: { + imports: [], + exports: ['default'], + execute(env) { + env.default = 42; }, - }; - } - return undefined; - }, + }, + }; + } + return undefined; }, - ); + __options__: true, + }); const compartment = new parent.globalThis.Compartment( // endowments: @@ -110,108 +92,90 @@ test('import hook returns parent compartment module source descriptor with strin }); test('import hook returns parent compartment module source reference with different specifier', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'parent', - resolveHook(relative, specifier) { - t.is(relative, './meaningful.js'); - t.is(specifier, './meaning.js'); - return relative; - }, - importHook(specifier) { - if (specifier === './meaningful.js') { - return { - source: { - imports: [], - exports: ['meaning'], - execute() { - throw new Error('should not execute'); - }, + const parent = new Compartment({ + name: 'parent', + resolveHook(relative, specifier) { + t.is(relative, './meaningful.js'); + t.is(specifier, './meaning.js'); + return relative; + }, + importHook(specifier) { + if (specifier === './meaningful.js') { + return { + source: { + imports: [], + exports: ['meaning'], + execute() { + throw new Error('should not execute'); }, - }; - } else if (specifier === './meaning.js') { - return { - source: { - imports: ['./meaningful.js'], - exports: ['default'], - execute(env, c, resolutions) { - // eslint-disable-next-line no-use-before-define - t.is(c, compartment); - const { meaning } = c.importNow(resolutions['./meaningful.js']); - env.default = meaning; - }, + }, + }; + } else if (specifier === './meaning.js') { + return { + source: { + imports: ['./meaningful.js'], + exports: ['default'], + execute(env, c, resolutions) { + // eslint-disable-next-line no-use-before-define + t.is(c, compartment); + const { meaning } = c.importNow(resolutions['./meaningful.js']); + env.default = meaning; }, - }; - } - return undefined; - }, + }, + }; + } + return undefined; }, - ); - - const compartment = new parent.globalThis.Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'child', - resolveHook(relative, specifier) { - t.is(relative, './meaningful.js'); - t.is(specifier, './lib/meaning.js'); - return './lib/meaningful.js'; - }, - importHook(specifier) { - if (specifier === './lib/meaningful.js') { - return { - source: { - imports: [], - exports: ['meaning'], - execute(env) { - env.meaning = 42; - }, + __options__: true, + }); + + const compartment = new parent.globalThis.Compartment({ + name: 'child', + resolveHook(relative, specifier) { + t.is(relative, './meaningful.js'); + t.is(specifier, './lib/meaning.js'); + return './lib/meaningful.js'; + }, + importHook(specifier) { + if (specifier === './lib/meaningful.js') { + return { + source: { + imports: [], + exports: ['meaning'], + execute(env) { + env.meaning = 42; }, - }; - } else if (specifier === './index.js') { - return { - source: './meaning.js', - specifier: './lib/meaning.js', - }; - } - return undefined; - }, - __noNamespaceBox__: true, + }, + }; + } else if (specifier === './index.js') { + return { + source: './meaning.js', + specifier: './lib/meaning.js', + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const index = await compartment.import('./index.js'); t.is(index.default, 42); }); test('import hook returns module source descriptor for parent compartment with string reference', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'parent', - importHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const parent = new Compartment({ + name: 'parent', + importHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); @@ -244,25 +208,19 @@ test('import hook returns module source descriptor for parent compartment with s }); test('import hook returns parent compartment module namespace descriptor', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'parent', - importHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const parent = new Compartment({ + name: 'parent', + importHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); @@ -295,49 +253,37 @@ test('import hook returns parent compartment module namespace descriptor', async }); test('import hook returns module source descriptor with string reference to parent compartment', async t => { - const compartment1 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'compartment1', - importHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment1 = new Compartment({ + name: 'compartment1', + importHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object1 } = await compartment1.import('./object.js'); t.is(object1.meaning, 42); - const compartment2 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'child', - importHook(specifier) { - if (specifier === './index.js') { - return { - source: './object.js', - compartment: compartment1, - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment2 = new Compartment({ + name: 'child', + importHook(specifier) { + if (specifier === './index.js') { + return { + source: './object.js', + compartment: compartment1, + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object2 } = await compartment2.import('./index.js'); t.is(object2.meaning, 42); @@ -346,49 +292,37 @@ test('import hook returns module source descriptor with string reference to pare }); test('import hook returns other compartment module namespace descriptor', async t => { - const compartment1 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'compartment1', - importHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment1 = new Compartment({ + name: 'compartment1', + importHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object1 } = await compartment1.import('./object.js'); t.is(object1.meaning, 42); - const compartment2 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'child', - importHook(specifier) { - if (specifier === './index.js') { - return { - namespace: './object.js', - compartment: compartment1, - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment2 = new Compartment({ + name: 'child', + importHook(specifier) { + if (specifier === './index.js') { + return { + namespace: './object.js', + compartment: compartment1, + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object2 } = await compartment2.import('./index.js'); t.is(object2.meaning, 42); @@ -397,82 +331,68 @@ test('import hook returns other compartment module namespace descriptor', async }); test('import hook returns module namespace descriptor and namespace object', async t => { - const compartment1 = new Compartment( - {}, - {}, - { - importHook(specifier) { - if (specifier === 'a') { - return { - source: new ModuleSource(`export default 42`), - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment1 = new Compartment({ + importHook(specifier) { + if (specifier === 'a') { + return { + source: new ModuleSource(`export default 42`), + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const namespace1 = await compartment1.import('a'); - const compartment2 = new Compartment( - {}, - {}, - { - importHook(specifier) { - if (specifier === 'z') { - return { namespace: namespace1 }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment2 = new Compartment({ + importHook(specifier) { + if (specifier === 'z') { + return { namespace: namespace1 }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const namespace2 = await compartment2.import('z'); t.is(namespace2.default, 42); t.is(namespace1, namespace2); }); test('import hook returns module namespace descriptor and non-namespace object', async t => { - const compartment = new Compartment( - {}, - {}, - { - importHook(specifier) { - if (specifier === '1') { - return { namespace: { meaning: 42 } }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment = new Compartment({ + importHook(specifier) { + if (specifier === '1') { + return { namespace: { meaning: 42 } }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('1'); t.is(namespace.meaning, 42); }); test('import hook returns module source descriptor for specifier in own compartment', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - importHook(specifier) { - if (specifier === './index.js') { - return { - source: './object.js', - compartment, - }; - } - return undefined; - }, - __noNamespaceBox__: true, + importHook(specifier) { + if (specifier === './index.js') { + return { + source: './object.js', + compartment, + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object1 } = await compartment.import('./object.js'); t.is(object1.meaning, 42); @@ -483,30 +403,25 @@ test('import hook returns module source descriptor for specifier in own compartm }); test('import hook returns module source descriptor for specifier in own compartment and overridden base specifier that collides', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - importHook(specifier) { - if (specifier === './index.js') { - return { - source: './object.js', - specifier: './object.js', - compartment, - }; - } - return undefined; - }, - __noNamespaceBox__: true, + importHook(specifier) { + if (specifier === './index.js') { + return { + source: './object.js', + specifier: './object.js', + compartment, + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object1 } = await compartment.import('./object.js'); t.is(object1.meaning, 42); @@ -517,29 +432,24 @@ test('import hook returns module source descriptor for specifier in own compartm }); test('import hook returns module namespace descriptor for specifier in own compartment', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - importHook(specifier) { - if (specifier === './index.js') { - return { - namespace: './object.js', - compartment, - }; - } - return undefined; - }, - __noNamespaceBox__: true, + importHook(specifier) { + if (specifier === './index.js') { + return { + namespace: './object.js', + compartment, + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object1 } = await compartment.import('./object.js'); t.is(object1.meaning, 42); @@ -550,29 +460,23 @@ test('import hook returns module namespace descriptor for specifier in own compa }); test('module map hook precedes import hook', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { - source: new ModuleSource(` + const compartment = new Compartment({ + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { + source: new ModuleSource(` export default 42; `), - }; - } - return undefined; - }, - importHook() { - throw new Error('not reached'); - }, - __noNamespaceBox__: true, + }; + } + return undefined; }, - ); + importHook() { + throw new Error('not reached'); + }, + __noNamespaceBox__: true, + __options__: true, + }); const { default: meaning } = await compartment.import('./index.js'); t.is(meaning, 42); diff --git a/packages/ses/test/import-non-esm.test.js b/packages/ses/test/import-non-esm.test.js index bb9e5211f4..644258e0a1 100644 --- a/packages/ses/test/import-non-esm.test.js +++ b/packages/ses/test/import-non-esm.test.js @@ -17,11 +17,12 @@ test('import a non-ESM', async t => { }; }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const module = compartment.module('.'); const { meaning } = await compartment.import('.'); @@ -56,11 +57,12 @@ test('non-ESM imports non-ESM by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -94,11 +96,12 @@ test('non-ESM imports non-ESM as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -131,11 +134,12 @@ test('ESM imports non-ESM as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -168,11 +172,12 @@ test('ESM imports non-ESM by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -205,11 +210,12 @@ test('non-ESM imports ESM as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -242,11 +248,12 @@ test('non-ESM imports ESM by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const { odd } = await compartment.import('./odd'); t.is(odd(1), true); @@ -304,10 +311,11 @@ test('cross import ESM and non-ESMs', async t => { throw Error(`Cannot load module for specifier ${specifier}`); }; - const compartment = new Compartment( - {}, - {}, - { resolveHook, importHook, __noNamespaceBox__: true }, - ); + const compartment = new Compartment({ + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); await compartment.import('./src/main.js'); }); diff --git a/packages/ses/test/import-now-hook.test.js b/packages/ses/test/import-now-hook.test.js index 4f5755a71b..b43cae9550 100644 --- a/packages/ses/test/import-now-hook.test.js +++ b/packages/ses/test/import-now-hook.test.js @@ -7,80 +7,62 @@ import { ModuleSource } from '@endo/module-source'; import '../index.js'; test('import now hook returns module source descriptor with precompiled module source', t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - resolveHook: specifier => specifier, - importNowHook(specifier) { - if (specifier === './index.js') { - return { source: new ModuleSource('export default 42') }; - } - return undefined; - }, + const compartment = new Compartment({ + resolveHook: specifier => specifier, + importNowHook(specifier) { + if (specifier === './index.js') { + return { source: new ModuleSource('export default 42') }; + } + return undefined; }, - ); + __options__: true, + }); const index = compartment.importNow('./index.js'); t.is(index.default, 42); }); test('import now hook returns module source descriptor with virtual module source', t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - resolveHook: specifier => specifier, - importNowHook(specifier) { - if (specifier === './index.js') { - return { - source: { - imports: [], - exports: ['default'], - execute(env) { - env.default = 42; - }, + const compartment = new Compartment({ + resolveHook: specifier => specifier, + importNowHook(specifier) { + if (specifier === './index.js') { + return { + source: { + imports: [], + exports: ['default'], + execute(env) { + env.default = 42; }, - }; - } - return undefined; - }, + }, + }; + } + return undefined; }, - ); + __options__: true, + }); const index = compartment.importNow('./index.js'); t.is(index.default, 42); }); test('import now hook returns parent compartment module source descriptor with string reference to parent compartment', t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - resolveHook: specifier => specifier, - importNowHook(specifier) { - if (specifier === './meaning.js') { - return { - source: { - imports: [], - exports: ['default'], - execute(env) { - env.default = 42; - }, + const parent = new Compartment({ + resolveHook: specifier => specifier, + importNowHook(specifier) { + if (specifier === './meaning.js') { + return { + source: { + imports: [], + exports: ['default'], + execute(env) { + env.default = 42; }, - }; - } - return undefined; - }, + }, + }; + } + return undefined; }, - ); + __options__: true, + }); const compartment = new parent.globalThis.Compartment( // endowments: @@ -105,48 +87,42 @@ test('import now hook returns parent compartment module source descriptor with s }); test('import now hook returns parent compartment module source reference with different specifier', t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'parent', - resolveHook(relative, specifier) { - t.is(relative, './meaningful.js'); - t.is(specifier, './meaning.js'); - return relative; - }, - importNowHook(specifier) { - if (specifier === './meaningful.js') { - return { - source: { - imports: [], - exports: ['meaning'], - execute() { - throw new Error('should not execute'); - }, + const parent = new Compartment({ + name: 'parent', + resolveHook(relative, specifier) { + t.is(relative, './meaningful.js'); + t.is(specifier, './meaning.js'); + return relative; + }, + importNowHook(specifier) { + if (specifier === './meaningful.js') { + return { + source: { + imports: [], + exports: ['meaning'], + execute() { + throw new Error('should not execute'); }, - }; - } else if (specifier === './meaning.js') { - return { - source: { - imports: ['./meaningful.js'], - exports: ['default'], - execute(env, c, resolutions) { - // eslint-disable-next-line no-use-before-define - t.is(c, compartment); - const { meaning } = c.importNow(resolutions['./meaningful.js']); - env.default = meaning; - }, + }, + }; + } else if (specifier === './meaning.js') { + return { + source: { + imports: ['./meaningful.js'], + exports: ['default'], + execute(env, c, resolutions) { + // eslint-disable-next-line no-use-before-define + t.is(c, compartment); + const { meaning } = c.importNow(resolutions['./meaningful.js']); + env.default = meaning; }, - }; - } - return undefined; - }, + }, + }; + } + return undefined; }, - ); + __options__: true, + }); const compartment = new parent.globalThis.Compartment( // endowments: @@ -187,24 +163,18 @@ test('import now hook returns parent compartment module source reference with di }); test('import now hook returns module source descriptor for parent compartment with string reference', t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'parent', - importNowHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, + const parent = new Compartment({ + name: 'parent', + importNowHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __options__: true, + }); const { default: parentObject } = parent.importNow('./object.js'); t.is(parentObject.meaning, 42); @@ -236,24 +206,18 @@ test('import now hook returns module source descriptor for parent compartment wi }); test('import now hook returns parent compartment module namespace descriptor', t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'parent', - importNowHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, + const parent = new Compartment({ + name: 'parent', + importNowHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __options__: true, + }); const { default: parentObject } = parent.importNow('./object.js'); t.is(parentObject.meaning, 42); @@ -285,47 +249,35 @@ test('import now hook returns parent compartment module namespace descriptor', t }); test('import now hook returns module source descriptor with string reference to parent compartment', t => { - const compartment1 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'compartment1', - importNowHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, + const compartment1 = new Compartment({ + name: 'compartment1', + importNowHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __options__: true, + }); const { default: object1 } = compartment1.importNow('./object.js'); t.is(object1.meaning, 42); - const compartment2 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'child', - importNowHook(specifier) { - if (specifier === './index.js') { - return { - source: './object.js', - compartment: compartment1, - }; - } - return undefined; - }, + const compartment2 = new Compartment({ + name: 'child', + importNowHook(specifier) { + if (specifier === './index.js') { + return { + source: './object.js', + compartment: compartment1, + }; + } + return undefined; }, - ); + __options__: true, + }); const { default: object2 } = compartment2.importNow('./index.js'); t.is(object2.meaning, 42); @@ -334,47 +286,35 @@ test('import now hook returns module source descriptor with string reference to }); test('import now hook returns other compartment module namespace descriptor', t => { - const compartment1 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'compartment1', - importNowHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, + const compartment1 = new Compartment({ + name: 'compartment1', + importNowHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __options__: true, + }); const { default: object1 } = compartment1.importNow('./object.js'); t.is(object1.meaning, 42); - const compartment2 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'child', - importNowHook(specifier) { - if (specifier === './index.js') { - return { - namespace: './object.js', - compartment: compartment1, - }; - } - return undefined; - }, + const compartment2 = new Compartment({ + name: 'child', + importNowHook(specifier) { + if (specifier === './index.js') { + return { + namespace: './object.js', + compartment: compartment1, + }; + } + return undefined; }, - ); + __options__: true, + }); const { default: object2 } = compartment2.importNow('./index.js'); t.is(object2.meaning, 42); @@ -383,78 +323,64 @@ test('import now hook returns other compartment module namespace descriptor', t }); test('import now hook returns module namespace descriptor and namespace object', t => { - const compartment1 = new Compartment( - {}, - {}, - { - importNowHook(specifier) { - if (specifier === 'a') { - return { - source: new ModuleSource(`export default 42`), - }; - } - return undefined; - }, + const compartment1 = new Compartment({ + importNowHook(specifier) { + if (specifier === 'a') { + return { + source: new ModuleSource(`export default 42`), + }; + } + return undefined; }, - ); + __options__: true, + }); const namespace1 = compartment1.importNow('a'); - const compartment2 = new Compartment( - {}, - {}, - { - importNowHook(specifier) { - if (specifier === 'z') { - return { namespace: namespace1 }; - } - return undefined; - }, + const compartment2 = new Compartment({ + importNowHook(specifier) { + if (specifier === 'z') { + return { namespace: namespace1 }; + } + return undefined; }, - ); + __options__: true, + }); const namespace2 = compartment2.importNow('z'); t.is(namespace2.default, 42); t.is(namespace1, namespace2); }); test('import now hook returns module namespace descriptor and non-namespace object', t => { - const compartment = new Compartment( - {}, - {}, - { - importNowHook(specifier) { - if (specifier === '1') { - return { namespace: { meaning: 42 } }; - } - return undefined; - }, + const compartment = new Compartment({ + importNowHook(specifier) { + if (specifier === '1') { + return { namespace: { meaning: 42 } }; + } + return undefined; }, - ); + __options__: true, + }); const namespace = compartment.importNow('1'); t.is(namespace.meaning, 42); }); test('import now hook returns module source descriptor for specifier in own compartment', t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - importNowHook(specifier) { - if (specifier === './index.js') { - return { - source: './object.js', - compartment, - }; - } - return undefined; - }, + importNowHook(specifier) { + if (specifier === './index.js') { + return { + source: './object.js', + compartment, + }; + } + return undefined; }, - ); + __options__: true, + }); const { default: object1 } = compartment.importNow('./object.js'); t.is(object1.meaning, 42); @@ -465,29 +391,24 @@ test('import now hook returns module source descriptor for specifier in own comp }); test('import now hook returns module source descriptor for specifier in own compartment and overridden base specifier that collides', t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - importNowHook(specifier) { - if (specifier === './index.js') { - return { - source: './object.js', - specifier: './object.js', - compartment, - }; - } - return undefined; - }, + importNowHook(specifier) { + if (specifier === './index.js') { + return { + source: './object.js', + specifier: './object.js', + compartment, + }; + } + return undefined; }, - ); + __options__: true, + }); const { default: object1 } = compartment.importNow('./object.js'); t.is(object1.meaning, 42); @@ -498,28 +419,22 @@ test('import now hook returns module source descriptor for specifier in own comp }); test('import now hook returns module namespace descriptor for specifier in own compartment', t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - importNowHook(specifier) { - if (specifier === './index.js') { - return { - namespace: './object.js', - compartment, - }; - } else if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, + const compartment = new Compartment({ + importNowHook(specifier) { + if (specifier === './index.js') { + return { + namespace: './object.js', + compartment, + }; + } else if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __options__: true, + }); const { default: object1 } = compartment.importNow('./object.js'); t.is(object1.meaning, 42); @@ -530,28 +445,22 @@ test('import now hook returns module namespace descriptor for specifier in own c }); test('module map hook precedes import now hook', t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { - source: new ModuleSource(` + const compartment = new Compartment({ + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { + source: new ModuleSource(` export default 42; `), - }; - } - return undefined; - }, - importNowHook() { - throw new Error('not reached'); - }, + }; + } + return undefined; }, - ); + importNowHook() { + throw new Error('not reached'); + }, + __options__: true, + }); const { default: meaning } = compartment.importNow('./index.js'); t.is(meaning, 42); diff --git a/packages/ses/test/import-stack-traces.test.js b/packages/ses/test/import-stack-traces.test.js index 54efaaca80..fe71f5b771 100644 --- a/packages/ses/test/import-stack-traces.test.js +++ b/packages/ses/test/import-stack-traces.test.js @@ -16,14 +16,11 @@ test('preserve file names in stack traces', async t => { `, }); - const compartment = new Compartment( - {}, // endowments - {}, // module map - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/packages/erroneous'), - }, - ); + const compartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/erroneous'), + __options__: true, + }); let error; try { diff --git a/packages/ses/test/import.test.js b/packages/ses/test/import.test.js index d2bc5365a9..85d21c3e4a 100644 --- a/packages/ses/test/import.test.js +++ b/packages/ses/test/import.test.js @@ -27,17 +27,15 @@ test('import within one compartment, web resolution', async t => { const resolveHook = (spec, referrer) => new URL(spec, referrer).toString(); const importHook = makeImporter(locate, retrieve); - const compartment = new Compartment( - { + const compartment = new Compartment({ + globals: { double: n => n * 2, }, - {}, - { - resolveHook, - importHook, - __noNamespaceBox__: true, - }, - ); + resolveHook, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import( 'https://example.com/packages/example/', @@ -61,17 +59,15 @@ test('import within one compartment, node resolution', async t => { `, }); - const compartment = new Compartment( - { + const compartment = new Compartment({ + globals: { double: n => n * 2, }, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/packages/example'), - __noNamespaceBox__: true, - }, - ); + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/example'), + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main.js'); @@ -99,20 +95,17 @@ test('two compartments, three modules, one endowment', async t => { `, }); - const doubleCompartment = new Compartment( - { + const doubleCompartment = new Compartment({ + globals: { double: n => n * 2, }, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/packages/double'), - }, - ); + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/double'), + __options__: true, + }); - const compartment = new Compartment( - {}, - { + const compartment = new Compartment({ + modules: { // Notably, this is the first case where we thread a depencency between // two compartments, using the sigil of one's namespace to indicate // linkage before the module has been loaded. @@ -121,12 +114,11 @@ test('two compartments, three modules, one endowment', async t => { compartment: doubleCompartment, }, }, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/packages/example'), - __noNamespaceBox__: true, - }, - ); + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/example'), + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main.js'); @@ -142,15 +134,12 @@ test('module exports namespace as an object', async t => { `, }); - const compartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/packages/meaning'), - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/meaning'), + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main.js'); @@ -215,15 +204,12 @@ test('modules are memoized', async t => { `, }); - const compartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/packages/example'), - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/example'), + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main.js'); const { clive, clerk } = namespace; @@ -240,25 +226,19 @@ test('compartments with same sources do not share instances', async t => { `, }); - const leftCompartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/packages/arm'), - __noNamespaceBox__: true, - }, - ); + const leftCompartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/arm'), + __noNamespaceBox__: true, + __options__: true, + }); - const rightCompartment = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/packages/arm'), - __noNamespaceBox__: true, - }, - ); + const rightCompartment = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/arm'), + __noNamespaceBox__: true, + __options__: true, + }); const [{ default: leftArm }, { default: rightArm }] = await Promise.all([ leftCompartment.import('./main.js'), @@ -300,38 +280,33 @@ test('module map hook', async t => { `, }); - const dependency = new Compartment( - {}, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/dependency'), - __noNamespaceBox__: true, - }, - ); + const dependency = new Compartment({ + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/dependency'), + __noNamespaceBox__: true, + __options__: true, + }); - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - moduleMapHook: moduleSpecifier => { - const remainder = trimModuleSpecifierPrefix( - moduleSpecifier, - 'dependency', - ); - if (remainder) { - return { - namespace: remainder, - compartment: dependency, - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + moduleMapHook: moduleSpecifier => { + const remainder = trimModuleSpecifierPrefix( + moduleSpecifier, + 'dependency', + ); + if (remainder) { + return { + namespace: remainder, + compartment: dependency, + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); await compartment.import('./main.js'); }); @@ -378,42 +353,33 @@ test('mutual dependency between compartments', async t => { return undefined; }; - const even = new Compartment( - {}, - {}, - { - name: 'https://example.com/even', - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/even'), - moduleMapHook, - __noNamespaceBox__: true, - }, - ); + const even = new Compartment({ + name: 'https://example.com/even', + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/even'), + moduleMapHook, + __noNamespaceBox__: true, + __options__: true, + }); - const odd = new Compartment( - {}, - {}, - { - name: 'https://example.com/odd', - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com/odd'), - moduleMapHook, - [Symbol.for('options')]: true, - __noNamespaceBox__: true, - }, - ); + const odd = new Compartment({ + name: 'https://example.com/odd', + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/odd'), + moduleMapHook, + __noNamespaceBox__: true, + __options__: true, + }); - const compartment = new Compartment( - { t }, - {}, - { - name: 'https://example.com', - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - moduleMapHook, - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + globals: { t }, + name: 'https://example.com', + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + moduleMapHook, + __noNamespaceBox__: true, + __options__: true, + }); await compartment.import('./main.js'); }); @@ -449,15 +415,13 @@ test('import redirect shorthand', async t => { throw Error(`Cannot find module ${specifier}`); }; - const compartment = new Compartment( - { Math }, - {}, - { - resolveHook: resolveNode, - importHook, - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + globals: { Math }, + resolveHook: resolveNode, + importHook, + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('./main'); t.is( @@ -519,16 +483,14 @@ test('import reflexive module alias', async t => { return undefined; }; - const compartment = new Compartment( - { t }, - {}, - { - resolveHook: resolveNode, - importHook, - moduleMapHook, - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + globals: { t }, + resolveHook: resolveNode, + importHook, + moduleMapHook, + __noNamespaceBox__: true, + __options__: true, + }); await compartment.import('./index.js'); }); @@ -569,18 +531,16 @@ test('import.meta populated from module record', async t => { `, }); - const compartment = new Compartment( - { t }, - {}, - { - name: 'https://example.com', - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com', { - meta: { url: 'https://example.com/index.js' }, - }), - __noNamespaceBox__: true, - }, - ); + const compartment = new Compartment({ + name: 'https://example.com', + globals: { t }, + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com', { + meta: { url: 'https://example.com/index.js' }, + }), + __noNamespaceBox__: true, + __options__: true, + }); const { default: metaurl } = await compartment.import('./index.js'); t.is(metaurl, 'https://example.com/index.js'); @@ -596,19 +556,17 @@ test('importMetaHook', async t => { `, }); - const compartment = new Compartment( - { t }, - {}, - { - name: 'https://example.com', - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com'), - importMetaHook: (_moduleSpecifier, meta) => { - meta.url = 'https://example.com/index.js'; - }, - __noNamespaceBox__: true, + const compartment = new Compartment({ + name: 'https://example.com', + globals: { t }, + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + importMetaHook: (_moduleSpecifier, meta) => { + meta.url = 'https://example.com/index.js'; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: metaurl } = await compartment.import('./index.js'); t.is(metaurl, 'https://example.com/index.js'); @@ -624,22 +582,20 @@ test('importMetaHook and meta from record', async t => { `, }); - const compartment = new Compartment( - { t }, - {}, - { - name: 'https://example.com', - resolveHook: resolveNode, - importHook: makeImportHook('https://example.com', { - meta: { url: 'https://example.com/index.js' }, - }), - importMetaHook: (_moduleSpecifier, meta) => { - meta.url += '?foo'; - meta.isStillMutableHopefully = 1; - }, - __noNamespaceBox__: true, + const compartment = new Compartment({ + name: 'https://example.com', + globals: { t }, + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com', { + meta: { url: 'https://example.com/index.js' }, + }), + importMetaHook: (_moduleSpecifier, meta) => { + meta.url += '?foo'; + meta.isStillMutableHopefully = 1; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: metaurl } = await compartment.import('./index.js'); t.is(metaurl, 'https://example.com/index.js?foo'); diff --git a/packages/ses/test/lockdown-compartment.test.js b/packages/ses/test/lockdown-compartment.test.js index 15426d3efe..4129b2745b 100644 --- a/packages/ses/test/lockdown-compartment.test.js +++ b/packages/ses/test/lockdown-compartment.test.js @@ -6,7 +6,7 @@ test('lockdown returns or throws', t => { // Compartment constructor does not throw before lockdown. (() => { - const c = new Compartment({}, {}, {}); + const c = new Compartment(); const functionConstructor = c.evaluate('Function.prototype.constructor'); t.is( functionConstructor, @@ -19,7 +19,7 @@ test('lockdown returns or throws', t => { // Compartment constructor does not throw after lockdown. (() => { - const c = new Compartment({}, {}, {}); + const c = new Compartment(); const cf = c.evaluate('Function.prototype.constructor'); t.not( cf, @@ -27,7 +27,7 @@ test('lockdown returns or throws', t => { 'after lockdown, Function.prototype.constructor must be tamed inside compartments', ); - const d = new Compartment({}, {}, {}); + const d = new Compartment(); const df = d.evaluate('Function.prototype.constructor'); t.is( cf, diff --git a/packages/ses/test/module-map-hook.test.js b/packages/ses/test/module-map-hook.test.js index 38c1238195..567e5fc49c 100644 --- a/packages/ses/test/module-map-hook.test.js +++ b/packages/ses/test/module-map-hook.test.js @@ -7,82 +7,64 @@ import { ModuleSource } from '@endo/module-source'; import '../index.js'; test('module map hook returns module source descriptor with precompiled module source', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - resolveHook: specifier => specifier, - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { source: new ModuleSource('export default 42') }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment = new Compartment({ + resolveHook: specifier => specifier, + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { source: new ModuleSource('export default 42') }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const index = await compartment.import('./index.js'); t.is(index.default, 42); }); test('module map hook returns module source descriptor with virtual module source', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - resolveHook: specifier => specifier, - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { - source: { - imports: [], - exports: ['default'], - execute(env) { - env.default = 42; - }, + const compartment = new Compartment({ + resolveHook: specifier => specifier, + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { + source: { + imports: [], + exports: ['default'], + execute(env) { + env.default = 42; }, - }; - } - return undefined; - }, - __noNamespaceBox__: true, + }, + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const index = await compartment.import('./index.js'); t.is(index.default, 42); }); test('module map hook returns parent compartment module source descriptor with string reference to parent compartment', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - resolveHook: specifier => specifier, - moduleMapHook(specifier) { - if (specifier === './meaning.js') { - return { - source: { - imports: [], - exports: ['default'], - execute(env) { - env.default = 42; - }, + const parent = new Compartment({ + resolveHook: specifier => specifier, + moduleMapHook(specifier) { + if (specifier === './meaning.js') { + return { + source: { + imports: [], + exports: ['default'], + execute(env) { + env.default = 42; }, - }; - } - return undefined; - }, + }, + }; + } + return undefined; }, - ); + __options__: true, + }); const compartment = new parent.globalThis.Compartment( // endowments: @@ -108,48 +90,42 @@ test('module map hook returns parent compartment module source descriptor with s }); test('module map hook returns parent compartment module source reference with different specifier', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'parent', - resolveHook(relative, specifier) { - t.is(relative, './meaningful.js'); - t.is(specifier, './meaning.js'); - return relative; - }, - moduleMapHook(specifier) { - if (specifier === './meaningful.js') { - return { - source: { - imports: [], - exports: ['meaning'], - execute() { - throw new Error('should not execute'); - }, + const parent = new Compartment({ + name: 'parent', + resolveHook(relative, specifier) { + t.is(relative, './meaningful.js'); + t.is(specifier, './meaning.js'); + return relative; + }, + moduleMapHook(specifier) { + if (specifier === './meaningful.js') { + return { + source: { + imports: [], + exports: ['meaning'], + execute() { + throw new Error('should not execute'); }, - }; - } else if (specifier === './meaning.js') { - return { - source: { - imports: ['./meaningful.js'], - exports: ['default'], - execute(env, c, resolutions) { - // eslint-disable-next-line no-use-before-define - t.is(c, compartment); - const { meaning } = c.importNow(resolutions['./meaningful.js']); - env.default = meaning; - }, + }, + }; + } else if (specifier === './meaning.js') { + return { + source: { + imports: ['./meaningful.js'], + exports: ['default'], + execute(env, c, resolutions) { + // eslint-disable-next-line no-use-before-define + t.is(c, compartment); + const { meaning } = c.importNow(resolutions['./meaningful.js']); + env.default = meaning; }, - }; - } - return undefined; - }, + }, + }; + } + return undefined; }, - ); + __options__: true, + }); const compartment = new parent.globalThis.Compartment( // endowments: @@ -191,49 +167,37 @@ test('module map hook returns parent compartment module source reference with di }); test('module map hook returns module source descriptor for parent compartment with string reference', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'parent', - moduleMapHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const parent = new Compartment({ + name: 'parent', + moduleMapHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); - const compartment = new parent.globalThis.Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'child', - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { - source: './object.js', - // implies parent compartment - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment = new parent.globalThis.Compartment({ + name: 'child', + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { + source: './object.js', + // implies parent compartment + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: childObject } = await compartment.import('./index.js'); t.is(childObject.meaning, 42); @@ -242,49 +206,37 @@ test('module map hook returns module source descriptor for parent compartment wi }); test('module map hook returns parent compartment module namespace descriptor', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'parent', - moduleMapHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const parent = new Compartment({ + name: 'parent', + moduleMapHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); - const compartment = new parent.globalThis.Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'child', - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { - namespace: './object.js', - // implies parent compartment - }; - } - return undefined; - }, - __noNamespaceBox__: true, + const compartment = new parent.globalThis.Compartment({ + name: 'child', + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { + namespace: './object.js', + // implies parent compartment + }; + } + return undefined; }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: childObject } = await compartment.import('./index.js'); t.is(childObject.meaning, 42); @@ -293,49 +245,37 @@ test('module map hook returns parent compartment module namespace descriptor', a }); test('module map hook returns module source descriptor with string reference to parent compartment', async t => { - const compartment1 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'compartment1', - moduleMapHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, + const compartment1 = new Compartment({ + name: 'compartment1', + moduleMapHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __options__: true, + }); const { namespace: { default: object1 }, } = await compartment1.import('./object.js'); t.is(object1.meaning, 42); - const compartment2 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'child', - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { - source: './object.js', - compartment: compartment1, - }; - } - return undefined; - }, + const compartment2 = new Compartment({ + name: 'child', + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { + source: './object.js', + compartment: compartment1, + }; + } + return undefined; }, - ); + __options__: true, + }); const { namespace: { default: object2 }, @@ -346,49 +286,37 @@ test('module map hook returns module source descriptor with string reference to }); test('module map hook returns other compartment module namespace descriptor', async t => { - const compartment1 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'compartment1', - moduleMapHook(specifier) { - if (specifier === './object.js') { - return { - source: new ModuleSource('export default { meaning: 42 }'), - }; - } - return undefined; - }, + const compartment1 = new Compartment({ + name: 'compartment1', + moduleMapHook(specifier) { + if (specifier === './object.js') { + return { + source: new ModuleSource('export default { meaning: 42 }'), + }; + } + return undefined; }, - ); + __options__: true, + }); const { namespace: { default: object1 }, } = await compartment1.import('./object.js'); t.is(object1.meaning, 42); - const compartment2 = new Compartment( - // endowments: - {}, - // modules: - {}, - // options: - { - name: 'child', - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { - namespace: './object.js', - compartment: compartment1, - }; - } - return undefined; - }, + const compartment2 = new Compartment({ + name: 'child', + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { + namespace: './object.js', + compartment: compartment1, + }; + } + return undefined; }, - ); + __options__: true, + }); const { namespace: { default: object2 }, @@ -399,78 +327,64 @@ test('module map hook returns other compartment module namespace descriptor', as }); test('module map hook returns module namespace descriptor and namespace object', async t => { - const compartment1 = new Compartment( - {}, - {}, - { - moduleMapHook(specifier) { - if (specifier === 'a') { - return { - source: new ModuleSource(`export default 42`), - }; - } - return undefined; - }, + const compartment1 = new Compartment({ + moduleMapHook(specifier) { + if (specifier === 'a') { + return { + source: new ModuleSource(`export default 42`), + }; + } + return undefined; }, - ); + __options__: true, + }); const { namespace: namespace1 } = await compartment1.import('a'); - const compartment2 = new Compartment( - {}, - {}, - { - moduleMapHook(specifier) { - if (specifier === 'z') { - return { namespace: namespace1 }; - } - return undefined; - }, + const compartment2 = new Compartment({ + moduleMapHook(specifier) { + if (specifier === 'z') { + return { namespace: namespace1 }; + } + return undefined; }, - ); + __options__: true, + }); const { namespace: namespace2 } = await compartment2.import('z'); t.is(namespace2.default, 42); t.is(namespace1, namespace2); }); test('module map hook returns module namespace descriptor and non-namespace object', async t => { - const compartment = new Compartment( - {}, - {}, - { - moduleMapHook(specifier) { - if (specifier === '1') { - return { namespace: { meaning: 42 } }; - } - return undefined; - }, + const compartment = new Compartment({ + moduleMapHook(specifier) { + if (specifier === '1') { + return { namespace: { meaning: 42 } }; + } + return undefined; }, - ); + __options__: true, + }); const { namespace } = await compartment.import('1'); t.is(namespace.meaning, 42); }); test('module map hook returns module source descriptor for specifier in own compartment', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { - source: './object.js', - compartment, - }; - } - return undefined; - }, + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { + source: './object.js', + compartment, + }; + } + return undefined; }, - ); + __options__: true, + }); const { namespace: { default: object1 }, @@ -485,29 +399,24 @@ test('module map hook returns module source descriptor for specifier in own comp }); test('module map hook returns module source descriptor for specifier in own compartment and overridden base specifier that collides', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { - source: './object.js', - specifier: './object.js', - compartment, - }; - } - return undefined; - }, + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { + source: './object.js', + specifier: './object.js', + compartment, + }; + } + return undefined; }, - ); + __options__: true, + }); const { namespace: { default: object1 }, @@ -522,28 +431,23 @@ test('module map hook returns module source descriptor for specifier in own comp }); test('module map hook returns module namespace descriptor for specifier in own compartment', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - moduleMapHook(specifier) { - if (specifier === './index.js') { - return { - namespace: './object.js', - compartment, - }; - } - return undefined; - }, + moduleMapHook(specifier) { + if (specifier === './index.js') { + return { + namespace: './object.js', + compartment, + }; + } + return undefined; }, - ); + __options__: true, + }); const { namespace: { default: object1 }, @@ -558,24 +462,19 @@ test('module map hook returns module namespace descriptor for specifier in own c }); test('module map precedes module map hook', t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './index.js': { source: new ModuleSource(` export default 42; `), }, }, - // options: - { - moduleMapHook() { - throw new Error('not reached'); - }, + moduleMapHook() { + throw new Error('not reached'); }, - ); + __options__: true, + }); const { default: meaning } = compartment.importNow('./index.js'); t.is(meaning, 42); diff --git a/packages/ses/test/module-map.test.js b/packages/ses/test/module-map.test.js index e69ce3d27e..8fd233db7f 100644 --- a/packages/ses/test/module-map.test.js +++ b/packages/ses/test/module-map.test.js @@ -5,31 +5,23 @@ import { ModuleSource } from '@endo/module-source'; import '../index.js'; test('module map primed with module source descriptor with precompiled module source', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './index.js': { source: new ModuleSource('export default 42'), }, }, - // options: - { - resolveHook: specifier => specifier, - __noNamespaceBox__: true, - }, - ); + resolveHook: specifier => specifier, + __noNamespaceBox__: true, + __options__: true, + }); const index = await compartment.import('./index.js'); t.is(index.default, 42); }); test('module map primed with module source descriptor with virtual module source', async t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './index.js': { source: { imports: [], @@ -40,22 +32,17 @@ test('module map primed with module source descriptor with virtual module source }, }, }, - // options: - { - resolveHook: specifier => specifier, - __noNamespaceBox__: true, - }, - ); + resolveHook: specifier => specifier, + __noNamespaceBox__: true, + __options__: true, + }); const index = await compartment.import('./index.js'); t.is(index.default, 42); }); test('module map primed with parent compartment module source descriptor with string reference to parent compartment', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - { + const parent = new Compartment({ + modules: { './meaning.js': { source: { imports: [], @@ -66,37 +53,27 @@ test('module map primed with parent compartment module source descriptor with st }, }, }, - // options: - { - resolveHook: specifier => specifier, - }, - ); + resolveHook: specifier => specifier, + __options__: true, + }); - const compartment = new parent.globalThis.Compartment( - // endowments: - {}, - // modules: - { + const compartment = new parent.globalThis.Compartment({ + modules: { './index.js': { source: './meaning.js', }, }, - // options: - { - resolveHook: specifier => specifier, - __noNamespaceBox__: true, - }, - ); + resolveHook: specifier => specifier, + __noNamespaceBox__: true, + __options__: true, + }); const index = await compartment.import('./index.js'); t.is(index.default, 42); }); test('module map primed with parent compartment module source reference with different specifier', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - { + const parent = new Compartment({ + modules: { './meaningful.js': { source: { imports: [], @@ -119,16 +96,14 @@ test('module map primed with parent compartment module source reference with dif }, }, }, - // options: - { - name: 'parent', - resolveHook(relative, specifier) { - t.is(relative, './meaningful.js'); - t.is(specifier, './meaning.js'); - return relative; - }, + name: 'parent', + resolveHook(relative, specifier) { + t.is(relative, './meaningful.js'); + t.is(specifier, './meaning.js'); + return relative; }, - ); + __options__: true, + }); const compartment = new parent.globalThis.Compartment( // endowments: @@ -165,41 +140,31 @@ test('module map primed with parent compartment module source reference with dif }); test('module map primed with module source descriptor for parent compartment with string reference', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - { + const parent = new Compartment({ + name: 'parent', + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - name: 'parent', - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); - const compartment = new parent.globalThis.Compartment( - // endowments: - {}, - // modules: - { + const compartment = new parent.globalThis.Compartment({ + name: 'child', + modules: { './index.js': { source: './object.js', // implies parent compartment }, }, - // options: - { - name: 'child', - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: childObject } = await compartment.import('./index.js'); t.is(childObject.meaning, 42); @@ -208,41 +173,31 @@ test('module map primed with module source descriptor for parent compartment wit }); test('module map primed with parent compartment module namespace descriptor', async t => { - const parent = new Compartment( - // endowments: - {}, - // modules: - { + const parent = new Compartment({ + name: 'parent', + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - name: 'parent', - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); - const compartment = new parent.globalThis.Compartment( - // endowments: - {}, - // modules: - { + const compartment = new parent.globalThis.Compartment({ + name: 'child', + modules: { './index.js': { namespace: './object.js', // implies parent compartment }, }, - // options: - { - name: 'child', - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: childObject } = await compartment.import('./index.js'); t.is(childObject.meaning, 42); @@ -251,41 +206,31 @@ test('module map primed with parent compartment module namespace descriptor', as }); test('module map primed with module source descriptor with string reference to parent compartment', async t => { - const compartment1 = new Compartment( - // endowments: - {}, - // modules: - { + const compartment1 = new Compartment({ + name: 'compartment1', + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - name: 'compartment1', - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object1 } = await compartment1.import('./object.js'); t.is(object1.meaning, 42); - const compartment2 = new Compartment( - // endowments: - {}, - // modules: - { + const compartment2 = new Compartment({ + name: 'child', + modules: { './index.js': { source: './object.js', compartment: compartment1, }, }, - // options: - { - name: 'child', - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object2 } = await compartment2.import('./index.js'); t.is(object2.meaning, 42); @@ -294,41 +239,31 @@ test('module map primed with module source descriptor with string reference to p }); test('module map primed with other compartment module namespace descriptor', async t => { - const compartment1 = new Compartment( - // endowments: - {}, - // modules: - { + const compartment1 = new Compartment({ + name: 'compartment1', + modules: { './object.js': { source: new ModuleSource('export default { meaning: 42 }'), }, }, - // options: - { - name: 'compartment1', - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object1 } = await compartment1.import('./object.js'); t.is(object1.meaning, 42); - const compartment2 = new Compartment( - // endowments: - {}, - // modules: - { + const compartment2 = new Compartment({ + name: 'child', + modules: { './index.js': { namespace: './object.js', compartment: compartment1, }, }, - // options: - { - name: 'child', - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const { default: object2 } = await compartment2.import('./index.js'); t.is(object2.meaning, 42); @@ -337,65 +272,54 @@ test('module map primed with other compartment module namespace descriptor', asy }); test('module map primed with module namespace descriptor and namespace object', async t => { - const compartment1 = new Compartment( - {}, - { + const compartment1 = new Compartment({ + modules: { a: { source: new ModuleSource(`export default 42`), }, }, - { - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const namespace1 = await compartment1.import('a'); - const compartment2 = new Compartment( - {}, - { + const compartment2 = new Compartment({ + modules: { z: { namespace: namespace1 }, }, - { - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const namespace2 = await compartment2.import('z'); t.is(namespace2.default, 42); t.is(namespace1, namespace2); }); test('module map primed with module namespace descriptor and non-namespace object', async t => { - const compartment = new Compartment( - {}, - { + const compartment = new Compartment({ + modules: { 1: { namespace: { meaning: 42 } }, }, - { - __noNamespaceBox__: true, - }, - ); + __noNamespaceBox__: true, + __options__: true, + }); const namespace = await compartment.import('1'); t.is(namespace.meaning, 42); }); test('module map precedes module map hook', t => { - const compartment = new Compartment( - // endowments: - {}, - // modules: - { + const compartment = new Compartment({ + modules: { './index.js': { source: new ModuleSource(` export default 42; `), }, }, - // options: - { - moduleMapHook() { - throw new Error('not reached'); - }, + moduleMapHook() { + throw new Error('not reached'); }, - ); + __options__: true, + }); const { default: meaning } = compartment.importNow('./index.js'); t.is(meaning, 42); diff --git a/packages/ses/test/ses.test.js b/packages/ses/test/ses.test.js index dc654beaf8..c6a443fe7e 100644 --- a/packages/ses/test/ses.test.js +++ b/packages/ses/test/ses.test.js @@ -23,7 +23,10 @@ test('tamed constructors', t => { t.throws(() => Error.__proto__.constructor(''), { instanceOf: TypeError }); t.throws(() => Function.prototype.constructor(''), { instanceOf: TypeError }); - const c = new Compartment({ console }); + const c = new Compartment({ + globals: { console }, + __options__: true, + }); t.throws(() => c.evaluate("Error.__proto__.constructor('')"), { instanceOf: TypeError, @@ -99,7 +102,10 @@ test('SES compartment also has compartments', t => { // // }); test('SES compartment has harden', t => { - const c = new Compartment({ a: 123 }); + const c = new Compartment({ + globals: { a: 123 }, + __options__: true, + }); const obj = c.evaluate('harden({a})'); t.is(obj.a, 123, 'expected object'); if (!harden.isFake) { @@ -151,11 +157,17 @@ test('main use case', t => { } return power(arg); } - const attenuatedPower = new Compartment({ power }).evaluate(`(${attenuate})`); + const attenuatedPower = new Compartment({ + globals: { power }, + __options__: true, + }).evaluate(`(${attenuate})`); function use(arg) { return power(arg); } - const c = new Compartment({ power: attenuatedPower }); + const c = new Compartment({ + globals: { power: attenuatedPower }, + __options__: true, + }); const user = c.evaluate(`(${use})`); t.is(user(1), 2); t.throws(() => user(-1), { instanceOf: c.globalThis.TypeError }); From 0e4228f87244de01a75e0c57418b4b5311171502 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Fri, 26 Jul 2024 18:20:59 -0700 Subject: [PATCH 3/4] docs(ses): Document compartment options migration --- packages/ses/NEWS.md | 42 ++++++++++++++++++++++---- packages/ses/README.md | 68 +++++++++++++++++++++++++++++------------- 2 files changed, 83 insertions(+), 27 deletions(-) diff --git a/packages/ses/NEWS.md b/packages/ses/NEWS.md index 10a93aa9af..dca15dc6cf 100644 --- a/packages/ses/NEWS.md +++ b/packages/ses/NEWS.md @@ -2,7 +2,43 @@ User-visible changes in SES: # Next version +- *NOTICE*: This version introduces multiple features to converge upon a + more common standard for [Hardened JavaScript](https://hardenedjs.org). + All code should begin migrating to these usage patterns as the older + patterns are now deprecated and will not be supported in a future major + version of SES. + +- To converge on a portable pattern for using `Compartment`, introduces an + `__options__` property for the first argument of the `Compartment` + constructor that must be `true` if present and indicates the object is the + options bag and not the global endowments. All code going forward should + include this flag until the next major version of SES, when we plan for it to + become vesgitial and drop support for three-argument `Compartment` + construction. + + In the unlikely event that existing code names an endowment `__options__`, + that code will break and need to be adjusted to adopt this version. + Because we rate this unlikely, we have elected not to mark this with + a major version bump. + +- Adds a `__noNamespaceBox__` option that aligns the behavior of the `import` + method on SES `Compartment` with the behavior of XS and the behavior we will + champion for compartment standards. + All use of `Compartment` should migrate to use this option as the standard + behavior will be enabled by default with the next major version of SES. + - Adds support for module descriptors better aligned with XS. + Compartments use module desriptors to load and link modules. + The importHook, importNowHook, and moduleMapHook all return module descriptors + (sometimes promises for module descriptors). + The modules option or argument to the Compatment constructor has module + descriptors for all its values. + - `{record, specifier, compartment}` should become `{source: record, + specifier, compartment}`. + - `{specifier, compartment}` should become `{source: specifier, + compartment}`. + - `{record: compartment.module(specifier)}` should become `{namespace: + specifier, compartment}`. - When running transpiled code on Node, the SES error taming gives line-numbers into the generated JavaScript, which often don't match the @@ -25,12 +61,6 @@ User-visible changes in SES: the stacktrace line-numbers point back into the original source, as they do on Node without SES. -- Adds a `__noNamespaceBox__` option that aligns the behavior of the `import` - method on SES `Compartment` with the behavior of XS and the behavior we will - champion for compartment standards. - All use of `Compartment` should migrate to use this option as the standard - behavior will be enabled by default with the next major version of SES. - # v1.5.0 (2024-05-06) - Adds `importNowHook` to the `Compartment` options. The compartment will invoke the hook whenever it encounters a missing dependency while running `compartmentInstance.importNow(specifier)`, which cannot use an asynchronous `importHook`. diff --git a/packages/ses/README.md b/packages/ses/README.md index 10a2cdc8d1..c2cba8449d 100644 --- a/packages/ses/README.md +++ b/packages/ses/README.md @@ -169,7 +169,10 @@ function on `globalThis`. import 'ses'; const c = new Compartment({ - print: harden(console.log), + globals: { + print: harden(console.log), + }, + __options__: true, // temporary migration affordance }); c.evaluate(` @@ -213,7 +216,10 @@ through: * by assigning them to the compartment's `globalThis` after construction. ```js -const powerfulCompartment = new Compartment({ Math }); +const powerfulCompartment = new Compartment({ + globals: { Math }, + __options__: true, // temporary migration affordance +}); powerfulCompartment.globalThis.Date = Date; ``` @@ -286,7 +292,7 @@ specifier for another module from a referrer module and the import specifier. import 'ses'; import { ModuleSource } from '@endo/module-source'; -const c1 = new Compartment({}, {}, { +const c1 = new Compartment({ name: "first compartment", resolveHook: (moduleSpecifier, moduleReferrer) => { return resolve(moduleSpecifier, moduleReferrer); @@ -298,6 +304,7 @@ const c1 = new Compartment({}, {}, { source: new ModuleSource(moduleText, moduleLocation); }; }, + __options__: true, // temporary migration affordance }); ``` @@ -310,15 +317,17 @@ const c1 = new Compartment({}, {}, { A compartment can also link a module in another compartment. ```js -const c2 = new Compartment({}, { - 'c1': { - source: './main.js', - compartment: c1, - }, -}, { +const c2 = new Compartment({ name: "second compartment", + modules: { + 'c1': { + source: './main.js', + compartment: c1, + }, + }, resolveHook, importHook, + __options__: true, // temporary migration affordance }); ``` @@ -406,9 +415,10 @@ const importHook = async specifier => { throw new Error(`Cannot find module ${specifier}`); }; -const compartment = new Compartment({}, {}, { +const compartment = new Compartment({ resolveHook, importHook, + __options__: true, // temporary migration affordance }); ``` @@ -443,16 +453,18 @@ const moduleMapHook = moduleSpecifier => { } }; -const even = new Compartment({}, {}, { +const even = new Compartment({ resolveHook: nodeResolveHook, importHook: makeImportHook('https://example.com/even'), moduleMapHook, + __options__: true, // temporary migration affordance }); -const odd = new Compartment({}, {}, { +const odd = new Compartment({ resolveHook: nodeResolveHook, importHook: makeImportHook('https://example.com/odd'), moduleMapHook, + __options__: true, // temporary migration affordance }); ``` @@ -471,12 +483,13 @@ receive the same record type as from `importHook` or throw if it cannot. import 'ses'; import { ModuleSource } from '@endo/module-source'; -const compartment = new Compartment({}, { - c: { - source: new ModuleSource(''), - }, -}, { +const compartment = new Compartment({ name: "first compartment", + modules: { + c: { + source: new ModuleSource(''), + }, + }, resolveHook: (moduleSpecifier, moduleReferrer) => { return resolve(moduleSpecifier, moduleReferrer); }, @@ -495,6 +508,7 @@ const compartment = new Compartment({}, { source: new ModuleSource(moduleText, moduleLocation), }; }, + __options__: true, // temporary migration affordance }); //... | importHook | importNowHook await compartment.import('a'); //| called | not called @@ -595,7 +609,11 @@ not a module. ```js const transforms = [addCodeCoverageInstrumentation]; -const c = new Compartment({ console, coverage }, null, { transforms }); +const c = new Compartment({ + globals: { console, coverage }, + transforms, + __options__: true, // temporary migration affordance +}); c.evaluate('console.log("Hello");'); ``` @@ -633,8 +651,10 @@ instead of `transforms`. ```js const __shimTransforms__ = [addCoverage]; -const c = new Compartment({ console, coverage }, null, { +const c = new Compartment({ + globals: { console, coverage }, __shimTransforms__, + __options__: true, // temporary migration affordance }); c.evaluate('console.log("Hello");'); ``` @@ -741,11 +761,17 @@ abilities. lockdown(); const promise = new Promise(resolve => { - const compartmentA = new Compartment(harden({ resolve })); + const compartmentA = new Compartment({ + globals: harden({ resolve }), + __options__: true, // temporary migration affordance + }); compartmentA.evaluate(programA); }); -const compartmentB = new Compartment(harden({ promise })); +const compartmentB = new Compartment({ + globals: harden({ promise }), + __options__: true, // temporary migration affordance +}); compartmentB.evaluate(programB); ``` From 59327f04beec1d2e5caa1641239d56f4defa6ecb Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Fri, 26 Jul 2024 18:32:56 -0700 Subject: [PATCH 4/4] refactor(compartment-mapper): Adopt Compartment usage --- .../src/import-archive-lite.js | 28 +------------------ packages/compartment-mapper/src/link.js | 5 ++-- .../compartment-mapper/test/bundle.test.js | 10 +++++-- packages/compartment-mapper/test/main.test.js | 13 +++++---- 4 files changed, 20 insertions(+), 36 deletions(-) diff --git a/packages/compartment-mapper/src/import-archive-lite.js b/packages/compartment-mapper/src/import-archive-lite.js index 774e8e0773..6d48630f7d 100644 --- a/packages/compartment-mapper/src/import-archive-lite.js +++ b/packages/compartment-mapper/src/import-archive-lite.js @@ -230,32 +230,6 @@ const makeArchiveImportHookMaker = ( return makeImportHook; }; -/** - * Creates a fake module namespace object that passes a brand check. - * - * @param {typeof Compartment} Compartment - * @returns {ModuleExportsNamespace} - */ -const makeFauxModuleExportsNamespace = Compartment => { - const compartment = new Compartment( - {}, - {}, - { - resolveHook() { - return '.'; - }, - async importHook() { - return { - imports: [], - execute() {}, - exports: [], - }; - }, - }, - ); - return compartment.module('.'); -}; - // Have to give it a name to capture the external meaning of Compartment // Otherwise @param {typeof Compartment} takes the Compartment to mean // the const variable defined within the function. @@ -377,7 +351,7 @@ export const parseArchive = async ( languageForExtension, modules: Object.fromEntries( Object.keys(modules || {}).map(specifier => { - return [specifier, makeFauxModuleExportsNamespace(Compartment)]; + return [specifier, { namespace: {} }]; }), ), Compartment, diff --git a/packages/compartment-mapper/src/link.js b/packages/compartment-mapper/src/link.js index c417c85974..f080687565 100644 --- a/packages/compartment-mapper/src/link.js +++ b/packages/compartment-mapper/src/link.js @@ -449,13 +449,14 @@ export const link = ( scopes, ); - const compartment = new Compartment(create(null), undefined, { + const compartment = new Compartment({ + name: location, resolveHook, importHook, moduleMapHook, transforms, __shimTransforms__, - name: location, + __options__: true, }); if (!archiveOnly) { diff --git a/packages/compartment-mapper/test/bundle.test.js b/packages/compartment-mapper/test/bundle.test.js index 2fcf01e692..abc5fc0075 100644 --- a/packages/compartment-mapper/test/bundle.test.js +++ b/packages/compartment-mapper/test/bundle.test.js @@ -61,7 +61,10 @@ test('bundles work', async t => { const print = entry => { log.push(entry); }; - const compartment = new Compartment({ print }); + const compartment = new Compartment({ + globals: { print }, + __options__: true, + }); compartment.evaluate(bundle); t.deepEqual(log, expectedLog); }); @@ -93,7 +96,10 @@ test.failing('bundle cjs-compat', async t => { const print = entry => { log.push(entry); }; - const compartment = new Compartment({ print }); + const compartment = new Compartment({ + globals: { print }, + __options__: true, + }); compartment.evaluate(bundle); t.deepEqual(log, expectedLog); }); diff --git a/packages/compartment-mapper/test/main.test.js b/packages/compartment-mapper/test/main.test.js index 47e24585e6..8200845e4f 100644 --- a/packages/compartment-mapper/test/main.test.js +++ b/packages/compartment-mapper/test/main.test.js @@ -95,11 +95,14 @@ test('makeBundle / importArchive', async t => { const archiverBundle = await makeBundle(readPowers.read, archiverLocation); const archiverCompartment = new Compartment({ - TextEncoder, - TextDecoder, - URL, - // See https://github.com/Agoric/agoric-sdk/issues/9515 - assert: globalThis.assert, + globals: { + TextEncoder, + TextDecoder, + URL, + // See https://github.com/Agoric/agoric-sdk/issues/9515 + assert: globalThis.assert, + }, + __options__: true, }); const evasiveArchiverBundle = archiverBundle .replace(/(?