From e0e57221a34ebefb0e4e4f8ecd7de879b555e5cd Mon Sep 17 00:00:00 2001 From: Yurii Chukhlib Date: Sun, 9 Aug 2026 05:01:41 +0200 Subject: [PATCH 1/2] Fix malformed @@ header in formatHunkHeader fallback The fallback branch of formatHunkHeader emitted a malformed unified-diff @@ header for the edge-case Hunk shape. Produce a valid @@ -l,s +l,s @@ header matching the standard git format. Adds a regression test. --- src/core/hunkHeader.test.ts | 77 +++++++++++++++++++++++++++++++++++++ src/core/hunkHeader.ts | 5 ++- 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/core/hunkHeader.test.ts diff --git a/src/core/hunkHeader.test.ts b/src/core/hunkHeader.test.ts new file mode 100644 index 000000000..f9c29dcdd --- /dev/null +++ b/src/core/hunkHeader.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, test } from "bun:test"; +import type { Hunk } from "@pierre/diffs"; +import { formatHunkHeader } from "./hunkHeader"; + +/** + * Minimal synthesized Hunk that forces the fallback branch of + * `formatHunkHeader` (no parsed `hunkSpecs`). Only the numeric header fields + * the fallback reads are populated; the rest are stubbed to satisfy the type. + */ +function fallbackHunk(overrides: Partial = {}): Hunk { + return { + collapsedBefore: 0, + additionStart: 0, + additionCount: 0, + additionLines: 0, + additionLineIndex: 0, + deletionStart: 0, + deletionCount: 0, + deletionLines: 0, + deletionLineIndex: 0, + hunkContent: [], + splitLineStart: 0, + splitLineCount: 0, + unifiedLineStart: 0, + unifiedLineCount: 0, + ...overrides, + } as unknown as Hunk; +} + +describe("formatHunkHeader", () => { + test("fallback uses the per-side line count (context + changes) for the @@ ranges", () => { + // A context-bearing hunk where git renders @@ -10,4 +10,4 @@: + // old side = lines 10, 11 (context) + 12 (removed) + 13 (context) = 4 + // new side = lines 10, 11 (context) + 13 (context) + 14 (added) = 4 + // `*Count` is the header count (context + changes); `*Lines` is only the + // changed `+`/`-` lines (1 each here), so they diverge — exactly the shape + // that used to emit a malformed `@@ -10,1 +10,1 @@`. + const hunk = fallbackHunk({ + deletionStart: 10, + deletionCount: 4, + deletionLines: 1, + additionStart: 10, + additionCount: 4, + additionLines: 1, + }); + + const header = formatHunkHeader(hunk); + + expect(header).toBe("@@ -10,4 +10,4 @@"); + expect(header).toMatch(/^@@ -\d+,\d+ \+\d+,\d+ @@$/); + }); + + test("fallback renders a new-file (pure-addition) hunk with a 0,0 old range", () => { + const hunk = fallbackHunk({ + deletionStart: 0, + deletionCount: 0, + deletionLines: 0, + additionStart: 1, + additionCount: 3, + additionLines: 3, + }); + + expect(formatHunkHeader(hunk)).toBe("@@ -0,0 +1,3 @@"); + }); + + test("fallback appends hunkContext after the @@ markers when present", () => { + const hunk = fallbackHunk({ + deletionStart: 1, + deletionCount: 1, + additionStart: 1, + additionCount: 1, + hunkContext: "function name()", + }); + + expect(formatHunkHeader(hunk)).toBe("@@ -1,1 +1,1 @@ function name()"); + }); +}); diff --git a/src/core/hunkHeader.ts b/src/core/hunkHeader.ts index 2faf1358f..0c449d332 100644 --- a/src/core/hunkHeader.ts +++ b/src/core/hunkHeader.ts @@ -4,6 +4,9 @@ import type { Hunk } from "@pierre/diffs"; export function formatHunkHeader(hunk: Hunk) { const specs = hunk.hunkSpecs ?? - `@@ -${hunk.deletionStart},${hunk.deletionLines} +${hunk.additionStart},${hunk.additionLines} @@`; + // The header count is the per-side line total (context + changes), i.e. + // `*Count` parsed from `-X,count` / `+X,count` — not `*Lines`, which is + // only the changed `+`/`-` lines and would undercount a context-bearing hunk. + `@@ -${hunk.deletionStart},${hunk.deletionCount} +${hunk.additionStart},${hunk.additionCount} @@`; return hunk.hunkContext ? `${specs} ${hunk.hunkContext}` : specs; } From 9a57e593ede1bf004f1fe79e0bc29656cae963aa Mon Sep 17 00:00:00 2001 From: Yurii Chukhlib Date: Sun, 9 Aug 2026 14:22:02 +0200 Subject: [PATCH 2/2] chore: add changeset for hunk header fix --- .changeset/clean-hunk-header.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clean-hunk-header.md diff --git a/.changeset/clean-hunk-header.md b/.changeset/clean-hunk-header.md new file mode 100644 index 000000000..9a754f1bc --- /dev/null +++ b/.changeset/clean-hunk-header.md @@ -0,0 +1,5 @@ +--- +"hunkdiff": patch +--- + +Fix malformed `@@` hunk headers so each side's line range and count are emitted correctly.