export interface DiffPosition { path: string; line: number; side: "LEFT" | "RIGHT"; } export interface ParsedDiff { positions: Map; } function key(path: string, line: number, side: "LEFT" | "RIGHT"): string { return `${side}:${path}:${line}`; } function cleanPath(raw: string): string { const withoutTimestamp = raw.split("\t", 1)[0]; if (withoutTimestamp === "/dev/null") return withoutTimestamp; return withoutTimestamp.replace(/^[ab]\//, ""); } export function parseDiff(diff: string): ParsedDiff { const positions = new Map(); let oldPath = ""; let newPath = ""; let oldLine = 0; let newLine = 0; let inHunk = false; for (const line of diff.split("\n")) { if (line.startsWith("diff --git ")) { inHunk = false; const match = /^diff --git a\/(.+) b\/(.+)$/.exec(line); if (match) { oldPath = match[1]; newPath = match[2]; } continue; } if (line.startsWith("--- ")) { oldPath = cleanPath(line.slice(4)); continue; } if (line.startsWith("+++ ")) { newPath = cleanPath(line.slice(4)); continue; } const hunk = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line); if (hunk) { oldLine = Number(hunk[1]); newLine = Number(hunk[2]); inHunk = true; continue; } if (!inHunk || line.startsWith("\\ No newline")) continue; if (line.startsWith("-") && oldPath !== "/dev/null") { positions.set(key(oldPath, oldLine, "LEFT"), { path: oldPath, line: oldLine, side: "LEFT", }); oldLine++; continue; } if (line.startsWith("+") && newPath !== "/dev/null") { positions.set(key(newPath, newLine, "RIGHT"), { path: newPath, line: newLine, side: "RIGHT", }); newLine++; continue; } oldLine++; newLine++; } return { positions }; } export function isChangedPosition( parsed: ParsedDiff, path: string, line: number, side: "LEFT" | "RIGHT", ): boolean { return parsed.positions.has(key(path, line, side)); }