From a907b08890adc91e914510bff4e647d18fd71a6f Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Wed, 27 May 2026 14:16:49 +0100 Subject: [PATCH] Emit a better error message when a patch is truncated --- src/patch/parse.ts | 17 +++++++++++++++-- test/patch/parse.js | 20 ++++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/patch/parse.ts b/src/patch/parse.ts index d894f302..d48f2d9a 100755 --- a/src/patch/parse.ts +++ b/src/patch/parse.ts @@ -16,6 +16,15 @@ import type { StructuredPatch } from '../types.js'; export function parsePatch(uniDiff: string): StructuredPatch[] { const diffstr = uniDiff.split(/\n/), list: Partial[] = []; + + // If the final line of the patch is missing the trailing newline character + // that should be present, we forgive that, but if the final character of + // the patch is a newline as expected, we don't consider there to be a + // further blank line afterwards. We remove that blank line here: + if (diffstr[diffstr.length - 1] == '') { + diffstr.pop(); + } + let i = 0; // These helper functions identify line types that can appear between files @@ -494,10 +503,14 @@ export function parsePatch(uniDiff: string): StructuredPatch[] { // Perform sanity checking if (addCount !== hunk.newLines) { - throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); + throw new Error( + `New line count did not match for hunk at line ${chunkHeaderIndex + 1}; expected ${hunk.newLines} but got ${addCount}` + ); } if (removeCount !== hunk.oldLines) { - throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); + throw new Error( + `Old line count did not match for hunk at line ${chunkHeaderIndex + 1}; expected ${hunk.oldLines} but got ${removeCount}` + ); } // Check for extra hunk-body-like lines after the declared line counts diff --git a/test/patch/parse.js b/test/patch/parse.js index 3fa88985..cf5de989 100644 --- a/test/patch/parse.js +++ b/test/patch/parse.js @@ -666,10 +666,10 @@ more garbage expect(function() { parsePatch('@@ -1 +1,4 @@'); - }).to['throw']('Added line count did not match for hunk at line 1'); + }).to['throw']('New line count did not match for hunk at line 1; expected 4 but got 0'); expect(function() { parsePatch('@@ -1,4 +1 @@'); - }).to['throw']('Removed line count did not match for hunk at line 1'); + }).to['throw']('Old line count did not match for hunk at line 1; expected 4 but got 0'); }); @@ -939,6 +939,22 @@ line3 expect(() => {parsePatch(patchStr);}).to.throw('Hunk at line 5 contained invalid line line3'); }); + it('should emit a useful error message if the final hunk of the patch file ends prematurely', () => { + const patchStr = `Index: test +=================================================================== +--- from\theader1 ++++ to\theader2 +@@ -1,4 +1,5 @@ + line2 + line3 ++line4 + line5`; + // eslint-disable-next-line dot-notation + expect(() => { parsePatch(patchStr); }).to.throw( + 'New line count did not match for hunk at line 5; expected 5 but got 4' + ); + }); + it('should parse a single-file `diff --git` patch', function() { expect(parsePatch( `diff --git a/file.txt b/file.txt