diff --git a/.changeset/quiet-diffs-moved.md b/.changeset/quiet-diffs-moved.md new file mode 100644 index 00000000..6c25db07 --- /dev/null +++ b/.changeset/quiet-diffs-moved.md @@ -0,0 +1,5 @@ +--- +"hunkdiff": patch +--- + +Keep moved-line classification aligned when a diff hunk adds or removes a line whose content begins with `++` or `--`. diff --git a/docs/screenshots/moved-lines-after.png b/docs/screenshots/moved-lines-after.png new file mode 100644 index 00000000..870746ad Binary files /dev/null and b/docs/screenshots/moved-lines-after.png differ diff --git a/docs/screenshots/moved-lines-before.png b/docs/screenshots/moved-lines-before.png new file mode 100644 index 00000000..91c33809 Binary files /dev/null and b/docs/screenshots/moved-lines-before.png differ diff --git a/src/core/changeset/fromPatch.test.ts b/src/core/changeset/fromPatch.test.ts new file mode 100644 index 00000000..561d8905 --- /dev/null +++ b/src/core/changeset/fromPatch.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, test } from "bun:test"; +import { changesetFromPatch } from "./fromPatch"; + +/** + * A hunk body line is always exactly one sign char plus its content, so a content line + * that happens to start with `++` or `--` (e.g. `++i;`, `--flag`) reads `+++…`/`---…` + * in the patch. File headers never appear inside a hunk, so content lines decide their + * own index in the per-side line arrays. + */ +const ADDITION_PLUS_PREFIX_PATCH = [ + "diff --git a/add_plus.txt b/add_plus.txt", + "index 0000000..1111111 100644", + "--- a/add_plus.txt", + "+++ b/add_plus.txt", + "@@ -1,3 +1,4 @@", + " one", + "\u001b[1;35m-two\u001b[m", + "\u001b[32m+++ plus\u001b[m", + "\u001b[36m+three\u001b[m", + " four", + "", +].join("\n"); + +const DELETION_MINUS_PREFIX_PATCH = [ + "diff --git a/del_minus.txt b/del_minus.txt", + "index 0000000..2222222 100644", + "--- a/del_minus.txt", + "+++ b/del_minus.txt", + "@@ -1,3 +1,3 @@", + " one", + "\u001b[1;35m--- flag\u001b[m", + "\u001b[36m+two\u001b[m", + " three", + "", +].join("\n"); + +describe("collectLineMoveKinds", () => { + test("indexes an added content line that starts with ++', not its following lines' move kind", () => { + const changeset = changesetFromPatch(ADDITION_PLUS_PREFIX_PATCH, "t", "probe", null); + const file = changeset.files.find((entry) => entry.path === "add_plus.txt")!; + + expect(file.metadata.additionLines).toEqual(["one\n", "++ plus\n", "three\n", "four\n"]); + expect(file.lineMoveKinds?.additionLines).toEqual([undefined, undefined, "moved", undefined]); + }); + + test("indexes a deleted content line that starts with --', keeping its move kind", () => { + const changeset = changesetFromPatch(DELETION_MINUS_PREFIX_PATCH, "t", "probe", null); + const file = changeset.files.find((entry) => entry.path === "del_minus.txt")!; + + expect(file.metadata.deletionLines).toEqual(["one\n", "-- flag\n", "three\n"]); + expect(file.lineMoveKinds?.deletionLines).toEqual([undefined, "moved", undefined]); + }); +}); diff --git a/src/core/changeset/fromPatch.ts b/src/core/changeset/fromPatch.ts index b07a1c35..1870b621 100644 --- a/src/core/changeset/fromPatch.ts +++ b/src/core/changeset/fromPatch.ts @@ -96,13 +96,15 @@ function collectLineMoveKinds(patchText: string): DiffLineMoveKinds[] { continue; } - if (plainLine.startsWith("+") && !plainLine.startsWith("+++")) { + // Every hunk-body `+`/`-` line is content — a line whose content starts with `++` + // reads `+++…` just like a file header, but headers never appear inside a hunk. + if (plainLine.startsWith("+")) { activeMoveKinds.additionLines[additionLineIndex] = movedLineKindFromAnsi(rawLine, "addition"); additionLineIndex += 1; continue; } - if (plainLine.startsWith("-") && !plainLine.startsWith("---")) { + if (plainLine.startsWith("-")) { activeMoveKinds.deletionLines[deletionLineIndex] = movedLineKindFromAnsi(rawLine, "deletion"); deletionLineIndex += 1; continue;