import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { FILES, FRONTEND, SRC } from "./guard-files.spec-helper"; /** * Baukasten guard (spec 10.7). Each T5 task adds the rule it fulfils, with every exception and * its reason. A rule scans the files of its scope (spec files excluded) for its pattern; an * exception names one file, relative to ApiCore-Frontend, and must still be needed. */ type GuardScope = "src/**/*.tsx" | "src/**/*.{ts,tsx}" | "src/**/*.{ts,tsx,css}"; interface GuardRule { id: string; pattern: RegExp; scope: GuardScope; allow: { file: string; reason: string }[]; /** Directories the rule does not cover at all (path prefix relative to ApiCore-Frontend). */ outside?: { prefix: string; reason: string }[]; /** Self-test: snippets the pattern must catch and snippets it must leave alone. */ examples: { catches: string[]; ignores: string[] }; } /** * `keys` (role, type) set to `value` by hand: JSX (role="x", role={on ? "x" : y}), object or * spread form (role: "x") and setAttribute("role", "x"). A CSS selector ([role="x"]) only finds * kit controls, and the `role` prop inside a tag is the kit's own API. */ function ownRole(keys: string, value: string): RegExp { const quote = "[\"'`]"; const quoted = `${quote}${value}${quote}`; // role="x" | role={... "x" ...} | role: ... "x" (not role?: or role === "x") const attribute = String.raw`(?]*)(?:${attribute}|${setAttribute})`); } /** * Spec 17.8 lets the hive stage move on its own (Beute §9.4, §9.8: lift on hover, flight into * focus). It does so in three.js, not with CSS classes, so no motion rule leaves it out today * (the stale-exception test would fail). Should the stage ever need a CSS exception, add an * `outside` entry with that reason to the one rule it needs. */ /** A duration above --motion-moment (420 ms): Tailwind steps and arbitrary ms values. */ const OVER_MOMENT_MS = String.raw`(?:42[1-9]|4[3-9]\d|[5-9]\d\d|[1-9]\d{3,})`; /** A px value below 13 (0–12.99…). */ const UNDER_13_PX = String.raw`(?:\d|1[0-2])(?:\.\d+)?px`; /** A rem/em value below 0.8125 (13 px at a 16 px root): the digits after the point sort below 8125. */ const UNDER_13_REM = String.raw`0?\.(?:[0-7]\d*|8(?:0\d*|1(?:[01]\d*|2(?:[0-4]\d*)?)?)?)r?em`; const RULES: GuardRule[] = [ // Task A3/A4/A10: the area tone replaced the gradients (spec 11); the gradient tokens are gone. // PageHeader, DetailHeader and EntityCard have no `gradient` prop any more (also a type error). // A canvas gradient that paints a 3D texture (createLinearGradient) is no CSS background. { id: "no gradient prop, gradient token or gradient background", pattern: new RegExp( [ String.raw`gradient=`, String.raw`--gradient-`, String.raw`(?', "", '', '"[background-image:var(--gradient-honey)]"', '"bg-gradient-to-r from-primary to-warning"', '"bg-linear-to-b from-card"', '"bg-radial"', "background: linear-gradient(90deg, red, blue);", ], ignores: ["context.createLinearGradient(0, 0, 0, 512)", '"bg-primary text-primary-foreground"', ''], }, }, // Task A3/A4: white text fails on honey and on the light sets. The hive stage uses // --holo-label-fg instead, so it needs no exception either. { id: "no text-white", pattern: /(?", '', "font-size: 11px;", "font-size: 0.75rem;", "style={{ fontSize: 11 }}", 'style={{ lineHeight: 1, fontSize: "12px" }}', "const AXIS_LABEL_SIZE = 10;", "const FONT_SIZE_PX = 12;", ], ignores: [ '"text-[13px]"', '"text-[0.8125rem]"', '"text-[1rem]"', '"text-xs"', "", "", '"forced-colors:text-[CanvasText]"', "fontSize: 10,", "--text-xs: 0.8125rem;", "style={{ fontSize: 14 }}", "const LABEL_FONT_SIZE = 16;", "const MAX_SIZE = 10;", "const LABEL_GAP = 4;", ], }, }, // Task A5: one focus style, drawn by the base rule :where(:focus-visible) (spec 4.6). { id: "no ring-ring/50", pattern: /ring-ring\/50/, scope: "src/**/*.{ts,tsx}", allow: [], examples: { catches: ['"focus-visible:ring-3 focus-visible:ring-ring/50"'], ignores: ['"ring-ring"', '"focus:inset-ring-2 focus:inset-ring-ring"'], }, }, { id: "no outline-none", pattern: /(?', '
', "