Skip to content

Commit e9cd4e0

Browse files
authored
Merge pull request #61 from rars/use-strict-tsconfig
refactor(diff-match-patch-ts): use strict tsconfig settings
2 parents 7b9d872 + f948143 commit e9cd4e0

8 files changed

Lines changed: 640 additions & 755 deletions

.github/workflows/node.js.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212

1313
strategy:
1414
matrix:
15-
node-version: [20.x, 22.x]
15+
node-version: [20.x, 22.x, 24.x]
1616

1717
steps:
1818
- uses: actions/checkout@v3

package-lock.json

Lines changed: 349 additions & 557 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "diff-match-patch-ts",
3-
"version": "0.7.0",
3+
"version": "0.8.0",
44
"description": "Port of diff-match-patch to TypeScript.",
55
"exports": {
66
"import": {

src/diff-match-patch.class.ts

Lines changed: 70 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import { DiffOp } from './diff-op.enum';
2929
import { Diff } from './diff.type';
3030
import { PatchOperation } from './patch-operation.class';
3131

32+
type HalfMatchResult = [string, string, string, string, string];
33+
3234
/**
3335
* Class containing the diff, match and patch methods.
3436
* @constructor
@@ -64,10 +66,10 @@ export class DiffMatchPatch {
6466
*/
6567

6668
// Define some regex patterns for matching boundaries.
67-
private whitespaceRegex = new RegExp('/\\s/');
68-
private linebreakRegex = new RegExp('/[\\r\\n]/');
69-
private blanklineEndRegex = new RegExp('/\\n\\r?\\n$/');
70-
private blanklineStartRegex = new RegExp('/^\\r?\\n\\r?\\n/');
69+
private readonly whitespaceRegex = /\s/;
70+
private readonly linebreakRegex = /\r\n/;
71+
private readonly blanklineEndRegex = /\n\r?\n$/;
72+
private readonly blanklineStartRegex = /^\r?\n\r?\n/;
7173

7274
/**
7375
* Find the differences between two texts. Simplifies the problem by stripping
@@ -512,7 +514,7 @@ export class DiffMatchPatch {
512514
return []; // Get rid of the null case.
513515
}
514516
const patches = [];
515-
let patch = new PatchOperation();
517+
let patch = new PatchOperation(0, 0);
516518
let patchDiffLength = 0; // Keeping our own length const is faster in JS.
517519
let char_count1 = 0; // Number of characters into the text1 string.
518520
let char_count2 = 0; // Number of characters into the text2 string.
@@ -562,7 +564,7 @@ export class DiffMatchPatch {
562564
if (patchDiffLength) {
563565
this.patch_addContext_(patch, prepatch_text);
564566
patches.push(patch);
565-
patch = new PatchOperation();
567+
patch = new PatchOperation(0, 0);
566568
patchDiffLength = 0;
567569
// Unlike Unidiff, our patch lists have a rolling context.
568570
// http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
@@ -687,21 +689,22 @@ export class DiffMatchPatch {
687689
for (const diff of patches[x].diffs) {
688690
if (diff[0] !== DiffOp.Equal) {
689691
index2 = this.diff_xIndex(diffs, index1);
690-
}
691-
if (diff[0] === DiffOp.Insert) {
692-
// Insertion
693-
text =
694-
text.substring(0, start_loc + index2) +
695-
diff[1] +
696-
text.substring(start_loc + index2);
697-
} else if (diff[0] === DiffOp.Delete) {
698-
// Deletion
699-
text =
700-
text.substring(0, start_loc + index2) +
701-
text.substring(
702-
start_loc +
703-
this.diff_xIndex(diffs, index1 + diff[1].length),
704-
);
692+
693+
if (diff[0] === DiffOp.Insert) {
694+
// Insertion
695+
text =
696+
text.substring(0, start_loc + index2) +
697+
diff[1] +
698+
text.substring(start_loc + index2);
699+
} else if (diff[0] === DiffOp.Delete) {
700+
// Deletion
701+
text =
702+
text.substring(0, start_loc + index2) +
703+
text.substring(
704+
start_loc +
705+
this.diff_xIndex(diffs, index1 + diff[1].length),
706+
);
707+
}
705708
}
706709
if (diff[0] !== DiffOp.Delete) {
707710
index1 += diff[1].length;
@@ -748,9 +751,8 @@ export class DiffMatchPatch {
748751
if (!m) {
749752
throw new Error('Invalid patch string: ' + text[textPointer]);
750753
}
751-
const patch = new PatchOperation();
754+
const patch = new PatchOperation(parseInt(m[1], 10), parseInt(m[3], 10));
752755
patches.push(patch);
753-
patch.start1 = parseInt(m[1], 10);
754756
if (m[2] === '') {
755757
patch.start1--;
756758
patch.length1 = 1;
@@ -761,7 +763,6 @@ export class DiffMatchPatch {
761763
patch.length1 = parseInt(m[2], 10);
762764
}
763765

764-
patch.start2 = parseInt(m[3], 10);
765766
if (m[4] === '') {
766767
patch.start2--;
767768
patch.length2 = 1;
@@ -775,7 +776,7 @@ export class DiffMatchPatch {
775776

776777
while (textPointer < text.length) {
777778
const sign = text[textPointer].charAt(0);
778-
let line: string;
779+
let line: string | undefined;
779780
try {
780781
line = decodeURI(text[textPointer].substring(1));
781782
} catch (_ex) {
@@ -1546,7 +1547,10 @@ export class DiffMatchPatch {
15461547
* text2 and the common middle. Or null if there was no match.
15471548
* @private
15481549
*/
1549-
private diff_halfMatch_(text1: string, text2: string): string[] {
1550+
private diff_halfMatch_(
1551+
text1: string,
1552+
text2: string,
1553+
): HalfMatchResult | null {
15501554
if (this.Diff_Timeout <= 0) {
15511555
// Don't risk returning a non-optimal diff if we have unlimited time.
15521556
return null;
@@ -1571,16 +1575,20 @@ export class DiffMatchPatch {
15711575
Math.ceil(longtext.length / 2),
15721576
this,
15731577
);
1574-
let hm;
1575-
if (!hm1 && !hm2) {
1578+
let hm: HalfMatchResult | undefined;
1579+
if (hm1 === null && hm2 === null) {
15761580
return null;
1577-
} else if (!hm2) {
1578-
hm = hm1;
1579-
} else if (!hm1) {
1580-
hm = hm2;
1581-
} else {
1581+
} else if (hm1 !== null && hm2 !== null) {
15821582
// Both matched. Select the longest.
15831583
hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
1584+
} else if (hm1 !== null) {
1585+
hm = hm1;
1586+
} else if (hm2 !== null) {
1587+
hm = hm2;
1588+
}
1589+
1590+
if (hm === undefined) {
1591+
throw new Error('hm expected to be defined but was undefined');
15841592
}
15851593

15861594
// A half-match was found, sort out the return data.
@@ -1620,15 +1628,15 @@ export class DiffMatchPatch {
16201628
shorttext: string,
16211629
i: number,
16221630
dmp: DiffMatchPatch,
1623-
): string[] {
1631+
): HalfMatchResult | null {
16241632
// Start with a 1/4 length substring at position i as a seed.
16251633
const seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
16261634
let j = -1;
16271635
let best_common = '';
1628-
let best_longtext_a;
1629-
let best_longtext_b;
1630-
let best_shorttext_a;
1631-
let best_shorttext_b;
1636+
let best_longtext_a = '';
1637+
let best_longtext_b = '';
1638+
let best_shorttext_a = '';
1639+
let best_shorttext_b = '';
16321640
// tslint:disable-next-line:no-conditional-assignment
16331641
while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
16341642
const prefixLength = dmp.diff_commonPrefix(
@@ -1657,9 +1665,9 @@ export class DiffMatchPatch {
16571665
best_shorttext_b,
16581666
best_common,
16591667
];
1660-
} else {
1661-
return null;
16621668
}
1669+
1670+
return null;
16631671
}
16641672

16651673
/**
@@ -1679,13 +1687,13 @@ export class DiffMatchPatch {
16791687
* @return {number} The score.
16801688
* @private
16811689
*/
1682-
function diff_cleanupSemanticScore_(one: string, two: string): number {
1690+
const diff_cleanupSemanticScore_ = (one: string, two: string): number => {
16831691
if (!one || !two) {
16841692
// Edges are the best.
16851693
return 6;
16861694
}
16871695

1688-
const nonAlphaNumericRegex_ = new RegExp('/[^a-zA-Z0-9]/');
1696+
const nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/;
16891697

16901698
// Each port of this function behaves slightly differently due to
16911699
// subtle differences in each language's definition of things like
@@ -1696,14 +1704,12 @@ export class DiffMatchPatch {
16961704
const char2 = two.charAt(0);
16971705
const nonAlphaNumeric1 = char1.match(nonAlphaNumericRegex_);
16981706
const nonAlphaNumeric2 = char2.match(nonAlphaNumericRegex_);
1699-
const whitespace1 =
1700-
nonAlphaNumeric1 && char1.match(this.whitespaceRegex_);
1701-
const whitespace2 =
1702-
nonAlphaNumeric2 && char2.match(this.whitespaceRegex_);
1703-
const lineBreak1 = whitespace1 && char1.match(this.linebreakRegex_);
1704-
const lineBreak2 = whitespace2 && char2.match(this.linebreakRegex_);
1705-
const blankLine1 = lineBreak1 && one.match(this.blanklineEndRegex_);
1706-
const blankLine2 = lineBreak2 && two.match(this.blanklineStartRegex_);
1707+
const whitespace1 = nonAlphaNumeric1 && char1.match(this.whitespaceRegex);
1708+
const whitespace2 = nonAlphaNumeric2 && char2.match(this.whitespaceRegex);
1709+
const lineBreak1 = whitespace1 && char1.match(this.linebreakRegex);
1710+
const lineBreak2 = whitespace2 && char2.match(this.linebreakRegex);
1711+
const blankLine1 = lineBreak1 && one.match(this.blanklineEndRegex);
1712+
const blankLine2 = lineBreak2 && two.match(this.blanklineStartRegex);
17071713

17081714
if (blankLine1 || blankLine2) {
17091715
// Five points for blank lines.
@@ -1722,7 +1728,7 @@ export class DiffMatchPatch {
17221728
return 1;
17231729
}
17241730
return 0;
1725-
}
1731+
};
17261732

17271733
let pointer = 1;
17281734
// Intentionally ignore the first and last element (don't need checking).
@@ -2038,6 +2044,12 @@ export class DiffMatchPatch {
20382044
// First pass: exact match.
20392045
rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
20402046
} else {
2047+
if (last_rd === undefined) {
2048+
throw new Error(
2049+
'last_rd should have been set by the previous loop but is undefined',
2050+
);
2051+
}
2052+
20412053
// Subsequent passes: fuzzy match.
20422054
rd[j] =
20432055
(((rd[j + 1] << 1) | 1) & charMatch) |
@@ -2146,22 +2158,7 @@ export class DiffMatchPatch {
21462158
* @return {!Array.<!diff_match_patch.PatchOperation>} Array of Patch objects.
21472159
*/
21482160
private patch_deepCopy(patches: PatchOperation[]): PatchOperation[] {
2149-
// Making deep copies is hard in JavaScript.
2150-
const patchesCopy = [];
2151-
for (let x = 0; x < patches.length; x++) {
2152-
const patch = patches[x];
2153-
const patchCopy = new PatchOperation();
2154-
patchCopy.diffs = [];
2155-
for (let y = 0; y < patch.diffs.length; y++) {
2156-
patchCopy.diffs[y] = [patch.diffs[y][0], patch.diffs[y][1]];
2157-
}
2158-
patchCopy.start1 = patch.start1;
2159-
patchCopy.start2 = patch.start2;
2160-
patchCopy.length1 = patch.length1;
2161-
patchCopy.length2 = patch.length2;
2162-
patchesCopy[x] = patchCopy;
2163-
}
2164-
return patchesCopy;
2161+
return patches.map((x) => x.clone());
21652162
}
21662163

21672164
/**
@@ -2237,15 +2234,15 @@ export class DiffMatchPatch {
22372234
const bigpatch = patches[x];
22382235
// Remove the big old patch.
22392236
patches.splice(x--, 1);
2240-
let start1 = bigpatch.start1;
2241-
let start2 = bigpatch.start2;
2237+
let { start1, start2 } = bigpatch;
22422238
let precontext = '';
22432239
while (bigpatch.diffs.length !== 0) {
22442240
// Create one of several smaller patches.
2245-
const patch = new PatchOperation();
2241+
const patch = new PatchOperation(
2242+
start1 - precontext.length,
2243+
start2 - precontext.length,
2244+
);
22462245
let empty = true;
2247-
patch.start1 = start1 - precontext.length;
2248-
patch.start2 = start2 - precontext.length;
22492246
if (precontext !== '') {
22502247
patch.length1 = patch.length2 = precontext.length;
22512248
patch.diffs.push([DiffOp.Equal, precontext]);
@@ -2260,7 +2257,7 @@ export class DiffMatchPatch {
22602257
// Insertions are harmless.
22612258
patch.length2 += diff_text.length;
22622259
start2 += diff_text.length;
2263-
patch.diffs.push(bigpatch.diffs.shift());
2260+
patch.diffs.push(bigpatch.diffs.shift()!);
22642261
empty = false;
22652262
} else if (
22662263
diff_type === DiffOp.Delete &&

src/patch-operation.class.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,36 +33,52 @@ import { Diff } from './diff.type';
3333
* @constructor
3434
*/
3535
export class PatchOperation {
36-
3736
public diffs: Diff[] = [];
38-
public start1: number = null;
39-
public start2: number = null;
4037
public length1: number = 0;
4138
public length2: number = 0;
4239

40+
public constructor(
41+
public start1: number,
42+
public start2: number,
43+
) {}
44+
45+
public clone(): PatchOperation {
46+
const patchCopy = new PatchOperation(this.start1, this.start2);
47+
48+
patchCopy.diffs = [];
49+
for (let i = 0; i < this.diffs.length; i++) {
50+
patchCopy.diffs[i] = [this.diffs[i][0], this.diffs[i][1]];
51+
}
52+
53+
patchCopy.length1 = this.length1;
54+
patchCopy.length2 = this.length2;
55+
56+
return patchCopy;
57+
}
58+
4359
/**
4460
* Emmulate GNU diff's format.
4561
* Header: @@ -382,8 +481,9 @@
4662
* Indicies are printed as 1-based, not 0-based.
4763
*/
4864
public toString(): string {
49-
let coords1;
50-
let coords2;
65+
let coords1: string | number;
66+
let coords2: string | number;
5167
if (this.length1 === 0) {
5268
coords1 = this.start1 + ',0';
5369
} else if (this.length1 === 1) {
5470
coords1 = this.start1 + 1;
5571
} else {
56-
coords1 = (this.start1 + 1) + ',' + this.length1;
72+
coords1 = `${this.start1 + 1},${this.length1}`;
5773
}
5874
if (this.length2 === 0) {
5975
coords2 = this.start2 + ',0';
6076
} else if (this.length2 === 1) {
6177
coords2 = this.start2 + 1;
6278
} else {
63-
coords2 = (this.start2 + 1) + ',' + this.length2;
79+
coords2 = `${this.start2 + 1},${this.length2}`;
6480
}
65-
const text = ['@@ -' + coords1 + ' +' + coords2 + ' @@\n'];
81+
const text = [`@@ -${coords1} +${coords2} @@\n`];
6682
let op;
6783
// Escape the body of the patch with %xx notation.
6884
for (let x = 0; x < this.diffs.length; x++) {

0 commit comments

Comments
 (0)