/** * VERIFIED grammar fixes — the propose → apply → recompile → compare loop. * * rustc can tell you a suggestion is machine-applicable. It cannot tell you the * suggestion is CORRECT: there is no cheap oracle for "this rewrite of your program * means the same thing". A parser generator has one. A grammar's whole observable * behaviour is the tree it produces for an input, so a rewrite can be applied, the * grammar recompiled, a corpus re-parsed, and the two outputs compared byte for byte. * Unchanged output is not a heuristic that the rewrite was safe; it is the evidence. * * propose → apply → recompile → compare parse output * unchanged → PROVEN over this corpus; offer it * changed → WRONG; discard, never show it * * TWO STATES, NEVER A THIRD * ------------------------- * Every candidate site ends as ACTIONABLE (here is the rewrite, here is the evidence, * here is the measured benefit) or LOCATED (here is the exact site and the exact reason * no rewrite can be offered). There is no "consider refactoring": a sentence of advice * with no location and no rewrite is the thing this module exists to replace. A rewrite * that moved the output is reported as LOCATED with the rejection as its reason — the * site is real, the rewrite is not shown, and the reader is never handed a wrong fix. * * WHAT "PROVEN" MEANS HERE, EXACTLY * --------------------------------- * Proven OVER THE SUPPLIED CORPUS, on every engine the grammar supports — both when it * compiles, the interpreted one alone when it does not — and that is stated in every * rendering rather than implied. A corpus that never reaches the rewritten arm proves * nothing about it, so the evidence carries the sample and byte counts and the caller * can judge. Two deliberate exclusions from the comparison: * * - FAILURE `expected` LABELS are compared as position only, not text. A keyword * rewrite changes `/\@media/` to `keyword` by design; that is diagnostic text, not * parse output. Values, spans, and WHERE a parse failed are compared exactly. * - The rebuild itself is not trusted, it is CHECKED: before any candidate is * considered, the grammar is rebuilt with NO substitution and that identity rebuild * must reproduce the corpus output exactly. If `rebuild.ts` threads any option * wrongly, this check fails and NO fix is offered. Wrong makes the tool silent. * * BENEFIT IS MEASURED TOO * ----------------------- * A verified-but-pointless rewrite is not offered either. Benefit is read off the same * analysis a CI gate reads — ungated choices, anti-patterns — plus the compiled artifact * size, which is deterministic and therefore safe to print. No timings: this must be * diffable. */ import type { Combinator } from '../types.ts'; import { type FrozenSubtree } from './rebuild.ts'; /** One corpus document. `name` is a label for the report; it is never a path lookup. */ export type FixSample = { name: string; text: string; }; /** Stable, greppable fix class. Mirrors the `anti-pattern` kinds `diagnoseGrammar` reports. */ export type FixCode = 'keyword-regex' | 'double-not'; /** The engines a comparison ran on. `compiled` is absent when the grammar does not compile. */ export type FixEngine = 'interpreted' | 'compiled'; export type FixEvidence = { samples: number; bytes: number; engines: FixEngine[]; /** Always true on a VERIFIED fix — the field exists so the JSON states the claim. */ outputUnchanged: true; }; export type FixBenefit = { ungatedChoicesBefore: number; ungatedChoicesAfter: number; antiPatternsBefore: number; antiPatternsAfter: number; gatedChoicesBefore: number; gatedChoicesAfter: number; /** Bytes of `compile().source` (the table artifact module). Deterministic. `null` when the grammar does not compile. */ artifactBytesBefore: number | null; artifactBytesAfter: number | null; }; /** A source edit, offered only when the site is UNAMBIGUOUS in the supplied text. */ export type FixEdit = { path: string; /** 1-based, for a diagnostic header. */ line: number; column: number; start: number; end: number; oldText: string; newText: string; /** The whole source line the edit sits on, so a renderer can draw a real code frame. */ lineText: string; }; export type VerifiedFix = { id: string; code: FixCode; rule: string; armIndex: number; /** How the site reads now, and how it would read. Rendering, not source text. */ before: string; after: string; /** The arm's deep first-set before and after — the number that makes the fix self-evident. */ armFirstSetBefore: string; armFirstSetAfter: string; /** The enclosing choice's gating verdict, before and after. */ choiceId: string; choiceGatesBefore: 'yes' | 'recoverable' | 'no'; choiceGatesAfter: 'yes' | 'recoverable' | 'no'; benefit: FixBenefit; evidence: FixEvidence; /** Present only when the site was located unambiguously in a supplied source file. */ edit?: FixEdit; }; export type LocatedFinding = { id: string; code: FixCode; rule: string; armIndex: number; /** How the site reads now. */ site: string; /** The exact reason no rewrite can be offered. Never advice. */ reason: string; }; export type FixReport = { schema: 'parseman.fix/1'; /** * True when every candidate site got a verdict — i.e. the corpus was usable and the * identity rebuild held. FALSE IS NOT "no fixes": it means the loop could not run, and * an empty `verified` list under it proves nothing. Fails closed. */ ok: boolean; /** Why `ok` is false, or `null`. */ blocked: string | null; corpus: { samples: number; bytes: number; }; engines: FixEngine[]; verified: VerifiedFix[]; located: LocatedFinding[]; /** Subtrees `rebuild.ts` reused verbatim; a candidate inside one cannot be applied. */ frozen: FrozenSubtree[]; }; export type ProposeFixOptions = { /** * The documents the before/after comparison runs over. REQUIRED and non-empty: with no * corpus there is no evidence, and this module does not offer unverified rewrites. */ corpus: readonly FixSample[]; /** Grammar source, to locate edits in. Without it, fixes carry no `edit`. */ source?: { path: string; text: string; }; /** Passed through to `analyzeGating` when measuring benefit. */ accept?: Iterable; entryName?: string; }; /** * Propose fixes, and offer only the ones proven output-neutral over `opts.corpus`. * * Never throws: a loop that cannot run reports `ok: false` with the reason, because a * thrown proposal and a grammar with nothing to fix must not look the same. */ export declare function proposeFixes(root: Combinator, opts: ProposeFixOptions): FixReport; /** * Apply verified edits to a source text, right-to-left so earlier offsets stay valid. * * Pure: it returns the new text and never touches the filesystem. Writing is the * caller's explicit second step, which is what keeps `--apply` from being something that * happens to you. */ export declare function applyFixEdits(sourceText: string, fixes: readonly VerifiedFix[]): { text: string; applied: number; }; //# sourceMappingURL=fix.d.ts.map