All files / src/components/tools parseDiff.ts

5.66% Statements 3/53
100% Branches 0/0
0% Functions 0/3
5.66% Lines 3/53

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100                                                        1x                                                                                   1x                               1x                          
/**
 * Lightweight diff parsing utilities for the FileEditCard.
 *
 * Handles two formats:
 * 1. Unified diff strings (from Copilot's `detailedContent` field)
 * 2. Old/new string pairs (from Claude Code's `Edit` tool args)
 */
 
/** A single line in a parsed diff. */
export interface DiffLine {
  /** Line classification. */
  type: "add" | "remove" | "context" | "header";
  /** Line content (without the leading +/- prefix). */
  content: string;
}
 
/** Addition/removal counts for a diff. */
export interface DiffStats {
  added: number;
  removed: number;
}
 
/**
 * Parses a unified diff string into typed lines.
 *
 * Recognizes `@@` hunk headers, `+`/`-` prefixed lines, and context lines.
 * Skips `---`/`+++` file header lines and `diff --git` preamble.
 */
export function parseUnifiedDiff(diff: string): DiffLine[] {
  const lines = diff.split("\n");
  const result: DiffLine[] = [];
 
  for (const line of lines) {
    if (line.startsWith("diff --git") || line.startsWith("index ")) {
      continue;
    }
    // Skip file header lines (--- a/file, +++ b/file) but not hunk content
    // lines that happen to start with "---" or "+++" (which appear as
    // "----..." or "++++..." in the diff and are handled by +/- rules below).
    if (
      (line.startsWith("--- ") || line.startsWith("+++ ")) &&
      !line.startsWith("---- ") &&
      !line.startsWith("++++ ")
    ) {
      continue;
    }
    if (line.startsWith("@@")) {
      result.push({ type: "header", content: line });
    } else if (line.startsWith("+")) {
      result.push({ type: "add", content: line.slice(1) });
    } else if (line.startsWith("-")) {
      result.push({ type: "remove", content: line.slice(1) });
    } else if (line.startsWith(" ")) {
      result.push({ type: "context", content: line.slice(1) });
    } else if (line === "") {
      // Empty lines in unified diffs are context lines
      result.push({ type: "context", content: "" });
    }
  }
 
  return result;
}
 
/**
 * Constructs a minimal diff from old and new strings.
 *
 * This is a simple line-by-line comparison — not a true LCS diff algorithm.
 * Shows all old lines as removals followed by all new lines as additions.
 * Good enough for the small, targeted edits agents typically make.
 */
export function diffFromOldNew(oldStr: string, newStr: string): DiffLine[] {
  const oldLines = oldStr.split("\n");
  const newLines = newStr.split("\n");
  const result: DiffLine[] = [];
 
  for (const line of oldLines) {
    result.push({ type: "remove", content: line });
  }
  for (const line of newLines) {
    result.push({ type: "add", content: line });
  }
 
  return result;
}
 
/** Counts additions and removals in a parsed diff. */
export function diffStats(lines: DiffLine[]): DiffStats {
  let added = 0;
  let removed = 0;
  for (const line of lines) {
    if (line.type === "add") {
      added++;
    }
    if (line.type === "remove") {
      removed++;
    }
  }
  return { added, removed };
}