Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/strata/draft.ts

Raw
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<string, Hunk>();
	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<string, Hunk | null> {
	const unique = new Map<string, Hunk | null>();
	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];
	});
}