Skip to content

Commit 323e8bb

Browse files
Fix parse patch bug (kpdecker#529)
* Fix patch parser, making most tests pass (but why do I have a weird new failure?...) * Fix bug I introduced, caught by test failure * Fix bugs with my tests * Remove mention of leadingGarbage (introduced by cherry-picking changes from kpdecker#522) * Improve test coverage * Add release notes
1 parent 5ecab06 commit 323e8bb

File tree

3 files changed

+35
-19
lines changed

3 files changed

+35
-19
lines changed

release-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
- [#489](github.com/kpdecker/jsdiff/pull/489) **`this.options` no longer exists on `Diff` objects.** Instead, `options` is now passed as an argument to methods that rely on options, like `equals(left, right, options)`. This fixes a race condition in async mode, where diffing behaviour could be changed mid-execution if a concurrent usage of the same `Diff` instances overwrote its `options`.
2727
- [#518](https://github.com/kpdecker/jsdiff/pull/518) **`linedelimiters` no longer exists** on patch objects; instead, when a patch with Windows-style CRLF line endings is parsed, **the lines in `lines` will end with `\r`**. There is now a **new `autoConvertLineEndings` option, on by default**, which makes it so that when a patch with Windows-style line endings is applied to a source file with Unix style line endings, the patch gets autoconverted to use Unix-style line endings, and when a patch with Unix-style line endings is applied to a source file with Windows-style line endings, it gets autoconverted to use Windows-style line endings.
2828
- [#521](https://github.com/kpdecker/jsdiff/pull/521) **the `callback` option is now supported by `structuredPatch`, `createPatch`, and `createTwoFilesPatch`**
29+
- [#529](https://github.com/kpdecker/jsdiff/pull/522) **`parsePatch` can now parse patches where lines starting with `--` or `++` are deleted/inserted**; previously, there were edge cases where the parser would choke on valid patches or give wrong results.
2930

3031
## v5.2.0
3132

src/patch/parse.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,12 @@ export function parsePatch(uniDiff) {
9292

9393
let addCount = 0,
9494
removeCount = 0;
95-
for (; i < diffstr.length; i++) {
96-
// Lines starting with '---' could be mistaken for the "remove line" operation
97-
// But they could be the header for the next file. Therefore prune such cases out.
98-
if (diffstr[i].indexOf('--- ') === 0
99-
&& (i + 2 < diffstr.length)
100-
&& diffstr[i + 1].indexOf('+++ ') === 0
101-
&& diffstr[i + 2].indexOf('@@') === 0) {
102-
break;
103-
}
95+
for (
96+
;
97+
i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || diffstr[i]?.startsWith('\\'));
98+
i++
99+
) {
104100
let operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0];
105-
106101
if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
107102
hunk.lines.push(diffstr[i]);
108103

@@ -115,7 +110,7 @@ export function parsePatch(uniDiff) {
115110
removeCount++;
116111
}
117112
} else {
118-
break;
113+
throw new Error(`Hunk at line ${chunkHeaderIndex + 1} contained invalid line ${diffstr[i]}`);
119114
}
120115
}
121116

test/patch/parse.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -430,8 +430,8 @@ Index: test2
430430
// Regression test for https://github.com/kpdecker/jsdiff/issues/524
431431
// Not only are these considered valid by GNU patch, but jsdiff's own formatPatch method
432432
// emits patches like this, which jsdiff used to then be unable to parse!
433-
const patchStr = `--- foo 2024-06-14 22:16:31.444276792 +0100
434-
+++ bar 2024-06-14 22:17:14.910611219 +0100
433+
const patchStr = `--- foo\t2024-06-14 22:16:31.444276792 +0100
434+
+++ bar\t2024-06-14 22:17:14.910611219 +0100
435435
@@ -1,7 +1,7 @@
436436
first
437437
second
@@ -503,8 +503,8 @@ Index: test2
503503
// determine where a hunk or file ends in a unified diff patch without heeding those line
504504
// counts.
505505

506-
const patchStr = `--- foo 2024-06-14 21:57:04.341065736 +0100
507-
+++ bar 2024-06-14 22:00:57.988080321 +0100
506+
const patchStr = `--- foo\t2024-06-14 21:57:04.341065736 +0100
507+
+++ bar\t2024-06-14 22:00:57.988080321 +0100
508508
@@ -4 +4 @@
509509
--- bla
510510
+++ bla
@@ -513,15 +513,35 @@ Index: test2
513513
`;
514514

515515
expect(parsePatch(patchStr)).to.eql([{
516-
oldFileName: 'foo 2024-06-14 21:57:04.341065736 +0100',
517-
oldHeader: '',
518-
newFileName: 'bar 2024-06-14 22:00:57.988080321 +0100',
519-
newHeader: '',
516+
oldFileName: 'foo',
517+
oldHeader: '2024-06-14 21:57:04.341065736 +0100',
518+
newFileName: 'bar',
519+
newHeader: '2024-06-14 22:00:57.988080321 +0100',
520520
hunks: [
521521
{ oldStart: 4, oldLines: 1, newStart: 4, newLines: 1, lines: ['--- bla', '+++ bla'] },
522522
{ oldStart: 7, oldLines: 0, newStart: 7, newLines: 1, lines: ['+seventh'] }
523523
]
524524
}]);
525525
});
526+
527+
it('should emit an error if a hunk contains an invalid line', () => {
528+
// Within a hunk, every line must either start with '+' (insertion), '-' (deletion),
529+
// ' ' (context line, i.e. not deleted nor inserted) or a backslash (for
530+
// '\\ No newline at end of file' lines). Seeing anything else before the end of the hunk is
531+
// an error.
532+
533+
const patchStr = `Index: test
534+
===================================================================
535+
--- from\theader1
536+
+++ to\theader2
537+
@@ -1,3 +1,4 @@
538+
line2
539+
line3
540+
+line4
541+
line5`;
542+
543+
// eslint-disable-next-line dot-notation
544+
expect(() => {parsePatch(patchStr);}).to.throw('Hunk at line 5 contained invalid line line3');
545+
});
526546
});
527547
});

0 commit comments

Comments
 (0)