tools/core/comparators/snapshot.comparator.ts
tools/core/comparators/snapshot.comparator.ts is a file in Coordination Surface. 91 lines of code and 23 definitions.
import { ANCHOR_KINDS, markedElsewhere, measured, sameKinds } from "../analyzers/snapshot.analyzer.ts";
import type { Comparison, Extent, Judgment, Measured, Retained, Verdict } from "../types/snapshot.types.ts";
import { frozenFinding, shortenedFinding } from "../factories/snapshot.factory.ts";
import type { Finding } from "../types/segment.types.ts";
import type { StepOptions } from "../types/rule.types.ts";
import { retainedFrom } from "../readers/snapshot.reader.ts";
const judgment = function judgment(path: string, verdict: Verdict, finding: Finding | null = null): Judgment {
return { finding, memberless: false, path, renamed: null, verdict };
};
const judgeAbsent = function judgeAbsent(path: string, prior: Extent, retained: Retained, extents: Measured): Judgment {
if (prior.anchors.length === 0) {
const carried = markedElsewhere(prior.mark, retained.surfaces, extents.current);
return carried === null
? { ...judgment(path, "not-comparable"), memberless: true }
: { ...judgment(path, "relocated"), renamed: `${path} → ${carried}` };
}
const stranded = prior.anchors.filter((anchor) => !extents.frozenAnchors.has(anchor));
return stranded.length === 0
? judgment(path, "relocated")
: judgment(path, "shortened", shortenedFinding(path, stranded, prior.lifetime));
};
const judgePresent = function judgePresent(path: string, prior: Extent, now: Extent, frozen: boolean): Judgment {
if (now.lifetime !== prior.lifetime) {
return judgment(path, "not-comparable");
}
const held = new Set(now.anchors);
const missing = prior.anchors.filter((anchor) => !held.has(anchor));
if (frozen) {
const priorHeld = new Set(prior.anchors);
const difference = [...missing, ...now.anchors.filter((anchor) => !priorHeld.has(anchor))];
return difference.length === 0
? judgment(path, "unchanged")
: judgment(path, "shortened", frozenFinding(path, difference, prior.lifetime));
}
return missing.length === 0
? judgment(path, "unchanged")
: judgment(path, "shortened", shortenedFinding(path, missing, prior.lifetime));
};
const compared = function compared(retained: Retained, extents: Measured): Judgment[] {
return Object.entries(retained.surfaces).map(([path, prior]) => {
const now = extents.current.get(path);
return now === undefined
? judgeAbsent(path, prior, retained, extents)
: judgePresent(path, prior, now, extents.frozen.has(path));
});
};
const verdictsOf = function verdictsOf(
judged: readonly Judgment[],
current: ReadonlyMap<string, Extent>,
unseen: Verdict,
): Record<string, Verdict> {
const verdicts: Record<string, Verdict> = Object.fromEntries(judged.map((entry) => [entry.path, entry.verdict]));
for (const path of current.keys()) {
verdicts[path] ??= unseen;
}
return verdicts;
};
const baselineOf = function baselineOf(
current: ReadonlyMap<string, Extent>,
retained: Retained | null,
verdicts: Readonly<Record<string, Verdict>>,
): Record<string, Extent> {
const surfaces: Record<string, Extent> = Object.fromEntries(current);
for (const [path, prior] of Object.entries(retained?.surfaces ?? {})) {
if (verdicts[path] === "shortened") {
surfaces[path] = prior;
}
}
return surfaces;
};
export const comparison = function comparison(options: StepOptions, paths: readonly string[]): Comparison {
const extents = measured(options.repoRoot, paths);
const { current } = extents;
const retained = retainedFrom(options.repoRoot);
const comparable = retained !== null && retained.range === options.scope && sameKinds(retained.anchorKinds);
const judged = comparable ? compared(retained, extents) : [];
const unseen: Verdict = comparable || retained === null ? "first-seen" : "not-comparable";
const verdicts = verdictsOf(judged, current, unseen);
const surfaces = baselineOf(current, retained, verdicts);
const fresh = options.authoritative || retained === null;
return {
carried: fresh ? { anchorKinds: ANCHOR_KINDS, range: options.scope, surfaces } : retained,
current,
findings: judged.flatMap((entry) => (entry.finding === null ? [] : [entry.finding])),
memberless: judged.filter((entry) => entry.memberless).map((entry) => entry.path),
renamed: judged.flatMap((entry) => (entry.renamed === null ? [] : [entry.renamed])),
retained,
surfaces,
verdicts,
};
};