All files / src/components/tools parseDiff.ts

97.05% Statements 33/34
94.73% Branches 18/19
100% Functions 3/3
97.05% Lines 33/34

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                                                          5x 5x   5x 95x 10x         85x         10x   75x 5x 70x 40x 30x   30x 20x 10x   10x       5x                     36x 36x 36x   36x 78x   36x 123x     36x         41x 41x 41x 276x 163x   276x 78x     41x    
/**
 * 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) });
    I} 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 };
}