diff --git a/src/__tests__/acting-account-boundary-guard.test.ts b/src/__tests__/acting-account-boundary-guard.test.ts index 107794519..5a3dc2bef 100644 --- a/src/__tests__/acting-account-boundary-guard.test.ts +++ b/src/__tests__/acting-account-boundary-guard.test.ts @@ -14,10 +14,9 @@ * those. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -30,7 +29,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } function filesMatching(re: RegExp): string[] { diff --git a/src/__tests__/ai-capability-surface-guard.test.ts b/src/__tests__/ai-capability-surface-guard.test.ts index e796fc236..b82232e09 100644 --- a/src/__tests__/ai-capability-surface-guard.test.ts +++ b/src/__tests__/ai-capability-surface-guard.test.ts @@ -26,14 +26,17 @@ * another module would slip it; `AI_ROUTE_PATTERNS` lists the literal shapes * the tree uses today. */ -import { readFileSync } from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; import type { AiCapabilityKey } from "@/lib/ai/capabilities/types"; -import { stripComments, walkSourceFiles } from "./helpers/source-files"; +import { + readSourceText, + stripComments, + walkSourceFiles, +} from "./helpers/source-files"; const SRC = path.resolve(__dirname, ".."); @@ -64,7 +67,7 @@ const FEATURE_FLAG_SWEEP = NON_TEST_FILES.filter( ); const read = (rel: string) => - stripComments(readFileSync(path.join(SRC, rel), "utf8")); + stripComments(readSourceText(path.join(SRC, rel))); /** Request literals of routes that call a model or serve model text. */ const AI_ROUTE_PATTERNS: readonly RegExp[] = [ diff --git a/src/__tests__/bearer-scope-enforcement-guard.test.ts b/src/__tests__/bearer-scope-enforcement-guard.test.ts index 6c0298f4b..21ab2d10a 100644 --- a/src/__tests__/bearer-scope-enforcement-guard.test.ts +++ b/src/__tests__/bearer-scope-enforcement-guard.test.ts @@ -13,10 +13,9 @@ * for that review. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -33,7 +32,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } function filesMatching(re: RegExp): string[] { diff --git a/src/__tests__/client-fetch-limit-guard.test.ts b/src/__tests__/client-fetch-limit-guard.test.ts index c9d73875a..87b9a9f6b 100644 --- a/src/__tests__/client-fetch-limit-guard.test.ts +++ b/src/__tests__/client-fetch-limit-guard.test.ts @@ -28,10 +28,9 @@ * outside `app/api/`); a server-side `take` is not a fetch. */ import { describe, expect, it } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -178,7 +177,7 @@ function findLimitHits(file: string, source: string): Hit[] { function allHits(): Hit[] { return clientFiles().flatMap((file) => - findLimitHits(file, readFileSync(join(SRC, file), "utf8")), + findLimitHits(file, readSourceText(join(SRC, file))), ); } diff --git a/src/__tests__/client-route-zod-guard.test.ts b/src/__tests__/client-route-zod-guard.test.ts index ad2f39e9a..b7d0e207b 100644 --- a/src/__tests__/client-route-zod-guard.test.ts +++ b/src/__tests__/client-route-zod-guard.test.ts @@ -20,9 +20,10 @@ * loaded chunk may still carry zod. */ import { describe, expect, it } from "vitest"; -import { existsSync, readFileSync } from "node:fs"; +import { existsSync } from "node:fs"; import { dirname, join, normalize } from "node:path"; import ts from "typescript"; +import { readSourceText } from "./helpers/source-files"; const ROOT = join(__dirname, "..", ".."); @@ -90,7 +91,7 @@ function clientZodImports(entry: string): { file: string; path: string[] }[] { const [file, inClient] = stack.pop()!; const prior = seen.get(file); if (prior === true || (prior === false && !inClient)) continue; - const source = readFileSync(file, "utf8"); + const source = readSourceText(file); const client = inClient || isClient(source); seen.set(file, client); walked += 1; @@ -141,7 +142,7 @@ describe("client routes without the schema library", () => { // The schema module is zod by definition; walked as a client entry it // must be reported, or the walk above proves nothing. const probe = join(ROOT, "src/lib/validations/inbound-documents.ts"); - const source = readFileSync(probe, "utf8"); + const source = readSourceText(probe); expect(staticImports(source, probe).some((s) => s.startsWith("zod"))).toBe( true, ); diff --git a/src/__tests__/compose-env-whitelist-guard.test.ts b/src/__tests__/compose-env-whitelist-guard.test.ts index 027cb5262..89f9a534e 100644 --- a/src/__tests__/compose-env-whitelist-guard.test.ts +++ b/src/__tests__/compose-env-whitelist-guard.test.ts @@ -35,10 +35,9 @@ * allowlist. Both the walk and every matcher therefore assert a floor. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const ROOT = process.cwd(); const SRC = join(ROOT, "src"); @@ -151,7 +150,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } /** Every environment variable shipped server code reads, however it reads it. */ @@ -175,7 +174,7 @@ function runtimeReads(): Set { * repeating it. */ function composeWhitelist(): Set { - const compose = readFileSync(join(ROOT, "docker-compose.yml"), "utf8"); + const compose = readSourceText(join(ROOT, "docker-compose.yml")); const keys = new Set(); for (const line of compose.split("\n")) { const m = line.match(/^\s{2,}([A-Z][A-Z0-9_]*):\s/); diff --git a/src/__tests__/correlation-single-implementation-guard.test.ts b/src/__tests__/correlation-single-implementation-guard.test.ts index 708118b23..c796e97d6 100644 --- a/src/__tests__/correlation-single-implementation-guard.test.ts +++ b/src/__tests__/correlation-single-implementation-guard.test.ts @@ -17,11 +17,14 @@ * inline under another name slips it. Comments are stripped first; the * planted cases prove it fires. */ -import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { stripComments, walkSourceFiles } from "./helpers/source-files"; +import { + readSourceText, + stripComments, + walkSourceFiles, +} from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -56,14 +59,8 @@ describe("one Pearson correlation", () => { }); it("both homes still hold their function", () => { - const engine = readFileSync( - join(SRC, "lib/insights/correlations.ts"), - "utf8", - ); - const wrapper = readFileSync( - join(SRC, "lib/analytics/correlations.ts"), - "utf8", - ); + const engine = readSourceText(join(SRC, "lib/insights/correlations.ts")); + const wrapper = readSourceText(join(SRC, "lib/analytics/correlations.ts")); expect(engine).toMatch(/export function pearson\s*\(/); expect(wrapper).toMatch( /export function significantPearsonCorrelation\s*\(/, @@ -73,14 +70,12 @@ describe("one Pearson correlation", () => { it("no other file defines a Pearson coefficient or calls the deleted one", () => { // The homes match the definition matcher, so a matcher gone blind fails here. const definers = sourceFiles().filter((file) => - PEARSON_DEFINITION.test( - stripComments(readFileSync(join(SRC, file), "utf8")), - ), + PEARSON_DEFINITION.test(stripComments(readSourceText(join(SRC, file)))), ); expect(definers.length).toBeGreaterThanOrEqual(2); const offenders = sourceFiles().filter((file) => { - const code = stripComments(readFileSync(join(SRC, file), "utf8")); + const code = stripComments(readSourceText(join(SRC, file))); if (DELETED.test(code)) return true; return !HOMES.has(file) && PEARSON_DEFINITION.test(code); }); diff --git a/src/__tests__/daily-reads-refetch-guard.test.ts b/src/__tests__/daily-reads-refetch-guard.test.ts index 201759d15..97bfa839b 100644 --- a/src/__tests__/daily-reads-refetch-guard.test.ts +++ b/src/__tests__/daily-reads-refetch-guard.test.ts @@ -32,10 +32,9 @@ * assertion. Both fixes were then restored and the guard re-ran green. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -91,7 +90,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } function count(s: string, re: RegExp): number { diff --git a/src/__tests__/discovery-matrix-guard.test.ts b/src/__tests__/discovery-matrix-guard.test.ts index 36e7d48d7..c45ebd42a 100644 --- a/src/__tests__/discovery-matrix-guard.test.ts +++ b/src/__tests__/discovery-matrix-guard.test.ts @@ -43,10 +43,9 @@ * how `bearer-scope-enforcement-guard` stayed green over a file it never saw. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -69,7 +68,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } function filesMatching(re: RegExp): string[] { diff --git a/src/__tests__/display-transform-rounding-guard.test.ts b/src/__tests__/display-transform-rounding-guard.test.ts index 73b928296..470ed94b8 100644 --- a/src/__tests__/display-transform-rounding-guard.test.ts +++ b/src/__tests__/display-transform-rounding-guard.test.ts @@ -23,10 +23,9 @@ * are pinned to a named allowlist, so a new converted-value surface * cannot quietly join them. */ -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; import { describe, it, expect } from "vitest"; @@ -83,7 +82,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } describe("display-transform rounding guard — the registry property", () => { diff --git a/src/__tests__/error-level-discipline-guard.test.ts b/src/__tests__/error-level-discipline-guard.test.ts index 027fe7e17..98459a233 100644 --- a/src/__tests__/error-level-discipline-guard.test.ts +++ b/src/__tests__/error-level-discipline-guard.test.ts @@ -40,12 +40,11 @@ * and asserts the sweep goes red without it, so every entry is shown to be * load-bearing, and the sweep is shown to find what it claims to find. */ -import { readFileSync } from "node:fs"; import { join } from "node:path"; import ts from "typescript"; import { describe, expect, it } from "vitest"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); const SCOPE = "lib/jobs/"; @@ -257,9 +256,7 @@ const ALLOWED: Readonly> = { function sweep(): Finding[] { return walkSourceFiles(join(SRC, SCOPE), { floor: 100 }) .filter((p) => !p.includes("__tests__") && !p.endsWith(".test.ts")) - .flatMap((p) => - scan(`${SCOPE}${p}`, readFileSync(join(SRC, SCOPE, p), "utf8")), - ); + .flatMap((p) => scan(`${SCOPE}${p}`, readSourceText(join(SRC, SCOPE, p)))); } function unallowed( diff --git a/src/__tests__/external-id-stability-guard.test.ts b/src/__tests__/external-id-stability-guard.test.ts index 0cffe0109..09b03fc17 100644 --- a/src/__tests__/external-id-stability-guard.test.ts +++ b/src/__tests__/external-id-stability-guard.test.ts @@ -22,10 +22,9 @@ * addition defeats them, and no test substitutes for that review. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); const VALIDATOR_MODULE = "@/lib/validations/external-id"; @@ -39,7 +38,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } /** @@ -377,9 +376,8 @@ describe("the intake idempotency key is never read across users", () => { }); it("the schema keeps the key unique per user, not globally", () => { - const schema = readFileSync( + const schema = readSourceText( join(process.cwd(), "prisma", "schema.prisma"), - "utf8", ); expect(schema).toContain("@@unique([userId, idempotencyKey])"); expect(schema).not.toMatch( diff --git a/src/__tests__/helpers/source-files.ts b/src/__tests__/helpers/source-files.ts index 70e92385a..3f7867917 100644 --- a/src/__tests__/helpers/source-files.ts +++ b/src/__tests__/helpers/source-files.ts @@ -51,17 +51,114 @@ * match. Every caller roots at `src/`, `e2e/` or a subtree of one. */ import { existsSync, readdirSync, readFileSync } from "node:fs"; -import { join, sep } from "node:path"; +import { join, relative, resolve, sep } from "node:path"; + +// ── The shared source index ──────────────────────────────────────────────── +// +// The guards walk and read the same tree, and the guards project shares one +// module graph per worker (`isolate: false`), so every walk and every read +// below is memoised for the life of the worker: the tree is listed once per +// top-level folder and each file is read once, however many guards ask. A +// guard that walked `src/` and read every file again for each pattern it +// tried ran for fifteen seconds; the same questions over the index take a +// fraction of one. +// +// Only folders of the repository that do not change while tests run are +// indexed (`INDEXED_ROOTS`). A walk or a read anywhere else (a temporary +// fixture tree a test writes and rewrites) goes to the disk every time. + +const INDEXED_ROOTS = ["src", "e2e", "prisma"].map((dir) => + resolve(process.cwd(), dir), +); + +function indexedRootOf(path: string): string | null { + const absolute = resolve(path); + return ( + INDEXED_ROOTS.find( + (root) => absolute === root || absolute.startsWith(`${root}${sep}`), + ) ?? null + ); +} + +const listingCache = new Map(); + +/** Every entry under `root`, posix-separated, sorted; memoised when indexed. */ +function listTree(root: string): string[] { + const absolute = resolve(root); + const anchor = indexedRootOf(absolute); + if (anchor === null) { + return readdirSync(absolute, { recursive: true }) + .map((entry) => String(entry).split(sep).join("/")) + .sort(); + } + let all = listingCache.get(anchor); + if (all === undefined) { + all = readdirSync(anchor, { recursive: true }) + .map((entry) => String(entry).split(sep).join("/")) + .sort(); + listingCache.set(anchor, all); + } + if (absolute === anchor) return all; + const prefix = `${relative(anchor, absolute).split(sep).join("/")}/`; + return all + .filter((rel) => rel.startsWith(prefix)) + .map((rel) => rel.slice(prefix.length)); +} + +const textCache = new Map(); + +/** + * A repository file's text, read once per worker when it lies in an indexed + * folder. Use it in place of `readFileSync(path, "utf8")` for source the + * guard reads and never writes. + */ +export function readSourceText(path: string): string { + const absolute = resolve(path); + if (indexedRootOf(absolute) === null) return readFileSync(absolute, "utf8"); + let text = textCache.get(absolute); + if (text === undefined) { + text = readFileSync(absolute, "utf8"); + textCache.set(absolute, text); + } + return text; +} + +/** + * The files under `root` (as `walkSourceFiles` lists them) whose text holds + * at least one of `markers`, with that text. The substring test runs before + * any regular expression or parse the caller applies, which is where most of + * a sweep's time used to go. The floor applies to the files walked, as in + * `walkSourceFiles`; a caller that expects matches states its own floor over + * the result. + */ +export function sourcesContaining( + root: string, + markers: string | readonly string[], + options: { + floor: number; + extensions?: readonly string[]; + include?: (rel: string) => boolean; + }, +): Array<[rel: string, text: string]> { + const wanted = typeof markers === "string" ? [markers] : markers; + const out: Array<[string, string]> = []; + for (const rel of walkSourceFiles(root, options).filter( + options.include ?? (() => true), + )) { + const text = readSourceText(join(root, rel)); + if (wanted.some((marker) => text.includes(marker))) out.push([rel, text]); + } + return out; +} export function walkSourceFiles( root: string, options: { floor: number; extensions?: readonly string[] }, ): string[] { const extensions = options.extensions ?? [".ts", ".tsx"]; - const files = readdirSync(root, { recursive: true }) - .map((entry) => String(entry).split(sep).join("/")) - .filter((rel) => extensions.some((ext) => rel.endsWith(ext))) - .sort(); + const files = listTree(root).filter((rel) => + extensions.some((ext) => rel.endsWith(ext)), + ); if (files.length < options.floor) { throw new Error( @@ -216,7 +313,7 @@ export function scanSourceMatches( const matches: SourceMatch[] = []; for (const file of files) { - const raw = readFileSync(join(root, file), "utf8"); + const raw = readSourceText(join(root, file)); const source = options.stripComments ? stripComments(raw) : raw; for (const m of source.matchAll(pattern)) { const line = source.slice(0, m.index).split("\n").length; @@ -273,7 +370,7 @@ export function readSource( `readSourceFolder and a floor.`, ); } - const text = readFileSync(path, "utf8"); + const text = readSourceText(path); const lost = list.filter((anchor) => anchorCount(text, anchor) === 0); if (lost.length > 0) { throw new Error( @@ -306,5 +403,5 @@ export function readSourceFolder( extensions: options.extensions, }).filter(options.include ?? (() => true)); requireFloor(`readSourceFolder(${relDir})`, files, options.floor); - return files.map((rel) => [rel, readFileSync(join(root, rel), "utf8")]); + return files.map((rel) => [rel, readSourceText(join(root, rel))]); } diff --git a/src/__tests__/idempotency-contract-publication.test.ts b/src/__tests__/idempotency-contract-publication.test.ts index 74af2402a..fe07fea1b 100644 --- a/src/__tests__/idempotency-contract-publication.test.ts +++ b/src/__tests__/idempotency-contract-publication.test.ts @@ -19,12 +19,13 @@ * hand-listed: the expected set is read out of the route tree on every run, so * the guard cannot fall behind the code it watches. */ -import { readFileSync, readdirSync } from "node:fs"; +import { readdirSync } from "node:fs"; import { join, relative, sep } from "node:path"; import { describe, expect, it } from "vitest"; import { buildOpenApiDocument } from "@/lib/openapi/registry"; +import { readSourceText } from "./helpers/source-files"; const API_ROOT = join(process.cwd(), "src", "app", "api"); @@ -74,7 +75,7 @@ function idempotentOperationsFromSource(): Set { const pattern = /export\s+const\s+(POST|PUT|PATCH|DELETE)\s*=\s*([\s\S]*?)(?=\n(?:export\s+const|export\s+async\s+function|$))/g; for (const file of routeFiles(API_ROOT)) { - const source = readFileSync(file, "utf8"); + const source = readSourceText(file); if (!source.includes("withIdempotency")) continue; for (const match of source.matchAll(pattern)) { if (!/\bwithIdempotency\s*[(<]/.test(match[2])) continue; diff --git a/src/__tests__/insights-cached-text-access-guard.test.ts b/src/__tests__/insights-cached-text-access-guard.test.ts index 68b95b00f..a5c71f349 100644 --- a/src/__tests__/insights-cached-text-access-guard.test.ts +++ b/src/__tests__/insights-cached-text-access-guard.test.ts @@ -12,12 +12,15 @@ * The readable name survives only as the snapshot builder's input field, which * carries the OPENED text, never the column. */ -import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { stripComments, walkSourceFiles } from "./helpers/source-files"; +import { + readSourceText, + stripComments, + walkSourceFiles, +} from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -45,9 +48,7 @@ function sourceFilesMatching(pattern: RegExp): string[] { return walkSourceFiles(SRC, { floor: 2000 }) .filter((f) => !f.startsWith("generated/")) .filter((f) => !f.includes("__tests__/") && !/\.test\.tsx?$/.test(f)) - .filter((f) => - pattern.test(stripComments(readFileSync(join(SRC, f), "utf8"))), - ) + .filter((f) => pattern.test(stripComments(readSourceText(join(SRC, f))))) .sort(); } diff --git a/src/__tests__/latest-per-type-read-guard.test.ts b/src/__tests__/latest-per-type-read-guard.test.ts index c7c5eca17..3480b7750 100644 --- a/src/__tests__/latest-per-type-read-guard.test.ts +++ b/src/__tests__/latest-per-type-read-guard.test.ts @@ -23,11 +23,14 @@ * Comments are stripped before matching, and the planted cases prove each * matcher finds what it claims to. */ -import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { stripComments, walkSourceFiles } from "./helpers/source-files"; +import { + readSourceText, + stripComments, + walkSourceFiles, +} from "./helpers/source-files"; /** The shared comment strip, plus SQL line comments inside template strings. */ function stripCode(text: string): string { @@ -98,7 +101,7 @@ describe("latest-per-type reads", () => { it("no production read keeps one row per type with DISTINCT ON", () => { const offenders = productionFiles().filter((file) => - LATEST_PER_TYPE.test(stripCode(readFileSync(join(SRC, file), "utf8"))), + LATEST_PER_TYPE.test(stripCode(readSourceText(join(SRC, file)))), ); expect(offenders).toEqual([]); }); @@ -129,9 +132,7 @@ describe("latest-per-type reads", () => { it("no production read groups every row for the latest per type", () => { const files = productionFiles(); const grouped = files.filter((file) => - /\.measurement\s*\.\s*groupBy\s*\(/.test( - readFileSync(join(SRC, file), "utf8"), - ), + /\.measurement\s*\.\s*groupBy\s*\(/.test(readSourceText(join(SRC, file))), ); // The tree groups measurements in a dozen places; finding none means // the walk read the wrong tree. @@ -139,16 +140,16 @@ describe("latest-per-type reads", () => { const offenders = files .filter((file) => !(file in GROUPBY_ALLOWED)) .flatMap((file) => - latestOnlyGroupBys( - stripComments(readFileSync(join(SRC, file), "utf8")), - ).map((arg) => `${file}: ${arg.slice(0, 100)}`), + latestOnlyGroupBys(stripComments(readSourceText(join(SRC, file)))).map( + (arg) => `${file}: ${arg.slice(0, 100)}`, + ), ); expect(offenders).toEqual([]); }); it("the summaries slice is built only behind its cache cell", () => { const callers = productionFiles().filter((file) => - SLICE_BUILDER_CALL.test(stripCode(readFileSync(join(SRC, file), "utf8"))), + SLICE_BUILDER_CALL.test(stripCode(readSourceText(join(SRC, file)))), ); expect(callers).toEqual(["lib/analytics/summaries-slice.ts"]); }); diff --git a/src/__tests__/linear-fit-single-implementation-guard.test.ts b/src/__tests__/linear-fit-single-implementation-guard.test.ts index 424ca3b43..ca5b7888a 100644 --- a/src/__tests__/linear-fit-single-implementation-guard.test.ts +++ b/src/__tests__/linear-fit-single-implementation-guard.test.ts @@ -7,11 +7,10 @@ * implementation. Comments are stripped first; the planted cases prove the * matcher fires. */ -import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -52,25 +51,21 @@ describe("one least-squares line", () => { const offenders = files.filter( (file) => !(file in ALLOWED) && - HAND_ROLLED_SLOPE.test( - stripComments(readFileSync(join(SRC, file), "utf8")), - ), + HAND_ROLLED_SLOPE.test(stripComments(readSourceText(join(SRC, file)))), ); expect(offenders).toEqual([]); // The six readers now call the implementation: a sweep that finds fewer // call sites read the wrong tree or lost one. const callers = files.filter((file) => /\b(?:linearFit|fitFromSums|trendLineByTime)\s*\(/.test( - stripComments(readFileSync(join(SRC, file), "utf8")), + stripComments(readSourceText(join(SRC, file))), ), ); expect(callers.length).toBeGreaterThanOrEqual(7); // The implementation itself matches, so the sweep read real files. expect( HAND_ROLLED_SLOPE.test( - stripComments( - readFileSync(join(SRC, "lib/stats/linear-fit.ts"), "utf8"), - ), + stripComments(readSourceText(join(SRC, "lib/stats/linear-fit.ts"))), ), ).toBe(true); }); diff --git a/src/__tests__/memory-block-egress-guard.test.ts b/src/__tests__/memory-block-egress-guard.test.ts index a874be864..7c0fa8bd7 100644 --- a/src/__tests__/memory-block-egress-guard.test.ts +++ b/src/__tests__/memory-block-egress-guard.test.ts @@ -18,18 +18,17 @@ * * Every check is also run against a source broken on purpose. */ -import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); const MEMORY = "lib/ai/coach/memory"; function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } function sourceFiles(): string[] { diff --git a/src/__tests__/naive-timestamp-zone-shift-guard.test.ts b/src/__tests__/naive-timestamp-zone-shift-guard.test.ts index 1ae978874..e9c0a5642 100644 --- a/src/__tests__/naive-timestamp-zone-shift-guard.test.ts +++ b/src/__tests__/naive-timestamp-zone-shift-guard.test.ts @@ -17,11 +17,14 @@ * (`bucket_start`) is out of its reach by name. Comments are stripped first; * the planted cases prove it fires. It is a tripwire, not a proof. */ -import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { stripComments, walkSourceFiles } from "./helpers/source-files"; +import { + readSourceText, + stripComments, + walkSourceFiles, +} from "./helpers/source-files"; /** The shared comment strip, plus SQL line comments inside template strings. */ function stripCode(text: string): string { @@ -59,8 +62,7 @@ describe("naive timestamp zone shifts", () => { !file.includes("__tests__/") && !/\.test\.tsx?$/.test(file), ); - const code = (file: string) => - stripCode(readFileSync(join(SRC, file), "utf8")); + const code = (file: string) => stripCode(readSourceText(join(SRC, file))); // The reads that shift correctly, so a sweep that reads nothing fails. const doubleShifts = files.filter((file) => DOUBLE_SHIFT.test(code(file))); expect(doubleShifts.length).toBeGreaterThanOrEqual(5); diff --git a/src/__tests__/no-control-bytes-guard.test.ts b/src/__tests__/no-control-bytes-guard.test.ts index aefe05080..f9368aeae 100644 --- a/src/__tests__/no-control-bytes-guard.test.ts +++ b/src/__tests__/no-control-bytes-guard.test.ts @@ -1,9 +1,9 @@ -import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { walkSourceFiles } from "@/__tests__/helpers/source-files"; +import { readSourceText } from "./helpers/source-files"; /** * No raw control byte in a source file, apart from tab, newline and carriage @@ -28,7 +28,7 @@ describe("source files carry no raw control bytes", () => { expect(files.length).toBeGreaterThan(3000); const offenders: string[] = []; for (const file of files) { - const lines = readFileSync(join(src, file), "utf8").split("\n"); + const lines = readSourceText(join(src, file)).split("\n"); lines.forEach((line, i) => { if (CONTROL.test(line)) offenders.push(`${file}:${i + 1}`); }); diff --git a/src/__tests__/oauth-kit-guard.test.ts b/src/__tests__/oauth-kit-guard.test.ts index a5e2ca106..4021ebb08 100644 --- a/src/__tests__/oauth-kit-guard.test.ts +++ b/src/__tests__/oauth-kit-guard.test.ts @@ -26,10 +26,10 @@ * that stops matching fails instead of passing. */ import { describe, expect, it } from "vitest"; -import { existsSync, readFileSync } from "node:fs"; +import { existsSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -54,7 +54,7 @@ function productionFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(process.cwd(), rel), "utf8"); + return readSourceText(join(process.cwd(), rel)); } describe("OAuth token lifecycle guard", () => { diff --git a/src/__tests__/profile-rejection-surface-guard.test.ts b/src/__tests__/profile-rejection-surface-guard.test.ts index e0b12d002..04aaaa894 100644 --- a/src/__tests__/profile-rejection-surface-guard.test.ts +++ b/src/__tests__/profile-rejection-surface-guard.test.ts @@ -20,10 +20,9 @@ * frozen so that adding one has to be a deliberate edit here. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -47,7 +46,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } /** diff --git a/src/__tests__/recent-proof-surface-guard.test.ts b/src/__tests__/recent-proof-surface-guard.test.ts index 4c818ed17..9e1838b78 100644 --- a/src/__tests__/recent-proof-surface-guard.test.ts +++ b/src/__tests__/recent-proof-surface-guard.test.ts @@ -14,15 +14,14 @@ * with every list. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } function sourceFiles(): string[] { diff --git a/src/__tests__/record-session-fence-guard.test.ts b/src/__tests__/record-session-fence-guard.test.ts index 9dc76eede..e6cacd6a0 100644 --- a/src/__tests__/record-session-fence-guard.test.ts +++ b/src/__tests__/record-session-fence-guard.test.ts @@ -39,10 +39,9 @@ * shipped several of those. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -55,7 +54,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } function filesMatching(re: RegExp): string[] { diff --git a/src/__tests__/recurrence-course-anchor-guard.test.ts b/src/__tests__/recurrence-course-anchor-guard.test.ts index d41f7bb95..c7e9fd1ca 100644 --- a/src/__tests__/recurrence-course-anchor-guard.test.ts +++ b/src/__tests__/recurrence-course-anchor-guard.test.ts @@ -19,10 +19,11 @@ * `RecurrenceContext` annotation is invisible here. Every medication path * today goes through one of the annotated builders below. */ -import { readdirSync, readFileSync } from "node:fs"; +import { readdirSync } from "node:fs"; import { join, relative, resolve } from "node:path"; import { describe, expect, it } from "vitest"; +import { readSourceText } from "./helpers/source-files"; const SRC = resolve(__dirname, ".."); const ROOT = resolve(SRC, ".."); @@ -73,7 +74,7 @@ interface Builder { function builders(): Builder[] { const out: Builder[] = []; for (const file of sourceFiles(SRC)) { - const text = readFileSync(file, "utf8"); + const text = readSourceText(file); for (const m of text.matchAll(DECLARATION)) { const open = m.index! + m[0].length - 1; out.push({ diff --git a/src/__tests__/session-surface-guard.test.ts b/src/__tests__/session-surface-guard.test.ts index 4916b1cee..c97358181 100644 --- a/src/__tests__/session-surface-guard.test.ts +++ b/src/__tests__/session-surface-guard.test.ts @@ -33,10 +33,9 @@ * matches nothing fails instead of passing. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -55,7 +54,7 @@ function appFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } function appFilesMatching(re: RegExp): string[] { diff --git a/src/__tests__/sharing-surface-guard.test.ts b/src/__tests__/sharing-surface-guard.test.ts index fe252a19d..b818a899f 100644 --- a/src/__tests__/sharing-surface-guard.test.ts +++ b/src/__tests__/sharing-surface-guard.test.ts @@ -65,7 +65,7 @@ * different set of symbols and answers it from the AST, because a route * explaining in a comment why it is NOT delegable is a thing worth writing. */ -import { readFileSync, readdirSync } from "node:fs"; +import { readdirSync } from "node:fs"; import { join, sep } from "node:path"; import ts from "typescript"; @@ -75,6 +75,7 @@ import { ENTIRE_RECORD, SHARE_DOMAINS } from "@/lib/sharing/scope"; import type { ShareScope } from "@/lib/sharing/scope"; import { ADMITTED_MUTATING_HANDLERS } from "../../tests/fixtures/v137/sharing-matrix"; +import { readSourceText } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -198,7 +199,7 @@ const textCache = new Map(); function read(rel: string): string { const cached = textCache.get(rel); if (cached !== undefined) return cached; - const text = readFileSync(join(SRC, rel), "utf8"); + const text = readSourceText(join(SRC, rel)); textCache.set(rel, text); return text; } diff --git a/src/__tests__/spread-single-implementation-guard.test.ts b/src/__tests__/spread-single-implementation-guard.test.ts index da69c33f5..815f3cb9c 100644 --- a/src/__tests__/spread-single-implementation-guard.test.ts +++ b/src/__tests__/spread-single-implementation-guard.test.ts @@ -16,11 +16,14 @@ * `glucoseSD`) and is out of the matcher's reach by shape. Comments are * stripped first; the planted case proves the matcher fires. */ -import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { stripComments, walkSourceFiles } from "./helpers/source-files"; +import { + readSourceText, + stripComments, + walkSourceFiles, +} from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -51,7 +54,7 @@ function computesSpread(code: string): boolean { } function read(file: string): string { - return stripComments(readFileSync(join(SRC, file), "utf8")); + return stripComments(readSourceText(join(SRC, file))); } describe("one standard deviation", () => { diff --git a/src/__tests__/sql-now-utc-guard.test.ts b/src/__tests__/sql-now-utc-guard.test.ts index 00df946cb..096f85055 100644 --- a/src/__tests__/sql-now-utc-guard.test.ts +++ b/src/__tests__/sql-now-utc-guard.test.ts @@ -13,9 +13,10 @@ * The few bare reads left are listed below with the reason they are safe: * each feeds a `timestamptz` column or returns the instant to JavaScript. */ -import { readdirSync, readFileSync, statSync } from "node:fs"; +import { readdirSync, statSync } from "node:fs"; import { join, relative } from "node:path"; import { describe, expect, it } from "vitest"; +import { readSourceText } from "./helpers/source-files"; const ROOT = join(__dirname, "..", ".."); const SRC = join(ROOT, "src"); @@ -69,7 +70,7 @@ function bareReads(): string[] { const found: string[] = []; for (const file of walk(SRC)) { const rel = relative(ROOT, file); - readFileSync(file, "utf8") + readSourceText(file) .split("\n") .forEach((line, index) => { if (/^\s*(\/\/|\*|\/\*|--)/.test(line)) return; @@ -84,7 +85,7 @@ function bareReads(): string[] { describe("raw SQL clock reads are anchored to UTC", () => { it("finds the anchored form across the swept files", () => { // The matcher must see something, or a broken walk would pass. - const passkey = readFileSync(join(SRC, "lib/auth/passkey.ts"), "utf8"); + const passkey = readSourceText(join(SRC, "lib/auth/passkey.ts")); expect(passkey.match(ANCHORED)?.length).toBeGreaterThanOrEqual(3); expect(SQL_CLOCK.test("AND expires_at > NOW()")).toBe(true); expect(SQL_CLOCK.test("updated_at = now()")).toBe(true); diff --git a/src/__tests__/step-up-elevation-guard.test.ts b/src/__tests__/step-up-elevation-guard.test.ts index 366abc900..e295dba3b 100644 --- a/src/__tests__/step-up-elevation-guard.test.ts +++ b/src/__tests__/step-up-elevation-guard.test.ts @@ -23,10 +23,9 @@ * only that it has not changed without someone editing this file. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -39,7 +38,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } /** Local names bound for `exportName` from any module matching `moduleRe`. */ diff --git a/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts b/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts index 2e94964d1..1539b8772 100644 --- a/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts +++ b/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts @@ -18,10 +18,9 @@ * settles is that there is only one of it. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -81,7 +80,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } /** A line that declares `relativeDueKey`, ignoring comment prose. */ diff --git a/src/__tests__/workout-sport-provenance-guard.test.ts b/src/__tests__/workout-sport-provenance-guard.test.ts index e44e79747..280c7fe55 100644 --- a/src/__tests__/workout-sport-provenance-guard.test.ts +++ b/src/__tests__/workout-sport-provenance-guard.test.ts @@ -19,10 +19,9 @@ * that a new ingest cannot appear without someone editing this file. */ import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { walkSourceFiles } from "./helpers/source-files"; +import { readSourceText, walkSourceFiles } from "./helpers/source-files"; const SRC = join(process.cwd(), "src"); @@ -36,7 +35,7 @@ function sourceFiles(): string[] { } function read(rel: string): string { - return readFileSync(join(SRC, rel), "utf8"); + return readSourceText(join(SRC, rel)); } interface ProviderIngest {