test(guards): strip comments the way the compiler reads them, in every guard

Forty-two guard files carried their own comment stripper, block comments
first in one non-greedy pass, so a /* inside a line comment opened a
block that hid the code after it, and the stricter copies hid code
behind a /* inside a string, which every e2e route glob has. They all
read through the shared stripComments now, and it scans the way the
parser does: strings, template literals and regular expressions keep
every character, a comment inside a template expression goes, and line
numbers are kept. A parity test strips every source file both ways,
with the scanner and with the TypeScript parser, and fails on the first
that differs.

No guard reports anything new. Each one fails when the stripper returns
nothing; three of them passed on an empty file and assert that the code
they ban is read at all now.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 18:09:10 +02:00
1 parent 584f799da3
commit c0d387025d
44 files changed
+392 -341

No files matched your search

@@ -69,6 +69,7 @@ import { readFileSync, readdirSync, statSync } from "node:fs";
import { join, relative } from "node:path";
import { describe, it, expect } from "vitest";
import { stripComments } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
const ME_ROUTE = join(SRC, "app", "api", "auth", "me", "route.ts");
@@ -201,16 +202,6 @@ function payloadFields(source: string): string[] {
return [...new Set(fields)];
}
/**
* Comments are stripped before matching. A doc comment naming a field is a
* claim about a consumer, not a consumer — `lastReportPracticeName` had one of
* those for its whole dead life, and counting it would make this guard agree
* with the very prose that was wrong.
*/
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "$1");
}
/**
* A property access (`user.glucoseUnit`) or a destructuring / object position
* (`{ glucoseUnit }`, `{ glucoseUnit: unit }`).
+1 -7
View File
@@ -38,17 +38,11 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
const AI_DIR = join(SRC, "lib", "ai");
function stripComments(source: string): string {
return source
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
}
function code(rel: string): string {
return stripComments(readFileSync(join(SRC, rel), "utf8"));
}
+1 -7
View File
@@ -23,16 +23,10 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
function stripComments(source: string): string {
return source
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
}
/** The text of the call that starts at `open` (the index of its `(`). */
function callText(source: string, open: number): string {
let depth = 0;
@@ -22,7 +22,7 @@ import { join } from "node:path";
import { DOCUMENT_AI_RUN_KINDS } from "@/lib/documents/ai-runs/types";
import type { runStatusCompletion } from "@/lib/insights/status-provider";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -38,13 +38,6 @@ const EGRESS_CALL =
const CAPABILITY_CHECK =
/\b(?:aiCapabilityForJob|aiCapabilityForRecord|requireAiCapability|getAiCapability|aiEgressRefusal|assertAiEgress)\s*\(/;
/** Comments name the helpers too; only code counts. */
function stripComments(source: string): string {
return source
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
}
function sourceFiles(): string[] {
return walkSourceFiles(SRC, { floor: 3000 })
.filter((p) => !p.startsWith("generated/"))
@@ -38,7 +38,7 @@ import {
type ReasoningSurface,
} from "@/lib/ai/reasoning/resolve";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -115,12 +115,6 @@ const SETTERS = [
const RESOLVER_IMPORT =
/from\s+["']@\/lib\/ai\/reasoning\/(?:resolve|controls)["']/;
function stripComments(source: string): string {
return source
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
}
describe("every call that carries reasoning took it from the resolver", () => {
const files = walkSourceFiles(SRC, { floor: 3000 })
.filter((p) => !p.startsWith("generated/"))
@@ -33,16 +33,10 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
function stripComments(source: string): string {
return source
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
}
function code(rel: string): string {
return stripComments(readFileSync(join(SRC, rel), "utf8"));
}
@@ -31,7 +31,7 @@ import { describe, expect, it } from "vitest";
import { BACKGROUND_REASONING_JOBS } from "@/lib/ai/reasoning/levels";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -62,12 +62,6 @@ const NEVER = [
"lib/jobs/workout-insight-generate.ts",
];
function stripComments(source: string): string {
return source
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
}
/** `reasoningJob:` as a value, not the optional declaration `reasoningJob?:`. */
const SETTER = /\breasoningJob\s*:(?!\s*BackgroundReasoningJob)/g;
@@ -19,6 +19,7 @@ import { guardSourceOf } from "./helpers/auth-guards";
import {
readSource,
readSourceText,
stripComments,
walkSourceFiles,
} from "./helpers/source-files";
@@ -90,10 +91,6 @@ function argsFrom(code: string, open: number): string {
return code.slice(open + 1, i);
}
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
/** Split an argument list at its top-level commas. */
function topLevelArgs(args: string): string[] {
const parts: string[] = [];
+11 -26
View File
@@ -30,7 +30,11 @@
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { readSourceText, walkSourceFiles } from "./helpers/source-files";
import {
readSourceText,
stripComments,
walkSourceFiles,
} from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -98,31 +102,13 @@ const LIMIT_PATTERNS: readonly RegExp[] = [
const PAGER_NEARBY = /\boffset\b|\bcursor\b|\bpageParam\b/;
const PAGER_LINES = 3;
/** The line with every block comment that opens and closes on it removed. */
function withoutInlineBlocks(text: string): string {
return text.replace(/\/\*.*?\*\//g, "");
}
/** A whole-line comment: it describes code, it sends nothing. */
function isCommentLine(text: string): boolean {
const trimmed = withoutInlineBlocks(text).trim();
return (
trimmed.startsWith("//") ||
trimmed.startsWith("/*") ||
trimmed.startsWith("*")
);
}
/**
* The line as code a pager could live in: a trailing `// …` comment cut off
* (only after whitespace, so a `https://` inside a string survives), and
* every offset pinned to a literal zero removed, in the URL spelling and
* both `URLSearchParams` spellings.
* The line as code a pager could live in: every offset pinned to a literal
* zero removed, in the URL spelling and both `URLSearchParams` spellings.
* The lines come comment-free from `stripComments`.
*/
function pagerCode(text: string): string {
if (isCommentLine(text)) return "";
return withoutInlineBlocks(text)
.replace(/\s\/\/.*$/, "")
return text
.replace(/[?&]offset=0(?!\d)/g, "")
.replace(/\.set\(\s*["']offset["']\s*,\s*["']0["']\s*\)/g, "")
.replace(/\boffset:\s*["']?0["']?(?![\d.])/g, "");
@@ -151,12 +137,11 @@ interface Hit {
/** Every literal limit in the given source, with whether a pager is beside it. */
function findLimitHits(file: string, source: string): Hit[] {
const lines = source.split("\n");
// Comments describe old shapes; they send nothing.
const lines = stripComments(source, file).split("\n");
const infinite = /\buseInfiniteQuery\b/.test(source);
const hits: Hit[] = [];
lines.forEach((text, index) => {
// Comments describe old shapes; they send nothing.
if (isCommentLine(text)) return;
for (const pattern of LIMIT_PATTERNS) {
for (const match of text.matchAll(pattern)) {
const near = lines
+1 -9
View File
@@ -24,6 +24,7 @@
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative, sep } from "node:path";
import { describe, expect, it } from "vitest";
import { stripComments } from "./helpers/source-files";
const ROOT = join(__dirname, "..", "..");
const SRC = join(ROOT, "src");
@@ -54,15 +55,6 @@ const SERVER_SIDE_EXEMPT = new Set([
/** A direct call, tolerant of whitespace between the parts. */
const DIRECT_CALL = /\bcrypto\s*\.\s*randomUUID\s*\(/;
/**
* Comments are prose, not calls. Without this the guard fires on the very
* doc comments that explain why the call was removed — a guard that its own
* explanation trips is one the next reader deletes.
*/
function stripComments(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
}
/** Every `.ts(x)` under `src/`, as a repo-relative POSIX path. */
function sourceFiles(dir: string = SRC, out: string[] = []): string[] {
for (const name of readdirSync(dir)) {
@@ -27,6 +27,7 @@ import { readFileSync, readdirSync } from "node:fs";
import { join, relative, sep } from "node:path";
import { describe, expect, it } from "vitest";
import { stripComments } from "./helpers/source-files";
const ROOT = process.cwd();
const SRC = join(ROOT, "src");
@@ -67,12 +68,6 @@ function rel(file: string): string {
return relative(ROOT, file).split(sep).join("/");
}
function stripComments(source: string): string {
return source
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "))
.replace(/(^|[^:"'`])\/\/[^\n]*/g, "$1");
}
/** The source spans of every `useEffect(` / `useLayoutEffect(` call. */
function effectSpans(source: string): Array<[number, number]> {
const spans: Array<[number, number]> = [];
@@ -23,6 +23,7 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { stripComments } from "./helpers/source-files";
const READS = join(process.cwd(), "src/lib/dashboard/summary-reads.ts");
/** The summary's builder, moved out of its route; it makes the reads. */
@@ -35,9 +36,7 @@ const ROUTE = join(process.cwd(), "src/lib/dashboard/summary/build.ts");
* shape being warned about.
*/
function code(path: string): string {
return readFileSync(path, "utf8")
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
return stripComments(readFileSync(path, "utf8"), path);
}
describe("dashboard summary reads — latest reading per type", () => {
+1 -7
View File
@@ -18,7 +18,7 @@ import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
const HELPER = "lib/validations/datetime.ts";
@@ -30,12 +30,6 @@ function sourceFiles(): string[] {
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"));
}
function stripComments(text: string): string {
return text
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:"'`])\/\/.*$/gm, "$1");
}
const DIRECT_DATETIME = /\.\s*datetime\s*\(/g;
const HELPER_CALL = /\bisoDateTime\s*\(/g;
@@ -44,6 +44,7 @@ import {
type ReportLeafId,
} from "@/lib/report-selection/catalogue";
import { selectionFromLeaves } from "@/lib/report-selection/selection";
import { stripComments } from "./helpers/source-files";
const REPO_ROOT = join(__dirname, "..", "..");
const COLLECT_PATH = join(
@@ -65,11 +66,6 @@ const PICKER_PATH = join(
const collectSource = readFileSync(COLLECT_PATH, "utf8");
const pickerSource = readFileSync(PICKER_PATH, "utf8");
/** Strip comments so a leaf named only in prose does not count as read. */
function stripComments(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
describe("report scope gating — structural guard", () => {
it("reads a plausible catalogue", () => {
// Sanity floor: a silently degraded catalogue would pass everything below
@@ -22,6 +22,7 @@
import { globSync, readFileSync } from "node:fs";
import { join, sep } from "node:path";
import { describe, expect, it } from "vitest";
import { stripComments } from "./helpers/source-files";
const E2E = join(process.cwd(), "e2e");
@@ -33,16 +34,6 @@ function specFiles(): string[] {
.sort();
}
/** Blank out comments, keeping offsets and newlines so line numbers hold. */
function stripComments(src: string): string {
return src
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "))
.replace(
/(^|[^:"'`])\/\/.*$/gm,
(m, lead: string) => lead + " ".repeat(m.length - lead.length),
);
}
/**
* The top-level arguments of the call whose `(` sits at `open`, as trimmed
* source text. Strings and template literals are skipped over whole, so a
@@ -38,6 +38,7 @@ import { describe, expect, it } from "vitest";
import {
readSourceText,
stripComments,
requireFloor,
walkSourceFiles,
} from "./helpers/source-files";
@@ -59,20 +60,6 @@ const CRITICAL_TEST =
/** A route interception call; the first argument is examined separately. */
const ROUTE_CALL = /\.route(?:FromHAR)?\(\s*/g;
/**
* The source without its comment LINES: a line whose first non-blank text is
* `//`, and a block that opens at the start of a line. Deliberately not the
* shared `stripComments`, which opens a block at any `/*` — and route globs
* (`"**\/api/day/**"`, `"https://host/**"`) are full of them, so it would
* swallow the code after the first glob, including any `.route(` there. Prose
* about this rule lives in comment lines, which is what has to go.
*/
function stripCommentLines(source: string): string {
return source
.replace(/^[ \t]*\/\*[\s\S]*?\*\//gm, "")
.replace(/^[ \t]*\/\/.*$/gm, "");
}
interface StubFinding {
/** Where the interception lives (the spec, or the helper it imports). */
where: string;
@@ -108,7 +95,7 @@ function firstArgument(
/** Every `/api` (or unprovable) interception in one source text. */
function apiStubsIn(source: string): string[] {
const code = stripCommentLines(source);
const code = stripComments(source);
const hits: string[] = [];
for (const m of code.matchAll(ROUTE_CALL)) {
const arg = firstArgument(code, (m.index ?? 0) + m[0].length);
@@ -125,7 +112,7 @@ function apiStubsIn(source: string): string[] {
function relativeImports(
source: string,
): Array<{ names: string[]; from: string }> {
const code = stripCommentLines(source);
const code = stripComments(source);
const out: Array<{ names: string[]; from: string }> = [];
for (const m of code.matchAll(
/import\s*(?:type\s*)?\{([^}]*)\}\s*from\s*["'](\.{1,2}\/[^"']+)["']/g,
@@ -150,7 +137,7 @@ function relativeImports(
* cut at each top-level `export`, so each slice is one export's body.
*/
function stubbingExports(helperSource: string): string[] {
const code = stripCommentLines(helperSource);
const code = stripComments(helperSource);
const starts = [...code.matchAll(/^export\s+/gm)].map((m) => m.index ?? 0);
const names: string[] = [];
starts.forEach((start, i) => {
@@ -206,7 +193,7 @@ function specFiles(): string[] {
function criticalTests(): Array<{ file: string; title: string }> {
const tests: Array<{ file: string; title: string }> = [];
for (const file of specFiles()) {
const code = stripCommentLines(readSourceText(join(E2E, file)));
const code = stripComments(readSourceText(join(E2E, file)));
for (const m of code.matchAll(CRITICAL_TEST)) {
tests.push({ file, title: m[2] });
}
@@ -224,9 +211,7 @@ describe("@critical e2e journeys run against the real API", () => {
// A file that mentions the tag outside a test title (a describe title, a
// `grep` option) is held to the same rule.
for (const file of specFiles()) {
if (
stripCommentLines(readSourceText(join(E2E, file))).includes("@critical")
)
if (stripComments(readSourceText(join(E2E, file))).includes("@critical"))
files.add(file);
}
const offenders = [...files].flatMap((file) =>
+1 -10
View File
@@ -17,7 +17,7 @@ import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const E2E = join(process.cwd(), "e2e");
@@ -42,15 +42,6 @@ function read(rel: string): string {
return readFileSync(join(E2E, rel), "utf8");
}
/**
* The file with its comments removed. The entry point documents the very API
* calls it makes, so a naive substring match would be satisfied by the prose
* alone — a fixture gutted to a no-op would still read as compliant.
*/
function stripComments(src: string): string {
return src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
/**
* Every `from "<module>"` import statement in a file, as
* `{ module, specifiers }` — `specifiers` is the raw brace body, or `"*"` for
@@ -22,6 +22,7 @@
import { describe, it, expect } from "vitest";
import { readFileSync, globSync } from "node:fs";
import { join, sep } from "node:path";
import { stripComments } from "./helpers/source-files";
const E2E = join(process.cwd(), "e2e");
@@ -44,15 +45,6 @@ function read(rel: string): string {
return readFileSync(join(E2E, rel), "utf8");
}
/**
* The file with comments removed. This guard's own subject matter is worth
* explaining in prose inside the specs, and a naive substring match would be
* satisfied by that prose alone.
*/
function stripComments(src: string): string {
return src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
function switchingSpecs(): string[] {
return specFiles().filter((rel) => {
const src = stripComments(read(rel));
+176 -54
View File
@@ -144,70 +144,192 @@ export function walkSourceFiles(
}
/**
* A source file with its comments removed, line comments FIRST.
* A source file with its comments removed, read the way the compiler reads it.
*
* The order is the whole reason this is shared rather than re-spelled per
* guard. Every private copy in this directory strips block comments first,
* with one non-greedy `/\*[\s\S]*?\*\//` pass, and that pass cannot tell a
* real block opener from a `/*` that happens to sit inside a line comment.
* This tree has one: `src/app/mood/page-client.tsx` explains its gate with
* "Every `/api/mood-entries/*` route also enforces the gate", and the `/*` in
* that path opened a fake block that swallowed the next 2.2 kB of real code —
* the module gate three lines below it included. A guard reading that source
* was searching a file with a hole in it and reporting a clean sweep.
* Shared rather than re-spelled per guard because the per-guard copies were
* one or two regular expressions, and every spelling was wrong somewhere:
* - block comments stripped first, with one non-greedy pass, cannot tell a
* real opener from a `/*` inside a line comment. `src/app/mood/
* page-client.tsx` explains its gate with "Every `/api/mood-entries/*`
* route also enforces the gate", and that `/*` opened a fake block that
* swallowed the next 2.2 kB of real code, the module gate included;
* - a scanner that takes the first opener on a line still opens a block at
* a `/*` inside a string, which every e2e route glob (`"**\/api/*"`) has;
* - a line comment matched only at the start of a line keeps a trailing
* `// …` after code, and one matched anywhere eats `https://`.
*
* So each line is walked once, left to right: whichever opener comes first
* wins, a line comment ends the line, and a block comment carries its state to
* the next one. Line-comment detection needs `//` at the start of a line or
* after whitespace, so a URL inside a string survives.
* So the text is scanned once, left to right, knowing what the parser knows:
* strings, template literals (with their `${…}` expressions, which may hold
* comments), and regular expressions keep every character, and a `/*` or
* `//` inside any of them opens nothing. A slash starts a regular expression
* where the language allows one: after an operator, an opening bracket, a
* keyword such as `return`, or at the start. Line breaks are kept, so a line
* number read off the result is the file's own.
*
* Its honest limit: it is a scanner, not a parser. A `/*` inside a string
* literal still opens a block, and a guard that needs more than this needs a
* real parse.
* It is a scanner, not the parser, and is held to the parser instead of
* trusted: `strip-comments-parity.test.ts` strips every source file both
* ways and fails on the first that differs. `//` right after a `:` is read
* as part of a URL in JSX text, never as a comment. CSS, which has no line
* comments, is stripped of its block comments only (pass its `fileName`).
*/
export function stripComments(source: string): string {
const lines: string[] = [];
let inBlock = false;
for (const line of source.split("\n")) {
let rest = line;
let kept = "";
while (rest.length > 0) {
if (inBlock) {
const closes = rest.indexOf("*/");
if (closes === -1) break;
rest = rest.slice(closes + 2);
inBlock = false;
continue;
}
const lineComment = rest.search(/(^|\s)\/\//);
const blockComment = rest.indexOf("/*");
if (
blockComment !== -1 &&
(lineComment === -1 || blockComment < lineComment)
export function stripComments(source: string, fileName?: string): string {
if (fileName?.endsWith(".css")) {
return source.replace(/\/\*[\s\S]*?\*\//g, blankKeepingLines);
}
const text = source;
const n = text.length;
let out = "";
let i = 0;
// Template nesting: "template" for a template body, a number for the brace
// depth of the `${…}` expression being read, restored when it closes.
const stack: Array<"template" | number> = [];
let depth = 0;
while (i < n) {
const top = stack[stack.length - 1];
if (top === "template") {
let j = i;
while (
j < n &&
text[j] !== "`" &&
!(text[j] === "$" && text[j + 1] === "{")
) {
kept += rest.slice(0, blockComment);
rest = rest.slice(blockComment + 2);
inBlock = true;
j += text[j] === "\\" ? 2 : 1;
}
out += text.slice(i, Math.min(j, n));
if (j >= n) break;
if (text[j] === "`") {
stack.pop();
out += "`";
i = j + 1;
continue;
}
if (lineComment !== -1) {
kept += rest.slice(0, rest.indexOf("//", lineComment));
break;
}
kept += rest;
break;
stack.push(depth);
depth = 0;
out += "${";
i = j + 2;
continue;
}
lines.push(kept);
}
SPECIAL.lastIndex = i;
const found = SPECIAL.exec(text);
if (!found) {
out += text.slice(i);
break;
}
const j = found.index;
out += text.slice(i, j);
const c = text[j];
const next = text[j + 1];
return lines.join("\n");
if (c === "/" && next === "/" && text[j - 1] !== ":") {
const end = text.indexOf("\n", j);
i = end === -1 ? n : end;
continue;
}
if (c === "/" && next === "*") {
const close = text.indexOf("*/", j + 2);
const end = close === -1 ? n : close + 2;
out += blankKeepingLines(text.slice(j, end));
i = end;
continue;
}
if (c === "'" || c === '"') {
let end = j + 1;
while (end < n && text[end] !== c && text[end] !== "\n") {
end += text[end] === "\\" ? 2 : 1;
}
if (text[end] === c) end += 1;
out += text.slice(j, end);
i = end;
continue;
}
if (c === "`") {
stack.push("template");
out += "`";
i = j + 1;
continue;
}
if (c === "/") {
const end = regexMayStart(out) ? regexEnd(text, j) : -1;
if (end !== -1) {
out += text.slice(j, end);
i = end;
continue;
}
out += "/";
i = j + 1;
continue;
}
if (c === "{") {
depth += 1;
} else if (depth === 0 && typeof top === "number") {
depth = stack.pop() as number;
} else {
depth -= 1;
}
out += c;
i = j + 1;
}
return out;
}
const SPECIAL = /[/'"`{}]/g;
const WORD = /[A-Za-z_$0-9]/;
const REGEX_AFTER = new Set("(,=:[!&|?{};+-*%<>~^".split(""));
const REGEX_KEYWORDS = new Set([
"return",
"typeof",
"case",
"do",
"else",
"in",
"of",
"void",
"yield",
"await",
"delete",
"instanceof",
"new",
"throw",
]);
function blankKeepingLines(comment: string): string {
return comment.replace(/[^\n]/g, "");
}
/** Whether a `/` after this code starts a regular expression. */
function regexMayStart(code: string): boolean {
let k = code.length - 1;
while (k >= 0 && /\s/.test(code[k]!)) k -= 1;
if (k < 0) return true;
const last = code[k]!;
if (REGEX_AFTER.has(last)) return true;
if (!WORD.test(last)) return false;
let start = k;
while (start > 0 && WORD.test(code[start - 1]!)) start -= 1;
return REGEX_KEYWORDS.has(code.slice(start, k + 1));
}
/** The end of the regular expression opening at `start`, or -1 if none. */
function regexEnd(text: string, start: number): number {
let end = start + 1;
let inClass = false;
while (end < text.length && text[end] !== "\n") {
const ch = text[end];
if (ch === "\\") {
end += 2;
continue;
}
if (ch === "[") inClass = true;
else if (ch === "]") inClass = false;
else if (ch === "/" && !inClass) {
end += 1;
while (end < text.length && /[a-z]/i.test(text[end]!)) end += 1;
return end;
}
end += 1;
}
return -1;
}
/**
@@ -34,7 +34,7 @@ import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
import {
DEFAULT_JOB_EXPIRE_SECONDS,
QUEUE_RUNTIME,
@@ -60,9 +60,7 @@ const source = new Map(
/** Strip comments so documentation that quotes code never matches. */
function code(text: string): string {
return text
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
return stripComments(text);
}
/** Index just past the bracket that closes the one at `open`. */
@@ -13,6 +13,7 @@ import { describe, expect, it } from "vitest";
import {
readSource,
readSourceText,
stripComments,
walkSourceFiles,
} from "./helpers/source-files";
@@ -25,10 +26,6 @@ const ALLOWED: Record<string, string> = {
const HAND_ROLLED_SLOPE =
/\bn\s*\*\s*sum_?xy\s*-\s*sum_?x\s*\*\s*sum_?y\b|\bsxy\s*\/\s*sxx\b|\bsumXy\s*-\s*\(\s*sumX\s*\*\s*sumY\s*\)\s*\/\s*n\b/i;
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
describe("one least-squares line", () => {
it("the matcher fires on a hand-rolled slope", () => {
expect(
+1 -5
View File
@@ -18,7 +18,7 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -28,10 +28,6 @@ const NO_LIST_ON_SCREEN: Record<string, string> = {
"The web reads it for one decorative badge (a record set in the last 30 days), not as a list; the native client owns the list.",
};
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
/** `app/api/a/[id]/b/route.ts` → `/api/a/[id]/b`. */
function routePathOf(file: string): string {
return `/${file.replace(/^app\//, "").replace(/\/route\.tsx?$/, "")}`;
+1 -5
View File
@@ -25,7 +25,7 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -37,10 +37,6 @@ const LIST_MODULES = new Set([
"lib/mcp/capped.ts",
]);
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
/** The text of a call's argument list, from the `(` after `name` to its match. */
function callBodies(code: string, name: string): string[] {
const out: string[] = [];
@@ -114,6 +114,7 @@ import { describe, expect, it } from "vitest";
import { openApiPaths } from "@/lib/openapi/routes";
import { RETIRED_ROUTES } from "@/lib/http/retired-routes";
import { stripComments } from "./helpers/source-files";
const ROOT = process.cwd();
@@ -378,10 +379,6 @@ const UNPUBLISHED: Readonly<Record<string, Exemption>> = {
},
};
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "$1");
}
/** `src/app/api/foo/[id]/route.ts` → `/api/foo/{id}`. */
function toApiPath(file: string): string {
return (
@@ -42,7 +42,7 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -50,14 +50,6 @@ const SRC = join(process.cwd(), "src");
const HELPER_IMPORT =
/from\s+["'](?:@\/lib\/measurements\/|\.\/)day-(?:mean|statistic)["']/;
/** Comments go first: a mean named in prose is not a mean computed. */
function stripComments(text: string): string {
return text
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/^\s*\/\/.*$/gm, "")
.replace(/^\s*--.*$/gm, "");
}
/**
* Database means over a measurement value: SQL `AVG(value)`, the per-day folds
* `SUM(total) / SUM(cnt)` and `SUM(count * mean)`, and Prisma's
+6 -7
View File
@@ -19,15 +19,14 @@ import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
import { stripComments } from "./helpers/source-files";
const ROOT = resolve(__dirname, "../..");
const read = (path: string) => readFileSync(resolve(ROOT, path), "utf8");
/** Drop `//` and block comments so prose about a class cannot satisfy a check. */
function code(source: string): string {
return source
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:"'`])\/\/.*$/gm, "$1");
function code(source: string, fileName?: string): string {
return stripComments(source, fileName);
}
describe("the shell owns the safe area", () => {
@@ -63,7 +62,7 @@ describe("the shell owns the safe area", () => {
it("bottom toasts clear the bar under the same query the bar follows", () => {
// Sonner's own mobile offset stops at 600 px; the bar shows up to `md`
// and on a phone held sideways, so the lift keys on `shell-mobile`.
const css = code(read("src/app/globals.css"));
const css = code(read("src/app/globals.css"), "globals.css");
expect(css).toMatch(
/html \[data-sonner-toaster\]\[data-y-position="bottom"\]\s*\{\s*@variant shell-mobile\s*\{\s*bottom:\s*calc\(4rem \+ env\(safe-area-inset-bottom/,
);
@@ -81,7 +80,7 @@ describe("the shell owns the safe area", () => {
it("a bottom sheet on the keyboard drops its home-indicator padding", () => {
// Standing on the keyboard, the safe-area inset left a ~34 px gap
// between the sheet's last row and the keys.
expect(code(read("src/app/globals.css"))).toMatch(
expect(code(read("src/app/globals.css"), "globals.css")).toMatch(
/@custom-variant keyboard-open \(html\[data-keyboard="open"\] &\);/,
);
const sheet = code(read("src/components/ui/responsive-sheet.tsx"));
@@ -97,7 +96,7 @@ describe("the shell owns the safe area", () => {
// `bg-*` on the shell or a `bottom-*` on a sheet would have been ignored
// without a trace. In the components layer a utility wins, so the bottom
// sheet must not carry one that would pin it under the keyboard.
const css = code(read("src/app/globals.css"));
const css = code(read("src/app/globals.css"), "globals.css");
const start = css.indexOf("@layer components {");
expect(start).toBeGreaterThan(-1);
let depth = 0;
@@ -28,6 +28,7 @@
import { readdirSync, readFileSync } from "node:fs";
import { join, relative } from "node:path";
import { describe, expect, it } from "vitest";
import { stripComments } from "./helpers/source-files";
const SRC = join(__dirname, "..");
@@ -64,11 +65,6 @@ function openingTags(source: string): string[] {
return tags;
}
/** Comments mention the primitives by name (`<QueryErrorCard>`); skip them. */
function stripComments(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
const sites = walk(SRC).flatMap((file) =>
openingTags(stripComments(readFileSync(file, "utf8"))).map((tag) => ({
file: relative(SRC, file),
@@ -64,6 +64,7 @@
import { describe, it, expect } from "vitest";
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
import { stripComments } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
const API_ROOT = join(SRC, "app", "api");
@@ -165,19 +166,6 @@ const API_LITERAL = /^[`"']((?:\/api\/)[^`"']*)[`"']$/;
* against a one-level alias map built from the file's own
* `const NAME = "/api/…"` declarations.
*/
/**
* Comments are stripped before matching.
*
* A doc comment that NAMES `src="/api/…"` while explaining why the code no
* longer does that is prose, not a transport — and this guard's own
* explanations are written in exactly that shape. Commented-out code is
* dropped with them, which is correct: it issues no request. Same treatment
* and the same reason as `account-payload-consumer-guard.test.ts`.
*/
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "$1");
}
function headerlessReferences(file: string, source: string): Reference[] {
const aliases = new Map<string, string>();
// `const NAME = "/api/…"`, and the ternary form
@@ -17,6 +17,7 @@
import { describe, it, expect } from "vitest";
import { readFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
import { stripComments } from "./helpers/source-files";
const SRC = join(__dirname, "..");
/**
@@ -52,11 +53,6 @@ function walk(dir: string, out: string[] = []): string[] {
return out;
}
/** Strip comments so a mention in prose is not read as a call. */
function stripComments(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
describe("doctor-report aggregator egress", () => {
// A sweep that finds nothing agrees with every allowlist, so the size of
// the tree being read is asserted before anything is concluded from it.
@@ -19,6 +19,7 @@ import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative } from "node:path";
import { describe, expect, it } from "vitest";
import { stripComments } from "./helpers/source-files";
const ROOT = join(__dirname, "..", "lib");
@@ -38,9 +39,7 @@ function sourceFiles(dir: string): string[] {
/** The text of a file with its comments blanked, so prose cannot match. */
function code(path: string): string {
return readFileSync(path, "utf8")
.replace(/\/\*[\s\S]*?\*\//g, " ")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
return stripComments(readFileSync(path, "utf8"), path);
}
const passes = sourceFiles(ROOT).filter((path) =>
@@ -20,7 +20,7 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { guardModuleOf } from "./helpers/auth-guards";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
import { SESSION_USER_SELECT } from "@/lib/auth/session-user";
const SRC = join(process.cwd(), "src");
@@ -29,12 +29,6 @@ function read(rel: string): string {
return readFileSync(join(SRC, rel), "utf8");
}
function stripComments(src: string): string {
return src
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
}
/** The `{ ... }` argument of every call matching `callee`, braces balanced. */
function callObjects(src: string, callee: RegExp): string[] {
const code = stripComments(src);
@@ -34,7 +34,11 @@
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { readSourceText, walkSourceFiles } from "./helpers/source-files";
import {
readSourceText,
stripComments,
walkSourceFiles,
} from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -51,11 +55,7 @@ const codeCache = new Map<string, string>();
function code(rel: string): string {
let stripped = codeCache.get(rel);
if (stripped === undefined) {
stripped = read(rel)
.replace(/\/\*[\s\S]*?\*\//g, "")
.split("\n")
.map((line) => line.replace(/(^|[^:"'`\\])\/\/.*$/, "$1"))
.join("\n");
stripped = stripComments(read(rel), rel);
codeCache.set(rel, stripped);
}
return stripped;
+130
View File
@@ -0,0 +1,130 @@
/**
* The shared comment stripper reads comments the way the compiler does.
*
* Every structural guard that must not read prose as code goes through
* `stripComments` (`helpers/source-files.ts`). The private copies it replaced
* each got one case wrong (a `/*` inside a line comment, a `/*` inside a
* string, a trailing `//` after code, a URL), and a wrong strip is silent: a
* guard that loses real code reports a clean sweep. So the stripper is a
* scanner held to the TypeScript parser: every source file is stripped both
* ways, and the first file where they differ fails here with the line.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import ts from "typescript";
import { describe, expect, it } from "vitest";
import {
requireFloor,
stripComments,
walkSourceFiles,
} from "./helpers/source-files";
const ROOT = join(__dirname, "..", "..");
/** The parser's answer: remove exactly the ranges it reports as comments. */
function parserStrip(text: string, fileName: string): string {
const kind = fileName.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
const file = ts.createSourceFile(
fileName,
text,
ts.ScriptTarget.Latest,
false,
kind,
);
const ranges = new Map<number, number>();
const take = (found: ts.CommentRange[] | undefined) => {
for (const range of found ?? []) ranges.set(range.pos, range.end);
};
const visit = (node: ts.Node) => {
take(ts.getLeadingCommentRanges(text, node.getFullStart()));
take(ts.getTrailingCommentRanges(text, node.getEnd()));
if (
node.kind >= ts.SyntaxKind.FirstToken &&
node.kind <= ts.SyntaxKind.LastToken
) {
return;
}
for (const child of node.getChildren(file)) visit(child);
};
visit(file);
let out = "";
let at = 0;
for (const [pos, end] of [...ranges].sort(([a], [b]) => a - b)) {
if (pos < at) continue;
out += text.slice(at, pos) + text.slice(pos, end).replace(/[^\n]/g, "");
at = end;
}
return out + text.slice(at);
}
/** Trailing whitespace differs where a comment left the line; nothing else may. */
function lines(text: string): string[] {
return text.split("\n").map((line) => line.trimEnd());
}
describe("stripComments", () => {
it("keeps the code a block opener inside a line comment used to hide", () => {
const source = [
"// Every `/api/mood-entries/*` route also enforces the gate.",
"if (!enabled) return notFound();",
"/* a real block */ const x = 1;",
].join("\n");
expect(lines(stripComments(source))).toEqual([
"",
"if (!enabled) return notFound();",
" const x = 1;",
]);
});
it("opens nothing inside strings, templates and regular expressions", () => {
const source = [
'await page.route("**/api/day/**", stub); // a stub',
'const url = "https://host/*"; const next = `/*${id /* gone */}*/`;',
"const slash = /\\/\\*/g; const ratio = a / b; // c",
].join("\n");
expect(lines(stripComments(source))).toEqual([
'await page.route("**/api/day/**", stub);',
'const url = "https://host/*"; const next = `/*${id }*/`;',
"const slash = /\\/\\*/g; const ratio = a / b;",
]);
});
it("keeps line numbers, so a finding points at the file's own line", () => {
const source = "/**\n * doc\n */\nexport const a = 1;\n";
expect(stripComments(source).split("\n")[3]).toBe("export const a = 1;");
});
it("strips only block comments from CSS", () => {
expect(
stripComments(".a { background: url(//cdn/x.png); } /* note */", "x.css"),
).toBe(".a { background: url(//cdn/x.png); } ");
});
it("agrees with the TypeScript parser on every source file", () => {
const files = requireFloor(
"source files",
[
...walkSourceFiles(join(ROOT, "src"), { floor: 3000 })
.filter((rel) => !rel.startsWith("generated/"))
.map((rel) => join("src", rel)),
...walkSourceFiles(join(ROOT, "e2e"), { floor: 100 }).map((rel) =>
join("e2e", rel),
),
].filter((rel) => /\.tsx?$/.test(rel)),
3500,
);
const differing: string[] = [];
for (const rel of files) {
const text = readFileSync(join(ROOT, rel), "utf8");
const ours = lines(stripComments(text, rel));
const parser = lines(parserStrip(text, rel));
const at = ours.findIndex((line, index) => line !== parser[index]);
if (at !== -1 || ours.length !== parser.length) {
differing.push(`${rel}:${at + 1}`);
}
}
expect(differing).toEqual([]);
}, 180_000);
});
@@ -48,6 +48,7 @@
import { describe, it, expect } from "vitest";
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
import { join, posix, relative, sep } from "node:path";
import { stripComments } from "./helpers/source-files";
const REPO_ROOT = join(__dirname, "..", "..");
const COMPONENT_ROOT = join(REPO_ROOT, "src", "components");
@@ -528,11 +529,6 @@ const RESULT_SURFACES: ReadonlyArray<{
},
];
/** Code only — a comment naming a marker is prose, not an affordance. */
function stripComments(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
function readCode(relativePath: string): string {
return stripComments(readFileSync(join(REPO_ROOT, relativePath), "utf8"));
}
+1 -5
View File
@@ -20,7 +20,7 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -57,10 +57,6 @@ const ALLOWED: Record<string, string> = {
"Write path, not analysis: resolves the row that won a raced external-id upsert, which owns the key whether or not it is deleted.",
};
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
function tombstonedModels(): string[] {
const schema = readFileSync(
join(process.cwd(), "prisma/schema.prisma"),
+1 -5
View File
@@ -27,7 +27,7 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { guardSourceOf } from "./helpers/auth-guards";
import { walkSourceFiles } from "./helpers/source-files";
import { stripComments, walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -92,10 +92,6 @@ const OWN_BUCKET: Record<string, string> = {
"The ECG sweep sends one recording per request with no client-side delay, newest first, over the whole HealthKit history; the route's own 600-a-minute ingest bucket meters it.",
};
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
const WRITES =
/export\s+(?:const|async\s+function)\s+(POST|PUT|PATCH|DELETE)\b/;
const OWN_LIMITER = /\b(?:check\w*RateLimit|checkRateLimit)\s*\(/;
@@ -20,6 +20,7 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { stripComments } from "../../../__tests__/helpers/source-files";
/** Mutating Prisma model methods, as they appear at a call site. */
const PRISMA_WRITE =
@@ -48,7 +49,7 @@ function sourceOf(relative: string): string {
* guard that fires on its own documentation.
*/
function code(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
return stripComments(source);
}
describe("read routes issue no writes", () => {
@@ -12,6 +12,7 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { guardSourceOf } from "@/__tests__/helpers/auth-guards";
import { stripComments } from "../../../../../__tests__/helpers/source-files";
const SRC = join(process.cwd(), "src");
const read = (rel: string) => readFileSync(join(SRC, rel), "utf8");
@@ -22,9 +23,7 @@ const read = (rel: string) => readFileSync(join(SRC, rel), "utf8");
* `redirect_uri` parameter").
*/
function code(rel: string): string {
return read(rel)
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
return stripComments(read(rel), rel);
}
const ROUTE_FILES = [
@@ -43,6 +42,8 @@ describe("T6 — open redirect / redirect_uri removed by construction", () => {
// Comments legitimately state "there is no redirect_uri parameter"; the
// invariant is that no CODE reads or builds one.
for (const rel of files) {
// A file read as nothing would pass the ban below.
expect(code(rel)).toMatch(/\bexport\b/);
expect(code(rel)).not.toMatch(/redirect_uri/);
}
});
@@ -56,6 +57,7 @@ describe("T6 — open redirect / redirect_uri removed by construction", () => {
// The routes build the Location only through the helper, never a bespoke
// scheme string in code (doc comments naming the scheme are fine).
for (const rel of ROUTE_FILES) {
expect(code(rel)).toMatch(/\bexport\b/);
expect(code(rel)).not.toMatch(/"healthlog:\/\//);
}
});
@@ -2,6 +2,7 @@ import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative } from "node:path";
import { describe, expect, it } from "vitest";
import { stripComments } from "../../../__tests__/helpers/source-files";
/**
* Every chart drawn in days is a door to its days, or it says why not.
@@ -120,17 +121,6 @@ function walk(path: string): string[] {
const files = SCAN.flatMap(walk).filter((f) => /\.tsx?$/.test(f));
const rel = (abs: string) => relative(ROOT, abs);
/**
* The code without its comments: a docblock that names `<HealthChart>` is
* not a mount. Block comments (JSX ones included) go whole; a line comment
* goes from `//` to the line end unless the slashes follow a colon (a URL).
*/
export function stripComments(text: string): string {
return text
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:"'`])\/\/.*$/gm, "$1");
}
const read = new Map(
files.map((f) => [rel(f), stripComments(readFileSync(f, "utf8"))]),
);
@@ -199,6 +199,7 @@ import {
} from "@/lib/ai/coach/tools/definitions";
import { createResultRefAllocator } from "@/lib/ai/coach/results/refs";
import type { PriorResultTurn } from "@/lib/ai/coach/results/refs";
import { stripComments } from "../../../../__tests__/helpers/source-files";
const REACH: CoachHistoryReach = { window: "last30days", days: LIMIT_DAYS };
@@ -398,7 +399,7 @@ function walk(dir: string): string[] {
/** `source` without its comments, so a doc mention is not read as a call. */
function code(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
return stripComments(source);
}
/** The argument text of every call to `fn(` in `source`, parentheses balanced. */
@@ -17,6 +17,7 @@ import { readdirSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { requireFloor } from "@/__tests__/helpers/source-files";
import { stripComments } from "../../../../__tests__/helpers/source-files";
const ROOT = resolve(__dirname, "../../../..");
@@ -65,9 +66,7 @@ const DE_DEFAULT_NULLISH = /\?\?\s*"de"/;
* Only executable source is checked.
*/
function readCode(rel: string): string {
return readFileSync(resolve(ROOT, rel), "utf8")
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/^\s*\/\/.*$/gm, "");
return stripComments(readFileSync(resolve(ROOT, rel), "utf8"), rel);
}
describe("no German-default locale binary", () => {
@@ -79,6 +78,11 @@ describe("no German-default locale binary", () => {
expect(readCode(rel)).not.toMatch(DE_DEFAULT_NULLISH);
});
it.each(GUARDED_FILES)("%s reads as code, not as an empty file", (rel) => {
// The bans above pass on nothing; the stripped file must still export.
expect(readCode(rel)).toMatch(/\bexport\b/);
});
it("finds the turn pipeline it guards", () => {
// An empty directory read would leave the pipeline unguarded and green.
expect(TURN_PIPELINE_FILES).toContain("lib/ai/coach/turn/context.ts");
@@ -44,6 +44,7 @@ import { describe, it, expect } from "vitest";
import { readFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
import { ENCRYPTED_COLUMNS, encryptedColumnKey } from "../encrypted-columns";
import { stripComments } from "../../../__tests__/helpers/source-files";
const ROOT = join(__dirname, "../../../..");
const SRC = join(ROOT, "src");
@@ -116,13 +117,6 @@ function sourceFiles(dir: string, out: string[] = []): string[] {
return out;
}
/** Strip comments so prose about `encrypt()` never counts as a call. */
function stripComments(src: string): string {
return src
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:"'`\\])\/\/.*$/gm, "$1");
}
const callRe = (names: string[]) =>
new RegExp(`(?<![.\\w])(?:${names.join("|")})\\s*\\(`);
/**
+4 -3
View File
@@ -163,6 +163,7 @@ import {
AI_AVAILABLE,
aiUnavailable,
} from "@/__tests__/helpers/ai-capability-fixtures";
import { stripComments } from "../../../__tests__/helpers/source-files";
const emptySummary = {
count: 0,
@@ -898,9 +899,9 @@ describe("buildDashboardSnapshot — no LLM in the path", () => {
);
// Strip line + block comments so the guard checks executable code,
// not the docstring that explicitly states the no-LLM contract.
const code = src
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
const code = stripComments(src, "snapshot.ts");
// The bans below pass on nothing; the builder must still be in the text.
expect(code).toMatch(/export async function buildDashboardSnapshot\b/);
expect(code).not.toMatch(/provider-runner|provider-chain|resolveProvider/);
expect(code).not.toMatch(/runRawCompletion|extractFeatures/);
expect(code).not.toMatch(/fetch\(\s*["'`][^"'`]*insights\/generate/);
@@ -20,6 +20,7 @@ import {
AI_CAPABILITY_KEYS,
type AiCapabilityKey,
} from "@/lib/ai/capabilities/types";
import { stripComments } from "../../../__tests__/helpers/source-files";
const ROOT = join(process.cwd(), "src");
@@ -89,9 +90,7 @@ const AI_JOBS: Record<string, JobGate> = {
};
function code(rel: string): string {
return readFileSync(join(ROOT, rel), "utf8")
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/(^|[^:])\/\/.*$/gm, "$1");
return stripComments(readFileSync(join(ROOT, rel), "utf8"), rel);
}
describe("AI job capability inventory", () => {