Something went wrong. Try again.
composable JavaScript eval templating
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186/** 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<unknown, string>();
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), }, });};