import { readdirSync, readFileSync } from "node:fs"; import { parseSync, transformSync, type types as t } from "@babel/core"; import reactCompiler from "babel-plugin-react-compiler"; // Runs the real React Compiler — the options reactPlugin.ts gives it — over the shipped source and // reports every function it SKIPS, keyed `file → function → category`. The compiler skips without a // word (`panicThreshold` is "none" and the build has no logger), so this is the only place a skip is // visible at all. Used by compilerCoverage.contract.test.ts. const SRC = new URL("../", import.meta.url); /** Every file the production build compiles: src, minus tests and test kits. */ export function shippedSources(): string[] { return (readdirSync(SRC, { recursive: true }) as string[]) .map((p) => p.split("\\").join("/")) .filter( (p) => /\.(ts|tsx)$/.test(p) && !/\.(test|testkit)\.tsx?$/.test(p) && !p.endsWith(".d.ts"), ) .sort(); } type Node = t.Node & { [k: string]: unknown }; const isFn = (n: Node) => n.type === "FunctionDeclaration" || n.type === "FunctionExpression" || n.type === "ArrowFunctionExpression"; /** The name a reader would look a function up by: its own, the variable it is assigned to (through * a `memo(…)` / `forwardRef(…)` wrapper), or `default` for an anonymous default export. */ function functionNames(ast: t.File): Map { const names = new Map(); const walk = (node: Node, parents: Node[]) => { if (isFn(node) && node.loc) { let name = (node as t.FunctionDeclaration).id?.name ?? null; for (let i = parents.length - 1; !name && i >= 0; i--) { const p = parents[i]!; if (p.type === "VariableDeclarator" && p.id.type === "Identifier") name = p.id.name; else if (p.type === "ExportDefaultDeclaration") name = "default"; else if (p.type !== "CallExpression" && p.type !== "TSAsExpression") break; } names.set( `${node.loc.start.line}:${node.loc.start.column}`, name ?? ``, ); } for (const key of Object.keys(node)) { if (key === "loc" || key === "leadingComments" || key === "trailingComments") continue; const v = node[key]; if (Array.isArray(v)) { for (const c of v) if (c && typeof c === "object" && "type" in c) walk(c as Node, [...parents, node]); } else if (v && typeof v === "object" && "type" in v) walk(v as Node, [...parents, node]); } }; walk(ast as unknown as Node, []); return names; } type Detail = { category?: string; reason?: string; options?: { category?: string; reason?: string }; }; export interface CompileReport { /** file → function → the compiler's category for the skip (Refs, Todo, Immutability, …). */ skipped: Record>; /** file → the functions it did compile. */ compiled: Record; } export function compileReport(files: string[]): CompileReport { const skipped: CompileReport["skipped"] = {}; const compiled: CompileReport["compiled"] = {}; for (const file of files) { const source = readFileSync(new URL(file, SRC), "utf8"); const opts = { filename: file, babelrc: false, configFile: false } as const; const ast = parseSync(source, { ...opts, parserOpts: { plugins: ["jsx", "typescript"] } }); if (!ast) throw new Error(`could not parse ${file}`); const names = functionNames(ast); const nameAt = (loc: t.SourceLocation | null) => loc ? (names.get(`${loc.start.line}:${loc.start.column}`) ?? ``) : ""; transformSync(source, { ...opts, parserOpts: { plugins: ["jsx", "typescript"] }, plugins: [ [ reactCompiler, { target: "19", logger: { logEvent: ( _f: string | null, e: { kind: string; fnLoc?: t.SourceLocation | null; detail?: Detail; reason?: string; }, ) => { if (e.kind === "CompileSuccess") { (compiled[file] ??= []).push(nameAt(e.fnLoc ?? null)); return; } let category: string; if (e.kind === "CompileError") category = e.detail?.options?.category ?? e.detail?.category ?? "Unknown"; else if (e.kind === "CompileSkip") category = `Skip: ${e.reason}`; else if (e.kind === "PipelineError") category = "PipelineError"; else return; const fns = (skipped[file] ??= {}); // The first reason is the one that stopped it; later ones are the same function. fns[nameAt(e.fnLoc ?? null)] ??= category; }, }, }, ], ], }); } return { skipped, compiled }; }