/** options for {@link create}. */ export interface CreateOptions { /** Trusted Types policy name; must be allowed by CSP. ignored if Trusted Types is unavailable. */ readonly policyName?: string; } /** a composable JavaScript function body. */ export interface CodeFragment { /** * evaluate the fragment. * * @returns the value returned by the function body, if any * @throws if compilation, CSP enforcement, or execution fails */ eval(): unknown; } /** a generated identifier, unique within each evaluated function body. */ export interface LocalIdent { /** sanitized hint used in the generated identifier. */ readonly name: string | undefined; } /** a template tag for composing code fragments. */ export interface CodeTag { /** * compose source, nested fragments, identifiers, and runtime values. * * @param strings literal source segments * @param values interpolated fragments, identifiers, or runtime values * @returns a code fragment */ (strings: TemplateStringsArray, ...values: unknown[]): CodeFragment; /** a fragment that contains nothing. */ readonly empty: CodeFragment; /** * create a unique local identifier. * * @param name optional readable hint * @returns an identifier whose interpolations share a name within each evaluation */ local(name?: string): LocalIdent; /** * insert trusted JavaScript source verbatim. * * @param source trusted source code * @returns a code fragment */ raw(source: string): CodeFragment; } class Identifier implements LocalIdent { readonly name: string | undefined; constructor(name: string | undefined) { this.name = name?.replace(/[^A-Za-z0-9_$]/g, '_'); } } // Map equates signed zeros, but generated code must preserve their distinction. const negativeZero = Symbol(); class Fragment implements CodeFragment { readonly strings: readonly string[]; readonly values: readonly unknown[]; readonly #trust: (source: string) => string | TrustedScript; constructor( strings: readonly string[], values: readonly unknown[], trust: (source: string) => string | TrustedScript, ) { this.strings = strings; this.values = values; this.#trust = trust; } eval(): unknown { const seen = new Map(); const params: string[] = []; const interpolations: unknown[] = []; let uid = 0; const resolve = (fragment: Fragment): string => { let source = fragment.strings[0]; for (let idx = 0; idx < fragment.values.length; idx++) { const value = fragment.values[idx]; if (value instanceof Fragment) { source += resolve(value); } else { const key = Object.is(value, -0) ? negativeZero : value; let name = seen.get(key); if (name === undefined) { name = '_$_' + uid++; if (value instanceof Identifier) { if (value.name !== undefined) { name += '_' + value.name; } } else { params.push(name); interpolations.push(value); } seen.set(key, name); } source += name; } source += fragment.strings[idx + 1]; } return source; }; const source = resolve(this); // oxlint-disable-next-line typescript/no-implied-eval -- eval is the point. const fn = new Function(this.#trust(params.join(',')), this.#trust(source)); return fn.apply(undefined, interpolations); } } const passthrough = (source: string): string => source; const EMPTY_ARRAY: never[] = []; /** * create a code template tag. * * @param options tag configuration * @returns a code template tag * @throws if Trusted Types policy creation fails */ // #__NO_SIDE_EFFECTS__ export const create = (options?: CreateOptions): CodeTag => { let trust: (source: string) => string | TrustedScript = passthrough; if (options?.policyName !== undefined) { const policy = globalThis.trustedTypes?.createPolicy(options.policyName, { createScript: passthrough, }); if (policy !== undefined) { trust = (source) => policy.createScript(source); } } const tag = (strings: TemplateStringsArray, ...values: unknown[]): CodeFragment => { return new Fragment(strings, values, trust); }; // oxlint-disable-next-line typescript/no-unsafe-type-assertion return Object.defineProperties(tag as CodeTag, { empty: { configurable: true, get() { const empty = new Fragment([''], EMPTY_ARRAY, trust); Object.defineProperty(tag, 'empty', { configurable: false, value: empty, }); return empty; }, }, local: { value: (name?: string): LocalIdent => new Identifier(name), }, raw: { value: (source: string): CodeFragment => new Fragment([source], EMPTY_ARRAY, trust), }, }); };