// Pulls named declarations out of src/app.js and evaluates them, so a check // exercises the code that ships rather than a copy of it. // // The app is one long IIFE with nothing exported — it has no build step and no // module system, and adding either to make it testable would be a large change // in service of a small one. Reading the source back is the cheaper trade: the // checks stay honest, and the app stays a file you can open in a browser. // // If a declaration is renamed or removed, load() throws by name. That is the // point: a check that quietly tested a stale copy would be worse than no check. import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; const SRC = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "app.js"); // Blanks out comments and string bodies so brace counting can't be fooled by a // `}` inside one. Positions are preserved, so offsets into the result are valid // offsets into the original. function mask(src) { const out = src.split(""); let i = 0; const blank = (from, to) => { for (let k = from; k < to; k++) if (out[k] !== "\n") out[k] = " "; }; while (i < src.length) { const c = src[i], next = src[i + 1]; if (c === "/" && next === "/") { const end = src.indexOf("\n", i); const stop = end === -1 ? src.length : end; blank(i, stop); i = stop; continue; } if (c === "/" && next === "*") { const end = src.indexOf("*/", i + 2); const stop = end === -1 ? src.length : end + 2; blank(i, stop); i = stop; continue; } if (c === '"' || c === "'" || c === "`") { let k = i + 1; while (k < src.length) { if (src[k] === "\\") { k += 2; continue; } if (src[k] === c) break; k++; } blank(i + 1, Math.min(k, src.length)); i = Math.min(k + 1, src.length); continue; } i++; } return out.join(""); } // The source of one top-level declaration, brace-matched from its opening line. function declaration(src, masked, name) { const patterns = [ new RegExp(`^ {2}(?:async )?function ${name}\\b`, "m"), new RegExp(`^ {2}(?:const|let) ${name}\\b`, "m"), ]; for (const re of patterns) { const m = re.exec(masked); if (!m) continue; const start = m.index; // A function runs to its matching close brace; a const/let to the newline // after the statement that balances its own brackets. let depth = 0, seen = false, i = start; for (; i < masked.length; i++) { const ch = masked[i]; if (ch === "{" || ch === "(" || ch === "[") { depth++; seen = true; } else if (ch === "}" || ch === ")" || ch === "]") { depth--; if (depth === 0 && seen && ch === "}" && /function/.test(m[0])) return src.slice(start, i + 1); } else if (ch === ";" && depth === 0 && !/function/.test(m[0])) { return src.slice(start, i + 1); } } } throw new Error( `checks/extract: could not find "${name}" in src/app.js.\n` + `It was probably renamed or removed — update the check that asks for it.`); } /** * load({ names, lets, stubs }) → { ...declarations, set: { : fn } } * * names declarations to pull across, in dependency order * lets of those, the mutable ones a check needs to assign (a setter is * generated for each, since a check can't reach the binding otherwise) * stubs names the extracted code calls but which are not worth extracting — * DOM lookups, chartDays(), and so on */ export function load({ names, lets = [], stubs = {} }) { const src = readFileSync(SRC, "utf8"); const masked = mask(src); const body = names.map(n => declaration(src, masked, n)).join("\n\n"); const stubNames = Object.keys(stubs); const exported = names.map(n => n.replace(/^.*\s/, "")); const setters = lets.map(n => `${n}: (v) => { ${n} = v; }`).join(", "); const factory = new Function(...stubNames, ` ${body} return { ${exported.join(", ")}, set: { ${setters} } }; `); return factory(...stubNames.map(n => stubs[n])); } export const appSource = () => readFileSync(SRC, "utf8");