/** * Selective remap: given the files a change touched, which declared surfaces * could have rendered differently? * * This is the sound core behind "capture only what a PR can affect, reuse the * committed base map for the rest" — an OPT-IN speed-up, never the default gate. * The default gate captures every surface and lets the map be the oracle; this * function only decides which surfaces a caller *may* skip, and it is built to * be wrong only in the safe direction. * * The hard constraint: in the committed-map model a wrong "unaffected" is silent * and fatal — a stale committed map matches the base, the diff is empty, and the * regression ships green. So this function OVER-APPROXIMATES. When it cannot * prove a surface is unaffected, it returns the sentinel `'all'`, meaning * "re-capture everything." Every uncertainty resolves to `'all'`: * * - a global style change (a reset, `:root`/theme token, `@tailwind`, a * `createGlobalStyle`, a design-system config) cascades everywhere → `'all'`; * - a vanilla (non-module) stylesheet has a global class namespace the import * graph cannot bound → `'all'`; * - a computed dynamic `import(x)` with no static prefix could load anything * → `'all'`; with a static prefix (`import(`../dir/${x}`)`) it is treated as * a bundler context module — every file under that dir is a possible target; * - a changed file the graph cannot place at all → `'all'`. * * The module graph is an INPUT, not a dependency: pass any tool's output in the * {@link ModuleEdge} shape (dependency-cruiser's `modules[].dependencies[]` maps * directly). StyleProof stays framework-agnostic and adds no dependency; the * caller owns graph production, which is where framework-specific resolution * lives. * * Pure and side-effect-free (I/O is injected via `readFile`) so it is fully * unit-testable and deterministic. */ /** One resolved import edge: `from` imports `to`. Mirrors a dependency-cruiser * `modules[].dependencies[]` entry (use `module.source` as `from`, dependency * `resolved` as `to`). `dynamic` is informational; resolution already happened. */ export type ModuleEdge = { from: string; to: string; dynamic?: boolean; }; export type AffectedSurfacesInput = { /** Repo-relative paths the change touched (as they appear in the graph). */ changedFiles: Iterable; /** Declared surfaces: capture key → the surface's entry module path. */ surfaces: Record; /** Resolved import edges for the source tree (node_modules edges are ignored). */ graph: Iterable; /** Every candidate source file path — the universe a context-module glob can * resolve within. Typically the graph's node set. */ files: Iterable; /** Read a source file's text (for style classification and dynamic-import * recovery). Throwing/returning nothing is treated as "unknown" → `'all'`. */ readFile: (path: string) => string; }; /** `'all'` means "re-capture everything" (some change could not be bounded). A * `Set` of surface keys means exactly those surfaces can be affected; any not * listed are provably unaffected and may reuse their committed base map. */ export type AffectedSurfaces = Set | 'all'; /** * Decide whether a single changed file's style scope is bounded to the files * that import it (`'scope'` → follow the import graph) or escapes them * (`'all'` → re-capture everything). Sound by construction: `'scope'` is * returned only for provably-scoped changes (a CSS Module without escapes, or * colocated CSS-in-JS with no global API); everything else, including anything * unrecognized, is `'all'`. */ export declare function classifyStyleChange(file: string, readFile: (p: string) => string): 'scope' | 'all'; /** * Canonicalize a repo-relative path so the same file spells the same regardless * of source (a `surfaces` value, a `changedFiles` entry, or a graph edge). Two * tools disagree on `./pages/Home.tsx` vs `pages/Home.tsx` vs `pages//Home.tsx`; * without one spelling, a reverse-reachability hit can silently miss the surface * whose entry key was spelled differently, dropping it from the affected set — * an unsound skip. Byte-cheap and fs-free: strip a leading `./`, collapse `//`, * and drop `.`/`..` segments as pure string math (no realpath, no resolution). */ export declare function canonicalPath(p: string): string; /** * Compute the set of declared surfaces a change could have altered, or `'all'`. * See the module doc for the soundness contract. Any not in the returned set are * provably unaffected and may reuse their committed base map. */ export declare function affectedSurfaces(input: AffectedSurfacesInput): AffectedSurfaces; /** * Render an {@link affectedSurfaces} verdict as human-readable lines a pre-push * hook (or CI log) can print, so a reviewer can sanity-check the skip list before * trusting it. Pure formatter — no I/O, no graph work. * * @param result the value {@link affectedSurfaces} returned. * @param allSurfaces every declared surface key (e.g. `Object.keys(surfaces)`), * so the helper can name what is *reused from base* — the ones * the verdict skips — not just what re-captures. * @param reason optional one-line explanation for an `'all'` verdict (e.g. * the classifying file, from {@link classifyStyleChange}). The * library doesn't attach a reason to the sentinel, so pass it * if the caller knows why; omitted, the `'all'` line stands alone. */ export declare function explainAffectedSurfaces(result: AffectedSurfaces, allSurfaces: Iterable, reason?: string): string;