diff --git a/AGENTS.md b/AGENTS.md index bd1cea15fe..81f7db1a00 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,19 @@ This file provides conventions and constraints for AI agents working in this repository. +## House style + +Coding style rules that apply across the entire repository are documented in +[`docs/house-style/`](docs/house-style/). + +- [Arrow and method syntax over the `function` keyword](docs/house-style/function-keyword.md): + We do not use the `function` keyword in package sources except in specific + exception categories (constructor emulation, generators, vendored code, and a + few others). + Arrow functions and concise method syntax are the default. + Read the doc for the rationale (hardened-JS hazards) and the full exception + list. + ## Repository structure - Monorepo managed with Yarn workspaces diff --git a/docs/house-style/function-keyword.md b/docs/house-style/function-keyword.md new file mode 100644 index 0000000000..2212a3c3ad --- /dev/null +++ b/docs/house-style/function-keyword.md @@ -0,0 +1,264 @@ +# House style: arrow and method syntax over the `function` keyword + +We do not use the `function` keyword in this repository's package sources +except in the specific categories listed under [Legitimate exceptions](#legitimate-exceptions). +New code uses arrow functions or concise method syntax instead. + +## Rationale + +`function`-keyword functions carry four distinct hazards inside +hardened-JavaScript code: + +1. They have both `[[Construct]]` and `[[Call]]` behaviors, so they can be + invoked with `new` even when the author never intended a constructor. +2. They have an initial `prototype` property that points at an irrelevant + prototype object. +3. Because of that extra object, `freeze` is not equivalent to `harden`; + the prototype object remains mutable and leaves hazardous reachable state. +4. Function-keyword declarations additionally have hoisting hazards. + A function declaration `f` is hoisted and fully initialized before the + module body runs — it has no temporal dead zone — masking + initialization-order bugs. For example, a `harden(f)` or `freeze(f)` + immediately after the function-keyword declaration of `f` does not + prevent other code from mutating `f` before it is frozen. + - This hazard exists even among code within one module, though these + are less urgent because the eslint `no-use-before-define` rule + reliably flags these hazards. + - For an `export function f` exported function-keyword function + declaration `f` in an import cycle, the importer can observe the + function as a value before the rest of the exporting module has run. + This case cannot be reliably avoided by other means. + +The arrow function `() => {}` form has none of these hazards: no +`[[Construct]]`, no `prototype`, no early initialization (a `const` binding +stays in its temporal dead zone until evaluated), and `freeze` is equivalent to +`harden`. An arrow function lexically binds `this`, meaning that it is +insensitive to the `this`-binding provided by its callers. +Concise-method syntax (`{ name() {} }`, `{ get name() {} }`, +`{ set name(v) {} }`) likewise has no `[[Construct]]` and no `prototype`, +while being sensitive to the `this`-binding provided by its callers. + +This rule was codified following erights's review on +[endojs/endo-but-for-bots#468](https://github.com/endojs/endo-but-for-bots/pull/468#issuecomment-3439684004). +The conversion itself landed on +[endojs/endo-but-for-bots#474](https://github.com/endojs/endo-but-for-bots/pull/474). + +## Conversion rules + +- Use an arrow function (`(...) => {}`) when the function does not use `this` + and is never called with `new`. +- Use concise method syntax (`{ name(...) {} }`, `{ get name() {} }`, + `{ set name(v) {} }`) when the function uses `this` (or `super`) but is + never called with `new`. + For a prototype monkey-patch that needs the method's `name` to surface in + stack traces and diagnostics, write the methods as concise methods on an + object literal and assign them onto the prototype (see + `packages/init/src/node-async-local-storage-patch.js`): concise methods retain + `name` while having no `[[Construct]]` and no `prototype`, so a named + function expression is not needed for this case. +- Use a concise generator method (`{ *name() {} }`) or concise async-generator + method (`{ async *name() {} }`) for generators and async generators: concise + method syntax can spell both, so the `function*`/`async function*` keyword is + not required to write one. +- Leave the `function` keyword in place for the legitimate-exception categories + listed below. + +The net behavioral diff when converting is intended to be zero: every +conversion preserves arity, return value, and `this` binding. +Hoisting changes from converting declarations are intentional (that is part of +the goal) but must not break existing call sites. + +## Legitimate exceptions + +The following uses of the `function` keyword stay in place. + +### Constructor emulation + +When the function is invoked with `new` (or is intended to be invokable with +`new`) to emulate a built-in constructor or a class constructor that +legitimately needs `[[Construct]]` and a `prototype` property: + +- `packages/immutable-arraybuffer/src/lib.js`: `function PseudoTypedArray`, + emulates a built-in TypedArray constructor (uses `new.target`, + `construct(...)`, and exposes `prototype`). +- `packages/eventual-send/src/handled-promise.js`: + `function BaseHandledPromise`, which the author already documented as + "*needs* to be a `function X` so that we can use it as a constructor" + (uses `new.target`). +- `packages/ses/src/tame-function-constructors.js`: + `const InertConstructor = function () { throw TypeError(...) }`. + The inert-constructor pattern depends on the function having `[[Construct]]` + and a writable `prototype` property so SES can rewire it to point at the + original constructor's prototype. +- Similar inert-constructor patterns inside SES's `tame-date-constructor`, + `tame-regexp-constructor`, `tame-error-constructor`, + `tame-v8-error-constructor`, `tame-symbol-constructor`, + `make-function-constructor`. + Each one is replacing a built-in constructor; the replacement must itself be + a constructor. + +### Standalone generator and async-generator expressions + +Concise method syntax can spell a generator (`{ *name() {} }`) or an async +generator (`{ async *name() {} }`), so the `function*`/`async function*` keyword +is **not** the only way to write one. +Prefer a concise generator method, consistent with the rest of this house style; +reserve the keyword form for the standalone cases below. + +The hazard profile is the same for both spellings: a generator (in either form) +cannot be invoked with `new` (the spec marks them non-constructable), so the +`[[Construct]]` hazard does not apply, but it still carries a `prototype` +property pointing at the generator's prototype, so `freeze` is not equivalent to +`harden` and the author must harden the wrapping closure, not just freeze it. +Concise method syntax does not remove that `prototype`; it only drops the +`function` keyword, which is why the preference is about house-style consistency +rather than a change in hazard. + +The `function*`/`async function*` keyword stays in place in two situations: an +anonymous generator used only to reach an intrinsic generator prototype, and a +standalone top-level generator *declaration* that is not naturally a member of an +object. In both situations the generator is not an object member, so the only +keyword-free spelling would be to wrap it in an object literal and immediately +extract the method (`{ *name() {} }.name`); for these cases that wrapper is pure +indirection with no readability gain, so the keyword stays. + +Intrinsic-prototype sentinels: + +- `packages/trampoline/src/trampoline.js`: `function* () {}` sentinel, used only + to extract the intrinsic generator prototype via `getPrototypeOf`. +- `packages/ses/src/commons.js` and `get-anonymous-intrinsics.js`: the same + intrinsic-extraction pattern (including the Hermes async-generator + feature-detection sentinel in `commons.js`). + +Standalone top-level generator declarations: + +- `packages/compartment-mapper/src/`: `function* enumerate` + (`compartment-map.js`), `function* chooseModuleDescriptor` (`import-hook.js`), + `function* getParserGenerator` (`map-parser.js`), and the `function*` + declarations in `infer-exports.js` (`interpretBrowserField`, + `interpretExports`, `interpretImports`, and the exported + `inferExportsEntries`). These are module-level helper declarations, not members + of any object. +- `packages/ses/src/module-load.js`: `function* loadWithoutErrorAnnotation`, a + module-level declaration. + +Generators that *are* naturally object members have been converted to concise +generator methods in this repository, so the keyword form is reserved for the +standalone cases above. The conversions include `packages/captp/src/atomics.js`'s +`trapHost` (now a concise `async *trapHost()` method on a hardened object) and the +`async function*` subscription generators in `packages/daemon/src/` — the +`followNameChanges`, `followIdNameChanges`, `followLocatorNameChanges`, and +`followMessages` generators in `pet-sitter.js`, `pet-store.js`, `mail.js`, +`directory.js`, and `daemon.js`, plus the `generateNumbers` connection-number +counters in `daemon-node-powers.js`, `networks/tcp-netstring.js`, and +`web-server-node.js`. Each was a `const` bound to a named generator expression and +later assembled into a returned or `Far()`/`makeExo`-wrapped object; each is now +written as a concise generator method on an object literal, preserving the `const` +binding, the generator's name, and its JSDoc `@type` annotation while dropping the +`function*` keyword. + +### Vendored or third-party-derived code + +Code we received from upstream projects and only lightly modify keeps the +upstream style so future merges remain tractable: + +- `packages/cjs-module-analyzer/index.js`: a port of `es-module-lexer` by + Guy Bedford. + The file uses around 38 inner `function` declarations as a single-pass lexer + with mutual recursion; `no-use-before-define` is intentionally disabled. + Converting these to arrows would force a manual reorder and risk a + performance regression in a hot path. +- `packages/test262-runner/test262/`: the upstream tc39/test262 suite, + vendored under the tc39 LICENSE. + Out of scope by license. + +### Sloppy-mode `this` detection + +`packages/ses/src/assert-sloppy-mode.js`: +`function getThis() { return this; }`. +The whole point of this function is that, called as a bare `getThis()`, it +returns the calling-context `this` (which is `globalThis` in sloppy mode and +`undefined` in strict mode), so SES can detect the ambient strictness. +An **arrow** function is disqualified: it binds `this` lexically — insensitive +to the caller — so it would return the module-scope `this` (always `undefined` +under modules) and defeat the check. +A **concise method**, by contrast, is sensitive to the caller-provided `this` +in the same way a function-keyword function is, so it would in fact work here. +We keep the `function`-keyword declaration only because this is a +security-critical SES-initialization tripwire where the bare +`function getThis() { return this; }` is the canonical, well-understood spelling +of an ambient-`this` probe; wrapping it in an object literal solely to extract a +concise method (`{ getThis() {} }.getThis`) would add indirection to a security +boundary for a purely stylistic gain. + +### Static-checker limitations: suppress, do not keep the runtime hazard + +When a TypeScript or lint limitation appears to push toward retaining the +`function` keyword, prefer the less-hazardous runtime form (arrow or concise +method) and suppress the checker with `@ts-expect-error` or `@ts-ignore`, rather +than keeping the hazardous runtime form in order to satisfy the checker. +The runtime behavior is what counts; a static-checker weakness is the cheaper +thing to work around. + +A motivating case was TypeScript assertion functions +(`@returns {asserts x is Y}`). +It is sometimes assumed that an assertion function must be a `function` +declaration and that converting it to an arrow drops the `asserts` narrowing +(the compiler's TS2775, "Assertions require every name in the call target to be +declared with an explicit type annotation"). +That turns out not to be so: an arrow function with a JSDoc +`@returns {asserts ...}` annotation — plus, where an implementation-only +parameter must be hidden from the public signature, a JSDoc `@overload` block, +which attaches to a `const` arrow just as it does to a declaration — carries the +assertion narrowing with no suppression at all. +`packages/compartment-mapper/src/compartment-map.js`'s +`assertModuleConfiguration` is written this way (arrow plus `@overload`), so it +needs neither the `function` keyword nor an `@ts-expect-error`. +If a genuine checker limitation ever does block such a conversion, reach for +`@ts-expect-error`/`@ts-ignore` before reaching for the `function` keyword. + +### Module-init-time forward references + +If the function is referenced by name during module top-level evaluation +(not from inside another function body), converting the declaration to a +`const` arrow puts the reference into TDZ. +We do not reorder the file to work around this; we keep the `function` +declaration and add a note. +Concrete sites: + +- `packages/captp/src/captp.js`: `convertValToSlot` and `convertSlotToVal` + are passed as arguments to `makeMarshal(...)` during module init from + hundreds of lines earlier than their declarations. +- `packages/ocapn/src/client/ocapn.js`: `function serializeAndSendMessage` + is passed into `makeOcapnCommsKit({...rawSend: serializeAndSendMessage})` + during module init before its declaration. +- `packages/eslint-plugin/lib/rules/assert-fail-as-throw.js`: top-level + `safeRequire(...)` calls at file head precede `function safeRequire`'s + declaration further down. + The file is adopted from mysticatea/eslint-plugin-node with an explicit + `/* eslint-disable no-use-before-define */` at the top to permit the + hoisting; converting it would force a full reorder of code we want to keep + diff-tractable against upstream. + +### Vendored runtime template literals + +The bundler in `packages/compartment-mapper/src/bundle-mjs.js` and +`packages/compartment-mapper/src/bundle-cjs.js` builds output JavaScript from +template-literal `runtime` strings. +The `function observeImports` and `function wrapCjsFunctor` inside those +strings are the bundler's output code, not module-side code, and are out of +scope. + +## Applying this rule to new code + +When writing a new function: + +1. Does it need `[[Construct]]` (called with `new`) or a `prototype` property? + Use a `class` or a `function`-keyword function, and document why. +2. Does it need to be sensitive to the `this`-binding provided by its callers? + Use concise method syntax inside an object literal or class body. +3. Otherwise, use an arrow function. + +When converting existing `function`-keyword code, verify that the conversion +falls outside all exception categories above, run `yarn test` for the affected +package, and confirm the behavioral diff is zero. diff --git a/packages/benchmark/src/benchmark.js b/packages/benchmark/src/benchmark.js index b00a681328..e90f3e02dc 100644 --- a/packages/benchmark/src/benchmark.js +++ b/packages/benchmark/src/benchmark.js @@ -1,6 +1,6 @@ const getTime = () => Date.now() * 1_000_000; -async function benchmark(name, t, fn, expedtedTime, iterations = 10_000) { +const benchmark = async (name, t, fn, expectedTime, iterations = 10_000) => { await null; const start = getTime(); for (let i = 0; i < iterations; i += 1) { @@ -12,20 +12,20 @@ async function benchmark(name, t, fn, expedtedTime, iterations = 10_000) { console.log(`${name} | Average time: ${avgTime}ns`); t.assert( - avgTime < Number(expedtedTime), - `Expected ${avgTime} to be less than ${expedtedTime}`, + avgTime < Number(expectedTime), + `Expected ${avgTime} to be less than ${expectedTime}`, ); -} +}; -function assert(condition, message = 'Assertion failed') { +const assert = (condition, message = 'Assertion failed') => { if (!condition) throw Error(message); -} +}; -function truthy(value, message = 'Expected a truthy value') { +const truthy = (value, message = 'Expected a truthy value') => { if (!value) throw Error(message); -} +}; -async function test(name, fn) { +const test = async (name, fn) => { await null; try { console.log('Running test: ', name); @@ -34,6 +34,6 @@ async function test(name, fn) { } catch (err) { console.log(`❌ Failed: ${/** @type {Error} */ (err).message}`); } -} +}; export { benchmark, test }; diff --git a/packages/bundle-source/src/script.js b/packages/bundle-source/src/script.js index b144d766ec..6c7c59156d 100644 --- a/packages/bundle-source/src/script.js +++ b/packages/bundle-source/src/script.js @@ -23,12 +23,12 @@ const readPowers = makeReadPowers({ fs, url, crypto }); * @param {BundleScriptOptions} [options] * @param {SharedPowers} [grantedPowers] */ -export async function bundleScript( +export const bundleScript = async ( startFilename, moduleFormat, options = {}, grantedPowers = {}, -) { +) => { const { dev = false, cacheSourceMaps = false, @@ -126,4 +126,4 @@ export async function bundleScript( // TODO sourceMap: '', }); -} +}; diff --git a/packages/bundle-source/src/zip-base64.js b/packages/bundle-source/src/zip-base64.js index 1a9b37e212..40d0ad3f74 100644 --- a/packages/bundle-source/src/zip-base64.js +++ b/packages/bundle-source/src/zip-base64.js @@ -24,11 +24,11 @@ const readPowers = makeReadPowers({ fs, url, crypto }); * @param {BundleZipBase64Options} [options] * @param {SharedPowers} [grantedPowers] */ -export async function bundleZipBase64( +export const bundleZipBase64 = async ( startFilename, options = {}, grantedPowers = {}, -) { +) => { const { dev = false, cacheSourceMaps = false, @@ -104,4 +104,4 @@ export async function bundleZipBase64( endoZipBase64, endoZipBase64Sha512: sha512, }); -} +}; diff --git a/packages/captp/src/atomics.js b/packages/captp/src/atomics.js index 274da96982..007d2770b8 100644 --- a/packages/captp/src/atomics.js +++ b/packages/captp/src/atomics.js @@ -55,45 +55,47 @@ export const makeAtomicsTrapHost = transferBuffer => { const te = new TextEncoder(); - return harden(async function* trapHost([isReject, serialized]) { - // Get the complete encoded message buffer. - const json = JSON.stringify(serialized); - const encoded = te.encode(json); - - // Send chunks in the data transfer buffer. - let i = 0; - let done = false; - while (!done) { - // Copy the next slice of the encoded arry to the data buffer. - const subenc = encoded.subarray(i, i + databuf.length); - databuf.set(subenc); - - // Save the length of the remaining data. - const remaining = BigInt(encoded.length - i); - lenbuf[0] = remaining; - - // Calculate the next slice, and whether this is the last one. - i += subenc.length; - done = i >= encoded.length; - - // Find bitflags to represent the rejected and finished state. - const rejectFlag = isReject ? STATUS_FLAG_REJECT : 0; - const doneFlag = done ? STATUS_FLAG_DONE : 0; - - // Notify our guest for this data buffer. - - // eslint-disable-next-line no-bitwise - statusbuf[0] = rejectFlag | doneFlag; - Atomics.notify(statusbuf, 0, +Infinity); - - if (!done) { - // Wait until the next call to `it.next()`. If the guest calls - // `it.return()` or `it.throw()`, then this yield will return or throw, - // terminating the generator function early. - yield; + return harden({ + async *trapHost([isReject, serialized]) { + // Get the complete encoded message buffer. + const json = JSON.stringify(serialized); + const encoded = te.encode(json); + + // Send chunks in the data transfer buffer. + let i = 0; + let done = false; + while (!done) { + // Copy the next slice of the encoded arry to the data buffer. + const subenc = encoded.subarray(i, i + databuf.length); + databuf.set(subenc); + + // Save the length of the remaining data. + const remaining = BigInt(encoded.length - i); + lenbuf[0] = remaining; + + // Calculate the next slice, and whether this is the last one. + i += subenc.length; + done = i >= encoded.length; + + // Find bitflags to represent the rejected and finished state. + const rejectFlag = isReject ? STATUS_FLAG_REJECT : 0; + const doneFlag = done ? STATUS_FLAG_DONE : 0; + + // Notify our guest for this data buffer. + + // eslint-disable-next-line no-bitwise + statusbuf[0] = rejectFlag | doneFlag; + Atomics.notify(statusbuf, 0, +Infinity); + + if (!done) { + // Wait until the next call to `it.next()`. If the guest calls + // `it.return()` or `it.throw()`, then this yield will return or throw, + // terminating the generator function early. + yield; + } } - } - }); + }, + }).trapHost; }; /** diff --git a/packages/captp/src/captp.js b/packages/captp/src/captp.js index bec0182485..697594d2a3 100644 --- a/packages/captp/src/captp.js +++ b/packages/captp/src/captp.js @@ -458,6 +458,10 @@ export const makeCapTP = ( * * @type {import('@endo/marshal').ConvertValToSlot} */ + // Retains the `function` keyword by deliberate exception: it is passed to + // `makeMarshal(...)` during module init, before its declaration, so a `const` + // arrow would be in its temporal dead zone. See + // docs/house-style/function-keyword.md. function convertValToSlot(val) { if (!valToSlot.has(val)) { /** @type {CapTPSlot} */ @@ -563,6 +567,10 @@ export const makeCapTP = ( * * @type {import('@endo/marshal').ConvertSlotToVal} */ + // Retains the `function` keyword by deliberate exception: it is passed to + // `makeMarshal(...)` during module init, before its declaration, so a `const` + // arrow would be in its temporal dead zone. See + // docs/house-style/function-keyword.md. function convertSlotToVal(theirSlot, iface = undefined) { const slot = reverseSlot(theirSlot); diff --git a/packages/cli/src/prompt.js b/packages/cli/src/prompt.js index 8f339db063..98da5591b7 100644 --- a/packages/cli/src/prompt.js +++ b/packages/cli/src/prompt.js @@ -8,7 +8,7 @@ import readline from 'readline'; * @param {string} question - The question to ask the user. * @returns {Promise} The user's answer. */ -export async function prompt(question) { +export const prompt = async question => { const rl = readline.createInterface({ input: stdin, output: stdout, @@ -20,4 +20,4 @@ export async function prompt(question) { resolve(answer.trim().toLowerCase()); }); }); -} +}; diff --git a/packages/compartment-mapper/src/compartment-map.js b/packages/compartment-mapper/src/compartment-map.js index 17682ba138..73f85aae74 100644 --- a/packages/compartment-mapper/src/compartment-map.js +++ b/packages/compartment-mapper/src/compartment-map.js @@ -275,14 +275,14 @@ const assertErrorModuleConfiguration = (moduleDescriptor, keypath, url) => { * @param {string} url * @returns {asserts allegedModule is ModuleConfiguration} */ - /** * @param {unknown} allegedModule * @param {string} keypath * @param {string} url - * @param {ModuleConfigurationKind[]} kinds + * @param {ModuleConfigurationKind[]} [kinds] + * @returns {asserts allegedModule is ModuleConfiguration} */ -function assertModuleConfiguration(allegedModule, keypath, url, kinds) { +const assertModuleConfiguration = (allegedModule, keypath, url, kinds = []) => { assertPlainObject(allegedModule, keypath, url); assertBaseModuleConfiguration(allegedModule, keypath, url); @@ -340,7 +340,7 @@ function assertModuleConfiguration(allegedModule, keypath, url, kinds) { errors.length < finalKinds.length || Fail`invalid module descriptor in ${q(url)} at ${q(keypath)}; expected to match one of ${q(kinds)}: ${errors.map(err => err.message).join('; ')}`; -} +}; /** * @param {unknown} allegedModules diff --git a/packages/compartment-mapper/src/import-hook.js b/packages/compartment-mapper/src/import-hook.js index fb3c040af3..50c2b212f6 100644 --- a/packages/compartment-mapper/src/import-hook.js +++ b/packages/compartment-mapper/src/import-hook.js @@ -97,10 +97,12 @@ const noop = () => {}; const resolveLocation = (rel, abs) => /** @type {FileUrlString} */ (new URL(rel, abs).toString()); -// this is annoying -function getImportsFromRecord(record) { - return (has(record, 'record') ? record.record.imports : record.imports) || []; -} +// A record arrives in one of two shapes — either wrapped, with the payload +// under a nested `record` property, or unwrapped — so we must probe for +// `record.record` before reading `imports`. That dual shape is the annoying +// part, and it is unchanged by the arrow/`function`-keyword spelling. +const getImportsFromRecord = record => + (has(record, 'record') ? record.record.imports : record.imports) || []; // Node.js default resolution allows for an incomplement specifier that does not include a suffix. // https://nodejs.org/api/modules.html#all-together @@ -756,7 +758,7 @@ export const makeImportHookMaker = ( * @param {MakeImportNowHookMakerOptions} options * @returns {ImportNowHookMaker} */ -export function makeImportNowHookMaker( +export const makeImportNowHookMaker = ( readPowers, baseLocation, { @@ -770,7 +772,7 @@ export function makeImportNowHookMaker( moduleSourceHook, log = noop, }, -) { +) => { // Set of specifiers for modules (scoped to compartment) whose parser is not // using heuristics to determine imports. /** @type {Map>} compartment name ->* module specifier */ @@ -849,7 +851,7 @@ export function makeImportNowHookMaker( }; if (!isSyncParseFn(parse)) { - return function impossibleTransformImportNowHook() { + return () => { throw new Error( 'Dynamic requires are only possible with synchronous parsers and no asynchronous module transforms in options', ); @@ -952,4 +954,4 @@ export function makeImportNowHookMaker( return importNowHook; }; return makeImportNowHook; -} +}; diff --git a/packages/compartment-mapper/src/link.js b/packages/compartment-mapper/src/link.js index a880bf0e73..2b0bfbb138 100644 --- a/packages/compartment-mapper/src/link.js +++ b/packages/compartment-mapper/src/link.js @@ -330,10 +330,8 @@ const makeModuleMapHook = ( /** * @type {ImportNowHookMaker} */ -const impossibleImportNowHookMaker = () => { - return function impossibleImportNowHook() { - throw new Error('Provided read powers do not support dynamic requires'); - }; +const impossibleImportNowHookMaker = () => () => { + throw new Error('Provided read powers do not support dynamic requires'); }; /** diff --git a/packages/compartment-mapper/src/policy.js b/packages/compartment-mapper/src/policy.js index fa198ee237..d202cc2087 100644 --- a/packages/compartment-mapper/src/policy.js +++ b/packages/compartment-mapper/src/policy.js @@ -279,12 +279,12 @@ export const makeDeferredAttenuatorsProvider = ( * @param {object} options.globalThis * @param {object} options.globals */ -async function attenuateGlobalThis({ +const attenuateGlobalThis = async ({ attenuators, attenuationDefinition, globalThis, globals, -}) { +}) => { const attenuate = await importAttenuatorForDefinition( attenuationDefinition, attenuators, @@ -307,7 +307,7 @@ async function attenuateGlobalThis({ if (typeof result === 'object' && result !== null) { assign(globalThis, result); } -} +}; /** * Filters available globals and returns a copy according to the policy @@ -497,11 +497,11 @@ export const enforcePackagePolicyByCanonicalName = ( * @param {VirtualModuleSource} options.moduleSource * @returns {Promise} */ -async function attenuateVirtualModuleSource({ +const attenuateVirtualModuleSource = async ({ attenuators, attenuationDefinition, moduleSource, -}) { +}) => { const attenuate = await importAttenuatorForDefinition( attenuationDefinition, attenuators, @@ -528,7 +528,7 @@ async function attenuateVirtualModuleSource({ }, }), ); -} +}; /** * Attenuates a module descriptor whose source is a virtual module source. @@ -543,11 +543,11 @@ async function attenuateVirtualModuleSource({ * @param {VirtualModuleSource | SourceModuleDescriptor} options.moduleDescriptor * @returns {Promise} */ -async function attenuateModule({ +const attenuateModule = async ({ attenuators, attenuationDefinition, moduleDescriptor, -}) { +}) => { await null; if ('source' in moduleDescriptor) { const { source: moduleSource } = moduleDescriptor; @@ -587,7 +587,7 @@ async function attenuateModule({ moduleDescriptor, )}`, ); -} +}; /** * Throws if importing of the specifier is not allowed by the policy diff --git a/packages/daemon/src/daemon-node-powers.js b/packages/daemon/src/daemon-node-powers.js index c030acb28a..9195da77e6 100644 --- a/packages/daemon/src/daemon-node-powers.js +++ b/packages/daemon/src/daemon-node-powers.js @@ -138,13 +138,15 @@ export const makeSocketPowers = ({ net, fsp: { access } }) => { export const makeNetworkPowers = ({ net, fsp }) => { const { servePort, servePath, connectPort } = makeSocketPowers({ net, fsp }); - const connectionNumbers = (function* generateNumbers() { - let n = 0; - for (;;) { - yield n; - n += 1; - } - })(); + const connectionNumbers = { + *generateNumbers() { + let n = 0; + for (;;) { + yield n; + n += 1; + } + }, + }.generateNumbers(); /** * @param {FarRef} endoBootstrap diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index 12b923d0a9..2566afd099 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -835,19 +835,19 @@ const makeDaemonCore = async ( ); }; - const followLocatorNameChanges = async function* followLocatorNameChanges( - locator, - ) { - const id = idFromLocator(locator); - const names = mailboxStore - .reverseIdentify(id) - .filter(isMessageNumberName); - if (names.length === 0) { + const followLocatorNameChanges = { + async *followLocatorNameChanges(locator) { + const id = idFromLocator(locator); + const names = mailboxStore + .reverseIdentify(id) + .filter(isMessageNumberName); + if (names.length === 0) { + return undefined; + } + yield { add: locator, names }; return undefined; - } - yield { add: locator, names }; - return undefined; - }; + }, + }.followLocatorNameChanges; const list = async (...petNamePath) => { assertNames(petNamePath); @@ -873,27 +873,27 @@ const makeDaemonCore = async ( return harden(Array.from(identities).sort()); }; - const followNameChanges = async function* followNameChanges( - ...petNamePath - ) { - await null; - assertNames(petNamePath); - if (petNamePath.length === 0) { - for await (const change of mailboxStore.followNameChanges()) { - if ('add' in change) { - if (isMessageNumberName(change.add)) { + const followNameChanges = { + async *followNameChanges(...petNamePath) { + await null; + assertNames(petNamePath); + if (petNamePath.length === 0) { + for await (const change of mailboxStore.followNameChanges()) { + if ('add' in change) { + if (isMessageNumberName(change.add)) { + yield change; + } + } else if (isMessageNumberName(change.remove)) { yield change; } - } else if (isMessageNumberName(change.remove)) { - yield change; } + return undefined; } + const hub = /** @type {NameHub} */ (await lookup(petNamePath)); + yield* await E(hub).followNameChanges(); return undefined; - } - const hub = /** @type {NameHub} */ (await lookup(petNamePath)); - yield* await E(hub).followNameChanges(); - return undefined; - }; + }, + }.followNameChanges; const reverseLookup = presence => { const id = getIdForRef(presence); @@ -1114,19 +1114,19 @@ const makeDaemonCore = async ( ); }; - const followLocatorNameChanges = async function* followLocatorNameChanges( - locator, - ) { - const id = idFromLocator(locator); - const locatorNames = orderedNames.filter( - name => idByName.get(name) === id, - ); - if (locatorNames.length === 0) { + const followLocatorNameChanges = { + async *followLocatorNameChanges(locator) { + const id = idFromLocator(locator); + const locatorNames = orderedNames.filter( + name => idByName.get(name) === id, + ); + if (locatorNames.length === 0) { + return undefined; + } + yield { add: locator, names: /** @type {Name[]} */ (locatorNames) }; return undefined; - } - yield { add: locator, names: /** @type {Name[]} */ (locatorNames) }; - return undefined; - }; + }, + }.followLocatorNameChanges; const list = async (...petNamePath) => { assertNames(petNamePath); @@ -1152,23 +1152,23 @@ const makeDaemonCore = async ( return harden(Array.from(identities).sort()); }; - const followNameChanges = async function* followNameChanges( - ...petNamePath - ) { - assertNames(petNamePath); - if (petNamePath.length === 0) { - for (const name of orderedNames) { - const id = idByName.get(name); - if (id !== undefined) { - yield { add: /** @type {Name} */ (name), value: parseId(id) }; + const followNameChanges = { + async *followNameChanges(...petNamePath) { + assertNames(petNamePath); + if (petNamePath.length === 0) { + for (const name of orderedNames) { + const id = idByName.get(name); + if (id !== undefined) { + yield { add: /** @type {Name} */ (name), value: parseId(id) }; + } } + return undefined; } + const hub = /** @type {NameHub} */ (await lookup(petNamePath)); + yield* await E(hub).followNameChanges(); return undefined; - } - const hub = /** @type {NameHub} */ (await lookup(petNamePath)); - yield* await E(hub).followNameChanges(); - return undefined; - }; + }, + }.followNameChanges; const reverseLookup = presence => { const id = getIdForRef(presence); @@ -1998,7 +1998,7 @@ const makeDaemonCore = async ( }; /** @type {DaemonCore['formulateMarshalValue']} */ - async function formulateMarshalValue(value, deferredTasks) { + const formulateMarshalValue = async (value, deferredTasks) => { const { marshalFormulaNumber } = await formulaGraphJobs.enqueue( async () => { const ownFormulaNumber = /** @type {FormulaNumber} */ ( @@ -2030,7 +2030,7 @@ const makeDaemonCore = async ( return /** @type {FormulateResult} */ ( formulate(marshalFormulaNumber, formula) ); - } + }; /** @type {DaemonCore['formulatePromise']} */ const formulatePromise = async () => { diff --git a/packages/daemon/src/directory.js b/packages/daemon/src/directory.js index 04d36cc19b..8212bbe12c 100644 --- a/packages/daemon/src/directory.js +++ b/packages/daemon/src/directory.js @@ -125,22 +125,23 @@ export const makeDirectoryMaker = ({ }; /** @type {EndoDirectory['followLocatorNameChanges']} */ - const followLocatorNameChanges = async function* followLocatorNameChanges( - locator, - ) { - const id = idFromLocator(locator); - for await (const idNameChange of petStore.followIdNameChanges(id)) { - /** @type {any} */ - const locatorNameChange = { - ...idNameChange, - ...(Object.hasOwn(idNameChange, 'add') - ? { add: locator } - : { remove: locator }), - }; + const followLocatorNameChanges = { + /** @returns {ReturnType} */ + async *followLocatorNameChanges(locator) { + const id = idFromLocator(locator); + for await (const idNameChange of petStore.followIdNameChanges(id)) { + /** @type {any} */ + const locatorNameChange = { + ...idNameChange, + ...(Object.hasOwn(idNameChange, 'add') + ? { add: locator } + : { remove: locator }), + }; - yield /** @type {LocatorNameChange} */ (locatorNameChange); - } - }; + yield /** @type {LocatorNameChange} */ (locatorNameChange); + } + }, + }.followLocatorNameChanges; /** @type {EndoDirectory['list']} */ const list = async (...petNamePath) => { @@ -169,17 +170,18 @@ export const makeDirectoryMaker = ({ }; /** @type {EndoDirectory['followNameChanges']} */ - const followNameChanges = async function* followNameChanges( - ...petNamePath - ) { - assertNames(petNamePath); - if (petNamePath.length === 0) { - yield* petStore.followNameChanges(); - return; - } - const hub = /** @type {NameHub} */ (await lookup(petNamePath)); - yield* await E(hub).followNameChanges(); - }; + const followNameChanges = { + /** @returns {ReturnType} */ + async *followNameChanges(...petNamePath) { + assertNames(petNamePath); + if (petNamePath.length === 0) { + yield* petStore.followNameChanges(); + return; + } + const hub = /** @type {NameHub} */ (await lookup(petNamePath)); + yield* await E(hub).followNameChanges(); + }, + }.followNameChanges; /** @type {EndoDirectory['remove']} */ const remove = async (...petNamePath) => { diff --git a/packages/daemon/src/mail.js b/packages/daemon/src/mail.js index e059dd6fb3..7d64f717fe 100644 --- a/packages/daemon/src/mail.js +++ b/packages/daemon/src/mail.js @@ -142,11 +142,14 @@ export const makeMailboxMaker = ({ const listMessages = async () => harden(Array.from(messages.values())); /** @type {Mail['followMessages']} */ - const followMessages = async function* currentAndSubsequentMessages() { - const subsequentRequests = messagesTopic.subscribe(); - yield* messages.values(); - yield* subsequentRequests; - }; + const followMessages = { + /** @returns {ReturnType} */ + async *currentAndSubsequentMessages() { + const subsequentRequests = messagesTopic.subscribe(); + yield* messages.values(); + yield* subsequentRequests; + }, + }.currentAndSubsequentMessages; /** * @param {string} description diff --git a/packages/daemon/src/networks/tcp-netstring.js b/packages/daemon/src/networks/tcp-netstring.js index 2e1623fff1..c0e6ed3ebe 100644 --- a/packages/daemon/src/networks/tcp-netstring.js +++ b/packages/daemon/src/networks/tcp-netstring.js @@ -35,13 +35,15 @@ export const make = async (powers, context) => { // default: '8080', // }); - const connectionNumbers = (function* generateNumbers() { - let n = 0; - for (;;) { - yield n; - n += 1; - } - })(); + const connectionNumbers = { + *generateNumbers() { + let n = 0; + for (;;) { + yield n; + n += 1; + } + }, + }.generateNumbers(); /** @type {Set>} */ const connectionClosedPromises = new Set(); diff --git a/packages/daemon/src/pet-sitter.js b/packages/daemon/src/pet-sitter.js index 105746142a..81ab66d040 100644 --- a/packages/daemon/src/pet-sitter.js +++ b/packages/daemon/src/pet-sitter.js @@ -52,37 +52,43 @@ export const makePetSitter = (petStore, specialNames) => { ); /** @type {PetStore['followNameChanges']} */ - const followNameChanges = async function* currentAndSubsequentNames() { - for (const name of Object.keys(specialNames).sort()) { - const idRecord = idRecordForName(name); - yield /** @type {{ add: Name, value: IdRecord }} */ ({ - add: /** @type {Name} */ (name), - value: idRecord, - }); - } - yield* petStore.followNameChanges(); - }; + const followNameChanges = { + /** @returns {ReturnType} */ + async *currentAndSubsequentNames() { + for (const name of Object.keys(specialNames).sort()) { + const idRecord = idRecordForName(name); + yield /** @type {{ add: Name, value: IdRecord }} */ ({ + add: /** @type {Name} */ (name), + value: idRecord, + }); + } + yield* petStore.followNameChanges(); + }, + }.currentAndSubsequentNames; /** @type {PetStore['followIdNameChanges']} */ - const followIdNameChanges = async function* currentAndSubsequentIds(id) { - const subscription = petStore.followIdNameChanges(id); + const followIdNameChanges = { + /** @returns {ReturnType} */ + async *currentAndSubsequentIds(id) { + const subscription = petStore.followIdNameChanges(id); - const idSpecialNames = /** @type {Name[]} */ ( - Object.entries(specialNames) - .filter(([_, specialId]) => specialId === id) - .map(([specialName, _]) => specialName) - ); + const idSpecialNames = /** @type {Name[]} */ ( + Object.entries(specialNames) + .filter(([_, specialId]) => specialId === id) + .map(([specialName, _]) => specialName) + ); - // The first published event contains the existing names for the id, if any. - const { value: existingNames } = await subscription.next(); - if (existingNames?.names) { - existingNames.names.unshift(...idSpecialNames); - } - existingNames?.names?.sort(); - yield /** @type {PetStoreIdNameChange} */ (existingNames); + // The first published event contains the existing names for the id, if any. + const { value: existingNames } = await subscription.next(); + if (existingNames?.names) { + existingNames.names.unshift(...idSpecialNames); + } + existingNames?.names?.sort(); + yield /** @type {PetStoreIdNameChange} */ (existingNames); - yield* subscription; - }; + yield* subscription; + }, + }.currentAndSubsequentIds; /** @type {PetStore['reverseIdentify']} */ const reverseIdentify = id => { diff --git a/packages/daemon/src/pet-store.js b/packages/daemon/src/pet-store.js index 3a3af49229..5c66ce8bff 100644 --- a/packages/daemon/src/pet-store.js +++ b/packages/daemon/src/pet-store.js @@ -136,37 +136,43 @@ export const makePetStoreMaker = (filePowers, config) => { const list = () => harden(idsToPetNames.getAll().sort()); /** @type {PetStore['followNameChanges']} */ - const followNameChanges = async function* currentAndSubsequentNames() { - const subscription = nameChangesTopic.subscribe(); - for (const name of idsToPetNames.getAll().sort()) { - const idRecord = parseId( - /** @type {string} */ (idsToPetNames.getKey(name)), - ); - - yield { - add: name, - value: idRecord, - }; - } - yield* subscription; - }; + const followNameChanges = { + /** @returns {ReturnType} */ + async *currentAndSubsequentNames() { + const subscription = nameChangesTopic.subscribe(); + for (const name of idsToPetNames.getAll().sort()) { + const idRecord = parseId( + /** @type {string} */ (idsToPetNames.getKey(name)), + ); + + yield { + add: name, + value: idRecord, + }; + } + yield* subscription; + }, + }.currentAndSubsequentNames; /** @type {PetStore['followIdNameChanges']} */ - const followIdNameChanges = async function* currentAndSubsequentIds(id) { - if (!idsToTopics.has(id)) { - idsToTopics.set(id, makeIdChangeTopic()); - } - const idTopic = /** @type {IdChangesTopic} */ (idsToTopics.get(id)); - const subscription = idTopic.subscribe(); + const followIdNameChanges = { + /** @returns {ReturnType} */ + async *currentAndSubsequentIds(id) { + if (!idsToTopics.has(id)) { + idsToTopics.set(id, makeIdChangeTopic()); + } + const idTopic = /** @type {IdChangesTopic} */ (idsToTopics.get(id)); + const subscription = idTopic.subscribe(); - const existingNames = idsToPetNames.getAllFor(id).sort(); - yield { - add: parseId(id), - names: existingNames, - }; + const existingNames = idsToPetNames.getAllFor(id).sort(); + yield { + add: parseId(id), + names: existingNames, + }; - yield* subscription; - }; + yield* subscription; + }, + }.currentAndSubsequentIds; /** @type {PetStore['remove']} */ const remove = async petName => { diff --git a/packages/daemon/src/web-server-node.js b/packages/daemon/src/web-server-node.js index b01690e69b..dcf50ff878 100644 --- a/packages/daemon/src/web-server-node.js +++ b/packages/daemon/src/web-server-node.js @@ -41,13 +41,15 @@ export const make = async (_powers, context) => { const serverCancelled = E(context).whenCancelled(); - const connectionNumbers = (function* generateNumbers() { - let n = 0; - for (;;) { - yield n; - n += 1; - } - })(); + const connectionNumbers = { + *generateNumbers() { + let n = 0; + for (;;) { + yield n; + n += 1; + } + }, + }.generateNumbers(); /** @type {Set>} */ const connectionClosedPromises = new Set(); diff --git a/packages/eslint-plugin/lib/rules/assert-fail-as-throw.js b/packages/eslint-plugin/lib/rules/assert-fail-as-throw.js index 999b095ef7..ce9cd6e19f 100644 --- a/packages/eslint-plugin/lib/rules/assert-fail-as-throw.js +++ b/packages/eslint-plugin/lib/rules/assert-fail-as-throw.js @@ -32,6 +32,11 @@ const originalLeaveNode = * @param {...string} moduleNames - module names to import. * @returns {object|null} The imported object, or null. */ +// Retains the `function` keyword by deliberate exception: the top-level +// `safeRequire(...)` calls at the head of this file precede this declaration +// (the `no-use-before-define` disable above permits the hoisting), and this file +// is adopted from mysticatea/eslint-plugin-node and kept diff-tractable against +// upstream. See docs/house-style/function-keyword.md. function safeRequire(...moduleNames) { for (const moduleName of moduleNames) { try { diff --git a/packages/eslint-plugin/lib/rules/harden-exports.js b/packages/eslint-plugin/lib/rules/harden-exports.js index d2a565e6b9..256f508d76 100644 --- a/packages/eslint-plugin/lib/rules/harden-exports.js +++ b/packages/eslint-plugin/lib/rules/harden-exports.js @@ -146,7 +146,7 @@ module.exports = { ExportNamedDeclaration(node) { exportNodes.push(node); }, - 'Program:exit': function () { + 'Program:exit': () => { const sourceCode = context.getSourceCode(); for (const exportNode of exportNodes) { diff --git a/packages/eslint-plugin/lib/rules/no-assign-to-exported-let-var-or-function.js b/packages/eslint-plugin/lib/rules/no-assign-to-exported-let-var-or-function.js index 978e2847a7..5bc9e02da6 100644 --- a/packages/eslint-plugin/lib/rules/no-assign-to-exported-let-var-or-function.js +++ b/packages/eslint-plugin/lib/rules/no-assign-to-exported-let-var-or-function.js @@ -58,21 +58,21 @@ module.exports = { * @param {string} name * @returns {Variable | null} */ - function findVariable(scope, name) { + const findVariable = (scope, name) => { for (let s = scope; s; s = s.upper) { const found = s.variables.find(v => v.name === name); if (found) return found; } return null; - } + }; /** * True if a Variable’s definition is a let/var or a function declaration. * @param {Variable} variable * @returns {boolean} */ - function isLetVarOrFunction(variable) { - return variable.defs.some(def => { + const isLetVarOrFunction = variable => + variable.defs.some(def => { if (def.type === 'Variable') { const declNode = def.parent; // VariableDeclaration return ( @@ -85,34 +85,33 @@ module.exports = { } return false; }); - } /** * Collect variables declared by a node and, if eligible, mark them exported. * @param {Node} nodeWithDecl * @returns {void} */ - function collectDeclaredAndMark(nodeWithDecl) { + const collectDeclaredAndMark = nodeWithDecl => { const vars = sourceCode.getDeclaredVariables(nodeWithDecl); for (const v of vars) { if (isLetVarOrFunction(v)) { exportedVars.add(v); } } - } + }; /** * Record a local name (from an export specifier) as exported if eligible. * @param {Identifier} nameNode * @returns {void} */ - function markLocalNameIfEligible(nameNode) { + const markLocalNameIfEligible = nameNode => { const scope = context.getScope(); const variable = findVariable(scope, nameNode.name); if (variable && isLetVarOrFunction(variable)) { exportedVars.add(variable); } - } + }; /** * Extract all identifiers on the left side of an Assignment target (handles patterns). @@ -120,7 +119,7 @@ module.exports = { * @param {Identifier[]} [acc] * @returns {Identifier[]} */ - function gatherAssignedIdentifiers(pattern, acc) { + const gatherAssignedIdentifiers = (pattern, acc) => { acc = acc || []; if (!pattern) return acc; @@ -158,14 +157,14 @@ module.exports = { break; } return acc; - } + }; /** * Report if the identifier resolves to one of the exported variables. * @param {Identifier} idNode * @returns {void} */ - function maybeReportIdentifier(idNode) { + const maybeReportIdentifier = idNode => { const scope = context.getScope(); const variable = findVariable(scope, idNode.name); if (variable && exportedVars.has(variable)) { @@ -175,7 +174,7 @@ module.exports = { data: { name: idNode.name }, }); } - } + }; return { // Collect directly exported declarations, e.g.: diff --git a/packages/eslint-plugin/lib/rules/no-polymorphic-call.js b/packages/eslint-plugin/lib/rules/no-polymorphic-call.js index 79cce07511..c117cccd83 100644 --- a/packages/eslint-plugin/lib/rules/no-polymorphic-call.js +++ b/packages/eslint-plugin/lib/rules/no-polymorphic-call.js @@ -31,7 +31,7 @@ module.exports = { }, }; -function prepareMemberExpressionHint(node) { +const prepareMemberExpressionHint = node => { const { object, property, computed } = node; let objectHint; let propertyHint; @@ -52,4 +52,4 @@ function prepareMemberExpressionHint(node) { propertyHint = `[[${property.type}]]`; } return `${objectHint}.${propertyHint}`; -} +}; diff --git a/packages/evasive-transform/src/index.js b/packages/evasive-transform/src/index.js index 02c06471ef..2da53704c1 100644 --- a/packages/evasive-transform/src/index.js +++ b/packages/evasive-transform/src/index.js @@ -62,7 +62,7 @@ comment contents, preserving code positions within each line * @param {EvadeCensorOptions} [options] - Options for the transform * @public */ -export function evadeCensorSync(source, options) { +export const evadeCensorSync = (source, options) => { const { sourceMap, sourceUrl, @@ -88,7 +88,7 @@ export function evadeCensorSync(source, options) { }); } return generate(ast, { source }); -} +}; /** * Apply SES censorship evasion transforms on the given code `source` @@ -126,6 +126,5 @@ export function evadeCensorSync(source, options) { * @param {EvadeCensorOptions} [options] - Options for the transform * @public */ -export async function evadeCensor(source, options) { - return evadeCensorSync(source, options); -} +export const evadeCensor = async (source, options) => + evadeCensorSync(source, options); diff --git a/packages/evasive-transform/src/parse-ast.js b/packages/evasive-transform/src/parse-ast.js index 7ef19b16cf..8e0486a115 100644 --- a/packages/evasive-transform/src/parse-ast.js +++ b/packages/evasive-transform/src/parse-ast.js @@ -33,12 +33,11 @@ const { parse: parseBabel } = babelParser; * @returns {any} * @internal */ -export function parseAst(source, opts = {}) { - return parseBabel(source, { +export const parseAst = (source, opts = {}) => + parseBabel(source, { tokens: true, createParenthesizedExpressions: true, allowReturnOutsideFunction: opts.sourceType === 'script' || opts.sourceType === 'commonjs', ...(opts.sourceType !== undefined && { sourceType: opts.sourceType }), }); -} diff --git a/packages/evasive-transform/src/transform-ast.js b/packages/evasive-transform/src/transform-ast.js index e5fddf8c5d..a838877ff2 100644 --- a/packages/evasive-transform/src/transform-ast.js +++ b/packages/evasive-transform/src/transform-ast.js @@ -83,14 +83,14 @@ export const makeEvasiveTransformVisitor = ({ * @param {TransformAstOptions} [opts] * @returns {void} */ -export function transformAst( +export const transformAst = ( ast, { elideComments = false, onlyComments = false, customVisitor } = {}, -) { +) => { const visitor = makeEvasiveTransformVisitor({ elideComments, onlyComments, customVisitor, }); traverse(ast, visitor); -} +}; diff --git a/packages/evasive-transform/src/transform-comment.js b/packages/evasive-transform/src/transform-comment.js index 813d5cb647..18ab230c4b 100644 --- a/packages/evasive-transform/src/transform-comment.js +++ b/packages/evasive-transform/src/transform-comment.js @@ -27,7 +27,7 @@ const HTML_COMMENT_END_RE = new RegExp(`--${'>'}`, 'g'); * * @param {import('@babel/types').Comment} node */ -export function evadeComment(node) { +export const evadeComment = node => { node.type = 'CommentBlock'; // Within comments... node.value = node.value @@ -41,7 +41,7 @@ export function evadeComment(node) { .replace(IMPORT_RE, 'IMPORT$2') // ...replace end-of-comment markers .replace(/\*\//g, '*X/'); -} +}; /** * Inspects a comment for a hint that it must be preserved by a transform. diff --git a/packages/eventual-send/src/handled-promise.js b/packages/eventual-send/src/handled-promise.js index cc789e338c..b0ad885df4 100644 --- a/packages/eventual-send/src/handled-promise.js +++ b/packages/eventual-send/src/handled-promise.js @@ -205,7 +205,7 @@ export const makeHandledPromise = () => { * @param {Handler>} [pendingHandler] * @returns {Promise} */ - function baseHandledPromise(executor, pendingHandler = undefined) { + function BaseHandledPromise(executor, pendingHandler = undefined) { new.target || Fail`must be invoked with "new"`; let handledResolve; let handledReject; @@ -563,17 +563,17 @@ export const makeHandledPromise = () => { }; // Add everything needed on the constructor. - baseHandledPromise.prototype = Promise.prototype; - setPrototypeOf(baseHandledPromise, Promise); + BaseHandledPromise.prototype = Promise.prototype; + setPrototypeOf(BaseHandledPromise, Promise); defineProperties( - baseHandledPromise, + BaseHandledPromise, getOwnPropertyDescriptors(staticMethods), ); // FIXME: This is really ugly to bypass the type system, but it will be better // once we use Promise.delegated and don't have any [[Constructor]] behaviours. // @ts-expect-error cast - HandledPromise = baseHandledPromise; + HandledPromise = BaseHandledPromise; // We're a vetted shim which runs before `lockdown` allows // `harden(HandledPromise)` to function, but single-level `freeze` is a diff --git a/packages/eventual-send/src/postponed.js b/packages/eventual-send/src/postponed.js index 878826c3f3..3625bc8c0a 100644 --- a/packages/eventual-send/src/postponed.js +++ b/packages/eventual-send/src/postponed.js @@ -17,16 +17,18 @@ export const makePostponedHandler = HandledPromise => { const makePostponedOperation = postponedOperation => { // Just wait until the handler is resolved/rejected. - return function postpone(x, ...args) { + // Named so stack traces and `.name` keep reporting `postpone` for + // postponed operations, without reintroducing the `function` keyword. + const postpone = (x, ...args) => // console.log(`forwarding ${postponedOperation} ${args[0]}`); - return new HandledPromise((resolve, reject) => { + new HandledPromise((resolve, reject) => { interlockP .then(_ => { resolve(HandledPromise[postponedOperation](x, ...args)); }) .catch(reject); }); - }; + return postpone; }; /** @type {Required>} */ diff --git a/packages/harden/make-hardener.js b/packages/harden/make-hardener.js index 5306f1cb3a..6c28e91cb2 100644 --- a/packages/harden/make-hardener.js +++ b/packages/harden/make-hardener.js @@ -344,7 +344,7 @@ export const makeHardener = ({ traversePrototypes = false } = {}) => { /** * @param {any} val */ - function enqueue(val) { + const enqueue = val => { if (isPrimitive(val)) { // ignore primitives return; @@ -360,7 +360,7 @@ export const makeHardener = ({ traversePrototypes = false } = {}) => { } // console.warn(`adding ${val} to toFreeze`, val); setAdd(toFreeze, val); - } + }; /** * @param {any} obj diff --git a/packages/import-bundle/src/compartment-wrapper.js b/packages/import-bundle/src/compartment-wrapper.js index 5062414327..24dfede683 100644 --- a/packages/import-bundle/src/compartment-wrapper.js +++ b/packages/import-bundle/src/compartment-wrapper.js @@ -49,11 +49,11 @@ const compartmentOptions = (...args) => { } }; -export function wrapInescapableCompartment( +export const wrapInescapableCompartment = ( OldCompartment, inescapableTransforms, inescapableGlobalProperties, -) { +) => { // This is the new Compartment constructor. We name it `Compartment` so // that it's .name property is correct, but we hold it in 'NewCompartment' // so that lint doesn't think we're shadowing the original. @@ -131,7 +131,7 @@ export function wrapInescapableCompartment( // class. Under SES, OldCompartment.prototype.constructor is tamed return NewCompartment; -} +}; // swingset would do this to each dynamic vat // c.globalThis.Compartment = wrapCompartment(c.globalThis.Compartment, ..); diff --git a/packages/import-bundle/src/index.js b/packages/import-bundle/src/index.js index 90f9270687..6e60bcdfe4 100644 --- a/packages/import-bundle/src/index.js +++ b/packages/import-bundle/src/index.js @@ -21,7 +21,7 @@ import { wrapInescapableCompartment } from './compartment-wrapper.js'; // Adding a type signature in-place proved difficult to migrate in-place. // See typedImportBundle below. -export async function importBundle(bundle, options = {}, powers = {}) { +export const importBundle = async (bundle, options = {}, powers = {}) => { await null; const { bundleUrl = undefined, @@ -188,7 +188,7 @@ export async function importBundle(bundle, options = {}, powers = {}) { // namespace.default has the default export return namespace; } -} +}; /** * typedImportBundle takes the output of `bundleSource` or diff --git a/packages/init/src/node-async-local-storage-patch.js b/packages/init/src/node-async-local-storage-patch.js index 55c0feaf42..f767b31e9d 100644 --- a/packages/init/src/node-async-local-storage-patch.js +++ b/packages/init/src/node-async-local-storage-patch.js @@ -17,7 +17,7 @@ const getStoreMap = key => /** @type {WeakMap} */ (resourceStoreMaps.get(key)); /** * @typedef {object} AsyncLocalStorageInternal * @property {boolean} enabled - * @property {typeof _propagate} _propagate + * @property {(resource: object, triggerResource: object, type?: string) => void} _propagate * @property {(this: AsyncLocalStorage) => void} _enable */ @@ -31,68 +31,88 @@ Object.defineProperty(AsyncLocalStorage.prototype, 'kResourceStore', { }, }); -/** - * @this {AsyncLocalStorage & AsyncLocalStorageInternal} - * @param {object} resource - * @param {object} triggerResource - * @param {string} [type] - */ -function _propagate(resource, triggerResource, type) { - if (!this.enabled) return; +// The methods below patch the AsyncLocalStorage prototype. They are written +// as concise methods on an object literal so they retain their `name` for +// stack traces while having no `[[Construct]]` and no `prototype` (unlike +// a `function`-keyword function expression). +const patches = { + /** + * @this {AsyncLocalStorage & AsyncLocalStorageInternal} + * @param {object} resource + * @param {object} triggerResource + * @param {string} [type] + */ + _propagate(resource, triggerResource, type) { + if (!this.enabled) return; - const storeMap = getStoreMap(this); - storeMap.set(resource, storeMap.get(triggerResource)); -} + const storeMap = getStoreMap(this); + storeMap.set(resource, storeMap.get(triggerResource)); + }, -// @ts-expect-error propagate is internal -AsyncLocalStorage.prototype._propagate = _propagate; + /** + * @this {AsyncLocalStorage & AsyncLocalStorageInternal} + * @param {any} store + */ + enterWith(store) { + this._enable(); + const resource = executionAsyncResource(); + getStoreMap(this).set(resource, store); + }, -/** - * @this {AsyncLocalStorage & AsyncLocalStorageInternal} - * @param {any} store - */ -AsyncLocalStorage.prototype.enterWith = function enterWith(store) { - this._enable(); - const resource = executionAsyncResource(); - getStoreMap(this).set(resource, store); -}; + /** + * @template R + * @template {any[]} TArgs + * @this {AsyncLocalStorage & AsyncLocalStorageInternal} + * @param {any} store + * @param {(...args: TArgs) => R} callback + * @param {...TArgs} args + * @returns {R} + */ + run(store, callback, ...args) { + // Avoid creation of an AsyncResource if store is already active + if (ObjectIs(store, this.getStore())) { + return ReflectApply(callback, null, args); + } -/** - * @template R - * @template {any[]} TArgs - * @this {AsyncLocalStorage & AsyncLocalStorageInternal} - * @param {any} store - * @param {(...args: TArgs) => R} callback - * @param {...TArgs} args - * @returns {R} - */ -AsyncLocalStorage.prototype.run = function run(store, callback, ...args) { - // Avoid creation of an AsyncResource if store is already active - if (ObjectIs(store, this.getStore())) { - return ReflectApply(callback, null, args); - } + this._enable(); + const storeMap = getStoreMap(this); - this._enable(); - const storeMap = getStoreMap(this); + const resource = executionAsyncResource(); - const resource = executionAsyncResource(); + const oldStore = storeMap.get(resource); - const oldStore = storeMap.get(resource); + storeMap.set(resource, store); - storeMap.set(resource, store); + try { + return ReflectApply(callback, null, args); + } finally { + storeMap.set(resource, oldStore); + } + }, - try { - return ReflectApply(callback, null, args); - } finally { - storeMap.set(resource, oldStore); - } + /** + * @this {AsyncLocalStorage & AsyncLocalStorageInternal} + */ + getStore() { + return this.enabled + ? getStoreMap(this).get(executionAsyncResource()) + : undefined; + }, }; -/** - * @this {AsyncLocalStorage & AsyncLocalStorageInternal} - */ -AsyncLocalStorage.prototype.getStore = function getStore() { - return this.enabled - ? getStoreMap(this).get(executionAsyncResource()) - : undefined; -}; +// Install the patched methods with the descriptor form +// (`defineProperties` over `getOwnPropertyDescriptors`) rather than per-property +// assignment. The descriptor form copies each method onto the prototype via +// `[[DefineOwnProperty]]`, which transfers the own-property descriptor faithfully +// and unconditionally; plain `proto.x = patches.x` routes through `[[Set]]`, +// which would honor an inherited setter and, against a non-writable inherited +// data property, would throw a TypeError in strict mode (the mode all module +// code runs in) rather than installing the method. One observable nuance: +// object-literal methods carry +// `enumerable: true`, so this makes `enterWith`, `run`, and `getStore` +// enumerable own properties of the prototype, whereas per-property assignment +// preserved the non-enumerability of the built-in methods it overwrote. +Object.defineProperties( + AsyncLocalStorage.prototype, + Object.getOwnPropertyDescriptors(patches), +); diff --git a/packages/module-source/src/babel-plugin.js b/packages/module-source/src/babel-plugin.js index 19e2a4612b..a9c75673b6 100644 --- a/packages/module-source/src/babel-plugin.js +++ b/packages/module-source/src/babel-plugin.js @@ -55,7 +55,7 @@ const collectPatternIdentifiers = (path, pattern) => { * @param {TransformSourceParams} options * @returns {{ analyzePlugin: VisitorPlugin, transformPlugin: VisitorPlugin }} */ -function makeModulePlugins(options) { +const makeModulePlugins = options => { const { sourceType, exportAlls, @@ -678,6 +678,6 @@ function makeModulePlugins(options) { analyzePlugin: rewriteModules(0), transformPlugin: rewriteModules(1), }; -} +}; export default makeModulePlugins; diff --git a/packages/module-source/src/transform-analyze.js b/packages/module-source/src/transform-analyze.js index 334c422c8b..38f0cfc083 100644 --- a/packages/module-source/src/transform-analyze.js +++ b/packages/module-source/src/transform-analyze.js @@ -66,16 +66,16 @@ const makeHubParentPath = ast => { * * @returns {(source: string, options?: ModuleSourceOptions & AnalysisOptions) => ModuleSourceRecord} */ -export const makeModuleSourceAnalyzer = () => +export const makeModuleSourceAnalyzer = () => { /** * @param {string} moduleSource * @param {ModuleSourceOptions & AnalysisOptions} [options] * @returns {ModuleSourceRecord} */ - function createStaticRecord( + const createStaticRecord = ( moduleSource, { sourceUrl, sourceMapUrl, sourceMap, sourceMapHook, allowHidden } = {}, - ) { + ) => { if (moduleSource.startsWith('#!')) { // Comment out the shebang lines. moduleSource = `//${moduleSource}`; @@ -143,6 +143,8 @@ export const makeModuleSourceAnalyzer = () => return ctx.buildRecord(scriptSource, sourceUrl); }; + return createStaticRecord; +}; // TODO: May be unused; referenced only in ses/test262 export const makeModuleTransformer = (_babel, importer) => { @@ -177,18 +179,16 @@ export const makeModuleTransformer = (_babel, importer) => { const { allowHidden, endowments, src: source, url } = ss; // Make an importer that uses our transform for its submodules. - function curryImporter(srcSpec) { - return importer(srcSpec, endowments); - } + const curryImporter = srcSpec => importer(srcSpec, endowments); // Create an import expression for the given URL. - function makeImportExpr() { + const makeImportExpr = () => { // TODO: Provide a way to allow hardening of the import expression. const importExpr = spec => curryImporter({ url, spec }); importExpr.meta = Object.create(null); importExpr.meta.url = url; return importExpr; - } + }; // Add the $h_import hidden endowment for import expressions. Object.assign(endowments, { diff --git a/packages/ocapn/src/client/ocapn.js b/packages/ocapn/src/client/ocapn.js index d78ee96c0d..cc909315c3 100644 --- a/packages/ocapn/src/client/ocapn.js +++ b/packages/ocapn/src/client/ocapn.js @@ -1191,6 +1191,10 @@ export const makeOcapn = ( const { readOcapnMessage, writeOcapnMessage } = makeCodecKit(referenceKit); + // Retains the `function` keyword by deliberate exception: it is passed into + // `makeOcapnCommsKit({ ...rawSend: serializeAndSendMessage })` during module + // init, before its declaration, so a `const` arrow would be in its temporal + // dead zone. See docs/house-style/function-keyword.md. function serializeAndSendMessage(message) { try { const bytes = writeOcapnMessage(message); diff --git a/packages/ocapn/src/syrup/compare.js b/packages/ocapn/src/syrup/compare.js index 80d993cf10..4835431be9 100644 --- a/packages/ocapn/src/syrup/compare.js +++ b/packages/ocapn/src/syrup/compare.js @@ -14,14 +14,14 @@ import { bytesFromImmutable } from '@endo/bytes/from-immutable.js'; * negative if the left byteArray is "less" than the right byteArray, * positive if the left byteArray is "greater" than the right byteArray. */ -export function compareUint8Arrays( +export const compareUint8Arrays = ( left, right, leftStart = 0, leftEnd = left.length, rightStart = 0, rightEnd = right.length, -) { +) => { if (!(left instanceof Uint8Array)) { throw Error(`Left is not a Uint8Array: ${left}`); } @@ -83,7 +83,7 @@ export function compareUint8Arrays( leftIndex += 1; rightIndex += 1; } -} +}; /** * Compare two immutable ArrayBuffers diff --git a/packages/ocapn/src/syrup/decode.js b/packages/ocapn/src/syrup/decode.js index 46d4bacc56..410f618830 100644 --- a/packages/ocapn/src/syrup/decode.js +++ b/packages/ocapn/src/syrup/decode.js @@ -40,7 +40,7 @@ const canonicalZero64 = freeze([0, 0, 0, 0, 0, 0, 0, 0]); * @param {string} name * @returns {boolean} */ -function readBoolean(bufferReader, name) { +const readBoolean = (bufferReader, name) => { const cc = bufferReader.readByte(); if (cc === TRUE) { return true; @@ -51,7 +51,7 @@ function readBoolean(bufferReader, name) { throw Error( `Unexpected byte ${quote(toChar(cc))}, Syrup booleans must start with ${quote(toChar(TRUE))} or ${quote(toChar(FALSE))} at index ${bufferReader.index} of ${name}`, ); -} +}; /** @typedef {'boolean' | 'float64' | 'integer' | 'bytestring' | 'string' | 'selector'} SyrupAtomType */ /** @typedef {'list' | 'set' | 'dictionary' | 'record'} SyrupStructuredType */ @@ -75,7 +75,7 @@ function readBoolean(bufferReader, name) { * @returns {ReadTypeAndMaybeValueResult} * Reads until it can determine the type of the next value. */ -function readTypeAndMaybeValue(bufferReader, name) { +const readTypeAndMaybeValue = (bufferReader, name) => { const start = bufferReader.index; const cc = bufferReader.readByte(); // Structure types, don't read value @@ -152,7 +152,7 @@ function readTypeAndMaybeValue(bufferReader, name) { throw Error( `Unexpected character ${quote(toChar(typeByte))}, at index ${bufferReader.index} of ${name}`, ); -} +}; /** * @param {BufferReader} bufferReader @@ -160,7 +160,7 @@ function readTypeAndMaybeValue(bufferReader, name) { * @param {string} name * @returns {any} */ -function readAndAssertType(bufferReader, expectedType, name) { +const readAndAssertType = (bufferReader, expectedType, name) => { const start = bufferReader.index; const { value, type } = readTypeAndMaybeValue(bufferReader, name); if (type !== expectedType) { @@ -169,43 +169,39 @@ function readAndAssertType(bufferReader, expectedType, name) { ); } return value; -} +}; /** * @param {BufferReader} bufferReader * @param {string} name * @returns {bigint} */ -function readInteger(bufferReader, name) { - return readAndAssertType(bufferReader, 'integer', name); -} +const readInteger = (bufferReader, name) => + readAndAssertType(bufferReader, 'integer', name); /** * @param {BufferReader} bufferReader * @param {string} name * @returns {string} */ -function readString(bufferReader, name) { - return readAndAssertType(bufferReader, 'string', name); -} +const readString = (bufferReader, name) => + readAndAssertType(bufferReader, 'string', name); /** * @param {BufferReader} bufferReader * @param {string} name * @returns {string} */ -function readSelectorAsString(bufferReader, name) { - return readAndAssertType(bufferReader, 'selector', name); -} +const readSelectorAsString = (bufferReader, name) => + readAndAssertType(bufferReader, 'selector', name); /** * @param {BufferReader} bufferReader * @param {string} name * @returns {ArrayBufferLike} */ -function readBytestring(bufferReader, name) { - return readAndAssertType(bufferReader, 'bytestring', name); -} +const readBytestring = (bufferReader, name) => + readAndAssertType(bufferReader, 'bytestring', name); /** * @param {BufferReader} bufferReader @@ -213,7 +209,7 @@ function readBytestring(bufferReader, name) { * @returns {{value: string, type: 'selector'} | {value: ArrayBufferLike, type: 'bytestring'} | {value: string, type: 'string'}} * see https://github.com/ocapn/syrup/issues/22 */ -function readRecordLabel(bufferReader, name) { +const readRecordLabel = (bufferReader, name) => { const start = bufferReader.index; const { value, type } = readTypeAndMaybeValue(bufferReader, name); if (type === 'selector' || type === 'string' || type === 'bytestring') { @@ -223,13 +219,13 @@ function readRecordLabel(bufferReader, name) { throw Error( `Unexpected type ${quote(type)}, Syrup record labels must be strings, selectors, or bytestrings at index ${start} of ${name}`, ); -} +}; /** * @param {BufferReader} bufferReader * @param {string} name */ -function readFloat64Body(bufferReader, name) { +const readFloat64Body = (bufferReader, name) => { const start = bufferReader.index; const value = bufferReader.readFloat64(false); // big end @@ -247,13 +243,13 @@ function readFloat64Body(bufferReader, name) { } return value; -} +}; /** * @param {BufferReader} bufferReader * @param {string} name */ -function readFloat64(bufferReader, name) { +const readFloat64 = (bufferReader, name) => { const cc = bufferReader.readByte(); if (cc !== FLOAT64) { throw Error( @@ -261,7 +257,7 @@ function readFloat64(bufferReader, name) { ); } return readFloat64Body(bufferReader, name); -} +}; /** @typedef {'float64' | 'number-prefix' | 'list' | 'set' | 'dictionary' | 'record' | 'boolean'} TypeHintTypes */ @@ -270,7 +266,7 @@ function readFloat64(bufferReader, name) { * @param {string} name * @returns {TypeHintTypes} */ -export function peekTypeHint(bufferReader, name) { +export const peekTypeHint = (bufferReader, name) => { const cc = bufferReader.peekByte(); if (cc >= ZERO && cc <= NINE) { return 'number-prefix'; @@ -297,7 +293,7 @@ export function peekTypeHint(bufferReader, name) { throw Error( `Unexpected character ${quote(toChar(cc))}, at index ${index} of ${name}`, ); -} +}; /** @typedef {{type: string, start: number}} SyrupReaderStackEntry */ diff --git a/packages/ocapn/src/syrup/encode.js b/packages/ocapn/src/syrup/encode.js index 67eb1ffcec..9318e947cf 100644 --- a/packages/ocapn/src/syrup/encode.js +++ b/packages/ocapn/src/syrup/encode.js @@ -41,20 +41,20 @@ const NAN64 = new Uint8Array([0x7f, 0xf8, 0, 0, 0, 0, 0, 0]); * @param {Uint8Array} bytes * @param {string} typeChar */ -function writeStringlike(bufferWriter, bytes, typeChar) { +const writeStringlike = (bufferWriter, bytes, typeChar) => { // write length prefix as ascii string const length = bytes.byteLength; const lengthPrefix = `${length}`; bufferWriter.writeString(lengthPrefix); bufferWriter.writeString(typeChar); bufferWriter.write(bytes); -} +}; /** * @param {import('./buffer-writer.js').BufferWriter} bufferWriter * @param {string} value */ -function writeString(bufferWriter, value) { +const writeString = (bufferWriter, value) => { if (typeof value !== 'string') { throw Error(`writeString: Expected string, got ${typeof value}`); } @@ -71,34 +71,34 @@ function writeString(bufferWriter, value) { ); } writeStringlike(bufferWriter, bytes, '"'); -} +}; /** * @param {import('./buffer-writer.js').BufferWriter} bufferWriter * @param {string} value */ -function writeSelectorFromString(bufferWriter, value) { +const writeSelectorFromString = (bufferWriter, value) => { const bytes = textEncoder.encode(value); writeStringlike(bufferWriter, bytes, "'"); -} +}; /** * @param {import('./buffer-writer.js').BufferWriter} bufferWriter * @param {ArrayBufferLike} value */ -function writeBytestring(bufferWriter, value) { +const writeBytestring = (bufferWriter, value) => { // Convert ArrayBuffer to Uint8Array for internal operations // Immutable ArrayBuffers need to be sliced first const mutableBuffer = value.slice(); const bytes = new Uint8Array(mutableBuffer); writeStringlike(bufferWriter, bytes, ':'); -} +}; /** * @param {import('./buffer-writer.js').BufferWriter} bufferWriter * @param {number} value */ -function writeFloat64(bufferWriter, value) { +const writeFloat64 = (bufferWriter, value) => { bufferWriter.writeByte(FLOAT64); if (value === 0) { // Canonicalize 0 @@ -109,27 +109,27 @@ function writeFloat64(bufferWriter, value) { } else { bufferWriter.writeFloat64(value, false); // big end } -} +}; /** * @param {import('./buffer-writer.js').BufferWriter} bufferWriter * @param {bigint} value */ -function writeInteger(bufferWriter, value) { +const writeInteger = (bufferWriter, value) => { if (typeof value !== 'bigint') { throw Error(`writeInteger: Expected bigint, got ${typeof value}`); } const string = value >= ZERO_N ? `${value}+` : `${-value}-`; bufferWriter.writeString(string); -} +}; /** * @param {import('./buffer-writer.js').BufferWriter} bufferWriter * @param {boolean} value */ -function writeBoolean(bufferWriter, value) { +const writeBoolean = (bufferWriter, value) => { bufferWriter.writeByte(value ? TRUE : FALSE); -} +}; export class SyrupWriter { /** @type {BufferWriter} */ @@ -229,8 +229,8 @@ export class SyrupWriter { } } -export function makeSyrupWriter(options = {}) { +export const makeSyrupWriter = (options = {}) => { const { length: capacity = defaultCapacity, ...writerOptions } = options; const bufferWriter = new BufferWriter(capacity); return new SyrupWriter(bufferWriter, writerOptions); -} +}; diff --git a/packages/ocapn/src/syrup/js-representation.js b/packages/ocapn/src/syrup/js-representation.js index 0b61e2d710..2b1e508464 100644 --- a/packages/ocapn/src/syrup/js-representation.js +++ b/packages/ocapn/src/syrup/js-representation.js @@ -281,10 +281,10 @@ const RecordCodec = makeCodec('SyrupRecordCodec', { * @param {number} [options.start] * @param {number} [options.end] */ -export function decodeSyrup(bytes, options = {}) { +export const decodeSyrup = (bytes, options = {}) => { const syrupReader = makeSyrupReader(bytes, options); return AnyCodec.read(syrupReader); -} +}; /** * @param {any} value @@ -293,8 +293,8 @@ export function decodeSyrup(bytes, options = {}) { * greater than zero. * @returns {Uint8Array} */ -export function encodeSyrup(value, options = {}) { +export const encodeSyrup = (value, options = {}) => { const syrupWriter = makeSyrupWriter(options); AnyCodec.write(value, syrupWriter); return syrupWriter.getBytes(); -} +}; diff --git a/packages/promise-kit/src/is-promise.js b/packages/promise-kit/src/is-promise.js index 6032bb69ed..15d1beb6f8 100644 --- a/packages/promise-kit/src/is-promise.js +++ b/packages/promise-kit/src/is-promise.js @@ -6,7 +6,6 @@ import harden from '@endo/harden'; * @param {unknown} maybePromise The value to examine * @returns {maybePromise is Promise} Whether it is a promise */ -export function isPromise(maybePromise) { - return Promise.resolve(maybePromise) === maybePromise; -} +export const isPromise = maybePromise => + Promise.resolve(maybePromise) === maybePromise; harden(isPromise); diff --git a/packages/ses/src/assert-sloppy-mode.js b/packages/ses/src/assert-sloppy-mode.js index e949a13a3a..19f6e4a205 100644 --- a/packages/ses/src/assert-sloppy-mode.js +++ b/packages/ses/src/assert-sloppy-mode.js @@ -2,6 +2,8 @@ import { TypeError } from './commons.js'; /** @this {unknown} */ // getThis returns globalThis in sloppy mode or undefined in strict mode. +// Retains the `function` keyword by deliberate exception (a caller-sensitive +// `this` probe, not a constructor); see docs/house-style/function-keyword.md. function getThis() { return this; } diff --git a/packages/ses/src/commons.js b/packages/ses/src/commons.js index d74183aaaa..658d0b3c1a 100644 --- a/packages/ses/src/commons.js +++ b/packages/ses/src/commons.js @@ -160,6 +160,9 @@ export const { prototype: weaksetPrototype } = WeakSet; export const { prototype: functionPrototype } = Function; export const { prototype: promisePrototype } = Promise; export const { prototype: generatorPrototype } = getPrototypeOf( + // Standalone generator expression retained by deliberate exception: an + // anonymous sentinel used only to reach the intrinsic generator prototype, not + // naturally an object member. See docs/house-style/function-keyword.md. // eslint-disable-next-line no-empty-function, func-names function* () {}, ); diff --git a/packages/ses/src/get-anonymous-intrinsics.js b/packages/ses/src/get-anonymous-intrinsics.js index afd442e8aa..76b85754e4 100644 --- a/packages/ses/src/get-anonymous-intrinsics.js +++ b/packages/ses/src/get-anonymous-intrinsics.js @@ -89,6 +89,10 @@ export const getAnonymousIntrinsics = () => { // 25.2.1 The GeneratorFunction Constructor + // GeneratorFunctionInstance and AsyncFunctionInstance below retain the + // `function` keyword by deliberate exception: named generator/async-function + // sentinels used only to reach intrinsic constructors via getConstructorOf, + // not naturally object members. See docs/house-style/function-keyword.md. // eslint-disable-next-line no-empty-function function* GeneratorFunctionInstance() {} const GeneratorFunction = getConstructorOf(GeneratorFunctionInstance); diff --git a/packages/ses/src/intrinsics.js b/packages/ses/src/intrinsics.js index d57611ba8c..a12f923c14 100644 --- a/packages/ses/src/intrinsics.js +++ b/packages/ses/src/intrinsics.js @@ -36,7 +36,7 @@ const isFunction = obj => typeof obj === 'function'; // conflict between, for example, two of SES's internal permits might // get masked as one overwrites the other. Accordingly, the thrown error // complains of a "Conflicting definition". -function initProperty(obj, name, desc) { +const initProperty = (obj, name, desc) => { if (hasOwn(obj, name)) { const preDesc = getOwnPropertyDescriptor(obj, name); if ( @@ -52,21 +52,21 @@ function initProperty(obj, name, desc) { } } defineProperty(obj, name, desc); -} +}; // Like defineProperties, but throws if it would modify an existing property. // This ensures that the intrinsics added to the intrinsics collector object // graph do not overlap. -function initProperties(obj, descs) { +const initProperties = (obj, descs) => { for (const [name, desc] of entries(descs)) { initProperty(obj, name, desc); } -} +}; // sampleGlobals creates an intrinsics object, suitable for // interinsicsCollector.addIntrinsics, from the named properties of a global // object. -function sampleGlobals(globalObject, newPropertyNames) { +const sampleGlobals = (globalObject, newPropertyNames) => { const newIntrinsics = { __proto__: null }; for (const [globalName, intrinsicName] of entries(newPropertyNames)) { if (hasOwn(globalObject, globalName)) { @@ -74,7 +74,7 @@ function sampleGlobals(globalObject, newPropertyNames) { } } return newIntrinsics; -} +}; /** * @param {Reporter} reporter diff --git a/packages/ses/src/module-instance.js b/packages/ses/src/module-instance.js index f2c4a5a641..add11b8660 100644 --- a/packages/ses/src/module-instance.js +++ b/packages/ses/src/module-instance.js @@ -381,7 +381,7 @@ export const makeModuleInstance = ( // The updateRecord must conform to moduleAnalysis.imports // updateRecord = Map // importUpdaters = Map - function imports(updateRecord) { + const imports = updateRecord => { // By the time imports is called, the importedInstances should already be // initialized with module instances that satisfy // imports. @@ -452,7 +452,7 @@ export const makeModuleInstance = ( freeze(exportsTarget); activate(); - } + }; let optFunctor; if (__syncModuleFunctor__ !== undefined) { @@ -466,7 +466,7 @@ export const makeModuleInstance = ( } let didThrow = false; let thrownError; - function execute() { + const execute = () => { if (optFunctor) { // uninitialized const functor = optFunctor; @@ -491,7 +491,7 @@ export const makeModuleInstance = ( if (didThrow) { throw thrownError; } - } + }; return freeze({ notifiers, diff --git a/packages/ses/src/module-link.js b/packages/ses/src/module-link.js index 8e300d2af0..ad08358890 100644 --- a/packages/ses/src/module-link.js +++ b/packages/ses/src/module-link.js @@ -53,11 +53,10 @@ export const link = ( return instantiate(compartmentPrivateFields, moduleAliases, moduleRecord); }; -function mayBePrecompiledModuleSource(moduleSource) { - return typeof moduleSource.__syncModuleProgram__ === 'string'; -} +const mayBePrecompiledModuleSource = moduleSource => + typeof moduleSource.__syncModuleProgram__ === 'string'; -function validatePrecompiledModuleSource(moduleSource, moduleSpecifier) { +const validatePrecompiledModuleSource = (moduleSource, moduleSpecifier) => { const { __fixedExportMap__, __liveExportMap__ } = moduleSource; !isPrimitive(__fixedExportMap__) || Fail`Property '__fixedExportMap__' of a precompiled module source must be an object, got ${q( @@ -67,21 +66,20 @@ function validatePrecompiledModuleSource(moduleSource, moduleSpecifier) { Fail`Property '__liveExportMap__' of a precompiled module source must be an object, got ${q( __liveExportMap__, )}, for module ${q(moduleSpecifier)}`; -} +}; -function mayBeVirtualModuleSource(moduleSource) { - return typeof moduleSource.execute === 'function'; -} +const mayBeVirtualModuleSource = moduleSource => + typeof moduleSource.execute === 'function'; -function validateVirtualModuleSource(moduleSource, moduleSpecifier) { +const validateVirtualModuleSource = (moduleSource, moduleSpecifier) => { const { exports } = moduleSource; isArray(exports) || Fail`Invalid module source: 'exports' of a virtual module source must be an array, got ${q( exports, )}, for module ${q(moduleSpecifier)}`; -} +}; -function validateModuleSource(moduleSource, moduleSpecifier) { +const validateModuleSource = (moduleSource, moduleSpecifier) => { !isPrimitive(moduleSource) || Fail`Invalid module source: must be of type object, got ${q( moduleSource, @@ -99,7 +97,7 @@ function validateModuleSource(moduleSource, moduleSpecifier) { Fail`Invalid module source: 'reexports' must be an array if present, got ${q( reexports, )}, for module ${q(moduleSpecifier)}`; -} +}; export const instantiate = ( compartmentPrivateFields, diff --git a/packages/ses/src/scope-constants.js b/packages/ses/src/scope-constants.js index 5f34d9a63f..af743e7e38 100644 --- a/packages/ses/src/scope-constants.js +++ b/packages/ses/src/scope-constants.js @@ -107,7 +107,7 @@ export const isValidIdentifierName = name => * isImmutableDataProperty */ -function isImmutableDataProperty(obj, name) { +const isImmutableDataProperty = (obj, name) => { const desc = getOwnPropertyDescriptor(obj, name); return ( desc && @@ -128,7 +128,7 @@ function isImmutableDataProperty(obj, name) { // case where Object.prototype has been poisoned. hasOwn(desc, 'value') ); -} +}; /** * getScopeConstants() diff --git a/packages/ses/src/transforms.js b/packages/ses/src/transforms.js index 4b8172a273..2621db8abd 100644 --- a/packages/ses/src/transforms.js +++ b/packages/ses/src/transforms.js @@ -21,7 +21,7 @@ import { getSourceURL } from './get-source-url.js'; * @param {RegExp} pattern * @returns {number} */ -function getLineNumber(src, pattern) { +const getLineNumber = (src, pattern) => { const index = regexpSearch(pattern, src); if (index < 0) { return -1; @@ -33,7 +33,7 @@ function getLineNumber(src, pattern) { const adjustment = src[index] === '\n' ? 1 : 0; return stringSplit(stringSlice(src, 0, index), '\n').length + adjustment; -} +}; // ///////////////////////////////////////////////////////////////////////////// diff --git a/packages/trampoline/src/trampoline.js b/packages/trampoline/src/trampoline.js index a9680b1cde..cccaa968f4 100644 --- a/packages/trampoline/src/trampoline.js +++ b/packages/trampoline/src/trampoline.js @@ -6,6 +6,9 @@ const { getPrototypeOf } = Object; const { bind } = Function.prototype; const uncurryThis = bind.bind(bind.call); // eslint-disable-line @endo/no-polymorphic-call export const { prototype: generatorPrototype } = getPrototypeOf( + // Standalone generator expression retained by deliberate exception: an + // anonymous sentinel used only to reach the intrinsic generator prototype, not + // naturally an object member. See docs/house-style/function-keyword.md. // eslint-disable-next-line no-empty-function, func-names function* () {}, ); @@ -21,7 +24,7 @@ const generatorThrow = uncurryThis(generatorPrototype.throw); * @param {TArgs} args Arguments to pass to `generatorFn` * @returns {SyncTrampolineResult} */ -export function syncTrampoline(generatorFn, ...args) { +export const syncTrampoline = (generatorFn, ...args) => { const iterator = generatorFn(...args); let result = generatorNext(iterator); while (!result.done) { @@ -32,7 +35,7 @@ export function syncTrampoline(generatorFn, ...args) { } } return result.value; -} +}; /** * Trampoline on {@link TrampolineGeneratorFn generatorFn} asynchronously. @@ -43,7 +46,7 @@ export function syncTrampoline(generatorFn, ...args) { * @param {TArgs} args Arguments to pass to `generatorFn` * @returns {Promise>} */ -export async function asyncTrampoline(generatorFn, ...args) { +export const asyncTrampoline = async (generatorFn, ...args) => { const iterator = generatorFn(...args); let result = generatorNext(iterator); while (!result.done) { @@ -56,4 +59,4 @@ export async function asyncTrampoline(generatorFn, ...args) { } } return result.value; -} +}; diff --git a/packages/zip/src/crc32.js b/packages/zip/src/crc32.js index e969f1dd6e..249b4e86da 100644 --- a/packages/zip/src/crc32.js +++ b/packages/zip/src/crc32.js @@ -11,7 +11,7 @@ /** * @returns {Array} */ -function makeTable() { +const makeTable = () => { let c; const table = []; @@ -24,7 +24,7 @@ function makeTable() { } return table; -} +}; // Initialize a table of 256 signed 32 bit integers. const table = makeTable(); @@ -35,7 +35,7 @@ const table = makeTable(); * @param {number} index * @param {number} crc */ -export function crc32(bytes, length = bytes.length, index = 0, crc = 0) { +export const crc32 = (bytes, length = bytes.length, index = 0, crc = 0) => { const end = index + length; crc ^= -1; @@ -45,4 +45,4 @@ export function crc32(bytes, length = bytes.length, index = 0, crc = 0) { } return (crc ^ -1) >>> 0; -} +}; diff --git a/packages/zip/src/format-reader.js b/packages/zip/src/format-reader.js index 05fd05c3c0..c134e911ac 100644 --- a/packages/zip/src/format-reader.js +++ b/packages/zip/src/format-reader.js @@ -63,9 +63,7 @@ const textDecoder = new TextDecoder(); * @param {number} bitFlag * @returns {boolean} */ -function isEncrypted(bitFlag) { - return (bitFlag & 0x0001) === 0x0001; -} +const isEncrypted = bitFlag => (bitFlag & 0x0001) === 0x0001; /** * @param {BufferReader} reader @@ -73,7 +71,7 @@ function isEncrypted(bitFlag) { * @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html * @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html */ -function readDosDateTime(reader) { +const readDosDateTime = reader => { const dosTime = reader.readUint32(true); return new Date( Date.UTC( @@ -85,29 +83,27 @@ function readDosDateTime(reader) { (dosTime & 0x1f) << 1, // second ), ); -} +}; /** * @param {BufferReader} reader * @returns {ArchiveHeaders} */ -function readHeaders(reader) { - return { - versionNeeded: reader.readUint16(true), - bitFlag: reader.readUint16(true), - compressionMethod: reader.readUint16(true), - date: readDosDateTime(reader), - crc32: reader.readUint32(true), - compressedLength: reader.readUint32(true), - uncompressedLength: reader.readUint32(true), - }; -} +const readHeaders = reader => ({ + versionNeeded: reader.readUint16(true), + bitFlag: reader.readUint16(true), + compressionMethod: reader.readUint16(true), + date: readDosDateTime(reader), + crc32: reader.readUint32(true), + compressedLength: reader.readUint32(true), + uncompressedLength: reader.readUint32(true), +}); /** * @param {BufferReader} reader * @returns {CentralFileRecord} */ -function readCentralFileHeader(reader) { +const readCentralFileHeader = reader => { const version = reader.readUint8(); const madeBy = reader.readUint8(); const headers = readHeaders(reader); @@ -149,14 +145,14 @@ function readCentralFileHeader(reader) { fileStart, comment, }; -} +}; /** * @param {BufferReader} reader * @param {CentralDirectoryLocator} locator * @returns {Array} */ -function readCentralDirectory(reader, locator) { +const readCentralDirectory = (reader, locator) => { const { centralDirectoryOffset, centralDirectoryRecords } = locator; reader.seek(centralDirectoryOffset); @@ -175,13 +171,13 @@ function readCentralDirectory(reader, locator) { } return entries; -} +}; /** * @param {BufferReader} reader * @returns {LocalFileRecord} */ -function readFile(reader) { +const readFile = reader => { reader.expect(signature.LOCAL_FILE_HEADER); const headers = readHeaders(reader); const nameLength = reader.readUint16(true); @@ -190,14 +186,14 @@ function readFile(reader) { reader.skip(extraFieldsLength); const content = reader.read(headers.compressedLength); return { name, ...headers, content }; -} +}; /** * @param {BufferReader} reader * @param {Array} records * @returns {Array} */ -function readLocalFiles(reader, records) { +const readLocalFiles = (reader, records) => { const files = []; for (const record of records) { reader.seek(record.fileStart); @@ -205,13 +201,13 @@ function readLocalFiles(reader, records) { files.push(file); } return files; -} +}; /** * @param {BufferReader} reader * @returns {CentralDirectoryLocator} */ -function readBlockEndOfCentral(reader) { +const readBlockEndOfCentral = reader => { if (!reader.expect(signature.CENTRAL_DIRECTORY_END)) { throw Error( 'Corrupt zip file, or zip file containing an unsupported variable-width end-of-archive comment, or an unsupported zip file with 64 bit sizes', @@ -238,13 +234,13 @@ function readBlockEndOfCentral(reader) { centralDirectoryOffset, comment, }; -} +}; /** * @param {BufferReader} reader * @returns {CentralDirectoryLocator} */ -function readEndOfCentralDirectoryRecord(reader) { +const readEndOfCentralDirectoryRecord = reader => { // Zip files are permitted to have a variable-width comment at the end of the // "end of central directory record" and may have subsequent Zip64 headers. // The prescribed method of finding the beginning of the "end of central @@ -297,14 +293,14 @@ function readEndOfCentralDirectoryRecord(reader) { reader.offset = extraBytes; return locator; -} +}; /** * @param {CentralFileRecord} centralRecord * @param {LocalFileRecord} localRecord * @param {string} archiveName */ -function checkRecords(centralRecord, localRecord, archiveName) { +const checkRecords = (centralRecord, localRecord, archiveName) => { const centralName = textDecoder.decode(centralRecord.name); const localName = textDecoder.decode(localRecord.name); @@ -337,7 +333,7 @@ function checkRecords(centralRecord, localRecord, archiveName) { * @param {boolean} value * @param {string} message */ - function check(value, message) { + const check = (value, message) => { if (!value) { throw Error( `Zip integrity error: ${message} for file ${q( @@ -345,7 +341,7 @@ function checkRecords(centralRecord, localRecord, archiveName) { )} in archive ${q(archiveName)}`, ); } - } + }; check( centralRecord.bitFlag === localRecord.bitFlag, @@ -379,21 +375,20 @@ function checkRecords(centralRecord, localRecord, archiveName) { checksum === localRecord.crc32, `CRC-32 checksum mismatch, wanted ${localRecord.crc32} but actual content is ${checksum}`, ); -} +}; /** * @param {number} externalFileAttributes */ -function modeForExternalAttributes(externalFileAttributes) { - return (externalFileAttributes >> 16) & 0xffff; -} +const modeForExternalAttributes = externalFileAttributes => + (externalFileAttributes >> 16) & 0xffff; /** * @param {CentralFileRecord} centralRecord * @param {LocalFileRecord} localRecord * @returns {CompressedFile} */ -function recordToFile(centralRecord, localRecord) { +const recordToFile = (centralRecord, localRecord) => { const mode = modeForExternalAttributes(centralRecord.externalFileAttributes); return { name: centralRecord.name, @@ -406,13 +401,13 @@ function recordToFile(centralRecord, localRecord) { content: localRecord.content, comment: centralRecord.comment, }; -} +}; /** * @param {CompressedFile} file * @returns {UncompressedFile} */ -function decompressFile(file) { +const decompressFile = file => { if (file.compressionMethod !== compression.STORE) { throw Error( `Cannot find decompressor for compression method ${q( @@ -427,13 +422,13 @@ function decompressFile(file) { content: file.content, comment: file.comment, }; -} +}; /** * @param {UncompressedFile} file * @returns {ArchivedFile} */ -function decodeFile(file) { +const decodeFile = file => { const name = textDecoder.decode(file.name); const comment = textDecoder.decode(file.comment); return { @@ -444,13 +439,13 @@ function decodeFile(file) { content: file.content, comment, }; -} +}; /** * @param {BufferReader} reader * @param {string} name */ -export function readZip(reader, name = '') { +export const readZip = (reader, name = '') => { const locator = readEndOfCentralDirectoryRecord(reader); const centralRecords = readCentralDirectory(reader, locator); const localRecords = readLocalFiles(reader, centralRecords); @@ -476,4 +471,4 @@ export function readZip(reader, name = '') { // TODO handle explicit directory entries } return files; -} +}; diff --git a/packages/zip/src/format-writer.js b/packages/zip/src/format-writer.js index 4c16d79f4d..a5d42fd1cc 100644 --- a/packages/zip/src/format-writer.js +++ b/packages/zip/src/format-writer.js @@ -46,7 +46,7 @@ const textEncoder = new TextEncoder(); * @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html * @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html */ -function writeDosDateTime(writer, date) { +const writeDosDateTime = (writer, date) => { const dosTime = date !== undefined && date !== null ? (((date.getUTCFullYear() - 1980) & 0x7f) << 25) | // year @@ -57,14 +57,14 @@ function writeDosDateTime(writer, date) { (date.getUTCSeconds() >> 1) // second : 0; // Epoch origin by default. writer.writeUint32(dosTime, true); -} +}; /** * @param {BufferWriter} writer * @param {FileRecord} file * @returns {LocalFileLocator} */ -function writeFile(writer, file) { +const writeFile = (writer, file) => { // Header const fileStart = writer.index; writer.write(signature.LOCAL_FILE_HEADER); @@ -91,14 +91,14 @@ function writeFile(writer, file) { headerStart, headerEnd, }; -} +}; /** * @param {BufferWriter} writer * @param {FileRecord} file * @param {LocalFileLocator} locator */ -function writeCentralFileHeader(writer, file, locator) { +const writeCentralFileHeader = (writer, file, locator) => { writer.write(signature.CENTRAL_FILE_HEADER); writer.writeUint8(file.version); writer.writeUint8(file.madeBy); @@ -113,7 +113,7 @@ function writeCentralFileHeader(writer, file, locator) { writer.write(file.centralName); // TODO extra fields writer.write(file.comment); -} +}; /** * @param {BufferWriter} writer @@ -122,13 +122,13 @@ function writeCentralFileHeader(writer, file, locator) { * @param {number} centralDirectoryLength * @param {Uint8Array} commentBytes */ -function writeEndOfCentralDirectoryRecord( +const writeEndOfCentralDirectoryRecord = ( writer, entriesCount, centralDirectoryStart, centralDirectoryLength, commentBytes, -) { +) => { writer.write(signature.CENTRAL_DIRECTORY_END); writer.writeUint16(0, true); writer.writeUint16(0, true); @@ -138,14 +138,14 @@ function writeEndOfCentralDirectoryRecord( writer.writeUint32(centralDirectoryStart, true); writer.writeUint16(commentBytes.length, true); writer.write(commentBytes); -} +}; /** * @param {BufferWriter} writer * @param {Array} records * @param {string} comment */ -export function writeZipRecords(writer, records, comment = '') { +export const writeZipRecords = (writer, records, comment = '') => { // Write records with local headers. const locators = []; for (let i = 0; i < records.length; i += 1) { @@ -169,13 +169,13 @@ export function writeZipRecords(writer, records, comment = '') { centralDirectoryLength, commentBytes, ); -} +}; /** * @param {import('./types.js').ArchivedFile} file * @returns {import('./types.js').UncompressedFile} */ -function encodeFile(file) { +const encodeFile = file => { const name = textEncoder.encode(file.name.replace(/\\/g, '/')); const comment = textEncoder.encode(file.comment); return { @@ -185,25 +185,23 @@ function encodeFile(file) { content: file.content, comment, }; -} +}; /** * @param {import('./types.js').UncompressedFile} file * @returns {import('./types.js').CompressedFile} */ -function compressFileWithStore(file) { - return { - name: file.name, - mode: file.mode, - date: file.date, - crc32: crc32(file.content), - compressionMethod: compression.STORE, - compressedLength: file.content.length, - uncompressedLength: file.content.length, - content: file.content, - comment: file.comment, - }; -} +const compressFileWithStore = file => ({ + name: file.name, + mode: file.mode, + date: file.date, + crc32: crc32(file.content), + compressionMethod: compression.STORE, + compressedLength: file.content.length, + uncompressedLength: file.content.length, + content: file.content, + comment: file.comment, +}); /** * Computes Zip external file attributes field from a UNIX mode for a file. @@ -211,9 +209,7 @@ function compressFileWithStore(file) { * @param {number} mode * @returns {number} */ -function externalFileAttributes(mode) { - return ((mode & 0o777) | 0o10_0000) << 16; -} +const externalFileAttributes = mode => ((mode & 0o777) | 0o10_0000) << 16; // TODO Add support for directory records. // /** @@ -229,36 +225,34 @@ function externalFileAttributes(mode) { * @param {import('./types.js').CompressedFile} file * @returns {FileRecord} */ -function makeFileRecord(file) { - return { - name: file.name, - centralName: file.name, - madeBy: UNIX, - version: UNIX_VERSION, - versionNeeded: 0, // TODO this is probably too lax. - bitFlag: 0, - compressionMethod: compression.STORE, - date: file.date, - crc32: file.crc32, - compressedLength: file.compressedLength, - uncompressedLength: file.uncompressedLength, - diskNumberStart: 0, - internalFileAttributes: 0, - externalFileAttributes: externalFileAttributes(file.mode), - comment: file.comment, - content: file.content, - }; -} +const makeFileRecord = file => ({ + name: file.name, + centralName: file.name, + madeBy: UNIX, + version: UNIX_VERSION, + versionNeeded: 0, // TODO this is probably too lax. + bitFlag: 0, + compressionMethod: compression.STORE, + date: file.date, + crc32: file.crc32, + compressedLength: file.compressedLength, + uncompressedLength: file.uncompressedLength, + diskNumberStart: 0, + internalFileAttributes: 0, + externalFileAttributes: externalFileAttributes(file.mode), + comment: file.comment, + content: file.content, +}); /** * @param {BufferWriter} writer * @param {Array} files * @param {string} comment */ -export function writeZip(writer, files, comment = '') { +export const writeZip = (writer, files, comment = '') => { const encodedFiles = files.map(encodeFile); const compressedFiles = encodedFiles.map(compressFileWithStore); // TODO collate directoryRecords from file bases. const fileRecords = compressedFiles.map(makeFileRecord); writeZipRecords(writer, fileRecords, comment); -} +}; diff --git a/packages/zip/src/signature.js b/packages/zip/src/signature.js index f61d8c9eb4..3aae9df7ed 100644 --- a/packages/zip/src/signature.js +++ b/packages/zip/src/signature.js @@ -5,13 +5,13 @@ * @param {string} string * @returns {Uint8Array} */ -function u(string) { +const u = string => { const array = new Uint8Array(string.length); for (let i = 0; i < string.length; i += 1) { array[i] = string.charCodeAt(i) & 0xff; } return array; -} +}; export const LOCAL_FILE_HEADER = u('PK\x03\x04'); export const CENTRAL_FILE_HEADER = u('PK\x01\x02');