import type { Draft, Finding, Hunk, Snapshot } from "./types.js"; type CarriedDraft = { draft: Draft; orphaned: Finding[]; }; export function carryDraft( previous: Snapshot, next: Snapshot, draft: Draft, ): CarriedDraft { const previousByKey = uniqueHunks(previous.hunks); const nextByKey = uniqueHunks(next.hunks); const previousById = new Map(previous.hunks.map((hunk) => [hunk.id, hunk])); const mapped = new Map(); for (const [key, hunk] of previousByKey) { const target = nextByKey.get(key); if (hunk && target) mapped.set(hunk.id, target); } const reviewed = draft.reviewed.flatMap((id) => { const target = mapped.get(id); return target ? [target.id] : []; }); const orphaned: Finding[] = []; const findings = draft.findings.flatMap((finding) => { const previousHunk = previousById.get(finding.hunkId); const target = mapped.get(finding.hunkId); const mappedFinding = previousHunk && target ? mapFinding(previousHunk, target, finding) : undefined; if (!mappedFinding) { orphaned.push(finding); return []; } return [mappedFinding]; }); return { draft: { reviewed, findings, notes: draft.notes }, orphaned }; } function uniqueHunks(hunks: Hunk[]): Map { const unique = new Map(); for (const hunk of hunks) { const header = /^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@(.*)$/u.exec( hunk.header, ); const key = JSON.stringify([ hunk.path, hunk.oldPath, header?.[1] ?? hunk.header, hunk.lines.map((line) => [ line.kind, line.text, line.oldLine !== undefined, line.newLine !== undefined, ]), ]); unique.set(key, unique.has(key) ? null : hunk); } return unique; } function mapFinding( previous: Hunk, next: Hunk, finding: Finding, ): Finding | undefined { const from = sideLines(previous, finding.side); const to = sideLines(next, finding.side); const endLine = finding.endLine ?? finding.line; const start = from.indexOf(finding.line); const end = from.indexOf(endLine); if (start < 0 || end < start || end - start !== endLine - finding.line) return; const targetStart = to[start]; const targetEnd = to[end]; if (targetStart === undefined || targetEnd === undefined) return; if (targetEnd - targetStart !== endLine - finding.line) return; return { ...finding, hunkId: next.id, line: targetStart, ...(finding.endLine === undefined ? {} : { endLine: targetEnd }), }; } function sideLines(hunk: Hunk, side: Finding["side"]): number[] { return hunk.lines.flatMap((line) => { const number = side === "old" ? line.oldLine : line.newLine; return number === undefined ? [] : [number]; }); }