@titan-design/fix-proof 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -1
- package/dist/index.js +25 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ type ConfigResult = {
|
|
|
18
18
|
/** Parses the config text found at the merge base; absent text gives the defaults. */
|
|
19
19
|
declare function parseFixProofConfig(text: string | null | undefined): ConfigResult;
|
|
20
20
|
|
|
21
|
+
/** Every alternative of a brace glob, innermost group first; throws past 256 alternatives, 32 brace groups or 1024 characters. */
|
|
22
|
+
declare function expandBraces(glob: string): string[];
|
|
21
23
|
/** A matcher for repo-relative POSIX paths supporting `**`, `*`, `?` and `{a,b}`. */
|
|
22
24
|
declare function compileGlobs(globs: readonly string[]): (path: string) => boolean;
|
|
23
25
|
|
|
@@ -121,4 +123,4 @@ declare function formatResultLine(result: FixProofResult): string;
|
|
|
121
123
|
/** Decodes a result line, refusing anything over 4 KB, of another version, with unknown fields or with duplicate or reordered keys. */
|
|
122
124
|
declare function parseResultLine(line: string): ParsedResultLine;
|
|
123
125
|
|
|
124
|
-
export { CONFIG_PATH, type Classification, type ClassifyInput, type ConfigResult, DEFAULT_CARRY_GLOBS, DEFAULT_TEST_GLOBS, type FileOutcome, type FileState, type FixProofConfig, type FixProofPlan, type FixProofResult, type ParsedResultLine, type PlanInput, type PlanResult, RESULT_LINE_MAX_BYTES, RESULT_PREFIX, type ReportRun, TEST_CLASSES, type TestClass, type TestOutcome, VERDICTS, type Verdict, classifyReports, compileGlobs, formatResultLine, parseFixProofConfig, parseResultLine, planFixProof, toResult };
|
|
126
|
+
export { CONFIG_PATH, type Classification, type ClassifyInput, type ConfigResult, DEFAULT_CARRY_GLOBS, DEFAULT_TEST_GLOBS, type FileOutcome, type FileState, type FixProofConfig, type FixProofPlan, type FixProofResult, type ParsedResultLine, type PlanInput, type PlanResult, RESULT_LINE_MAX_BYTES, RESULT_PREFIX, type ReportRun, TEST_CLASSES, type TestClass, type TestOutcome, VERDICTS, type Verdict, classifyReports, compileGlobs, expandBraces, formatResultLine, parseFixProofConfig, parseResultLine, planFixProof, toResult };
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
// src/glob.ts
|
|
2
2
|
var MAX_EXPANSIONS = 256;
|
|
3
|
+
var MAX_GLOB_LENGTH = 1024;
|
|
4
|
+
var MAX_BRACE_GROUPS = 32;
|
|
3
5
|
var INNERMOST_BRACE = /\{([^{}]*)\}/;
|
|
4
6
|
function expandBraces(glob) {
|
|
7
|
+
assertBounded(glob);
|
|
8
|
+
const out = [];
|
|
9
|
+
collect(glob, out, glob);
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
function assertBounded(glob) {
|
|
13
|
+
if (glob.length > MAX_GLOB_LENGTH) throw new Error(`glob is longer than ${MAX_GLOB_LENGTH} characters`);
|
|
14
|
+
const groups = glob.split("{").length - 1;
|
|
15
|
+
if (groups > MAX_BRACE_GROUPS) throw new Error(`glob has more than ${MAX_BRACE_GROUPS} brace groups`);
|
|
16
|
+
}
|
|
17
|
+
function collect(glob, out, original) {
|
|
5
18
|
const match = INNERMOST_BRACE.exec(glob);
|
|
6
|
-
if (!match)
|
|
19
|
+
if (!match) {
|
|
20
|
+
out.push(glob);
|
|
21
|
+
if (out.length > MAX_EXPANSIONS) throw tooMany(original);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
7
24
|
const [group, body = ""] = match;
|
|
8
25
|
const head = glob.slice(0, match.index);
|
|
9
26
|
const tail = glob.slice(match.index + group.length);
|
|
10
|
-
const
|
|
11
|
-
if (
|
|
12
|
-
|
|
27
|
+
const choices = body.split(",");
|
|
28
|
+
if (choices.length > MAX_EXPANSIONS) throw tooMany(original);
|
|
29
|
+
for (const choice of choices) collect(head + choice + tail, out, original);
|
|
30
|
+
}
|
|
31
|
+
function tooMany(glob) {
|
|
32
|
+
return new Error(`glob expands past ${MAX_EXPANSIONS} alternatives: ${glob.length > 80 ? `${glob.slice(0, 80)}...` : glob}`);
|
|
13
33
|
}
|
|
14
34
|
function escapeRegExp(char) {
|
|
15
35
|
return /[.+^$()|[\]\\{}]/.test(char) ? `\\${char}` : char;
|
|
@@ -438,6 +458,7 @@ export {
|
|
|
438
458
|
VERDICTS,
|
|
439
459
|
classifyReports,
|
|
440
460
|
compileGlobs,
|
|
461
|
+
expandBraces,
|
|
441
462
|
formatResultLine,
|
|
442
463
|
parseFixProofConfig,
|
|
443
464
|
parseResultLine,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/glob.ts","../src/config.ts","../src/paths.ts","../src/plan.ts","../src/report.ts","../src/classify.ts","../src/result-line.ts"],"sourcesContent":["const MAX_EXPANSIONS = 256;\nconst INNERMOST_BRACE = /\\{([^{}]*)\\}/;\n\n/** Every alternative of a brace glob, innermost group first; throws past 256 alternatives. */\nexport function expandBraces(glob: string): string[] {\n const match = INNERMOST_BRACE.exec(glob);\n if (!match) return [glob];\n const [group, body = \"\"] = match;\n const head = glob.slice(0, match.index);\n const tail = glob.slice(match.index + group.length);\n const expanded = body.split(\",\").flatMap((choice) => expandBraces(head + choice + tail));\n if (expanded.length > MAX_EXPANSIONS) throw new Error(`glob expands past ${MAX_EXPANSIONS} alternatives: ${glob}`);\n return expanded;\n}\n\nfunction escapeRegExp(char: string): string {\n return /[.+^$()|[\\]\\\\{}]/.test(char) ? `\\\\${char}` : char;\n}\n\nfunction globToRegExp(glob: string): RegExp {\n let source = \"\";\n let index = 0;\n while (index < glob.length) {\n if (glob.startsWith(\"**/\", index)) {\n source += \"(?:[^/]+/)*\";\n index += 3;\n } else if (glob.startsWith(\"**\", index)) {\n source += \".*\";\n index += 2;\n } else {\n const char = glob.charAt(index);\n source += char === \"*\" ? \"[^/]*\" : char === \"?\" ? \"[^/]\" : escapeRegExp(char);\n index += 1;\n }\n }\n return new RegExp(`^${source}$`);\n}\n\n/** A matcher for repo-relative POSIX paths supporting `**`, `*`, `?` and `{a,b}`. */\nexport function compileGlobs(globs: readonly string[]): (path: string) => boolean {\n const patterns = globs.flatMap(expandBraces).map(globToRegExp);\n return (path) => patterns.some((pattern) => pattern.test(path));\n}\n","import { expandBraces } from \"./glob.js\";\n\n/** Where a target repo keeps its fix-proof config; read at the merge base, never at head. */\nexport const CONFIG_PATH = \".github/fix-proof.json\";\nexport const DEFAULT_TEST_GLOBS: readonly string[] = [\"**/*.{test,spec}.{ts,tsx,mts,js,mjs}\"];\nexport const DEFAULT_CARRY_GLOBS: readonly string[] = [\n \"**/fixtures/**\",\n \"**/__fixtures__/**\",\n \"**/test-support/**\",\n \"**/testdata/**\",\n];\n\nexport interface FixProofConfig {\n tests: string[];\n carry: string[];\n command?: string;\n build?: string;\n}\n\nexport type ConfigResult = { ok: true; config: FixProofConfig } | { ok: false; error: string };\n\nconst KEYS = new Set([\"tests\", \"carry\", \"command\", \"build\"]);\n\nfunction readGlobs(value: unknown, key: string, fallback: readonly string[]): string[] {\n if (value === undefined) return [...fallback];\n if (!Array.isArray(value) || !value.every((glob) => typeof glob === \"string\" && glob !== \"\")) {\n throw new Error(`${key} must be an array of non-empty strings`);\n }\n value.forEach((glob: string) => expandBraces(glob));\n return [...value] as string[];\n}\n\nfunction readString(value: unknown, key: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\" || value === \"\") throw new Error(`${key} must be a non-empty string`);\n return value;\n}\n\nfunction readConfig(raw: unknown): FixProofConfig {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) throw new Error(\"config must be a JSON object\");\n const record = raw as Record<string, unknown>;\n const unknownKey = Object.keys(record).find((key) => !KEYS.has(key));\n if (unknownKey !== undefined) throw new Error(`unknown config key: ${unknownKey}`);\n const config: FixProofConfig = {\n tests: readGlobs(record.tests, \"tests\", DEFAULT_TEST_GLOBS),\n carry: readGlobs(record.carry, \"carry\", DEFAULT_CARRY_GLOBS),\n };\n const command = readString(record.command, \"command\");\n const build = readString(record.build, \"build\");\n if (command !== undefined) config.command = command;\n if (build !== undefined) config.build = build;\n return config;\n}\n\n/** Parses the config text found at the merge base; absent text gives the defaults. */\nexport function parseFixProofConfig(text: string | null | undefined): ConfigResult {\n if (text === null || text === undefined) return { ok: true, config: readConfig({}) };\n try {\n return { ok: true, config: readConfig(JSON.parse(text)) };\n } catch (error) {\n return { ok: false, error: `${CONFIG_PATH}: ${(error as Error).message}` };\n }\n}\n","const UNSAFE = /[\\\\\\0]/;\n\n/** A repo-relative POSIX path with `.` and empty segments removed, or null when it is absolute or escapes with `..`. */\nexport function normalizeRelative(path: string): string | null {\n if (path === \"\" || path.startsWith(\"/\") || UNSAFE.test(path)) return null;\n const segments: string[] = [];\n for (const segment of path.split(\"/\")) {\n if (segment === \"\" || segment === \".\") continue;\n if (segment === \"..\") return null;\n segments.push(segment);\n }\n return segments.length > 0 ? segments.join(\"/\") : null;\n}\n\nfunction absoluteSegments(path: string): string[] | null {\n if (!path.startsWith(\"/\") || UNSAFE.test(path)) return null;\n const segments: string[] = [];\n for (const segment of path.split(\"/\")) {\n if (segment === \"\" || segment === \".\") continue;\n if (segment === \"..\") {\n if (segments.pop() === undefined) return null;\n continue;\n }\n segments.push(segment);\n }\n return segments;\n}\n\n/** The path of an absolute file below an absolute root, or null when either is not absolute or the file is outside the root. */\nexport function relativeToRoot(file: string, root: string): string | null {\n const fileSegments = absoluteSegments(file);\n const rootSegments = absoluteSegments(root);\n if (!fileSegments || !rootSegments || fileSegments.length <= rootSegments.length) return null;\n if (rootSegments.some((segment, index) => fileSegments[index] !== segment)) return null;\n return fileSegments.slice(rootSegments.length).join(\"/\");\n}\n","import { CONFIG_PATH, parseFixProofConfig, type FixProofConfig } from \"./config.js\";\nimport { compileGlobs } from \"./glob.js\";\nimport { normalizeRelative } from \"./paths.js\";\n\nexport interface PlanInput {\n /** Output of `git diff -M --name-status <mergeBase> <head>`. */\n nameStatus: string;\n /** `.github/fix-proof.json` at the merge base; null or absent when the file does not exist there. */\n baseConfig?: string | null;\n /** The same file at head, used only to tell the reviewer the PR edited it. */\n headConfig?: string | null;\n}\n\nexport interface FixProofPlan {\n /** Head paths of added or changed tests, to check out over the merge base and run. */\n tests: string[];\n /** Added or changed support files matching the carry globs. */\n carried: string[];\n /** Tests the PR deletes: never run, named to the reviewer. */\n deletedTests: string[];\n /** Old paths of partially renamed tests, removed from the overlay. */\n overlayRemovals: string[];\n config: FixProofConfig;\n configEdited: boolean;\n}\n\nexport type PlanResult = { ok: true; plan: FixProofPlan } | { ok: false; error: string };\n\ninterface DiffEntry {\n status: string;\n score: number | null;\n path: string;\n oldPath: string | null;\n}\n\nconst STATUS = /^(?:([AMDT])|([RC])(\\d{1,3}))$/;\n\nfunction parsePath(raw: string | undefined, line: string): string {\n if (raw === undefined || raw.startsWith('\"')) throw new Error(`unsupported path in diff line: ${line}`);\n const path = normalizeRelative(raw);\n if (path === null) throw new Error(`unsafe path in diff line: ${line}`);\n return path;\n}\n\nfunction parseLine(line: string): DiffEntry {\n const [code = \"\", ...paths] = line.split(\"\\t\");\n const match = STATUS.exec(code);\n if (!match) throw new Error(`unsupported diff status: ${line}`);\n const pair = match[2] !== undefined;\n if (paths.length !== (pair ? 2 : 1)) throw new Error(`malformed diff line: ${line}`);\n const score = pair ? Number(match[3]) : null;\n if (score !== null && score > 100) throw new Error(`malformed similarity score: ${line}`);\n const status = match[1] ?? match[2] ?? \"\";\n if (!pair) return { status, score, path: parsePath(paths[0], line), oldPath: null };\n return { status, score, path: parsePath(paths[1], line), oldPath: parsePath(paths[0], line) };\n}\n\nfunction parseNameStatus(text: string): DiffEntry[] {\n return text\n .split(\"\\n\")\n .filter((line) => line !== \"\")\n .map(parseLine);\n}\n\ninterface Sorter {\n isTest: (path: string) => boolean;\n isCarry: (path: string) => boolean;\n add: (bucket: keyof Buckets, path: string) => void;\n}\n\ninterface Buckets {\n tests: Set<string>;\n carried: Set<string>;\n deletedTests: Set<string>;\n overlayRemovals: Set<string>;\n}\n\nfunction addChanged(sorter: Sorter, path: string): void {\n if (sorter.isTest(path)) sorter.add(\"tests\", path);\n else if (sorter.isCarry(path)) sorter.add(\"carried\", path);\n}\n\nfunction sortRename(sorter: Sorter, entry: DiffEntry & { oldPath: string }): void {\n const oldIsTest = sorter.isTest(entry.oldPath);\n if (!sorter.isTest(entry.path)) {\n if (oldIsTest) sorter.add(\"deletedTests\", entry.oldPath);\n if (sorter.isCarry(entry.path)) sorter.add(\"carried\", entry.path);\n return;\n }\n if (entry.score === 100) return;\n sorter.add(\"tests\", entry.path);\n if (oldIsTest) sorter.add(\"overlayRemovals\", entry.oldPath);\n}\n\nfunction sortEntry(sorter: Sorter, entry: DiffEntry): void {\n if (entry.status === \"D\") {\n if (sorter.isTest(entry.path)) sorter.add(\"deletedTests\", entry.path);\n } else if (entry.status === \"R\" && entry.oldPath !== null) {\n sortRename(sorter, { ...entry, oldPath: entry.oldPath });\n } else {\n addChanged(sorter, entry.path);\n }\n}\n\nfunction buildPlan(entries: DiffEntry[], config: FixProofConfig, configEdited: boolean): FixProofPlan {\n const buckets: Buckets = { tests: new Set(), carried: new Set(), deletedTests: new Set(), overlayRemovals: new Set() };\n const sorter: Sorter = {\n isTest: compileGlobs(config.tests),\n isCarry: compileGlobs(config.carry),\n add: (bucket, path) => buckets[bucket].add(path),\n };\n entries.forEach((entry) => sortEntry(sorter, entry));\n const sorted = (set: Set<string>) => [...set].sort();\n return {\n tests: sorted(buckets.tests),\n carried: sorted(buckets.carried),\n deletedTests: sorted(buckets.deletedTests),\n overlayRemovals: sorted(buckets.overlayRemovals),\n config,\n configEdited,\n };\n}\n\nfunction touchesConfig(entries: DiffEntry[]): boolean {\n return entries.some((entry) => entry.path === CONFIG_PATH || entry.oldPath === CONFIG_PATH);\n}\n\n/** Splits a merge-base-to-head diff into tests to run, carried support files and deleted tests, using globs from the base config only. */\nexport function planFixProof(input: PlanInput): PlanResult {\n const parsed = parseFixProofConfig(input.baseConfig);\n if (!parsed.ok) return parsed;\n let entries: DiffEntry[];\n try {\n entries = parseNameStatus(input.nameStatus);\n } catch (error) {\n return { ok: false, error: (error as Error).message };\n }\n const headDiffers = input.headConfig !== undefined && (input.headConfig ?? null) !== (input.baseConfig ?? null);\n return { ok: true, plan: buildPlan(entries, parsed.config, headDiffers || touchesConfig(entries)) };\n}\n","import { relativeToRoot } from \"./paths.js\";\n\nexport type TestStatus = \"passed\" | \"failed\" | \"other\";\n\nexport interface TestRun {\n name: string;\n status: TestStatus;\n failures: string[];\n}\n\nexport interface FileRun {\n /** The file failed before any test was collected, as vitest reports a suite load error. */\n loadError: boolean;\n /** Keyed by full name and occurrence, so duplicate names pair up in order. */\n tests: Map<string, TestRun>;\n}\n\nexport type ReadReport = { ok: true; files: Map<string, FileRun> } | { ok: false; error: string };\n\ntype UnknownRecord = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction testName(result: UnknownRecord): string | null {\n if (typeof result.fullName === \"string\") return result.fullName;\n const ancestors = Array.isArray(result.ancestorTitles) ? result.ancestorTitles : [];\n if (typeof result.title !== \"string\" || !ancestors.every((title) => typeof title === \"string\")) return null;\n return [...(ancestors as string[]), result.title].join(\" \");\n}\n\nfunction readTest(result: unknown): TestRun | null {\n if (!isRecord(result)) return null;\n const name = testName(result);\n if (name === null) return null;\n const status: TestStatus = result.status === \"passed\" ? \"passed\" : result.status === \"failed\" ? \"failed\" : \"other\";\n const messages = Array.isArray(result.failureMessages) ? result.failureMessages : [];\n return { name, status, failures: messages.filter((message): message is string => typeof message === \"string\") };\n}\n\nfunction readFile(fileResult: UnknownRecord): FileRun {\n const raw = Array.isArray(fileResult.assertionResults) ? fileResult.assertionResults : [];\n const tests = new Map<string, TestRun>();\n const seen = new Map<string, number>();\n for (const test of raw.map(readTest)) {\n if (test === null) continue;\n const occurrence = seen.get(test.name) ?? 0;\n seen.set(test.name, occurrence + 1);\n tests.set(`${test.name}\\u0000${occurrence}`, test);\n }\n return { loadError: fileResult.status === \"failed\" && tests.size === 0, tests };\n}\n\n/** Reads a vitest `--reporter=json` report, keeping only files whose path below root exactly equals a selected path. */\nexport function readVitestReport(report: unknown, root: string, selected: ReadonlySet<string>): ReadReport {\n if (!isRecord(report) || !Array.isArray(report.testResults)) {\n return { ok: false, error: \"report is not a vitest JSON report\" };\n }\n const files = new Map<string, FileRun>();\n for (const fileResult of report.testResults) {\n if (!isRecord(fileResult) || typeof fileResult.name !== \"string\") continue;\n const path = relativeToRoot(fileResult.name, root);\n if (path === null || !selected.has(path)) continue;\n if (files.has(path)) return { ok: false, error: `report lists ${path} twice` };\n files.set(path, readFile(fileResult));\n }\n return { ok: true, files };\n}\n","import { normalizeRelative } from \"./paths.js\";\nimport { readVitestReport, type FileRun, type TestRun } from \"./report.js\";\n\nexport const TEST_CLASSES = [\"reproduces\", \"passes-on-base\", \"new-api\", \"fails-on-head\", \"not-run\"] as const;\nexport type TestClass = (typeof TEST_CLASSES)[number];\n\nexport const VERDICTS = [\"reproduced\", \"unproven\", \"vacuous\", \"no-tests\", \"error\"] as const;\nexport type Verdict = (typeof VERDICTS)[number];\n\nexport type FileState = \"ran\" | \"load-error\" | \"not-collected\";\n\nexport interface ReportRun {\n /** Absolute directory the report's file paths are relative to. */\n root: string;\n /** Parsed JSON from vitest's `--reporter=json --outputFile`. */\n report: unknown;\n}\n\nexport interface ClassifyInput {\n /** The plan's test paths, the only files whose results count. */\n selected: readonly string[];\n base: ReportRun;\n head: ReportRun;\n}\n\nexport interface TestOutcome {\n file: string;\n name: string;\n class: TestClass;\n}\n\nexport interface FileOutcome {\n file: string;\n base: FileState;\n head: FileState;\n}\n\nexport interface Classification {\n verdict: Verdict;\n tests: TestOutcome[];\n files: FileOutcome[];\n error?: string;\n}\n\nconst NEW_API_FAILURES = [\n /is not a function/,\n /is not a constructor/,\n /Cannot find module/,\n /does not provide an export/,\n /Failed to load url/,\n];\n\nfunction isNewApiFailure(message: string): boolean {\n return NEW_API_FAILURES.some((pattern) => pattern.test(message));\n}\n\nfunction classifyTest(key: string, head: TestRun, baseFile: FileRun | undefined): TestClass {\n if (head.status === \"failed\") return \"fails-on-head\";\n if (head.status !== \"passed\" || baseFile === undefined) return \"not-run\";\n if (baseFile.loadError) return \"new-api\";\n const base = baseFile.tests.get(key);\n if (base === undefined || base.status === \"other\") return \"not-run\";\n if (base.status === \"passed\") return \"passes-on-base\";\n return base.failures.some(isNewApiFailure) ? \"new-api\" : \"reproduces\";\n}\n\nfunction fileState(file: FileRun | undefined): FileState {\n if (file === undefined) return \"not-collected\";\n return file.loadError ? \"load-error\" : \"ran\";\n}\n\nfunction errorResult(error: string): Classification {\n return { verdict: \"error\", tests: [], files: [], error };\n}\n\nfunction verdictOf(tests: TestOutcome[]): Verdict {\n if (tests.some((test) => test.class === \"reproduces\")) return \"reproduced\";\n if (tests.some((test) => test.class === \"new-api\")) return \"unproven\";\n return \"vacuous\";\n}\n\nfunction outcomes(selected: string[], base: Map<string, FileRun>, head: Map<string, FileRun>): Classification {\n const files = selected.map((file) => ({ file, base: fileState(base.get(file)), head: fileState(head.get(file)) }));\n const tests = selected.flatMap((file) =>\n [...(head.get(file)?.tests ?? new Map<string, TestRun>())].map(([key, test]) => ({\n file,\n name: test.name,\n class: classifyTest(key, test, base.get(file)),\n })),\n );\n const missingOn = (side: \"base\" | \"head\") => files.every((file) => file[side] === \"not-collected\");\n if (missingOn(\"head\")) return { verdict: \"error\", tests, files, error: \"no selected file in the head report\" };\n if (missingOn(\"base\")) return { verdict: \"error\", tests, files, error: \"no selected file in the base report\" };\n return { verdict: verdictOf(tests), tests, files };\n}\n\n/** Compares base and head vitest reports for the selected test files; anything missing or malformed classifies away from `reproduced`. */\nexport function classifyReports(input: ClassifyInput): Classification {\n if (input.selected.length === 0) return { verdict: \"no-tests\", tests: [], files: [] };\n const selected = input.selected.map(normalizeRelative);\n if (selected.some((path) => path === null)) return errorResult(\"unsafe selected path\");\n const paths = [...new Set(selected as string[])].sort();\n const pathSet = new Set(paths);\n const base = readVitestReport(input.base.report, input.base.root, pathSet);\n if (!base.ok) return errorResult(`base: ${base.error}`);\n const head = readVitestReport(input.head.report, input.head.root, pathSet);\n if (!head.ok) return errorResult(`head: ${head.error}`);\n return outcomes(paths, base.files, head.files);\n}\n","import { TEST_CLASSES, VERDICTS, type Classification, type TestClass, type TestOutcome, type Verdict } from \"./classify.js\";\nimport type { FixProofPlan } from \"./plan.js\";\n\nexport const RESULT_PREFIX = \"fix-proof/v1 \";\nexport const RESULT_LINE_MAX_BYTES = 4096;\nconst MAX_ERROR_CHARS = 200;\nconst SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;\n\nexport interface FixProofResult {\n head: string;\n mergeBase: string;\n verdict: Verdict;\n counts: Record<TestClass, number>;\n notCollected: string[];\n deletedTests: string[];\n configEdited: boolean;\n tests: TestOutcome[];\n /** True when format dropped entries to fit the 4 KB line. */\n truncated: boolean;\n error?: string;\n}\n\nexport type ParsedResultLine = { ok: true; result: FixProofResult } | { ok: false; error: string };\n\n/** Joins a plan and its classification into the result a runner prints for head. */\nexport function toResult(args: { head: string; mergeBase: string; plan: FixProofPlan; classification: Classification }): FixProofResult {\n const { classification, plan } = args;\n const counts = Object.fromEntries(TEST_CLASSES.map((name) => [name, 0])) as Record<TestClass, number>;\n classification.tests.forEach((test) => (counts[test.class] += 1));\n const result: FixProofResult = {\n head: args.head,\n mergeBase: args.mergeBase,\n verdict: classification.verdict,\n counts,\n notCollected: classification.files.filter((file) => file.head === \"not-collected\").map((file) => file.file),\n deletedTests: [...plan.deletedTests],\n configEdited: plan.configEdited,\n tests: classification.tests.map((test) => ({ ...test })),\n truncated: false,\n };\n if (classification.error !== undefined) result.error = classification.error;\n return result;\n}\n\nfunction canonical(result: FixProofResult): FixProofResult {\n const out: FixProofResult = {\n head: result.head,\n mergeBase: result.mergeBase,\n verdict: result.verdict,\n counts: Object.fromEntries(TEST_CLASSES.map((name) => [name, result.counts[name]])) as Record<TestClass, number>,\n notCollected: [...result.notCollected],\n deletedTests: [...result.deletedTests],\n configEdited: result.configEdited,\n tests: result.tests.map((test) => ({ file: test.file, name: test.name, class: test.class })),\n truncated: result.truncated,\n };\n if (result.error !== undefined) out.error = result.error;\n return out;\n}\n\nfunction byteLength(text: string): number {\n return new TextEncoder().encode(text).length;\n}\n\nfunction encode(result: FixProofResult): string {\n return RESULT_PREFIX + JSON.stringify(canonical(result));\n}\n\nfunction shrink(result: FixProofResult): boolean {\n if (result.tests.length > 0) result.tests.pop();\n else if (result.deletedTests.length > 0) result.deletedTests.pop();\n else if (result.notCollected.length > 0) result.notCollected.pop();\n else if (result.error !== undefined && result.error.length > MAX_ERROR_CHARS) result.error = result.error.slice(0, MAX_ERROR_CHARS);\n else return false;\n result.truncated = true;\n return true;\n}\n\n/** Encodes one `fix-proof/v1 <json>` line of at most 4 KB, dropping list entries to fit; throws when the result would not parse back. */\nexport function formatResultLine(result: FixProofResult): string {\n const invalid = validate(result);\n if (invalid !== null) throw new Error(`invalid fix-proof result: ${invalid}`);\n const working = canonical(result);\n let line = encode(working);\n while (byteLength(line) > RESULT_LINE_MAX_BYTES) {\n if (!shrink(working)) throw new Error(\"fix-proof result does not fit in 4 KB\");\n line = encode(working);\n }\n return line;\n}\n\n/** Decodes a result line, refusing anything over 4 KB, of another version, with unknown fields or with duplicate or reordered keys. */\nexport function parseResultLine(line: string): ParsedResultLine {\n if (byteLength(line) > RESULT_LINE_MAX_BYTES) return { ok: false, error: \"result line exceeds 4 KB\" };\n if (!line.startsWith(RESULT_PREFIX)) return { ok: false, error: \"not a fix-proof/v1 line\" };\n const json = line.slice(RESULT_PREFIX.length);\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return { ok: false, error: \"result is not valid JSON\" };\n }\n const invalid = validate(parsed);\n if (invalid !== null) return { ok: false, error: invalid };\n const result = parsed as FixProofResult;\n if (JSON.stringify(canonical(result)) !== json) return { ok: false, error: \"result is not canonical: duplicate, reordered or padded keys\" };\n return { ok: true, result };\n}\n\ntype UnknownRecord = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction checkKeys(value: UnknownRecord, required: readonly string[], optional: readonly string[], at: string): string | null {\n const unknownKey = Object.keys(value).find((key) => !required.includes(key) && !optional.includes(key));\n if (unknownKey !== undefined) return `unknown field ${at}${unknownKey}`;\n const missing = required.find((key) => !(key in value));\n return missing === undefined ? null : `missing field ${at}${missing}`;\n}\n\nfunction isStringList(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\nfunction validateCounts(value: unknown): string | null {\n if (!isRecord(value)) return \"counts must be an object\";\n const keys = checkKeys(value, TEST_CLASSES, [], \"counts.\");\n if (keys !== null) return keys;\n const bad = TEST_CLASSES.find((name) => !Number.isSafeInteger(value[name]) || (value[name] as number) < 0);\n return bad === undefined ? null : `counts.${bad} must be a non-negative integer`;\n}\n\nfunction validateTest(value: unknown): string | null {\n if (!isRecord(value)) return \"tests entries must be objects\";\n const keys = checkKeys(value, [\"file\", \"name\", \"class\"], [], \"tests[].\");\n if (keys !== null) return keys;\n if (typeof value.file !== \"string\" || typeof value.name !== \"string\") return \"tests[].file and name must be strings\";\n return (TEST_CLASSES as readonly unknown[]).includes(value.class) ? null : \"tests[].class is unknown\";\n}\n\nconst RESULT_KEYS = [\"head\", \"mergeBase\", \"verdict\", \"counts\", \"notCollected\", \"deletedTests\", \"configEdited\", \"tests\", \"truncated\"];\n\nfunction validateScalars(value: UnknownRecord): string | null {\n if (typeof value.head !== \"string\" || !SHA.test(value.head)) return \"head must be a full lowercase sha\";\n if (typeof value.mergeBase !== \"string\" || !SHA.test(value.mergeBase)) return \"mergeBase must be a full lowercase sha\";\n if (!(VERDICTS as readonly unknown[]).includes(value.verdict)) return \"verdict is unknown\";\n if (typeof value.configEdited !== \"boolean\" || typeof value.truncated !== \"boolean\") return \"configEdited and truncated must be booleans\";\n if (value.error !== undefined && typeof value.error !== \"string\") return \"error must be a string\";\n if (!isStringList(value.notCollected) || !isStringList(value.deletedTests)) return \"notCollected and deletedTests must be string arrays\";\n return null;\n}\n\nfunction validate(value: unknown): string | null {\n if (!isRecord(value)) return \"result must be an object\";\n const problem = checkKeys(value, RESULT_KEYS, [\"error\"], \"\") ?? validateScalars(value) ?? validateCounts(value.counts);\n if (problem !== null) return problem;\n if (value.verdict === \"reproduced\" && (value.counts as UnknownRecord).reproduces === 0) return \"verdict reproduced with no reproducing test\";\n if (!Array.isArray(value.tests)) return \"tests must be an array\";\n return value.tests.map(validateTest).find((error) => error !== null) ?? null;\n}\n"],"mappings":"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","names":["isRecord"]}
|
|
1
|
+
{"version":3,"sources":["../src/glob.ts","../src/config.ts","../src/paths.ts","../src/plan.ts","../src/report.ts","../src/classify.ts","../src/result-line.ts"],"sourcesContent":["const MAX_EXPANSIONS = 256;\nconst MAX_GLOB_LENGTH = 1024;\nconst MAX_BRACE_GROUPS = 32;\nconst INNERMOST_BRACE = /\\{([^{}]*)\\}/;\n\n/** Every alternative of a brace glob, innermost group first; throws past 256 alternatives, 32 brace groups or 1024 characters. */\nexport function expandBraces(glob: string): string[] {\n assertBounded(glob);\n const out: string[] = [];\n collect(glob, out, glob);\n return out;\n}\n\n// Single-choice groups never grow the alternative count, so recursion depth and the copy of the\n// glob each frame keeps are bounded by rejecting oversized globs before any expansion.\nfunction assertBounded(glob: string): void {\n if (glob.length > MAX_GLOB_LENGTH) throw new Error(`glob is longer than ${MAX_GLOB_LENGTH} characters`);\n const groups = glob.split(\"{\").length - 1;\n if (groups > MAX_BRACE_GROUPS) throw new Error(`glob has more than ${MAX_BRACE_GROUPS} brace groups`);\n}\n\n// The cap is checked as alternatives are produced, so a glob that expands past it stops\n// after 257 strings instead of building the whole product first.\nfunction collect(glob: string, out: string[], original: string): void {\n const match = INNERMOST_BRACE.exec(glob);\n if (!match) {\n out.push(glob);\n if (out.length > MAX_EXPANSIONS) throw tooMany(original);\n return;\n }\n const [group, body = \"\"] = match;\n const head = glob.slice(0, match.index);\n const tail = glob.slice(match.index + group.length);\n const choices = body.split(\",\");\n if (choices.length > MAX_EXPANSIONS) throw tooMany(original);\n for (const choice of choices) collect(head + choice + tail, out, original);\n}\n\nfunction tooMany(glob: string): Error {\n return new Error(`glob expands past ${MAX_EXPANSIONS} alternatives: ${glob.length > 80 ? `${glob.slice(0, 80)}...` : glob}`);\n}\n\nfunction escapeRegExp(char: string): string {\n return /[.+^$()|[\\]\\\\{}]/.test(char) ? `\\\\${char}` : char;\n}\n\nfunction globToRegExp(glob: string): RegExp {\n let source = \"\";\n let index = 0;\n while (index < glob.length) {\n if (glob.startsWith(\"**/\", index)) {\n source += \"(?:[^/]+/)*\";\n index += 3;\n } else if (glob.startsWith(\"**\", index)) {\n source += \".*\";\n index += 2;\n } else {\n const char = glob.charAt(index);\n source += char === \"*\" ? \"[^/]*\" : char === \"?\" ? \"[^/]\" : escapeRegExp(char);\n index += 1;\n }\n }\n return new RegExp(`^${source}$`);\n}\n\n/** A matcher for repo-relative POSIX paths supporting `**`, `*`, `?` and `{a,b}`. */\nexport function compileGlobs(globs: readonly string[]): (path: string) => boolean {\n const patterns = globs.flatMap(expandBraces).map(globToRegExp);\n return (path) => patterns.some((pattern) => pattern.test(path));\n}\n","import { expandBraces } from \"./glob.js\";\n\n/** Where a target repo keeps its fix-proof config; read at the merge base, never at head. */\nexport const CONFIG_PATH = \".github/fix-proof.json\";\nexport const DEFAULT_TEST_GLOBS: readonly string[] = [\"**/*.{test,spec}.{ts,tsx,mts,js,mjs}\"];\nexport const DEFAULT_CARRY_GLOBS: readonly string[] = [\n \"**/fixtures/**\",\n \"**/__fixtures__/**\",\n \"**/test-support/**\",\n \"**/testdata/**\",\n];\n\nexport interface FixProofConfig {\n tests: string[];\n carry: string[];\n command?: string;\n build?: string;\n}\n\nexport type ConfigResult = { ok: true; config: FixProofConfig } | { ok: false; error: string };\n\nconst KEYS = new Set([\"tests\", \"carry\", \"command\", \"build\"]);\n\nfunction readGlobs(value: unknown, key: string, fallback: readonly string[]): string[] {\n if (value === undefined) return [...fallback];\n if (!Array.isArray(value) || !value.every((glob) => typeof glob === \"string\" && glob !== \"\")) {\n throw new Error(`${key} must be an array of non-empty strings`);\n }\n value.forEach((glob: string) => expandBraces(glob));\n return [...value] as string[];\n}\n\nfunction readString(value: unknown, key: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\" || value === \"\") throw new Error(`${key} must be a non-empty string`);\n return value;\n}\n\nfunction readConfig(raw: unknown): FixProofConfig {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) throw new Error(\"config must be a JSON object\");\n const record = raw as Record<string, unknown>;\n const unknownKey = Object.keys(record).find((key) => !KEYS.has(key));\n if (unknownKey !== undefined) throw new Error(`unknown config key: ${unknownKey}`);\n const config: FixProofConfig = {\n tests: readGlobs(record.tests, \"tests\", DEFAULT_TEST_GLOBS),\n carry: readGlobs(record.carry, \"carry\", DEFAULT_CARRY_GLOBS),\n };\n const command = readString(record.command, \"command\");\n const build = readString(record.build, \"build\");\n if (command !== undefined) config.command = command;\n if (build !== undefined) config.build = build;\n return config;\n}\n\n/** Parses the config text found at the merge base; absent text gives the defaults. */\nexport function parseFixProofConfig(text: string | null | undefined): ConfigResult {\n if (text === null || text === undefined) return { ok: true, config: readConfig({}) };\n try {\n return { ok: true, config: readConfig(JSON.parse(text)) };\n } catch (error) {\n return { ok: false, error: `${CONFIG_PATH}: ${(error as Error).message}` };\n }\n}\n","const UNSAFE = /[\\\\\\0]/;\n\n/** A repo-relative POSIX path with `.` and empty segments removed, or null when it is absolute or escapes with `..`. */\nexport function normalizeRelative(path: string): string | null {\n if (path === \"\" || path.startsWith(\"/\") || UNSAFE.test(path)) return null;\n const segments: string[] = [];\n for (const segment of path.split(\"/\")) {\n if (segment === \"\" || segment === \".\") continue;\n if (segment === \"..\") return null;\n segments.push(segment);\n }\n return segments.length > 0 ? segments.join(\"/\") : null;\n}\n\nfunction absoluteSegments(path: string): string[] | null {\n if (!path.startsWith(\"/\") || UNSAFE.test(path)) return null;\n const segments: string[] = [];\n for (const segment of path.split(\"/\")) {\n if (segment === \"\" || segment === \".\") continue;\n if (segment === \"..\") {\n if (segments.pop() === undefined) return null;\n continue;\n }\n segments.push(segment);\n }\n return segments;\n}\n\n/** The path of an absolute file below an absolute root, or null when either is not absolute or the file is outside the root. */\nexport function relativeToRoot(file: string, root: string): string | null {\n const fileSegments = absoluteSegments(file);\n const rootSegments = absoluteSegments(root);\n if (!fileSegments || !rootSegments || fileSegments.length <= rootSegments.length) return null;\n if (rootSegments.some((segment, index) => fileSegments[index] !== segment)) return null;\n return fileSegments.slice(rootSegments.length).join(\"/\");\n}\n","import { CONFIG_PATH, parseFixProofConfig, type FixProofConfig } from \"./config.js\";\nimport { compileGlobs } from \"./glob.js\";\nimport { normalizeRelative } from \"./paths.js\";\n\nexport interface PlanInput {\n /** Output of `git diff -M --name-status <mergeBase> <head>`. */\n nameStatus: string;\n /** `.github/fix-proof.json` at the merge base; null or absent when the file does not exist there. */\n baseConfig?: string | null;\n /** The same file at head, used only to tell the reviewer the PR edited it. */\n headConfig?: string | null;\n}\n\nexport interface FixProofPlan {\n /** Head paths of added or changed tests, to check out over the merge base and run. */\n tests: string[];\n /** Added or changed support files matching the carry globs. */\n carried: string[];\n /** Tests the PR deletes: never run, named to the reviewer. */\n deletedTests: string[];\n /** Old paths of partially renamed tests, removed from the overlay. */\n overlayRemovals: string[];\n config: FixProofConfig;\n configEdited: boolean;\n}\n\nexport type PlanResult = { ok: true; plan: FixProofPlan } | { ok: false; error: string };\n\ninterface DiffEntry {\n status: string;\n score: number | null;\n path: string;\n oldPath: string | null;\n}\n\nconst STATUS = /^(?:([AMDT])|([RC])(\\d{1,3}))$/;\n\nfunction parsePath(raw: string | undefined, line: string): string {\n if (raw === undefined || raw.startsWith('\"')) throw new Error(`unsupported path in diff line: ${line}`);\n const path = normalizeRelative(raw);\n if (path === null) throw new Error(`unsafe path in diff line: ${line}`);\n return path;\n}\n\nfunction parseLine(line: string): DiffEntry {\n const [code = \"\", ...paths] = line.split(\"\\t\");\n const match = STATUS.exec(code);\n if (!match) throw new Error(`unsupported diff status: ${line}`);\n const pair = match[2] !== undefined;\n if (paths.length !== (pair ? 2 : 1)) throw new Error(`malformed diff line: ${line}`);\n const score = pair ? Number(match[3]) : null;\n if (score !== null && score > 100) throw new Error(`malformed similarity score: ${line}`);\n const status = match[1] ?? match[2] ?? \"\";\n if (!pair) return { status, score, path: parsePath(paths[0], line), oldPath: null };\n return { status, score, path: parsePath(paths[1], line), oldPath: parsePath(paths[0], line) };\n}\n\nfunction parseNameStatus(text: string): DiffEntry[] {\n return text\n .split(\"\\n\")\n .filter((line) => line !== \"\")\n .map(parseLine);\n}\n\ninterface Sorter {\n isTest: (path: string) => boolean;\n isCarry: (path: string) => boolean;\n add: (bucket: keyof Buckets, path: string) => void;\n}\n\ninterface Buckets {\n tests: Set<string>;\n carried: Set<string>;\n deletedTests: Set<string>;\n overlayRemovals: Set<string>;\n}\n\nfunction addChanged(sorter: Sorter, path: string): void {\n if (sorter.isTest(path)) sorter.add(\"tests\", path);\n else if (sorter.isCarry(path)) sorter.add(\"carried\", path);\n}\n\nfunction sortRename(sorter: Sorter, entry: DiffEntry & { oldPath: string }): void {\n const oldIsTest = sorter.isTest(entry.oldPath);\n if (!sorter.isTest(entry.path)) {\n if (oldIsTest) sorter.add(\"deletedTests\", entry.oldPath);\n if (sorter.isCarry(entry.path)) sorter.add(\"carried\", entry.path);\n return;\n }\n if (entry.score === 100) return;\n sorter.add(\"tests\", entry.path);\n if (oldIsTest) sorter.add(\"overlayRemovals\", entry.oldPath);\n}\n\nfunction sortEntry(sorter: Sorter, entry: DiffEntry): void {\n if (entry.status === \"D\") {\n if (sorter.isTest(entry.path)) sorter.add(\"deletedTests\", entry.path);\n } else if (entry.status === \"R\" && entry.oldPath !== null) {\n sortRename(sorter, { ...entry, oldPath: entry.oldPath });\n } else {\n addChanged(sorter, entry.path);\n }\n}\n\nfunction buildPlan(entries: DiffEntry[], config: FixProofConfig, configEdited: boolean): FixProofPlan {\n const buckets: Buckets = { tests: new Set(), carried: new Set(), deletedTests: new Set(), overlayRemovals: new Set() };\n const sorter: Sorter = {\n isTest: compileGlobs(config.tests),\n isCarry: compileGlobs(config.carry),\n add: (bucket, path) => buckets[bucket].add(path),\n };\n entries.forEach((entry) => sortEntry(sorter, entry));\n const sorted = (set: Set<string>) => [...set].sort();\n return {\n tests: sorted(buckets.tests),\n carried: sorted(buckets.carried),\n deletedTests: sorted(buckets.deletedTests),\n overlayRemovals: sorted(buckets.overlayRemovals),\n config,\n configEdited,\n };\n}\n\nfunction touchesConfig(entries: DiffEntry[]): boolean {\n return entries.some((entry) => entry.path === CONFIG_PATH || entry.oldPath === CONFIG_PATH);\n}\n\n/** Splits a merge-base-to-head diff into tests to run, carried support files and deleted tests, using globs from the base config only. */\nexport function planFixProof(input: PlanInput): PlanResult {\n const parsed = parseFixProofConfig(input.baseConfig);\n if (!parsed.ok) return parsed;\n let entries: DiffEntry[];\n try {\n entries = parseNameStatus(input.nameStatus);\n } catch (error) {\n return { ok: false, error: (error as Error).message };\n }\n const headDiffers = input.headConfig !== undefined && (input.headConfig ?? null) !== (input.baseConfig ?? null);\n return { ok: true, plan: buildPlan(entries, parsed.config, headDiffers || touchesConfig(entries)) };\n}\n","import { relativeToRoot } from \"./paths.js\";\n\nexport type TestStatus = \"passed\" | \"failed\" | \"other\";\n\nexport interface TestRun {\n name: string;\n status: TestStatus;\n failures: string[];\n}\n\nexport interface FileRun {\n /** The file failed before any test was collected, as vitest reports a suite load error. */\n loadError: boolean;\n /** Keyed by full name and occurrence, so duplicate names pair up in order. */\n tests: Map<string, TestRun>;\n}\n\nexport type ReadReport = { ok: true; files: Map<string, FileRun> } | { ok: false; error: string };\n\ntype UnknownRecord = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction testName(result: UnknownRecord): string | null {\n if (typeof result.fullName === \"string\") return result.fullName;\n const ancestors = Array.isArray(result.ancestorTitles) ? result.ancestorTitles : [];\n if (typeof result.title !== \"string\" || !ancestors.every((title) => typeof title === \"string\")) return null;\n return [...(ancestors as string[]), result.title].join(\" \");\n}\n\nfunction readTest(result: unknown): TestRun | null {\n if (!isRecord(result)) return null;\n const name = testName(result);\n if (name === null) return null;\n const status: TestStatus = result.status === \"passed\" ? \"passed\" : result.status === \"failed\" ? \"failed\" : \"other\";\n const messages = Array.isArray(result.failureMessages) ? result.failureMessages : [];\n return { name, status, failures: messages.filter((message): message is string => typeof message === \"string\") };\n}\n\nfunction readFile(fileResult: UnknownRecord): FileRun {\n const raw = Array.isArray(fileResult.assertionResults) ? fileResult.assertionResults : [];\n const tests = new Map<string, TestRun>();\n const seen = new Map<string, number>();\n for (const test of raw.map(readTest)) {\n if (test === null) continue;\n const occurrence = seen.get(test.name) ?? 0;\n seen.set(test.name, occurrence + 1);\n tests.set(`${test.name}\\u0000${occurrence}`, test);\n }\n return { loadError: fileResult.status === \"failed\" && tests.size === 0, tests };\n}\n\n/** Reads a vitest `--reporter=json` report, keeping only files whose path below root exactly equals a selected path. */\nexport function readVitestReport(report: unknown, root: string, selected: ReadonlySet<string>): ReadReport {\n if (!isRecord(report) || !Array.isArray(report.testResults)) {\n return { ok: false, error: \"report is not a vitest JSON report\" };\n }\n const files = new Map<string, FileRun>();\n for (const fileResult of report.testResults) {\n if (!isRecord(fileResult) || typeof fileResult.name !== \"string\") continue;\n const path = relativeToRoot(fileResult.name, root);\n if (path === null || !selected.has(path)) continue;\n if (files.has(path)) return { ok: false, error: `report lists ${path} twice` };\n files.set(path, readFile(fileResult));\n }\n return { ok: true, files };\n}\n","import { normalizeRelative } from \"./paths.js\";\nimport { readVitestReport, type FileRun, type TestRun } from \"./report.js\";\n\nexport const TEST_CLASSES = [\"reproduces\", \"passes-on-base\", \"new-api\", \"fails-on-head\", \"not-run\"] as const;\nexport type TestClass = (typeof TEST_CLASSES)[number];\n\nexport const VERDICTS = [\"reproduced\", \"unproven\", \"vacuous\", \"no-tests\", \"error\"] as const;\nexport type Verdict = (typeof VERDICTS)[number];\n\nexport type FileState = \"ran\" | \"load-error\" | \"not-collected\";\n\nexport interface ReportRun {\n /** Absolute directory the report's file paths are relative to. */\n root: string;\n /** Parsed JSON from vitest's `--reporter=json --outputFile`. */\n report: unknown;\n}\n\nexport interface ClassifyInput {\n /** The plan's test paths, the only files whose results count. */\n selected: readonly string[];\n base: ReportRun;\n head: ReportRun;\n}\n\nexport interface TestOutcome {\n file: string;\n name: string;\n class: TestClass;\n}\n\nexport interface FileOutcome {\n file: string;\n base: FileState;\n head: FileState;\n}\n\nexport interface Classification {\n verdict: Verdict;\n tests: TestOutcome[];\n files: FileOutcome[];\n error?: string;\n}\n\nconst NEW_API_FAILURES = [\n /is not a function/,\n /is not a constructor/,\n /Cannot find module/,\n /does not provide an export/,\n /Failed to load url/,\n];\n\nfunction isNewApiFailure(message: string): boolean {\n return NEW_API_FAILURES.some((pattern) => pattern.test(message));\n}\n\nfunction classifyTest(key: string, head: TestRun, baseFile: FileRun | undefined): TestClass {\n if (head.status === \"failed\") return \"fails-on-head\";\n if (head.status !== \"passed\" || baseFile === undefined) return \"not-run\";\n if (baseFile.loadError) return \"new-api\";\n const base = baseFile.tests.get(key);\n if (base === undefined || base.status === \"other\") return \"not-run\";\n if (base.status === \"passed\") return \"passes-on-base\";\n return base.failures.some(isNewApiFailure) ? \"new-api\" : \"reproduces\";\n}\n\nfunction fileState(file: FileRun | undefined): FileState {\n if (file === undefined) return \"not-collected\";\n return file.loadError ? \"load-error\" : \"ran\";\n}\n\nfunction errorResult(error: string): Classification {\n return { verdict: \"error\", tests: [], files: [], error };\n}\n\nfunction verdictOf(tests: TestOutcome[]): Verdict {\n if (tests.some((test) => test.class === \"reproduces\")) return \"reproduced\";\n if (tests.some((test) => test.class === \"new-api\")) return \"unproven\";\n return \"vacuous\";\n}\n\nfunction outcomes(selected: string[], base: Map<string, FileRun>, head: Map<string, FileRun>): Classification {\n const files = selected.map((file) => ({ file, base: fileState(base.get(file)), head: fileState(head.get(file)) }));\n const tests = selected.flatMap((file) =>\n [...(head.get(file)?.tests ?? new Map<string, TestRun>())].map(([key, test]) => ({\n file,\n name: test.name,\n class: classifyTest(key, test, base.get(file)),\n })),\n );\n const missingOn = (side: \"base\" | \"head\") => files.every((file) => file[side] === \"not-collected\");\n if (missingOn(\"head\")) return { verdict: \"error\", tests, files, error: \"no selected file in the head report\" };\n if (missingOn(\"base\")) return { verdict: \"error\", tests, files, error: \"no selected file in the base report\" };\n return { verdict: verdictOf(tests), tests, files };\n}\n\n/** Compares base and head vitest reports for the selected test files; anything missing or malformed classifies away from `reproduced`. */\nexport function classifyReports(input: ClassifyInput): Classification {\n if (input.selected.length === 0) return { verdict: \"no-tests\", tests: [], files: [] };\n const selected = input.selected.map(normalizeRelative);\n if (selected.some((path) => path === null)) return errorResult(\"unsafe selected path\");\n const paths = [...new Set(selected as string[])].sort();\n const pathSet = new Set(paths);\n const base = readVitestReport(input.base.report, input.base.root, pathSet);\n if (!base.ok) return errorResult(`base: ${base.error}`);\n const head = readVitestReport(input.head.report, input.head.root, pathSet);\n if (!head.ok) return errorResult(`head: ${head.error}`);\n return outcomes(paths, base.files, head.files);\n}\n","import { TEST_CLASSES, VERDICTS, type Classification, type TestClass, type TestOutcome, type Verdict } from \"./classify.js\";\nimport type { FixProofPlan } from \"./plan.js\";\n\nexport const RESULT_PREFIX = \"fix-proof/v1 \";\nexport const RESULT_LINE_MAX_BYTES = 4096;\nconst MAX_ERROR_CHARS = 200;\nconst SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;\n\nexport interface FixProofResult {\n head: string;\n mergeBase: string;\n verdict: Verdict;\n counts: Record<TestClass, number>;\n notCollected: string[];\n deletedTests: string[];\n configEdited: boolean;\n tests: TestOutcome[];\n /** True when format dropped entries to fit the 4 KB line. */\n truncated: boolean;\n error?: string;\n}\n\nexport type ParsedResultLine = { ok: true; result: FixProofResult } | { ok: false; error: string };\n\n/** Joins a plan and its classification into the result a runner prints for head. */\nexport function toResult(args: { head: string; mergeBase: string; plan: FixProofPlan; classification: Classification }): FixProofResult {\n const { classification, plan } = args;\n const counts = Object.fromEntries(TEST_CLASSES.map((name) => [name, 0])) as Record<TestClass, number>;\n classification.tests.forEach((test) => (counts[test.class] += 1));\n const result: FixProofResult = {\n head: args.head,\n mergeBase: args.mergeBase,\n verdict: classification.verdict,\n counts,\n notCollected: classification.files.filter((file) => file.head === \"not-collected\").map((file) => file.file),\n deletedTests: [...plan.deletedTests],\n configEdited: plan.configEdited,\n tests: classification.tests.map((test) => ({ ...test })),\n truncated: false,\n };\n if (classification.error !== undefined) result.error = classification.error;\n return result;\n}\n\nfunction canonical(result: FixProofResult): FixProofResult {\n const out: FixProofResult = {\n head: result.head,\n mergeBase: result.mergeBase,\n verdict: result.verdict,\n counts: Object.fromEntries(TEST_CLASSES.map((name) => [name, result.counts[name]])) as Record<TestClass, number>,\n notCollected: [...result.notCollected],\n deletedTests: [...result.deletedTests],\n configEdited: result.configEdited,\n tests: result.tests.map((test) => ({ file: test.file, name: test.name, class: test.class })),\n truncated: result.truncated,\n };\n if (result.error !== undefined) out.error = result.error;\n return out;\n}\n\nfunction byteLength(text: string): number {\n return new TextEncoder().encode(text).length;\n}\n\nfunction encode(result: FixProofResult): string {\n return RESULT_PREFIX + JSON.stringify(canonical(result));\n}\n\nfunction shrink(result: FixProofResult): boolean {\n if (result.tests.length > 0) result.tests.pop();\n else if (result.deletedTests.length > 0) result.deletedTests.pop();\n else if (result.notCollected.length > 0) result.notCollected.pop();\n else if (result.error !== undefined && result.error.length > MAX_ERROR_CHARS) result.error = result.error.slice(0, MAX_ERROR_CHARS);\n else return false;\n result.truncated = true;\n return true;\n}\n\n/** Encodes one `fix-proof/v1 <json>` line of at most 4 KB, dropping list entries to fit; throws when the result would not parse back. */\nexport function formatResultLine(result: FixProofResult): string {\n const invalid = validate(result);\n if (invalid !== null) throw new Error(`invalid fix-proof result: ${invalid}`);\n const working = canonical(result);\n let line = encode(working);\n while (byteLength(line) > RESULT_LINE_MAX_BYTES) {\n if (!shrink(working)) throw new Error(\"fix-proof result does not fit in 4 KB\");\n line = encode(working);\n }\n return line;\n}\n\n/** Decodes a result line, refusing anything over 4 KB, of another version, with unknown fields or with duplicate or reordered keys. */\nexport function parseResultLine(line: string): ParsedResultLine {\n if (byteLength(line) > RESULT_LINE_MAX_BYTES) return { ok: false, error: \"result line exceeds 4 KB\" };\n if (!line.startsWith(RESULT_PREFIX)) return { ok: false, error: \"not a fix-proof/v1 line\" };\n const json = line.slice(RESULT_PREFIX.length);\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return { ok: false, error: \"result is not valid JSON\" };\n }\n const invalid = validate(parsed);\n if (invalid !== null) return { ok: false, error: invalid };\n const result = parsed as FixProofResult;\n if (JSON.stringify(canonical(result)) !== json) return { ok: false, error: \"result is not canonical: duplicate, reordered or padded keys\" };\n return { ok: true, result };\n}\n\ntype UnknownRecord = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction checkKeys(value: UnknownRecord, required: readonly string[], optional: readonly string[], at: string): string | null {\n const unknownKey = Object.keys(value).find((key) => !required.includes(key) && !optional.includes(key));\n if (unknownKey !== undefined) return `unknown field ${at}${unknownKey}`;\n const missing = required.find((key) => !(key in value));\n return missing === undefined ? null : `missing field ${at}${missing}`;\n}\n\nfunction isStringList(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\nfunction validateCounts(value: unknown): string | null {\n if (!isRecord(value)) return \"counts must be an object\";\n const keys = checkKeys(value, TEST_CLASSES, [], \"counts.\");\n if (keys !== null) return keys;\n const bad = TEST_CLASSES.find((name) => !Number.isSafeInteger(value[name]) || (value[name] as number) < 0);\n return bad === undefined ? null : `counts.${bad} must be a non-negative integer`;\n}\n\nfunction validateTest(value: unknown): string | null {\n if (!isRecord(value)) return \"tests entries must be objects\";\n const keys = checkKeys(value, [\"file\", \"name\", \"class\"], [], \"tests[].\");\n if (keys !== null) return keys;\n if (typeof value.file !== \"string\" || typeof value.name !== \"string\") return \"tests[].file and name must be strings\";\n return (TEST_CLASSES as readonly unknown[]).includes(value.class) ? null : \"tests[].class is unknown\";\n}\n\nconst RESULT_KEYS = [\"head\", \"mergeBase\", \"verdict\", \"counts\", \"notCollected\", \"deletedTests\", \"configEdited\", \"tests\", \"truncated\"];\n\nfunction validateScalars(value: UnknownRecord): string | null {\n if (typeof value.head !== \"string\" || !SHA.test(value.head)) return \"head must be a full lowercase sha\";\n if (typeof value.mergeBase !== \"string\" || !SHA.test(value.mergeBase)) return \"mergeBase must be a full lowercase sha\";\n if (!(VERDICTS as readonly unknown[]).includes(value.verdict)) return \"verdict is unknown\";\n if (typeof value.configEdited !== \"boolean\" || typeof value.truncated !== \"boolean\") return \"configEdited and truncated must be booleans\";\n if (value.error !== undefined && typeof value.error !== \"string\") return \"error must be a string\";\n if (!isStringList(value.notCollected) || !isStringList(value.deletedTests)) return \"notCollected and deletedTests must be string arrays\";\n return null;\n}\n\nfunction validate(value: unknown): string | null {\n if (!isRecord(value)) return \"result must be an object\";\n const problem = checkKeys(value, RESULT_KEYS, [\"error\"], \"\") ?? validateScalars(value) ?? validateCounts(value.counts);\n if (problem !== null) return problem;\n if (value.verdict === \"reproduced\" && (value.counts as UnknownRecord).reproduces === 0) return \"verdict reproduced with no reproducing test\";\n if (!Array.isArray(value.tests)) return \"tests must be an array\";\n return value.tests.map(validateTest).find((error) => error !== null) ?? null;\n}\n"],"mappings":"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","names":["isRecord"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@titan-design/fix-proof",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Proves a fix PR's new tests fail on the merge base and pass at head: diff plan, vitest report classification, fix-proof/v1 result line",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|