From 3157696e5b80ab3a0c4c6f74cb8c50b5182f1d7c Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 28 Feb 2025 10:31:16 +0000 Subject: [PATCH 01/46] First steps towards TypeScript conversion --- package.json | 1 + src/convert/{dmp.js => dmp.ts} | 6 +++++- src/types.ts | 6 ++++++ yarn.lock | 5 +++++ 4 files changed, 17 insertions(+), 1 deletion(-) rename src/convert/{dmp.js => dmp.ts} (68%) create mode 100644 src/types.ts diff --git a/package.json b/package.json index 5142b6238..2cb411cde 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "nyc": "^17.1.0", "rollup": "^4.34.8", "rollup-plugin-babel": "^4.4.0", + "typescript": "^5.7.3", "uglify-js": "^3.19.3", "webpack": "^5.98.0", "webpack-dev-server": "^5.2.0" diff --git a/src/convert/dmp.js b/src/convert/dmp.ts similarity index 68% rename from src/convert/dmp.js rename to src/convert/dmp.ts index b411dc2de..a0563c72c 100644 --- a/src/convert/dmp.js +++ b/src/convert/dmp.ts @@ -1,5 +1,9 @@ +import {ChangeObject} from '../types'; + +type DmpOperation = 1 | 0 | -1; + // See: http://code.google.com/p/google-diff-match-patch/wiki/API -export function convertChangesToDMP(changes) { +export function convertChangesToDMP(changes: ChangeObject[]): [DmpOperation, ValueT][] { let ret = [], change, operation; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..2e31d63a1 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,6 @@ +export interface ChangeObject { + value: ValueT; + added: boolean; + removed: boolean; + count: number; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5bd00ab96..63ca7d483 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4851,6 +4851,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" + integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== + ua-parser-js@^0.7.30: version "0.7.40" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.40.tgz#c87d83b7bb25822ecfa6397a0da5903934ea1562" From 0e25a14104d689fc6cb157212625d4ba7359eff5 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 7 Mar 2025 10:27:15 +0000 Subject: [PATCH 02/46] Convert xml.js->xml.ts --- src/convert/{xml.js => xml.ts} | 6 ++++-- src/types.ts | 6 ------ 2 files changed, 4 insertions(+), 8 deletions(-) rename src/convert/{xml.js => xml.ts} (77%) delete mode 100644 src/types.ts diff --git a/src/convert/xml.js b/src/convert/xml.ts similarity index 77% rename from src/convert/xml.js rename to src/convert/xml.ts index 34aa46366..e8fe15527 100644 --- a/src/convert/xml.js +++ b/src/convert/xml.ts @@ -1,4 +1,6 @@ -export function convertChangesToXML(changes) { +import {ChangeObject} from '../types'; + +export function convertChangesToXML(changes: ChangeObject[]): string { let ret = []; for (let i = 0; i < changes.length; i++) { let change = changes[i]; @@ -19,7 +21,7 @@ export function convertChangesToXML(changes) { return ret.join(''); } -function escapeHTML(s) { +function escapeHTML(s: string): string { let n = s; n = n.replace(/&/g, '&'); n = n.replace(/ { - value: ValueT; - added: boolean; - removed: boolean; - count: number; -} \ No newline at end of file From 0262a2ec65d03865d431e9280489e18060473949 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 7 Mar 2025 12:51:28 +0000 Subject: [PATCH 03/46] Add compilation to build step --- .gitignore | 1 + package.json | 2 +- src/types.ts | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/types.ts diff --git a/.gitignore b/.gitignore index 21b6d99a4..b78c4327d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dist yarn-error.log .vscode .nyc_output +/src/**/*.js \ No newline at end of file diff --git a/package.json b/package.json index 2cb411cde..dd039a99b 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "scripts": { "clean": "rm -rf lib/ dist/ coverage/ .nyc_output/", "lint": "yarn eslint 'src/**/*.js' 'test/**/*.js'", - "build": "yarn lint && yarn run-babel && yarn run-rollup && yarn run-uglify", + "build": "yarn tsc src/**/*.ts --module es2015 --moduleResolution bundler && yarn run-babel && yarn run-rollup && yarn run-uglify", "test": "nyc yarn _test", "_test": "yarn build && cross-env NODE_ENV=test yarn run-mocha", "run-babel": "babel --out-dir lib --source-maps=inline src", diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..95cabfe64 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,6 @@ +export interface ChangeObject { + value: ValueT; + added: boolean; + removed: boolean; + count: number; +} From 5aaa6331c3914cda601f820269742588b66626f2 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Sun, 9 Mar 2025 15:43:21 +0000 Subject: [PATCH 04/46] Rewrite base.js in TypeScript (maybe shit, need to review own work once I'm done) --- README.md | 4 +- src/diff/{base.js => base.ts} | 233 ++++++++++++++++++++++------------ src/diff/json.js | 1 + 3 files changed, 158 insertions(+), 80 deletions(-) rename src/diff/{base.js => base.ts} (56%) diff --git a/README.md b/README.md index e14adb8a3..68bdb6b2c 100644 --- a/README.md +++ b/README.md @@ -194,8 +194,8 @@ For even more customisation of the diffing behavior, you can create a `new Diff. * `tokenize(value, options)`: used to convert each of `oldString` and `newString` (after they've gone through `castInput`) to an array of tokens. Defaults to returning `value.split('')` (returning an array of individual characters). * `removeEmpty(array)`: called on the arrays of tokens returned by `tokenize` and can be used to modify them. Defaults to stripping out falsey tokens, such as empty strings. `diffArrays` overrides this to simply return the `array`, which means that falsey values like empty strings can be handled like any other token by `diffArrays`. * `equals(left, right, options)`: called to determine if two tokens (one from the old string, one from the new string) should be considered equal. Defaults to comparing them with `===`. -* `join(tokens)`: gets called with an array of consecutive tokens that have either all been added, all been removed, or are all common. Needs to join them into a single value that can be used as the `value` property of the [change object](#change-objects) for these tokens. Defaults to simply returning `tokens.join('')`. -* `postProcess(changeObjects)`: gets called at the end of the algorithm with the [change objects](#change-objects) produced, and can do final cleanups on them. Defaults to simply returning `changeObjects` unchanged. +* `join(tokens)`: gets called with an array of consecutive tokens that have either all been added, all been removed, or are all common. Needs to join them into a single value that can be used as the `value` property of the [change object](#change-objects) for these tokens. Defaults to simply returning `tokens.join('')` (and therefore by default will error out if your tokens are not strings; differs that support non-string tokens like `diffArrays` should override it to be a no-op to fix this). +* `postProcess(changeObjects, options)`: gets called at the end of the algorithm with the [change objects](#change-objects) produced, and can do final cleanups on them. Defaults to simply returning `changeObjects` unchanged. ### Change Objects Many of the methods above return change objects. These objects consist of the following fields: diff --git a/src/diff/base.js b/src/diff/base.ts similarity index 56% rename from src/diff/base.js rename to src/diff/base.ts index d58b5797e..320222fb1 100644 --- a/src/diff/base.js +++ b/src/diff/base.ts @@ -1,15 +1,72 @@ -export default function Diff() {} +import {ChangeObject} from '../types'; -Diff.prototype = { - diff(oldString, newString, options = {}) { - let callback = options.callback; +export type DiffCallback = (result?: ChangeObject[]) => void; + +/** + * Note that this contains the union of ALL options accepted by any of the built-in diffing + * functions. The README notes which options are usable which functions. Using an option with a + * diffing function that doesn't support it might yield unreasonable results. + */ +export interface DiffOptions { + // Universal: + callback?: DiffCallback, + maxEditLength?: number, + timeout?: number, + oneChangePerToken?: boolean, + + // diffArrays only: + comparator?: (a: any, b: any) => boolean, + + // diffChars or diffWords only: + ignoreCase?: boolean, +} + +/** + * Like a ChangeObject, but with no value and an extra `previousComponent` property. + * A linked list of these (linked via `.previousComponent`) is used internally in the code below to + * keep track of the state of the diffing algorithm, but gets converted to an array of + * ChangeObjects before being returned to the caller. + */ +interface DraftChangeObject { + added: boolean; + removed: boolean; + count: number; + previousComponent: DraftChangeObject; +} + +interface Path { + oldPos: number; + lastComponent: DraftChangeObject +} + +export default class Diff< + TokenT, + ValueT extends Iterable = Iterable +> { + diff( + oldString: ValueT, + newString: ValueT, + options: DiffCallback | DiffOptions = {} + ): ChangeObject[] { + let callback; if (typeof options === 'function') { callback = options; options = {}; + } else { + callback = options.callback; } + // Allow subclasses to massage the input prior to running + oldString = this.castInput(oldString, options); + newString = this.castInput(newString, options); - let self = this; + const oldTokens = this.removeEmpty(this.tokenize(oldString, options)); + const newTokens = this.removeEmpty(this.tokenize(newString, options)); + + return this.diffWithOptionsObj(oldTokens, newTokens, options, callback); + } + private diffWithOptionsObj(oldTokens: TokenT[], newTokens: TokenT[], options: DiffOptions, callback: DiffCallback) { + let self = this; function done(value) { value = self.postProcess(value, options); if (callback) { @@ -20,14 +77,7 @@ Diff.prototype = { } } - // Allow subclasses to massage the input prior to running - oldString = this.castInput(oldString, options); - newString = this.castInput(newString, options); - - oldString = this.removeEmpty(this.tokenize(oldString, options)); - newString = this.removeEmpty(this.tokenize(newString, options)); - - let newLen = newString.length, oldLen = oldString.length; + let newLen = newTokens.length, oldLen = oldTokens.length; let editLength = 1; let maxEditLength = newLen + oldLen; if(options.maxEditLength != null) { @@ -39,10 +89,10 @@ Diff.prototype = { let bestPath = [{ oldPos: -1, lastComponent: undefined }]; // Seed editLength = 0, i.e. the content starts with the same values - let newPos = this.extractCommon(bestPath[0], newString, oldString, 0, options); + let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options); if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) { // Identity per the equality and tokenizer - return done(buildValues(self, bestPath[0].lastComponent, newString, oldString, self.useLongestToken)); + return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens)); } // Once we hit the right edge of the edit graph on some diagonal k, we can @@ -102,11 +152,11 @@ Diff.prototype = { basePath = self.addToPath(removePath, false, true, 1, options); } - newPos = self.extractCommon(basePath, newString, oldString, diagonalPath, options); + newPos = self.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options); if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) { // If we have hit the end of both strings, then we are done - return done(buildValues(self, basePath.lastComponent, newString, oldString, self.useLongestToken)); + return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)); } else { bestPath[diagonalPath] = basePath; if (basePath.oldPos + 1 >= oldLen) { @@ -145,9 +195,15 @@ Diff.prototype = { } } } - }, + } - addToPath(path, added, removed, oldPosInc, options) { + private addToPath( + path: Path, + added: boolean, + removed: boolean, + oldPosInc: number, + options: DiffOptions + ): Path { let last = path.lastComponent; if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) { return { @@ -160,15 +216,22 @@ Diff.prototype = { lastComponent: {count: 1, added: added, removed: removed, previousComponent: last } }; } - }, - extractCommon(basePath, newString, oldString, diagonalPath, options) { - let newLen = newString.length, - oldLen = oldString.length, + } + + private extractCommon( + basePath: Path, + newTokens: TokenT[], + oldTokens: TokenT[], + diagonalPath: number, + options: DiffOptions + ): number { + let newLen = newTokens.length, + oldLen = oldTokens.length, oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0; - while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldString[oldPos + 1], newString[newPos + 1], options)) { + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) { newPos++; oldPos++; commonCount++; @@ -183,17 +246,18 @@ Diff.prototype = { basePath.oldPos = oldPos; return newPos; - }, + } - equals(left, right, options) { + protected equals(left: TokenT, right: TokenT, options: DiffOptions): boolean { if (options.comparator) { return options.comparator(left, right); } else { return left === right - || (options.ignoreCase && left.toLowerCase() === right.toLowerCase()); + || (options.ignoreCase && (left as string).toLowerCase() === (right as string).toLowerCase()); } - }, - removeEmpty(array) { + } + + protected removeEmpty(array: TokenT[]): TokenT[] { let ret = []; for (let i = 0; i < array.length; i++) { if (array[i]) { @@ -201,64 +265,77 @@ Diff.prototype = { } } return ret; - }, - castInput(value) { + } + + protected castInput(value: ValueT, options: DiffOptions): ValueT { return value; - }, - tokenize(value) { + } + + protected tokenize(value: ValueT, options: DiffOptions): TokenT[] { return Array.from(value); - }, - join(chars) { - return chars.join(''); - }, - postProcess(changeObjects) { + } + + protected join(chars: TokenT[]): ValueT { + // Assumes ValueT is string, which is the case for most subclasses. + // When it's false, e.g. in diffArrays, this method needs to be overridden (e.g. with a no-op) + // Yes, the casts are verbose and ugly, because this pattern - of having the base class SORT OF + // assume tokens and values are strings, but not completely - is weird and janky. + return (chars as string[]).join('') as unknown as ValueT; + } + + protected postProcess(changeObjects: ChangeObject[], options: DiffOptions): ChangeObject[] { return changeObjects; } -}; -function buildValues(diff, lastComponent, newString, oldString, useLongestToken) { - // First we convert our linked list of components in reverse order to an - // array in the right order: - const components = []; - let nextComponent; - while (lastComponent) { - components.push(lastComponent); - nextComponent = lastComponent.previousComponent; - delete lastComponent.previousComponent; - lastComponent = nextComponent; + protected get useLongestToken() { + return false; } - components.reverse(); - - let componentPos = 0, - componentLen = components.length, - newPos = 0, - oldPos = 0; - - for (; componentPos < componentLen; componentPos++) { - let component = components[componentPos]; - if (!component.removed) { - if (!component.added && useLongestToken) { - let value = newString.slice(newPos, newPos + component.count); - value = value.map(function(value, i) { - let oldValue = oldString[oldPos + i]; - return oldValue.length > value.length ? oldValue : value; - }); - - component.value = diff.join(value); - } else { - component.value = diff.join(newString.slice(newPos, newPos + component.count)); - } - newPos += component.count; + + private buildValues(lastComponent, newTokens: TokenT[], oldTokens: TokenT[]) { + // First we convert our linked list of components in reverse order to an + // array in the right order: + const components = []; + let nextComponent; + while (lastComponent) { + components.push(lastComponent); + nextComponent = lastComponent.previousComponent; + delete lastComponent.previousComponent; + lastComponent = nextComponent; + } + components.reverse(); + + let componentPos = 0, + componentLen = components.length, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + let component = components[componentPos]; + if (!component.removed) { + if (!component.added && this.useLongestToken) { + let value = newTokens.slice(newPos, newPos + component.count); + value = value.map(function(value, i) { + let oldValue = oldTokens[oldPos + i]; + return (oldValue as string).length > (value as string).length ? oldValue : value; + }); + + component.value = this.join(value); + } else { + component.value = this.join(newTokens.slice(newPos, newPos + component.count)); + } + newPos += component.count; - // Common case - if (!component.added) { + // Common case + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count)); oldPos += component.count; } - } else { - component.value = diff.join(oldString.slice(oldPos, oldPos + component.count)); - oldPos += component.count; } + + return components; } +}; - return components; -} diff --git a/src/diff/json.js b/src/diff/json.js index 301d1a946..4c3772b66 100644 --- a/src/diff/json.js +++ b/src/diff/json.js @@ -4,6 +4,7 @@ import {lineDiff} from './line'; export const jsonDiff = new Diff(); // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: +// TODO: Override getter jsonDiff.useLongestToken = true; jsonDiff.tokenize = lineDiff.tokenize; From cfafa1089e7a82e44d1d767291740fcac80adb47 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 10:29:22 +0000 Subject: [PATCH 05/46] Rewrite json.js -> json.ts and get tests passing (I'm honestly shocked they passed) --- src/diff/base.ts | 8 +++++-- src/diff/{json.js => json.ts} | 39 +++++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 18 deletions(-) rename src/diff/{json.js => json.ts} (56%) diff --git a/src/diff/base.ts b/src/diff/base.ts index 320222fb1..08cecd401 100644 --- a/src/diff/base.ts +++ b/src/diff/base.ts @@ -19,6 +19,10 @@ export interface DiffOptions { // diffChars or diffWords only: ignoreCase?: boolean, + + // diffJson only: + undefinedReplacement?: any, + stringifyReplacer?: (k: string, v: any) => any, } /** @@ -66,7 +70,7 @@ export default class Diff< } private diffWithOptionsObj(oldTokens: TokenT[], newTokens: TokenT[], options: DiffOptions, callback: DiffCallback) { - let self = this; + const self = this; function done(value) { value = self.postProcess(value, options); if (callback) { @@ -156,7 +160,7 @@ export default class Diff< if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) { // If we have hit the end of both strings, then we are done - return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)); + return done(self.buildValues(basePath.lastComponent, newTokens, oldTokens)); } else { bestPath[diagonalPath] = basePath; if (basePath.oldPos + 1 >= oldLen) { diff --git a/src/diff/json.js b/src/diff/json.ts similarity index 56% rename from src/diff/json.js rename to src/diff/json.ts index 4c3772b66..cb08488ef 100644 --- a/src/diff/json.js +++ b/src/diff/json.ts @@ -1,27 +1,34 @@ -import Diff from './base'; +import Diff, {DiffOptions} from './base'; import {lineDiff} from './line'; -export const jsonDiff = new Diff(); -// Discriminate between two lines of pretty-printed, serialized JSON where one of them has a -// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: -// TODO: Override getter -jsonDiff.useLongestToken = true; +class JsonDiff extends Diff { + protected get useLongestToken() { + // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a + // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + return true; + } + + protected tokenize = lineDiff.tokenize + + protected castInput(value: string, options: DiffOptions) { + const {undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v} = options; + + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); + }; + + protected equals(left: string, right: string, options: DiffOptions) { + return super.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); + }; +} -jsonDiff.tokenize = lineDiff.tokenize; -jsonDiff.castInput = function(value, options) { - const {undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v} = options; +const jsonDiff = new JsonDiff(); - return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); -}; -jsonDiff.equals = function(left, right, options) { - return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); -}; -export function diffJson(oldObj, newObj, options) { return jsonDiff.diff(oldObj, newObj, options); } +export function diffJson(oldObj: any, newObj: any, options: DiffOptions) { return jsonDiff.diff(oldObj, newObj, options); } // This function handles the presence of circular references by bailing out when encountering an // object that is already on the "stack" of items being processed. Accepts an optional replacer -export function canonicalize(obj, stack, replacementStack, replacer, key) { +export function canonicalize(obj: any, stack: Array | null, replacementStack: Array | null, replacer: (string, any) => any, key?: string) { stack = stack || []; replacementStack = replacementStack || []; From 2ac1627aab4e5b81f7dadafb3cab8afeaf8ae179 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 11:31:58 +0000 Subject: [PATCH 06/46] array.js -> array.ts --- src/diff/array.js | 11 ----------- src/diff/array.ts | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 11 deletions(-) delete mode 100644 src/diff/array.js create mode 100644 src/diff/array.ts diff --git a/src/diff/array.js b/src/diff/array.js deleted file mode 100644 index 99de08fcb..000000000 --- a/src/diff/array.js +++ /dev/null @@ -1,11 +0,0 @@ -import Diff from './base'; - -export const arrayDiff = new Diff(); -arrayDiff.tokenize = function(value) { - return value.slice(); -}; -arrayDiff.join = arrayDiff.removeEmpty = function(value) { - return value; -}; - -export function diffArrays(oldArr, newArr, callback) { return arrayDiff.diff(oldArr, newArr, callback); } diff --git a/src/diff/array.ts b/src/diff/array.ts new file mode 100644 index 000000000..09f4539a4 --- /dev/null +++ b/src/diff/array.ts @@ -0,0 +1,25 @@ +import Diff, {DiffOptions} from './base'; + +class ArrayDiff extends Diff> { + protected tokenize(value: Array) { + return value.slice(); + } + + protected join(value: Array) { + return value; + } + + protected removeEmpty(value: Array) { + return value; + } +} + +export const arrayDiff = new ArrayDiff(); + +export function diffArrays( + oldArr: Array, + newArr: Array, + options: DiffOptions> +) { + return arrayDiff.diff(oldArr, newArr, options); +} From f4f78804984108aa3a9f03e8d2e7bea720675967 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 11:44:50 +0000 Subject: [PATCH 07/46] character.js -> character.ts --- src/diff/character.js | 4 ---- src/diff/character.ts | 9 +++++++++ 2 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 src/diff/character.js create mode 100644 src/diff/character.ts diff --git a/src/diff/character.js b/src/diff/character.js deleted file mode 100644 index e9f17b111..000000000 --- a/src/diff/character.js +++ /dev/null @@ -1,4 +0,0 @@ -import Diff from './base'; - -export const characterDiff = new Diff(); -export function diffChars(oldStr, newStr, options) { return characterDiff.diff(oldStr, newStr, options); } diff --git a/src/diff/character.ts b/src/diff/character.ts new file mode 100644 index 000000000..625ceba95 --- /dev/null +++ b/src/diff/character.ts @@ -0,0 +1,9 @@ +import Diff, { DiffOptions } from './base'; + +class CharacterDiff extends Diff {} + +export const characterDiff = new CharacterDiff(); + +export function diffChars(oldStr: string, newStr: string, options: DiffOptions) { + return characterDiff.diff(oldStr, newStr, options); +} \ No newline at end of file From 2317d9cd1225c6ea5976a9134d68460bbe95c8b0 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 11:55:44 +0000 Subject: [PATCH 08/46] css.js->css.ts --- src/diff/css.js | 8 -------- src/diff/css.ts | 13 +++++++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) delete mode 100644 src/diff/css.js create mode 100644 src/diff/css.ts diff --git a/src/diff/css.js b/src/diff/css.js deleted file mode 100644 index e2e445a60..000000000 --- a/src/diff/css.js +++ /dev/null @@ -1,8 +0,0 @@ -import Diff from './base'; - -export const cssDiff = new Diff(); -cssDiff.tokenize = function(value) { - return value.split(/([{}:;,]|\s+)/); -}; - -export function diffCss(oldStr, newStr, callback) { return cssDiff.diff(oldStr, newStr, callback); } diff --git a/src/diff/css.ts b/src/diff/css.ts new file mode 100644 index 000000000..6f07534a6 --- /dev/null +++ b/src/diff/css.ts @@ -0,0 +1,13 @@ +import Diff, { DiffOptions } from './base'; + +class CssDiff extends Diff { + protected tokenize(value: string) { + return value.split(/([{}:;,]|\s+)/); + } +} + +export const cssDiff = new CssDiff(); + +export function diffCss(oldStr: string, newStr: string, options: DiffOptions) { + return cssDiff.diff(oldStr, newStr, options); +} \ No newline at end of file From 4bd69ee69319500f1dff6af47a289b2d4ac935ae Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 11:57:12 +0000 Subject: [PATCH 09/46] sentence.js->sentence.ts --- src/diff/sentence.js | 9 --------- src/diff/sentence.ts | 13 +++++++++++++ 2 files changed, 13 insertions(+), 9 deletions(-) delete mode 100644 src/diff/sentence.js create mode 100644 src/diff/sentence.ts diff --git a/src/diff/sentence.js b/src/diff/sentence.js deleted file mode 100644 index fe699b6dd..000000000 --- a/src/diff/sentence.js +++ /dev/null @@ -1,9 +0,0 @@ -import Diff from './base'; - - -export const sentenceDiff = new Diff(); -sentenceDiff.tokenize = function(value) { - return value.split(/(?<=[.!?])(\s+|$)/); -}; - -export function diffSentences(oldStr, newStr, callback) { return sentenceDiff.diff(oldStr, newStr, callback); } diff --git a/src/diff/sentence.ts b/src/diff/sentence.ts new file mode 100644 index 000000000..1929a6b6a --- /dev/null +++ b/src/diff/sentence.ts @@ -0,0 +1,13 @@ +import Diff, { DiffOptions } from './base'; + +class SentenceDiff extends Diff { + protected tokenize(value: string) { + return value.split(/(?<=[.!?])(\s+|$)/); + } +} + +export const sentenceDiff = new SentenceDiff(); + +export function diffSentences(oldStr: string, newStr: string, options: DiffOptions) { + return sentenceDiff.diff(oldStr, newStr, options); +} \ No newline at end of file From 9b4fc4f6ecef4e0c44ceac3f0b4b8997996dc3cf Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 12:17:43 +0000 Subject: [PATCH 10/46] word.js -> word.ts --- package.json | 2 +- src/diff/base.ts | 3 + src/diff/{word.js => word.ts} | 181 ++++++++++++++++++---------------- 3 files changed, 98 insertions(+), 88 deletions(-) rename src/diff/{word.js => word.ts} (72%) diff --git a/package.json b/package.json index dd039a99b..dc60a82ef 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "scripts": { "clean": "rm -rf lib/ dist/ coverage/ .nyc_output/", "lint": "yarn eslint 'src/**/*.js' 'test/**/*.js'", - "build": "yarn tsc src/**/*.ts --module es2015 --moduleResolution bundler && yarn run-babel && yarn run-rollup && yarn run-uglify", + "build": "yarn tsc src/**/*.ts --module es2015 --moduleResolution bundler --target es2022 && yarn run-babel && yarn run-rollup && yarn run-uglify", "test": "nyc yarn _test", "_test": "yarn build && cross-env NODE_ENV=test yarn run-mocha", "run-babel": "babel --out-dir lib --source-maps=inline src", diff --git a/src/diff/base.ts b/src/diff/base.ts index 08cecd401..453b01266 100644 --- a/src/diff/base.ts +++ b/src/diff/base.ts @@ -23,6 +23,9 @@ export interface DiffOptions { // diffJson only: undefinedReplacement?: any, stringifyReplacer?: (k: string, v: any) => any, + + // diffWords only: + intlSegmenter?: Intl.Segmenter, } /** diff --git a/src/diff/word.js b/src/diff/word.ts similarity index 72% rename from src/diff/word.js rename to src/diff/word.ts index 3bd534ed4..66d9d3da1 100644 --- a/src/diff/word.js +++ b/src/diff/word.ts @@ -1,4 +1,4 @@ -import Diff from './base'; +import Diff, { DiffOptions } from './base'; import { longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap, leadingWs, trailingWs } from '../util/string'; // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode @@ -48,94 +48,98 @@ const extendedWordChars = 'a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2 // tokens. const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, 'ug'); -export const wordDiff = new Diff(); -wordDiff.equals = function(left, right, options) { - if (options.ignoreCase) { - left = left.toLowerCase(); - right = right.toLowerCase(); - } - - return left.trim() === right.trim(); -}; -wordDiff.tokenize = function(value, options = {}) { - let parts; - if (options.intlSegmenter) { - if (options.intlSegmenter.resolvedOptions().granularity != 'word') { - throw new Error('The segmenter passed must have a granularity of "word"'); +class WordDiff extends Diff { + protected equals(left: string, right: string, options: DiffOptions) { + if (options.ignoreCase) { + left = left.toLowerCase(); + right = right.toLowerCase(); } - parts = Array.from(options.intlSegmenter.segment(value), segment => segment.segment); - } else { - parts = value.match(tokenizeIncludingWhitespace) || []; + + return left.trim() === right.trim(); } - const tokens = []; - let prevPart = null; - parts.forEach(part => { - if ((/\s/).test(part)) { - if (prevPart == null) { - tokens.push(part); - } else { - tokens.push(tokens.pop() + part); - } - } else if ((/\s/).test(prevPart)) { - if (tokens[tokens.length - 1] == prevPart) { - tokens.push(tokens.pop() + part); - } else { - tokens.push(prevPart + part); + + protected tokenize(value: string, options: DiffOptions = {}) { + let parts; + if (options.intlSegmenter) { + if (options.intlSegmenter.resolvedOptions().granularity != 'word') { + throw new Error('The segmenter passed must have a granularity of "word"'); } + parts = Array.from(options.intlSegmenter.segment(value), segment => segment.segment); } else { - tokens.push(part); + parts = value.match(tokenizeIncludingWhitespace) || []; } + const tokens: string[] = []; + let prevPart = null; + parts.forEach(part => { + if ((/\s/).test(part)) { + if (prevPart == null) { + tokens.push(part); + } else { + tokens.push(tokens.pop() + part); + } + } else if (prevPart != null && (/\s/).test(prevPart)) { + if (tokens[tokens.length - 1] == prevPart) { + tokens.push(tokens.pop() + part); + } else { + tokens.push(prevPart + part); + } + } else { + tokens.push(part); + } - prevPart = part; - }); - return tokens; -}; - -wordDiff.join = function(tokens) { - // Tokens being joined here will always have appeared consecutively in the - // same text, so we can simply strip off the leading whitespace from all the - // tokens except the first (and except any whitespace-only tokens - but such - // a token will always be the first and only token anyway) and then join them - // and the whitespace around words and punctuation will end up correct. - return tokens.map((token, i) => { - if (i == 0) { - return token; - } else { - return token.replace((/^\s+/), ''); - } - }).join(''); -}; + prevPart = part; + }); + return tokens; + } -wordDiff.postProcess = function(changes, options) { - if (!changes || options.oneChangePerToken) { - return changes; + protected join(tokens) { + // Tokens being joined here will always have appeared consecutively in the + // same text, so we can simply strip off the leading whitespace from all the + // tokens except the first (and except any whitespace-only tokens - but such + // a token will always be the first and only token anyway) and then join them + // and the whitespace around words and punctuation will end up correct. + return tokens.map((token, i) => { + if (i == 0) { + return token; + } else { + return token.replace((/^\s+/), ''); + } + }).join(''); } - let lastKeep = null; - // Change objects representing any insertion or deletion since the last - // "keep" change object. There can be at most one of each. - let insertion = null; - let deletion = null; - changes.forEach(change => { - if (change.added) { - insertion = change; - } else if (change.removed) { - deletion = change; - } else { - if (insertion || deletion) { // May be false at start of text - dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change); + protected postProcess(changes, options) { + if (!changes || options.oneChangePerToken) { + return changes; + } + + let lastKeep = null; + // Change objects representing any insertion or deletion since the last + // "keep" change object. There can be at most one of each. + let insertion = null; + let deletion = null; + changes.forEach(change => { + if (change.added) { + insertion = change; + } else if (change.removed) { + deletion = change; + } else { + if (insertion || deletion) { // May be false at start of text + dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change); + } + lastKeep = change; + insertion = null; + deletion = null; } - lastKeep = change; - insertion = null; - deletion = null; + }); + if (insertion || deletion) { + dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null); } - }); - if (insertion || deletion) { - dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null); + return changes; } - return changes; -}; +} + +export const wordDiff = new WordDiff(); export function diffWords(oldStr, newStr, options) { // This option has never been documented and never will be (it's clearer to @@ -273,16 +277,19 @@ function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep } -export const wordWithSpaceDiff = new Diff(); -wordWithSpaceDiff.tokenize = function(value) { - // Slightly different to the tokenizeIncludingWhitespace regex used above in - // that this one treats each individual newline as a distinct tokens, rather - // than merging them into other surrounding whitespace. This was requested - // in https://github.com/kpdecker/jsdiff/issues/180 & - // https://github.com/kpdecker/jsdiff/issues/211 - const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, 'ug'); - return value.match(regex) || []; -}; +class WordsWithSpaceDiff extends Diff { + protected tokenize(value: string) { + // Slightly different to the tokenizeIncludingWhitespace regex used above in + // that this one treats each individual newline as a distinct tokens, rather + // than merging them into other surrounding whitespace. This was requested + // in https://github.com/kpdecker/jsdiff/issues/180 & + // https://github.com/kpdecker/jsdiff/issues/211 + const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, 'ug'); + return value.match(regex) || []; + } +} + +export const wordsWithSpaceDiff = new WordsWithSpaceDiff(); export function diffWordsWithSpace(oldStr, newStr, options) { - return wordWithSpaceDiff.diff(oldStr, newStr, options); + return wordsWithSpaceDiff.diff(oldStr, newStr, options); } From cb03e0aa1fcbfd1daf893445cb7f9a10bcf2f99e Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 12:49:59 +0000 Subject: [PATCH 11/46] line.js -> line.ts --- src/diff/base.ts | 6 ++++ src/diff/json.ts | 2 +- src/diff/line.js | 70 ------------------------------------------- src/diff/line.ts | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ src/diff/word.ts | 2 +- 5 files changed, 85 insertions(+), 72 deletions(-) delete mode 100644 src/diff/line.js create mode 100644 src/diff/line.ts diff --git a/src/diff/base.ts b/src/diff/base.ts index 453b01266..d5f08a9e8 100644 --- a/src/diff/base.ts +++ b/src/diff/base.ts @@ -26,6 +26,12 @@ export interface DiffOptions { // diffWords only: intlSegmenter?: Intl.Segmenter, + + // diffLines only: + stripTrailingCr?: boolean, + newlineIsToken?: boolean, + ignoreNewlineAtEof?: boolean, + ignoreWhitespace?: boolean, // TODO: This is SORT OF supported by diffWords. What to do? } /** diff --git a/src/diff/json.ts b/src/diff/json.ts index cb08488ef..7bddc67ce 100644 --- a/src/diff/json.ts +++ b/src/diff/json.ts @@ -17,7 +17,7 @@ class JsonDiff extends Diff { }; protected equals(left: string, right: string, options: DiffOptions) { - return super.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); + return super.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); }; } diff --git a/src/diff/line.js b/src/diff/line.js deleted file mode 100644 index 5d313013f..000000000 --- a/src/diff/line.js +++ /dev/null @@ -1,70 +0,0 @@ -import Diff from './base'; -import {generateOptions} from '../util/params'; - -export const lineDiff = new Diff(); -lineDiff.tokenize = function(value, options) { - if(options.stripTrailingCr) { - // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior - value = value.replace(/\r\n/g, '\n'); - } - - let retLines = [], - linesAndNewlines = value.split(/(\n|\r\n)/); - - // Ignore the final empty token that occurs if the string ends with a new line - if (!linesAndNewlines[linesAndNewlines.length - 1]) { - linesAndNewlines.pop(); - } - - // Merge the content and line separators into single tokens - for (let i = 0; i < linesAndNewlines.length; i++) { - let line = linesAndNewlines[i]; - - if (i % 2 && !options.newlineIsToken) { - retLines[retLines.length - 1] += line; - } else { - retLines.push(line); - } - } - - return retLines; -}; - -lineDiff.equals = function(left, right, options) { - // If we're ignoring whitespace, we need to normalise lines by stripping - // whitespace before checking equality. (This has an annoying interaction - // with newlineIsToken that requires special handling: if newlines get their - // own token, then we DON'T want to trim the *newline* tokens down to empty - // strings, since this would cause us to treat whitespace-only line content - // as equal to a separator between lines, which would be weird and - // inconsistent with the documented behavior of the options.) - if (options.ignoreWhitespace) { - if (!options.newlineIsToken || !left.includes('\n')) { - left = left.trim(); - } - if (!options.newlineIsToken || !right.includes('\n')) { - right = right.trim(); - } - } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) { - if (left.endsWith('\n')) { - left = left.slice(0, -1); - } - if (right.endsWith('\n')) { - right = right.slice(0, -1); - } - } - return Diff.prototype.equals.call(this, left, right, options); -}; - -export function diffLines(oldStr, newStr, callback) { return lineDiff.diff(oldStr, newStr, callback); } - -// Kept for backwards compatibility. This is a rather arbitrary wrapper method -// that just calls `diffLines` with `ignoreWhitespace: true`. It's confusing to -// have two ways to do exactly the same thing in the API, so we no longer -// document this one (library users should explicitly use `diffLines` with -// `ignoreWhitespace: true` instead) but we keep it around to maintain -// compatibility with code that used old versions. -export function diffTrimmedLines(oldStr, newStr, callback) { - let options = generateOptions(callback, {ignoreWhitespace: true}); - return lineDiff.diff(oldStr, newStr, options); -} diff --git a/src/diff/line.ts b/src/diff/line.ts new file mode 100644 index 000000000..44ca302c4 --- /dev/null +++ b/src/diff/line.ts @@ -0,0 +1,77 @@ +import Diff, { DiffOptions } from './base'; +import {generateOptions} from '../util/params'; + + +class LineDiff extends Diff { + // public so it can be copied by jsonDiff + public tokenize(value: string, options: DiffOptions) { + if(options.stripTrailingCr) { + // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior + value = value.replace(/\r\n/g, '\n'); + } + + let retLines = [], + linesAndNewlines = value.split(/(\n|\r\n)/); + + // Ignore the final empty token that occurs if the string ends with a new line + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + + // Merge the content and line separators into single tokens + for (let i = 0; i < linesAndNewlines.length; i++) { + let line = linesAndNewlines[i]; + + if (i % 2 && !options.newlineIsToken) { + retLines[retLines.length - 1] += line; + } else { + retLines.push(line); + } + } + + return retLines; + }; + + protected equals(left: string, right: string, options: DiffOptions) { + // If we're ignoring whitespace, we need to normalise lines by stripping + // whitespace before checking equality. (This has an annoying interaction + // with newlineIsToken that requires special handling: if newlines get their + // own token, then we DON'T want to trim the *newline* tokens down to empty + // strings, since this would cause us to treat whitespace-only line content + // as equal to a separator between lines, which would be weird and + // inconsistent with the documented behavior of the options.) + if (options.ignoreWhitespace) { + if (!options.newlineIsToken || !left.includes('\n')) { + left = left.trim(); + } + if (!options.newlineIsToken || !right.includes('\n')) { + right = right.trim(); + } + } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) { + if (left.endsWith('\n')) { + left = left.slice(0, -1); + } + if (right.endsWith('\n')) { + right = right.slice(0, -1); + } + } + return super.equals(left, right, options); + }; +} + +export const lineDiff = new LineDiff(); + +export function diffLines(oldStr: string, newStr: string, options: DiffOptions) { + return lineDiff.diff(oldStr, newStr, options); +} + +// Kept for backwards compatibility. This is a rather arbitrary wrapper method +// that just calls `diffLines` with `ignoreWhitespace: true`. It's confusing to +// have two ways to do exactly the same thing in the API, so we no longer +// document this one (library users should explicitly use `diffLines` with +// `ignoreWhitespace: true` instead) but we keep it around to maintain +// compatibility with code that used old versions. +export function diffTrimmedLines(oldStr, newStr, callback) { + let options = generateOptions(callback, {ignoreWhitespace: true}); + return lineDiff.diff(oldStr, newStr, options); +} diff --git a/src/diff/word.ts b/src/diff/word.ts index 66d9d3da1..5d9cef4f6 100644 --- a/src/diff/word.ts +++ b/src/diff/word.ts @@ -141,7 +141,7 @@ class WordDiff extends Diff { export const wordDiff = new WordDiff(); -export function diffWords(oldStr, newStr, options) { +export function diffWords(oldStr: string, newStr: string, options) { // This option has never been documented and never will be (it's clearer to // just call `diffWordsWithSpace` directly if you need that behavior), but // has existed in jsdiff for a long time, so we retain support for it here From 90cbe7b7d4284a9f67f697d5faf71969a1c4cfd9 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 12:55:18 +0000 Subject: [PATCH 12/46] index.js->index.ts --- package.json | 2 +- src/{index.js => index.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{index.js => index.ts} (100%) diff --git a/package.json b/package.json index dc60a82ef..cd5fb5f02 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "scripts": { "clean": "rm -rf lib/ dist/ coverage/ .nyc_output/", "lint": "yarn eslint 'src/**/*.js' 'test/**/*.js'", - "build": "yarn tsc src/**/*.ts --module es2015 --moduleResolution bundler --target es2022 && yarn run-babel && yarn run-rollup && yarn run-uglify", + "build": "yarn tsc src/*.ts src/**/*.ts --module es2015 --moduleResolution bundler --target es2022 && yarn run-babel && yarn run-rollup && yarn run-uglify", "test": "nyc yarn _test", "_test": "yarn build && cross-env NODE_ENV=test yarn run-mocha", "run-babel": "babel --out-dir lib --source-maps=inline src", diff --git a/src/index.js b/src/index.ts similarity index 100% rename from src/index.js rename to src/index.ts From 56cce5c2163a1755f72593f24d6779209594f9b4 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 16:04:31 +0000 Subject: [PATCH 13/46] Convert a couple more fiiles --- src/util/{array.js => array.ts} | 4 ++-- src/util/{distance-iterator.js => distance-iterator.ts} | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) rename src/util/{array.js => array.ts} (67%) rename src/util/{distance-iterator.js => distance-iterator.ts} (87%) diff --git a/src/util/array.js b/src/util/array.ts similarity index 67% rename from src/util/array.js rename to src/util/array.ts index 5de3cab9d..602714aab 100644 --- a/src/util/array.js +++ b/src/util/array.ts @@ -1,4 +1,4 @@ -export function arrayEqual(a, b) { +export function arrayEqual(a: any[], b: any[]): boolean { if (a.length !== b.length) { return false; } @@ -6,7 +6,7 @@ export function arrayEqual(a, b) { return arrayStartsWith(a, b); } -export function arrayStartsWith(array, start) { +export function arrayStartsWith(array: any[], start: any[]): boolean { if (start.length > array.length) { return false; } diff --git a/src/util/distance-iterator.js b/src/util/distance-iterator.ts similarity index 87% rename from src/util/distance-iterator.js rename to src/util/distance-iterator.ts index 1ffe6413d..e2fe316cc 100644 --- a/src/util/distance-iterator.js +++ b/src/util/distance-iterator.ts @@ -1,13 +1,13 @@ // Iterator that traverses in the range of [min, max], stepping // by distance from a given start position. I.e. for [0, 4], with // start of 2, this will iterate 2, 3, 1, 4, 0. -export default function(start, minLine, maxLine) { +export default function(start: number, minLine: number, maxLine: number): () => number | undefined { let wantForward = true, backwardExhausted = false, forwardExhausted = false, localOffset = 1; - return function iterator() { + return function iterator(): number | undefined { if (wantForward && !forwardExhausted) { if (backwardExhausted) { localOffset++; @@ -41,5 +41,6 @@ export default function(start, minLine, maxLine) { // We tried to fit hunk before text beginning and beyond text length, then // hunk can't fit on the text. Return undefined + return undefined; }; } From 143706bd8e0cb2f548b612a7f5e48392b69ae37a Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 16:14:35 +0000 Subject: [PATCH 14/46] Convert params.ts --- src/diff/array.ts | 3 ++- src/diff/base.ts | 36 +------------------------------ src/diff/character.ts | 3 ++- src/diff/css.ts | 3 ++- src/diff/json.ts | 3 ++- src/diff/line.ts | 5 +++-- src/diff/sentence.ts | 3 ++- src/diff/word.ts | 3 ++- src/types.ts | 34 +++++++++++++++++++++++++++++ src/util/{params.js => params.ts} | 4 +++- 10 files changed, 53 insertions(+), 44 deletions(-) rename src/util/{params.js => params.ts} (60%) diff --git a/src/diff/array.ts b/src/diff/array.ts index 09f4539a4..1ad07e3ae 100644 --- a/src/diff/array.ts +++ b/src/diff/array.ts @@ -1,4 +1,5 @@ -import Diff, {DiffOptions} from './base'; +import Diff from './base' +import {DiffOptions} from '../types'; class ArrayDiff extends Diff> { protected tokenize(value: Array) { diff --git a/src/diff/base.ts b/src/diff/base.ts index d5f08a9e8..4d426fb53 100644 --- a/src/diff/base.ts +++ b/src/diff/base.ts @@ -1,38 +1,4 @@ -import {ChangeObject} from '../types'; - -export type DiffCallback = (result?: ChangeObject[]) => void; - -/** - * Note that this contains the union of ALL options accepted by any of the built-in diffing - * functions. The README notes which options are usable which functions. Using an option with a - * diffing function that doesn't support it might yield unreasonable results. - */ -export interface DiffOptions { - // Universal: - callback?: DiffCallback, - maxEditLength?: number, - timeout?: number, - oneChangePerToken?: boolean, - - // diffArrays only: - comparator?: (a: any, b: any) => boolean, - - // diffChars or diffWords only: - ignoreCase?: boolean, - - // diffJson only: - undefinedReplacement?: any, - stringifyReplacer?: (k: string, v: any) => any, - - // diffWords only: - intlSegmenter?: Intl.Segmenter, - - // diffLines only: - stripTrailingCr?: boolean, - newlineIsToken?: boolean, - ignoreNewlineAtEof?: boolean, - ignoreWhitespace?: boolean, // TODO: This is SORT OF supported by diffWords. What to do? -} +import {ChangeObject, DiffOptions, DiffCallback} from '../types'; /** * Like a ChangeObject, but with no value and an extra `previousComponent` property. diff --git a/src/diff/character.ts b/src/diff/character.ts index 625ceba95..461c6f5ea 100644 --- a/src/diff/character.ts +++ b/src/diff/character.ts @@ -1,4 +1,5 @@ -import Diff, { DiffOptions } from './base'; +import Diff from './base' +import { DiffOptions } from '../types'; class CharacterDiff extends Diff {} diff --git a/src/diff/css.ts b/src/diff/css.ts index 6f07534a6..a7e55da50 100644 --- a/src/diff/css.ts +++ b/src/diff/css.ts @@ -1,4 +1,5 @@ -import Diff, { DiffOptions } from './base'; +import Diff from './base' +import { DiffOptions } from '../types'; class CssDiff extends Diff { protected tokenize(value: string) { diff --git a/src/diff/json.ts b/src/diff/json.ts index 7bddc67ce..28140d41a 100644 --- a/src/diff/json.ts +++ b/src/diff/json.ts @@ -1,4 +1,5 @@ -import Diff, {DiffOptions} from './base'; +import Diff from './base' +import {DiffOptions} from '../types'; import {lineDiff} from './line'; class JsonDiff extends Diff { diff --git a/src/diff/line.ts b/src/diff/line.ts index 44ca302c4..107589253 100644 --- a/src/diff/line.ts +++ b/src/diff/line.ts @@ -1,4 +1,5 @@ -import Diff, { DiffOptions } from './base'; +import Diff from './base' +import { DiffCallback, DiffOptions } from '../types'; import {generateOptions} from '../util/params'; @@ -71,7 +72,7 @@ export function diffLines(oldStr: string, newStr: string, options: DiffOptions) { let options = generateOptions(callback, {ignoreWhitespace: true}); return lineDiff.diff(oldStr, newStr, options); } diff --git a/src/diff/sentence.ts b/src/diff/sentence.ts index 1929a6b6a..5dd31e993 100644 --- a/src/diff/sentence.ts +++ b/src/diff/sentence.ts @@ -1,4 +1,5 @@ -import Diff, { DiffOptions } from './base'; +import Diff from './base' +import { DiffOptions } from '../types'; class SentenceDiff extends Diff { protected tokenize(value: string) { diff --git a/src/diff/word.ts b/src/diff/word.ts index 5d9cef4f6..ed5c67d6e 100644 --- a/src/diff/word.ts +++ b/src/diff/word.ts @@ -1,4 +1,5 @@ -import Diff, { DiffOptions } from './base'; +import Diff from './base' +import { DiffOptions } from '../types'; import { longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap, leadingWs, trailingWs } from '../util/string'; // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode diff --git a/src/types.ts b/src/types.ts index 95cabfe64..b138dcead 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,3 +4,37 @@ export interface ChangeObject { removed: boolean; count: number; } + +/** + * Note that this contains the union of ALL options accepted by any of the built-in diffing + * functions. The README notes which options are usable which functions. Using an option with a + * diffing function that doesn't support it might yield unreasonable results. + */ +export interface DiffOptions { + // Universal: + callback?: DiffCallback, + maxEditLength?: number, + timeout?: number, + oneChangePerToken?: boolean, + + // diffArrays only: + comparator?: (a: any, b: any) => boolean, + + // diffChars or diffWords only: + ignoreCase?: boolean, + + // diffJson only: + undefinedReplacement?: any, + stringifyReplacer?: (k: string, v: any) => any, + + // diffWords only: + intlSegmenter?: Intl.Segmenter, + + // diffLines only: + stripTrailingCr?: boolean, + newlineIsToken?: boolean, + ignoreNewlineAtEof?: boolean, + ignoreWhitespace?: boolean, // TODO: This is SORT OF supported by diffWords. What to do? +} + +export type DiffCallback = (result?: ChangeObject[]) => void; diff --git a/src/util/params.js b/src/util/params.ts similarity index 60% rename from src/util/params.js rename to src/util/params.ts index 07e03e59e..3bfe1fcf0 100644 --- a/src/util/params.js +++ b/src/util/params.ts @@ -1,4 +1,6 @@ -export function generateOptions(options, defaults) { +import {DiffOptions, DiffCallback} from '../types'; + +export function generateOptions(options: DiffOptions | DiffCallback | null, defaults: DiffOptions): DiffOptions { if (typeof options === 'function') { defaults.callback = options; } else if (options) { From 4cb52b43df2c674d11256d575a427b7c419527b6 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 16:19:10 +0000 Subject: [PATCH 15/46] string.js -> string.ts --- src/util/{string.js => string.ts} | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) rename src/util/{string.js => string.ts} (79%) diff --git a/src/util/string.js b/src/util/string.ts similarity index 79% rename from src/util/string.js rename to src/util/string.ts index 80cf54786..55dce220c 100644 --- a/src/util/string.js +++ b/src/util/string.ts @@ -1,4 +1,4 @@ -export function longestCommonPrefix(str1, str2) { +export function longestCommonPrefix(str1: string, str2: string): string { let i; for (i = 0; i < str1.length && i < str2.length; i++) { if (str1[i] != str2[i]) { @@ -8,7 +8,7 @@ export function longestCommonPrefix(str1, str2) { return str1.slice(0, i); } -export function longestCommonSuffix(str1, str2) { +export function longestCommonSuffix(str1: string, str2: string): string { let i; // Unlike longestCommonPrefix, we need a special case to handle all scenarios @@ -26,14 +26,14 @@ export function longestCommonSuffix(str1, str2) { return str1.slice(-i); } -export function replacePrefix(string, oldPrefix, newPrefix) { +export function replacePrefix(string: string, oldPrefix: string, newPrefix: string): string { if (string.slice(0, oldPrefix.length) != oldPrefix) { throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`); } return newPrefix + string.slice(oldPrefix.length); } -export function replaceSuffix(string, oldSuffix, newSuffix) { +export function replaceSuffix(string: string, oldSuffix: string, newSuffix: string): string { if (!oldSuffix) { return string + newSuffix; } @@ -44,20 +44,20 @@ export function replaceSuffix(string, oldSuffix, newSuffix) { return string.slice(0, -oldSuffix.length) + newSuffix; } -export function removePrefix(string, oldPrefix) { +export function removePrefix(string: string, oldPrefix: string): string { return replacePrefix(string, oldPrefix, ''); } -export function removeSuffix(string, oldSuffix) { +export function removeSuffix(string: string, oldSuffix: string): string { return replaceSuffix(string, oldSuffix, ''); } -export function maximumOverlap(string1, string2) { +export function maximumOverlap(string1: string, string2: string): string { return string2.slice(0, overlapCount(string1, string2)); } // Nicked from https://stackoverflow.com/a/60422853/1709587 -function overlapCount(a, b) { +function overlapCount(a: string, b: string): number { // Deal with cases where the strings differ in length let startA = 0; if (a.length > b.length) { startA = a.length - b.length; } @@ -91,18 +91,18 @@ function overlapCount(a, b) { /** * Returns true if the string consistently uses Windows line endings. */ -export function hasOnlyWinLineEndings(string) { +export function hasOnlyWinLineEndings(string: string): boolean { return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/); } /** * Returns true if the string consistently uses Unix line endings. */ -export function hasOnlyUnixLineEndings(string) { +export function hasOnlyUnixLineEndings(string: string): boolean { return !string.includes('\r\n') && string.includes('\n'); } -export function trailingWs(string) { +export function trailingWs(string: string): string { // Yes, this looks overcomplicated and dumb - why not replace the whole function with // return string match(/\s*$/)[0] // you ask? Because: @@ -123,7 +123,8 @@ export function trailingWs(string) { return string.substring(i + 1); } -export function leadingWs(string) { +export function leadingWs(string: string): string { // Thankfully the annoying considerations described in trailingWs don't apply here: - return string.match(/^\s*/)[0]; + const match = string.match(/^\s*/); + return match ? match[0] : ''; } From 5ad83c60e8c0d38acc281b74af440c0857dd7ba0 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Mon, 10 Mar 2025 19:46:27 +0000 Subject: [PATCH 16/46] Partly convert patch-related files to TypeScript; more to do --- src/patch/{apply.js => apply.ts} | 46 +++++++++++++++++++++++--------- src/patch/{parse.js => parse.ts} | 10 ++++--- src/types.ts | 17 ++++++++++++ 3 files changed, 56 insertions(+), 17 deletions(-) rename src/patch/{apply.js => apply.ts} (89%) rename src/patch/{parse.js => parse.ts} (94%) diff --git a/src/patch/apply.js b/src/patch/apply.ts similarity index 89% rename from src/patch/apply.js rename to src/patch/apply.ts index 7a16605a5..a9d2ddcc2 100644 --- a/src/patch/apply.js +++ b/src/patch/apply.ts @@ -2,31 +2,51 @@ import {hasOnlyWinLineEndings, hasOnlyUnixLineEndings} from '../util/string'; import {isWin, isUnix, unixToWin, winToUnix} from './line-endings'; import {parsePatch} from './parse'; import distanceIterator from '../util/distance-iterator'; +import { StructuredPatch } from '../types'; -export function applyPatch(source, uniDiff, options = {}) { +export interface ApplyPatchOptions { + fuzzFactor?: number, + autoConvertLineEndings?: boolean, + compareLine?: (lineNumber: number, line: string, operation: string, patchContent: string) => boolean, +} + +export function applyPatch( + source: string, + uniDiff: string | StructuredPatch | [StructuredPatch], + options: ApplyPatchOptions = {} +): string | boolean { + let patches: StructuredPatch[]; if (typeof uniDiff === 'string') { - uniDiff = parsePatch(uniDiff); + patches = parsePatch(uniDiff); + } else if (Array.isArray(uniDiff)) { + patches = uniDiff; + } else { + patches = [uniDiff]; } - if (Array.isArray(uniDiff)) { - if (uniDiff.length > 1) { - throw new Error('applyPatch only works with a single input.'); - } - - uniDiff = uniDiff[0]; + if (patches.length > 1) { + throw new Error('applyPatch only works with a single input.'); } + return applyStructuredPatch(source, patches[0], options); +} + +function applyStructuredPatch( + source: string, + patch: StructuredPatch, + options: ApplyPatchOptions = {} +): string | boolean { if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) { - if (hasOnlyWinLineEndings(source) && isUnix(uniDiff)) { - uniDiff = unixToWin(uniDiff); - } else if (hasOnlyUnixLineEndings(source) && isWin(uniDiff)) { - uniDiff = winToUnix(uniDiff); + if (hasOnlyWinLineEndings(source) && isUnix(patch)) { + patch = unixToWin(patch); + } else if (hasOnlyUnixLineEndings(source) && isWin(patch)) { + patch = winToUnix(patch); } } // Apply the diff to the input let lines = source.split('\n'), - hunks = uniDiff.hunks, + hunks = patch.hunks, compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), fuzzFactor = options.fuzzFactor || 0, diff --git a/src/patch/parse.js b/src/patch/parse.ts similarity index 94% rename from src/patch/parse.js rename to src/patch/parse.ts index 61334b23b..d3e014833 100755 --- a/src/patch/parse.js +++ b/src/patch/parse.ts @@ -1,10 +1,12 @@ -export function parsePatch(uniDiff) { +import { StructuredPatch } from '../types'; + +export function parsePatch(uniDiff: string): StructuredPatch[] { let diffstr = uniDiff.split(/\n/), - list = [], + list: Partial[] = [], i = 0; function parseIndex() { - let index = {}; + let index: Partial = {}; list.push(index); // Parse diff metadata @@ -137,5 +139,5 @@ export function parsePatch(uniDiff) { parseIndex(); } - return list; + return list as StructuredPatch[]; } diff --git a/src/types.ts b/src/types.ts index b138dcead..6ccae3f35 100644 --- a/src/types.ts +++ b/src/types.ts @@ -38,3 +38,20 @@ export interface DiffOptions { } export type DiffCallback = (result?: ChangeObject[]) => void; + +export interface StructuredPatch { + oldFileName: string, + newFileName: string, + oldHeader: string, + newHeader: string, + hunks: StructuredPatchHunk[], + index?: string, +} + +export interface StructuredPatchHunk { + oldStart: number, + oldLines: number, + newStart: number, + newLines: number, + lines: string[], +} From 2dd993ea1067df5282f5d5c2a181ee4e19356420 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Tue, 11 Mar 2025 12:34:32 +0000 Subject: [PATCH 17/46] Type more stuff --- src/patch/apply.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/patch/apply.ts b/src/patch/apply.ts index a9d2ddcc2..6593964b9 100644 --- a/src/patch/apply.ts +++ b/src/patch/apply.ts @@ -114,13 +114,13 @@ function applyStructuredPatch( * `replacementLines`. Otherwise, returns null. */ function applyHunk( - hunkLines, - toPos, - maxErrors, - hunkLinesI = 0, - lastContextLineMatched = true, - patchedLines = [], - patchedLinesLength = 0, + hunkLines: string[], + toPos: number, + maxErrors: number, + hunkLinesI: number = 0, + lastContextLineMatched: boolean = true, + patchedLines: string[] = [], + patchedLinesLength: number = 0, ) { let nConsecutiveOldContextLines = 0; let nextContextLineMustMatch = false; @@ -224,7 +224,7 @@ function applyStructuredPatch( }; } - const resultLines = []; + const resultLines: string[] = []; // Search best fit offsets for each hunk based on the previous ones let prevHunkOffset = 0; @@ -279,14 +279,20 @@ function applyStructuredPatch( return resultLines.join('\n'); } +export interface ApplyPatchesOptions extends ApplyPatchOptions { + loadFile: (index: StructuredPatch, callback: (err: any, data: string) => void) => void, + patched: (index: StructuredPatch, content: string, callback: (err: any) => void) => void, + complete: (err?: any) => void, +} + // Wrapper that supports multiple file patches via callbacks. -export function applyPatches(uniDiff, options) { +export function applyPatches(uniDiff: string | StructuredPatch[], options): void { if (typeof uniDiff === 'string') { uniDiff = parsePatch(uniDiff); } let currentIndex = 0; - function processIndex() { + function processIndex(): void { let index = uniDiff[currentIndex++]; if (!index) { return options.complete(); From 88838c9af61b10e7d90dc8fb6d503cfc8c24295d Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Tue, 11 Mar 2025 13:23:39 +0000 Subject: [PATCH 18/46] Begin converting line-endings.js. Interesting TypeScript bug along the way. --- src/patch/{line-endings.js => line-endings.ts} | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) rename src/patch/{line-endings.js => line-endings.ts} (67%) diff --git a/src/patch/line-endings.js b/src/patch/line-endings.ts similarity index 67% rename from src/patch/line-endings.js rename to src/patch/line-endings.ts index d1907b47a..b0ce52b33 100644 --- a/src/patch/line-endings.js +++ b/src/patch/line-endings.ts @@ -1,6 +1,15 @@ -export function unixToWin(patch) { +import { StructuredPatch } from "../types"; + +export function unixToWin(patch: StructuredPatch): StructuredPatch; +export function unixToWin(patches: StructuredPatch[]): StructuredPatch[]; +export function unixToWin(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { if (Array.isArray(patch)) { - return patch.map(unixToWin); + // It would be cleaner if instead of the line below we could just write + // return patch.map(unixToWin) + // but mysteriously TypeScript (v5.7.3 at the time of writing) does not like this and it will + // refuse to compile, thinking that unixToWin could then return StructuredPatch[][] and the + // result would be incompatible with the overload signatures. + return patch.map(function (p) { return unixToWin(p) }); } return { From 7e0d68b15253dbcd33ec09649666d492964b6e0a Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Tue, 11 Mar 2025 13:23:56 +0000 Subject: [PATCH 19/46] Bump TypeScript --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 63ca7d483..874938104 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4852,9 +4852,9 @@ typedarray-to-buffer@^3.1.5: is-typedarray "^1.0.0" typescript@^5.7.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" - integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== + version "5.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4" + integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ== ua-parser-js@^0.7.30: version "0.7.40" From bbe952049f39a9cbb4dbd137358fef4da3747388 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Tue, 11 Mar 2025 14:39:22 +0000 Subject: [PATCH 20/46] Add link to bug report --- src/patch/line-endings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/patch/line-endings.ts b/src/patch/line-endings.ts index b0ce52b33..9b3b5dcda 100644 --- a/src/patch/line-endings.ts +++ b/src/patch/line-endings.ts @@ -9,6 +9,7 @@ export function unixToWin(patch: StructuredPatch | StructuredPatch[]): Structure // but mysteriously TypeScript (v5.7.3 at the time of writing) does not like this and it will // refuse to compile, thinking that unixToWin could then return StructuredPatch[][] and the // result would be incompatible with the overload signatures. + // See bug report at https://github.com/microsoft/TypeScript/issues/61398. return patch.map(function (p) { return unixToWin(p) }); } From a21648e51a791003db2eb722b04120c36fa3c47a Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Wed, 12 Mar 2025 11:57:29 +0000 Subject: [PATCH 21/46] Finish converted line-endings.js to TypeScript --- src/patch/line-endings.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/patch/line-endings.ts b/src/patch/line-endings.ts index 9b3b5dcda..99e00be61 100644 --- a/src/patch/line-endings.ts +++ b/src/patch/line-endings.ts @@ -27,9 +27,12 @@ export function unixToWin(patch: StructuredPatch | StructuredPatch[]): Structure }; } -export function winToUnix(patch) { +export function winToUnix(patch: StructuredPatch): StructuredPatch; +export function winToUnix(patches: StructuredPatch[]): StructuredPatch[]; +export function winToUnix(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { if (Array.isArray(patch)) { - return patch.map(winToUnix); + // (See comment above equivalent line in unixToWin) + return patch.map(function (p) { return winToUnix(p) }); } return { @@ -45,7 +48,7 @@ export function winToUnix(patch) { * Returns true if the patch consistently uses Unix line endings (or only involves one line and has * no line endings). */ -export function isUnix(patch) { +export function isUnix(patch: StructuredPatch | StructuredPatch[]): boolean { if (!Array.isArray(patch)) { patch = [patch]; } return !patch.some( index => index.hunks.some( @@ -59,7 +62,7 @@ export function isUnix(patch) { /** * Returns true if the patch uses Windows line endings and only Windows line endings. */ -export function isWin(patch) { +export function isWin(patch: StructuredPatch | StructuredPatch[]): boolean { if (!Array.isArray(patch)) { patch = [patch]; } return patch.some(index => index.hunks.some(hunk => hunk.lines.some(line => line.endsWith('\r')))) && patch.every( From 4b3691a39534fb01e31000dea76528aeb017d44d Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Wed, 12 Mar 2025 12:03:25 +0000 Subject: [PATCH 22/46] Add missing overloads to support case where argument type isn't known statically --- src/patch/line-endings.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/patch/line-endings.ts b/src/patch/line-endings.ts index 99e00be61..01ab5c6ff 100644 --- a/src/patch/line-endings.ts +++ b/src/patch/line-endings.ts @@ -2,6 +2,7 @@ import { StructuredPatch } from "../types"; export function unixToWin(patch: StructuredPatch): StructuredPatch; export function unixToWin(patches: StructuredPatch[]): StructuredPatch[]; +export function unixToWin(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[]; export function unixToWin(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { if (Array.isArray(patch)) { // It would be cleaner if instead of the line below we could just write @@ -29,6 +30,7 @@ export function unixToWin(patch: StructuredPatch | StructuredPatch[]): Structure export function winToUnix(patch: StructuredPatch): StructuredPatch; export function winToUnix(patches: StructuredPatch[]): StructuredPatch[]; +export function winToUnix(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[]; export function winToUnix(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { if (Array.isArray(patch)) { // (See comment above equivalent line in unixToWin) From 88a55137ae8b7aa6869ba10d663083ef14f5a6d4 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Wed, 12 Mar 2025 12:23:24 +0000 Subject: [PATCH 23/46] reverse.js -> reverse.ts --- src/patch/{reverse.js => reverse.ts} | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) rename src/patch/{reverse.js => reverse.ts} (50%) diff --git a/src/patch/reverse.js b/src/patch/reverse.ts similarity index 50% rename from src/patch/reverse.js rename to src/patch/reverse.ts index e839eebaa..611bba97b 100644 --- a/src/patch/reverse.js +++ b/src/patch/reverse.ts @@ -1,6 +1,12 @@ -export function reversePatch(structuredPatch) { +import { StructuredPatch } from "../types"; + +export function reversePatch(structuredPatch: StructuredPatch): StructuredPatch; +export function reversePatch(structuredPatch: StructuredPatch[]): StructuredPatch[]; +export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[]; +export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { if (Array.isArray(structuredPatch)) { - return structuredPatch.map(reversePatch).reverse(); + // (See comment in unixToWin for why we need the pointless-looking anonymous function here) + return structuredPatch.map(function (patch) { return reversePatch(patch); }).reverse(); } return { From 72bc4dcf72498a6a7d177794aa7c8d9bdeb873fc Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Wed, 12 Mar 2025 12:36:26 +0000 Subject: [PATCH 24/46] add todo --- src/diff/base.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/diff/base.ts b/src/diff/base.ts index 4d426fb53..13add5956 100644 --- a/src/diff/base.ts +++ b/src/diff/base.ts @@ -18,6 +18,8 @@ interface Path { lastComponent: DraftChangeObject } +// TODO: Types are wrong for case with a callback + export default class Diff< TokenT, ValueT extends Iterable = Iterable From c62827e5f23e96a9d7ed46d47215fb4207f07fba Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Wed, 12 Mar 2025 13:43:09 +0000 Subject: [PATCH 25/46] Rewrite base.ts types (breaks everything for now) --- src/diff/base.ts | 67 +++++++++++++++++++++++++++++++++--------------- src/types.ts | 12 +++++++-- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/diff/base.ts b/src/diff/base.ts index 13add5956..6f5f680b1 100644 --- a/src/diff/base.ts +++ b/src/diff/base.ts @@ -1,4 +1,4 @@ -import {ChangeObject, DiffOptions, DiffCallback} from '../types'; +import {ChangeObject, DiffOptionsWithoutCallback, DiffOptionsWithCallback, DiffCallback} from '../types'; /** * Like a ChangeObject, but with no value and an extra `previousComponent` property. @@ -10,12 +10,15 @@ interface DraftChangeObject { added: boolean; removed: boolean; count: number; - previousComponent: DraftChangeObject; + previousComponent?: DraftChangeObject; + + // Only added in buildValues: + value?: any; } interface Path { oldPos: number; - lastComponent: DraftChangeObject + lastComponent?: DraftChangeObject } // TODO: Types are wrong for case with a callback @@ -27,13 +30,23 @@ export default class Diff< diff( oldString: ValueT, newString: ValueT, - options: DiffCallback | DiffOptions = {} - ): ChangeObject[] { - let callback; + options: DiffCallback | DiffOptionsWithCallback + ): undefined + diff( + oldString: ValueT, + newString: ValueT, + options: DiffOptionsWithoutCallback + ): ChangeObject[] + diff( + oldString: ValueT, + newString: ValueT, + options: DiffCallback | DiffOptionsWithCallback | DiffOptionsWithoutCallback = {} + ): ChangeObject[] | undefined { + let callback: DiffCallback | undefined = undefined; if (typeof options === 'function') { callback = options; options = {}; - } else { + } else if ("callback" in options) { callback = options.callback; } // Allow subclasses to massage the input prior to running @@ -46,7 +59,12 @@ export default class Diff< return this.diffWithOptionsObj(oldTokens, newTokens, options, callback); } - private diffWithOptionsObj(oldTokens: TokenT[], newTokens: TokenT[], options: DiffOptions, callback: DiffCallback) { + private diffWithOptionsObj( + oldTokens: TokenT[], + newTokens: TokenT[], + options: DiffOptionsWithoutCallback | DiffOptionsWithCallback, + callback: DiffCallback | undefined + ): ChangeObject[] | undefined { const self = this; function done(value) { value = self.postProcess(value, options); @@ -107,6 +125,7 @@ export default class Diff< addPath = bestPath[diagonalPath + 1]; if (removePath) { // No one else is going to attempt to use this value, clear it + // @ts-ignore bestPath[diagonalPath - 1] = undefined; } @@ -120,6 +139,7 @@ export default class Diff< let canRemove = removePath && removePath.oldPos + 1 < oldLen; if (!canAdd && !canRemove) { // If this path is a terminal then prune + // @ts-ignore bestPath[diagonalPath] = undefined; continue; } @@ -183,7 +203,7 @@ export default class Diff< added: boolean, removed: boolean, oldPosInc: number, - options: DiffOptions + options: DiffOptionsWithoutCallback ): Path { let last = path.lastComponent; if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) { @@ -204,7 +224,7 @@ export default class Diff< newTokens: TokenT[], oldTokens: TokenT[], diagonalPath: number, - options: DiffOptions + options: DiffOptionsWithoutCallback ): number { let newLen = newTokens.length, oldLen = oldTokens.length, @@ -229,17 +249,17 @@ export default class Diff< return newPos; } - protected equals(left: TokenT, right: TokenT, options: DiffOptions): boolean { + protected equals(left: TokenT, right: TokenT, options: DiffOptionsWithoutCallback): boolean { if (options.comparator) { return options.comparator(left, right); } else { return left === right - || (options.ignoreCase && (left as string).toLowerCase() === (right as string).toLowerCase()); + || (!!options.ignoreCase && (left as string).toLowerCase() === (right as string).toLowerCase()); } } protected removeEmpty(array: TokenT[]): TokenT[] { - let ret = []; + let ret: TokenT[] = []; for (let i = 0; i < array.length; i++) { if (array[i]) { ret.push(array[i]); @@ -248,11 +268,11 @@ export default class Diff< return ret; } - protected castInput(value: ValueT, options: DiffOptions): ValueT { + protected castInput(value: ValueT, options: DiffOptionsWithoutCallback): ValueT { return value; } - protected tokenize(value: ValueT, options: DiffOptions): TokenT[] { + protected tokenize(value: ValueT, options: DiffOptionsWithoutCallback): TokenT[] { return Array.from(value); } @@ -264,18 +284,25 @@ export default class Diff< return (chars as string[]).join('') as unknown as ValueT; } - protected postProcess(changeObjects: ChangeObject[], options: DiffOptions): ChangeObject[] { + protected postProcess( + changeObjects: ChangeObject[], + options: DiffOptionsWithoutCallback, + ): ChangeObject[] { return changeObjects; } - protected get useLongestToken() { + protected get useLongestToken(): boolean { return false; } - private buildValues(lastComponent, newTokens: TokenT[], oldTokens: TokenT[]) { + private buildValues( + lastComponent: DraftChangeObject | undefined, + newTokens: TokenT[], + oldTokens: TokenT[] + ): ChangeObject[] { // First we convert our linked list of components in reverse order to an // array in the right order: - const components = []; + const components: DraftChangeObject[] = []; let nextComponent; while (lastComponent) { components.push(lastComponent); @@ -316,7 +343,7 @@ export default class Diff< } } - return components; + return components as ChangeObject[]; } }; diff --git a/src/types.ts b/src/types.ts index 6ccae3f35..07f9d0fc3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,9 +10,8 @@ export interface ChangeObject { * functions. The README notes which options are usable which functions. Using an option with a * diffing function that doesn't support it might yield unreasonable results. */ -export interface DiffOptions { +export interface DiffOptionsWithoutCallback { // Universal: - callback?: DiffCallback, maxEditLength?: number, timeout?: number, oneChangePerToken?: boolean, @@ -37,6 +36,15 @@ export interface DiffOptions { ignoreWhitespace?: boolean, // TODO: This is SORT OF supported by diffWords. What to do? } +/** + * This is a distinct type from DiffOptionsWithoutCallback so that we can have different overloads + * with different return types depending upon whether a callback option is given (and thus whether + * we are running in async or sync mode). + */ +export interface DiffOptionsWithCallback extends DiffOptionsWithoutCallback { + callback?: DiffCallback, +} + export type DiffCallback = (result?: ChangeObject[]) => void; export interface StructuredPatch { From be36cc0554fd5e00b084c83b78849ed37c9f06b3 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Wed, 12 Mar 2025 15:14:28 +0000 Subject: [PATCH 26/46] Per-diff-function options types. Still not compiling but close. --- src/diff/array.ts | 20 +++++++++++---- src/diff/character.ts | 12 ++++++--- src/diff/css.ts | 12 ++++++--- src/diff/json.ts | 17 ++++++++++--- src/diff/line.ts | 24 +++++++++++++----- src/diff/sentence.ts | 12 ++++++--- src/diff/word.ts | 22 ++++++++++++---- src/types.ts | 59 ++++++++++++++++++++++++++++--------------- 8 files changed, 129 insertions(+), 49 deletions(-) diff --git a/src/diff/array.ts b/src/diff/array.ts index 1ad07e3ae..c34291616 100644 --- a/src/diff/array.ts +++ b/src/diff/array.ts @@ -1,5 +1,5 @@ import Diff from './base' -import {DiffOptions} from '../types'; +import {CallbackOption, DiffOptionsWithoutCallback, DiffCallback, ChangeObject, DiffArraysOptions} from '../types'; class ArrayDiff extends Diff> { protected tokenize(value: Array) { @@ -18,9 +18,19 @@ class ArrayDiff extends Diff> { export const arrayDiff = new ArrayDiff(); export function diffArrays( - oldArr: Array, - newArr: Array, - options: DiffOptions> -) { + oldArr: any[], + newArr: any[], + options: (DiffArraysOptions & CallbackOption) | DiffCallback +): undefined; +export function diffArrays( + oldArr: any[], + newArr: any[], + options: DiffArraysOptions +): ChangeObject[]; +export function diffArrays( + oldArr: any[], + newArr: any[], + options +): undefined | ChangeObject[] { return arrayDiff.diff(oldArr, newArr, options); } diff --git a/src/diff/character.ts b/src/diff/character.ts index 461c6f5ea..6eb410895 100644 --- a/src/diff/character.ts +++ b/src/diff/character.ts @@ -1,10 +1,16 @@ import Diff from './base' -import { DiffOptions } from '../types'; +import {CallbackOption, DiffOptionsWithoutCallback, DiffCallback, ChangeObject, DiffCharsOptions} from '../types'; class CharacterDiff extends Diff {} export const characterDiff = new CharacterDiff(); -export function diffChars(oldStr: string, newStr: string, options: DiffOptions) { +export function diffChars( + oldStr: string, + newStr: string, + options: (DiffCharsOptions & CallbackOption) | DiffCallback +): undefined +export function diffChars(oldStr: string, newStr: string, options: DiffCharsOptions): ChangeObject[]; +export function diffChars(oldStr: string, newStr: string, options): undefined | ChangeObject[] { return characterDiff.diff(oldStr, newStr, options); -} \ No newline at end of file +} diff --git a/src/diff/css.ts b/src/diff/css.ts index a7e55da50..90f2c367e 100644 --- a/src/diff/css.ts +++ b/src/diff/css.ts @@ -1,5 +1,5 @@ import Diff from './base' -import { DiffOptions } from '../types'; +import { CallbackOption, ChangeObject, DiffCallback, DiffCssOptions } from '../types'; class CssDiff extends Diff { protected tokenize(value: string) { @@ -9,6 +9,12 @@ class CssDiff extends Diff { export const cssDiff = new CssDiff(); -export function diffCss(oldStr: string, newStr: string, options: DiffOptions) { +export function diffCss( + oldStr: string, + newStr: string, + options: (DiffCssOptions & CallbackOption) | DiffCallback +): undefined +export function diffCss(oldStr: string, newStr: string, options: DiffCssOptions): ChangeObject[]; +export function diffCss(oldStr: string, newStr: string, options): undefined | ChangeObject[] { return cssDiff.diff(oldStr, newStr, options); -} \ No newline at end of file +} diff --git a/src/diff/json.ts b/src/diff/json.ts index 28140d41a..8bbdb8fc1 100644 --- a/src/diff/json.ts +++ b/src/diff/json.ts @@ -1,5 +1,5 @@ import Diff from './base' -import {DiffOptions} from '../types'; +import { CallbackOption, ChangeObject, DiffCallback, DiffJsonOptions } from '../types'; import {lineDiff} from './line'; class JsonDiff extends Diff { @@ -11,13 +11,13 @@ class JsonDiff extends Diff { protected tokenize = lineDiff.tokenize - protected castInput(value: string, options: DiffOptions) { + protected castInput(value: string, options: DiffJsonOptions) { const {undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v} = options; return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); }; - protected equals(left: string, right: string, options: DiffOptions) { + protected equals(left: string, right: string, options: DiffJsonOptions) { return super.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); }; } @@ -25,7 +25,16 @@ class JsonDiff extends Diff { const jsonDiff = new JsonDiff(); -export function diffJson(oldObj: any, newObj: any, options: DiffOptions) { return jsonDiff.diff(oldObj, newObj, options); } +export function diffJson( + oldStr: string, + newStr: string, + options: (DiffJsonOptions & CallbackOption) | DiffCallback +): undefined +export function diffJson(oldStr: string, newStr: string, options: DiffJsonOptions): ChangeObject[]; +export function diffJson(oldStr: string, newStr: string, options): undefined | ChangeObject[] { + return jsonDiff.diff(oldStr, newStr, options); +} + // This function handles the presence of circular references by bailing out when encountering an // object that is already on the "stack" of items being processed. Accepts an optional replacer diff --git a/src/diff/line.ts b/src/diff/line.ts index 107589253..8ed005d4d 100644 --- a/src/diff/line.ts +++ b/src/diff/line.ts @@ -1,11 +1,11 @@ import Diff from './base' -import { DiffCallback, DiffOptions } from '../types'; +import { CallbackOption, ChangeObject, DiffCallback, DiffLinesOptions } from '../types'; import {generateOptions} from '../util/params'; class LineDiff extends Diff { // public so it can be copied by jsonDiff - public tokenize(value: string, options: DiffOptions) { + public tokenize(value: string, options: DiffLinesOptions) { if(options.stripTrailingCr) { // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior value = value.replace(/\r\n/g, '\n'); @@ -33,7 +33,7 @@ class LineDiff extends Diff { return retLines; }; - protected equals(left: string, right: string, options: DiffOptions) { + protected equals(left: string, right: string, options: DiffLinesOptions) { // If we're ignoring whitespace, we need to normalise lines by stripping // whitespace before checking equality. (This has an annoying interaction // with newlineIsToken that requires special handling: if newlines get their @@ -62,7 +62,13 @@ class LineDiff extends Diff { export const lineDiff = new LineDiff(); -export function diffLines(oldStr: string, newStr: string, options: DiffOptions) { +export function diffLines( + oldStr: string, + newStr: string, + options: (DiffLinesOptions & CallbackOption) | DiffCallback +): undefined +export function diffLines(oldStr: string, newStr: string, options: DiffLinesOptions): ChangeObject[]; +export function diffLines(oldStr: string, newStr: string, options): undefined | ChangeObject[] { return lineDiff.diff(oldStr, newStr, options); } @@ -72,7 +78,13 @@ export function diffLines(oldStr: string, newStr: string, options: DiffOptions) { - let options = generateOptions(callback, {ignoreWhitespace: true}); +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options: (DiffLinesOptions & CallbackOption) | DiffCallback +): undefined +export function diffTrimmedLines(oldStr: string, newStr: string, options: DiffLinesOptions): ChangeObject[]; +export function diffTrimmedLines(oldStr: string, newStr: string, options): undefined | ChangeObject[] { + options = generateOptions(options, {ignoreWhitespace: true}); return lineDiff.diff(oldStr, newStr, options); } diff --git a/src/diff/sentence.ts b/src/diff/sentence.ts index 5dd31e993..0d4fd1dc0 100644 --- a/src/diff/sentence.ts +++ b/src/diff/sentence.ts @@ -1,5 +1,5 @@ import Diff from './base' -import { DiffOptions } from '../types'; +import { CallbackOption, ChangeObject, DiffCallback, DiffSentencesOptions } from '../types'; class SentenceDiff extends Diff { protected tokenize(value: string) { @@ -9,6 +9,12 @@ class SentenceDiff extends Diff { export const sentenceDiff = new SentenceDiff(); -export function diffSentences(oldStr: string, newStr: string, options: DiffOptions) { +export function diffSentences( + oldStr: string, + newStr: string, + options: (DiffSentencesOptions & CallbackOption) | DiffCallback +): undefined +export function diffSentences(oldStr: string, newStr: string, options: DiffSentencesOptions): ChangeObject[]; +export function diffSentences(oldStr: string, newStr: string, options): undefined | ChangeObject[] { return sentenceDiff.diff(oldStr, newStr, options); -} \ No newline at end of file +} diff --git a/src/diff/word.ts b/src/diff/word.ts index ed5c67d6e..655414805 100644 --- a/src/diff/word.ts +++ b/src/diff/word.ts @@ -1,5 +1,5 @@ import Diff from './base' -import { DiffOptions } from '../types'; +import { CallbackOption, ChangeObject, DiffCallback, DiffWordsOptions } from '../types'; import { longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap, leadingWs, trailingWs } from '../util/string'; // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode @@ -51,7 +51,7 @@ const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^$ class WordDiff extends Diff { - protected equals(left: string, right: string, options: DiffOptions) { + protected equals(left: string, right: string, options: DiffWordsOptions) { if (options.ignoreCase) { left = left.toLowerCase(); right = right.toLowerCase(); @@ -60,7 +60,7 @@ class WordDiff extends Diff { return left.trim() === right.trim(); } - protected tokenize(value: string, options: DiffOptions = {}) { + protected tokenize(value: string, options: DiffWordsOptions = {}) { let parts; if (options.intlSegmenter) { if (options.intlSegmenter.resolvedOptions().granularity != 'word') { @@ -142,7 +142,13 @@ class WordDiff extends Diff { export const wordDiff = new WordDiff(); -export function diffWords(oldStr: string, newStr: string, options) { +export function diffWords( + oldStr: string, + newStr: string, + options: (DiffWordsOptions & CallbackOption) | DiffCallback +): undefined +export function diffWords(oldStr: string, newStr: string, options: DiffWordsOptions): ChangeObject[]; +export function diffWords(oldStr: string, newStr: string, options): undefined | ChangeObject[] { // This option has never been documented and never will be (it's clearer to // just call `diffWordsWithSpace` directly if you need that behavior), but // has existed in jsdiff for a long time, so we retain support for it here @@ -291,6 +297,12 @@ class WordsWithSpaceDiff extends Diff { } export const wordsWithSpaceDiff = new WordsWithSpaceDiff(); -export function diffWordsWithSpace(oldStr, newStr, options) { +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options: (DiffWordsOptions & CallbackOption) | DiffCallback +): undefined +export function diffWordsWithSpace(oldStr: string, newStr: string, options: DiffWordsOptions): ChangeObject[]; +export function diffWordsWithSpace(oldStr: string, newStr: string, options): undefined | ChangeObject[] { return wordsWithSpaceDiff.diff(oldStr, newStr, options); } diff --git a/src/types.ts b/src/types.ts index 07f9d0fc3..53d50b151 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,45 +5,64 @@ export interface ChangeObject { count: number; } -/** - * Note that this contains the union of ALL options accepted by any of the built-in diffing - * functions. The README notes which options are usable which functions. Using an option with a - * diffing function that doesn't support it might yield unreasonable results. - */ -export interface DiffOptionsWithoutCallback { - // Universal: +export interface CommonDiffOptions { maxEditLength?: number, timeout?: number, oneChangePerToken?: boolean, +} - // diffArrays only: +export interface DiffArraysOptions extends CommonDiffOptions { comparator?: (a: any, b: any) => boolean, +} - // diffChars or diffWords only: +export interface DiffCharsOptions extends CommonDiffOptions { ignoreCase?: boolean, +} - // diffJson only: - undefinedReplacement?: any, - stringifyReplacer?: (k: string, v: any) => any, - - // diffWords only: - intlSegmenter?: Intl.Segmenter, - - // diffLines only: +export interface DiffLinesOptions extends CommonDiffOptions { stripTrailingCr?: boolean, newlineIsToken?: boolean, ignoreNewlineAtEof?: boolean, ignoreWhitespace?: boolean, // TODO: This is SORT OF supported by diffWords. What to do? } +export interface DiffWordsOptions extends CommonDiffOptions { + ignoreCase?: boolean + intlSegmenter?: Intl.Segmenter, +} + +export interface DiffSentencesOptions extends CommonDiffOptions {} + +export interface DiffJsonOptions extends CommonDiffOptions { + undefinedReplacement?: any, + stringifyReplacer?: (k: string, v: any) => any, +} + +export interface DiffCssOptions extends CommonDiffOptions {} + + +export interface CallbackOption { + callback: DiffCallback, +} + +/** + * Note that this contains the union of ALL options accepted by any of the built-in diffing + * functions. The README notes which options are usable which functions. Using an option with a + * diffing function that doesn't support it might yield unreasonable results. + */ +export type DiffOptionsWithoutCallback = + DiffArraysOptions & + DiffCharsOptions & + DiffWordsOptions & + DiffLinesOptions & + DiffJsonOptions; + /** * This is a distinct type from DiffOptionsWithoutCallback so that we can have different overloads * with different return types depending upon whether a callback option is given (and thus whether * we are running in async or sync mode). */ -export interface DiffOptionsWithCallback extends DiffOptionsWithoutCallback { - callback?: DiffCallback, -} +export type DiffOptionsWithCallback = DiffOptionsWithoutCallback & CallbackOption; export type DiffCallback = (result?: ChangeObject[]) => void; From 268e96bba62d38ee9e0ffba58e1280fb2734043d Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 11:00:47 +0000 Subject: [PATCH 27/46] Fix build --- src/util/params.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/util/params.ts b/src/util/params.ts index 3bfe1fcf0..36978341c 100644 --- a/src/util/params.ts +++ b/src/util/params.ts @@ -1,8 +1,12 @@ -import {DiffOptions, DiffCallback} from '../types'; +import {DiffCallback, DiffOptionsWithCallback, DiffOptionsWithoutCallback} from '../types'; -export function generateOptions(options: DiffOptions | DiffCallback | null, defaults: DiffOptions): DiffOptions { + +export function generateOptions( + options: DiffOptionsWithoutCallback | DiffCallback, + defaults: DiffOptionsWithoutCallback +): DiffOptionsWithoutCallback | DiffOptionsWithCallback { if (typeof options === 'function') { - defaults.callback = options; + (defaults as DiffOptionsWithCallback).callback = options; } else if (options) { for (let name in options) { /* istanbul ignore else */ From f0cfaf6a865654c1233f4747278ac28aa3ce1899 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 13:38:10 +0000 Subject: [PATCH 28/46] Run 'yarn add @eslint/js typescript-eslint --save-dev' as suggested at https://typescript-eslint.io/getting-started --- package.json | 5 +- yarn.lock | 187 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 178 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index aebb737fa..18b86b72e 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,10 @@ "webpack-dev-server": "^5.2.0" }, "optionalDependencies": {}, - "dependencies": {}, + "dependencies": { + "@eslint/js": "^9.22.0", + "typescript-eslint": "^8.26.1" + }, "nyc": { "require": [ "@babel/register" diff --git a/yarn.lock b/yarn.lock index af553ed2a..c7e776124 100644 --- a/yarn.lock +++ b/yarn.lock @@ -829,14 +829,14 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.5.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz#b0fc7e06d0c94f801537fd4237edc2706d3b8e4c" integrity sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w== dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.12.1": +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": version "4.12.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== @@ -877,7 +877,7 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.22.0": +"@eslint/js@9.22.0", "@eslint/js@^9.22.0": version "9.22.0" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.22.0.tgz#4ff53649ded7cbce90b444b494c234137fa1aa3d" integrity sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ== @@ -1028,6 +1028,27 @@ dependencies: eslint-scope "5.1.1" +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -1320,6 +1341,87 @@ dependencies: "@types/node" "*" +"@typescript-eslint/eslint-plugin@8.26.1": + version "8.26.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz#3e48eb847924161843b092c87a9b65176b53782f" + integrity sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.26.1" + "@typescript-eslint/type-utils" "8.26.1" + "@typescript-eslint/utils" "8.26.1" + "@typescript-eslint/visitor-keys" "8.26.1" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/parser@8.26.1": + version "8.26.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.26.1.tgz#0e2f915a497519fc43f52cf2ecbfa607ff56f72e" + integrity sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ== + dependencies: + "@typescript-eslint/scope-manager" "8.26.1" + "@typescript-eslint/types" "8.26.1" + "@typescript-eslint/typescript-estree" "8.26.1" + "@typescript-eslint/visitor-keys" "8.26.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.26.1": + version "8.26.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz#5e6ad0ac258ccf79462e91c3f43a3f1f7f31a6cc" + integrity sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg== + dependencies: + "@typescript-eslint/types" "8.26.1" + "@typescript-eslint/visitor-keys" "8.26.1" + +"@typescript-eslint/type-utils@8.26.1": + version "8.26.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz#462f0bae09de72ac6e8e1af2ebe588c23224d7f8" + integrity sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg== + dependencies: + "@typescript-eslint/typescript-estree" "8.26.1" + "@typescript-eslint/utils" "8.26.1" + debug "^4.3.4" + ts-api-utils "^2.0.1" + +"@typescript-eslint/types@8.26.1": + version "8.26.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.26.1.tgz#d5978721670cff263348d5062773389231a64132" + integrity sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ== + +"@typescript-eslint/typescript-estree@8.26.1": + version "8.26.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz#eb0e4ce31753683d83be53441a409fd5f0b34afd" + integrity sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA== + dependencies: + "@typescript-eslint/types" "8.26.1" + "@typescript-eslint/visitor-keys" "8.26.1" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@8.26.1": + version "8.26.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.26.1.tgz#54cc58469955f25577f659753b71a0e117a0539f" + integrity sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.26.1" + "@typescript-eslint/types" "8.26.1" + "@typescript-eslint/typescript-estree" "8.26.1" + +"@typescript-eslint/visitor-keys@8.26.1": + version "8.26.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz#c5267fcc82795cf10280363023837deacad2647c" + integrity sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg== + dependencies: + "@typescript-eslint/types" "8.26.1" + eslint-visitor-keys "^4.2.0" + "@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": version "1.14.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" @@ -2482,6 +2584,17 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2497,6 +2610,13 @@ fast-uri@^3.0.1: resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + faye-websocket@^0.11.3: version "0.11.4" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" @@ -2728,6 +2848,13 @@ get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" @@ -2735,13 +2862,6 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" @@ -2796,6 +2916,11 @@ graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + handle-thing@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" @@ -2921,7 +3046,7 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ignore@^5.2.0: +ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -3486,12 +3611,17 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.2: +micromatch@^4.0.2, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -3984,6 +4114,11 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -4134,6 +4269,11 @@ retry@^0.13.1: resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + rfdc@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" @@ -4194,6 +4334,13 @@ run-applescript@^7.0.0: resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -4251,7 +4398,7 @@ semver@^6.0.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.3, semver@^7.5.4: +semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: version "7.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== @@ -4681,6 +4828,11 @@ tree-dump@^1.0.1: resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.2.tgz#c460d5921caeb197bde71d0e9a7b479848c5b8ac" integrity sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ== +ts-api-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz#660729385b625b939aaa58054f45c058f33f10cd" + integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w== + tslib@^2.0.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" @@ -4718,6 +4870,15 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript-eslint@^8.26.1: + version "8.26.1" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.26.1.tgz#d17a638a7543bc535157b83cdf5876513c71493b" + integrity sha512-t/oIs9mYyrwZGRpDv3g+3K6nZ5uhKEMt2oNmAPwaY4/ye0+EH4nXIPYNtkYFS6QHm+1DFg34DbglYBz5P9Xysg== + dependencies: + "@typescript-eslint/eslint-plugin" "8.26.1" + "@typescript-eslint/parser" "8.26.1" + "@typescript-eslint/utils" "8.26.1" + typescript@^5.7.3: version "5.8.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4" From 38faa6912cdddb6cd89a009691b7f8085a226b7d Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 13:45:04 +0000 Subject: [PATCH 29/46] Turn on recommended eslint rules as recommended by Getting Started guide at https://typescript-eslint.io/packages/typescript-eslint/#config --- eslint.config.mjs | 350 +++++++++++++++++++++++----------------------- 1 file changed, 178 insertions(+), 172 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index ddc7551c8..696a3369b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,176 +1,182 @@ +// @ts-check + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; import globals from "globals"; import babelParser from "@babel/eslint-parser"; -export default [ - { - languageOptions: { - globals: { - ...globals.browser, - }, - - parser: babelParser, - }, - - rules: { - // Possible Errors // - //-----------------// - "comma-dangle": [2, "never"], - "no-cond-assign": [2, "except-parens"], - "no-console": 1, // Allow for debugging - "no-constant-condition": 2, - "no-control-regex": 2, - "no-debugger": 1, // Allow for debugging - "no-dupe-args": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-empty": 2, - "no-empty-character-class": 2, - "no-ex-assign": 2, - "no-extra-boolean-cast": 2, - "no-extra-parens": [2, "functions"], - "no-extra-semi": 2, - "no-func-assign": 2, - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-negated-in-lhs": 2, - "no-obj-calls": 2, - "no-regex-spaces": 2, - "no-unreachable": 1, // Optimizer and coverage will handle/highlight this and can be useful for debugging - "use-isnan": 2, - "valid-typeof": 2, - - // Best Practices // - //----------------// - curly: 2, - "default-case": 1, - - "dot-notation": [2, { - allowKeywords: false, - }], - - "guard-for-in": 1, - "no-alert": 2, - "no-caller": 2, - "no-div-regex": 1, - "no-eval": 2, - "no-extend-native": 2, - "no-extra-bind": 2, - "no-fallthrough": 2, - "no-floating-decimal": 2, - "no-implied-eval": 2, - "no-iterator": 2, - "no-labels": 2, - "no-lone-blocks": 2, - "no-multi-spaces": 2, - "no-multi-str": 1, - "no-native-reassign": 2, - "no-new": 2, - "no-new-func": 2, - "no-new-wrappers": 2, - "no-octal": 2, - "no-octal-escape": 2, - "no-process-env": 2, - "no-proto": 2, - "no-redeclare": 2, - "no-return-assign": 2, - "no-script-url": 2, - "no-self-compare": 2, - "no-sequences": 2, - "no-throw-literal": 2, - "no-unused-expressions": 2, - "no-warning-comments": 1, - "no-with": 2, - radix: 2, - "wrap-iife": 2, - - // Variables // - //-----------// - "no-catch-shadow": 2, - "no-delete-var": 2, - "no-label-var": 2, - "no-undef": 2, - "no-undef-init": 2, - - "no-unused-vars": [2, { - vars: "all", - args: "after-used", - }], - - "no-use-before-define": [2, "nofunc"], - - // Node.js // - //---------// - - // Stylistic // - //-----------// - "brace-style": [2, "1tbs", { - allowSingleLine: true, - }], - - camelcase: 2, - - "comma-spacing": [2, { - before: false, - after: true, - }], - - "comma-style": [2, "last"], - "consistent-this": [1, "self"], - "eol-last": 2, - "func-style": [2, "declaration"], - - "key-spacing": [2, { - beforeColon: false, - afterColon: true, - }], - - "new-cap": 2, - "new-parens": 2, - "no-array-constructor": 2, - "no-lonely-if": 2, - "no-mixed-spaces-and-tabs": 2, - "no-nested-ternary": 1, - "no-new-object": 2, - "no-spaced-func": 2, - "no-trailing-spaces": 2, - - "quote-props": [2, "as-needed", { - keywords: true, - }], - - quotes: [2, "single", "avoid-escape"], - semi: 2, - - "semi-spacing": [2, { - before: false, - after: true, - }], - - "space-before-blocks": [2, "always"], - - "space-before-function-paren": [2, { - anonymous: "never", - named: "never", - }], - - "space-in-parens": [2, "never"], - "space-infix-ops": 2, - "space-unary-ops": 2, - "spaced-comment": [2, "always"], - "wrap-regex": 1, - "no-var": 2, - }, +export default tseslint.config( + eslint.configs.recommended, + tseslint.configs.recommended, + { + languageOptions: { + globals: { + ...globals.browser, + }, + + parser: babelParser, + }, + + rules: { + // Possible Errors // + //-----------------// + "comma-dangle": [2, "never"], + "no-cond-assign": [2, "except-parens"], + "no-console": 1, // Allow for debugging + "no-constant-condition": 2, + "no-control-regex": 2, + "no-debugger": 1, // Allow for debugging + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-empty": 2, + "no-empty-character-class": 2, + "no-ex-assign": 2, + "no-extra-boolean-cast": 2, + "no-extra-parens": [2, "functions"], + "no-extra-semi": 2, + "no-func-assign": 2, + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-negated-in-lhs": 2, + "no-obj-calls": 2, + "no-regex-spaces": 2, + "no-unreachable": 1, // Optimizer and coverage will handle/highlight this and can be useful for debugging + "use-isnan": 2, + "valid-typeof": 2, + + // Best Practices // + //----------------// + curly: 2, + "default-case": 1, + + "dot-notation": [2, { + allowKeywords: false, + }], + + "guard-for-in": 1, + "no-alert": 2, + "no-caller": 2, + "no-div-regex": 1, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-implied-eval": 2, + "no-iterator": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-multi-spaces": 2, + "no-multi-str": 1, + "no-native-reassign": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-wrappers": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-process-env": 2, + "no-proto": 2, + "no-redeclare": 2, + "no-return-assign": 2, + "no-script-url": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-throw-literal": 2, + "no-unused-expressions": 2, + "no-warning-comments": 1, + "no-with": 2, + radix: 2, + "wrap-iife": 2, + + // Variables // + //-----------// + "no-catch-shadow": 2, + "no-delete-var": 2, + "no-label-var": 2, + "no-undef": 2, + "no-undef-init": 2, + + "no-unused-vars": [2, { + vars: "all", + args: "after-used", + }], + + "no-use-before-define": [2, "nofunc"], + + // Node.js // + //---------// + + // Stylistic // + //-----------// + "brace-style": [2, "1tbs", { + allowSingleLine: true, + }], + + camelcase: 2, + + "comma-spacing": [2, { + before: false, + after: true, + }], + + "comma-style": [2, "last"], + "consistent-this": [1, "self"], + "eol-last": 2, + "func-style": [2, "declaration"], + + "key-spacing": [2, { + beforeColon: false, + afterColon: true, + }], + + "new-cap": 2, + "new-parens": 2, + "no-array-constructor": 2, + "no-lonely-if": 2, + "no-mixed-spaces-and-tabs": 2, + "no-nested-ternary": 1, + "no-new-object": 2, + "no-spaced-func": 2, + "no-trailing-spaces": 2, + + "quote-props": [2, "as-needed", { + keywords: true, + }], + + quotes: [2, "single", "avoid-escape"], + semi: 2, + + "semi-spacing": [2, { + before: false, + after: true, + }], + + "space-before-blocks": [2, "always"], + + "space-before-function-paren": [2, { + anonymous: "never", + named: "never", + }], + + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-unary-ops": 2, + "spaced-comment": [2, "always"], + "wrap-regex": 1, + "no-var": 2, + }, + }, + { + files: ['test/**/*.js'], + languageOptions: { + globals: { + ...globals.node, + ...globals.mocha, + }, + }, + rules: { + "no-unused-expressions": 0, // Needs disabling to support Chai `.to.be.undefined` etc syntax }, - { - files: ['test/**/*.js'], - languageOptions: { - globals: { - ...globals.node, - ...globals.mocha, - }, - }, - rules: { - "no-unused-expressions": 0, // Needs disabling to support Chai `.to.be.undefined` etc syntax - }, - } -]; \ No newline at end of file + } +); \ No newline at end of file From 3de3195fbbbd867b44b3e351d8f7c2db4c800af9 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 13:54:34 +0000 Subject: [PATCH 30/46] Get linting of TypeScript working (now with the officially recommended rules) --- eslint.config.mjs | 10 +++++++--- package.json | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 696a3369b..66d04fba1 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,9 +3,15 @@ import eslint from '@eslint/js'; import tseslint from 'typescript-eslint'; import globals from "globals"; -import babelParser from "@babel/eslint-parser"; export default tseslint.config( + { + ignores: [ + "**/*", // ignore everything... + "!src/**/", "!src/**/*.ts", // ... except our TypeScript source files... + "!test/**/", "!test/**/*.js", // ... and our tests + ], + }, eslint.configs.recommended, tseslint.configs.recommended, { @@ -13,8 +19,6 @@ export default tseslint.config( globals: { ...globals.browser, }, - - parser: babelParser, }, rules: { diff --git a/package.json b/package.json index 18b86b72e..53533f4d7 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ }, "scripts": { "clean": "rm -rf lib/ dist/ coverage/ .nyc_output/", - "lint": "yarn eslint 'src/**/*.js' 'test/**/*.js'", + "lint": "yarn eslint", "build": "yarn tsc src/*.ts src/**/*.ts --module es2015 --moduleResolution bundler --target es2022 && yarn run-babel && yarn run-rollup && yarn run-uglify", "test": "nyc yarn _test", "_test": "yarn build && cross-env NODE_ENV=test yarn run-mocha", From 371437095169b4960ce184dd6b44a40a19106e45 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 13:56:46 +0000 Subject: [PATCH 31/46] yarn lint --fix --- src/convert/xml.ts | 4 ++-- src/diff/array.ts | 2 +- src/diff/base.ts | 30 +++++++++++++++--------------- src/diff/character.ts | 2 +- src/diff/css.ts | 2 +- src/diff/json.ts | 8 ++++---- src/diff/line.ts | 10 +++++----- src/diff/sentence.ts | 2 +- src/diff/word.ts | 2 +- src/patch/apply.ts | 10 +++++----- src/patch/line-endings.ts | 6 +++--- src/patch/parse.ts | 16 ++++++++-------- src/patch/reverse.ts | 4 ++-- src/util/params.ts | 2 +- src/util/string.ts | 2 +- 15 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/convert/xml.ts b/src/convert/xml.ts index e8fe15527..ab65f8ffd 100644 --- a/src/convert/xml.ts +++ b/src/convert/xml.ts @@ -1,9 +1,9 @@ import {ChangeObject} from '../types'; export function convertChangesToXML(changes: ChangeObject[]): string { - let ret = []; + const ret = []; for (let i = 0; i < changes.length; i++) { - let change = changes[i]; + const change = changes[i]; if (change.added) { ret.push(''); } else if (change.removed) { diff --git a/src/diff/array.ts b/src/diff/array.ts index c34291616..dda50e0f8 100644 --- a/src/diff/array.ts +++ b/src/diff/array.ts @@ -1,4 +1,4 @@ -import Diff from './base' +import Diff from './base'; import {CallbackOption, DiffOptionsWithoutCallback, DiffCallback, ChangeObject, DiffArraysOptions} from '../types'; class ArrayDiff extends Diff> { diff --git a/src/diff/base.ts b/src/diff/base.ts index 6f5f680b1..2a10fbded 100644 --- a/src/diff/base.ts +++ b/src/diff/base.ts @@ -36,17 +36,17 @@ export default class Diff< oldString: ValueT, newString: ValueT, options: DiffOptionsWithoutCallback - ): ChangeObject[] + ): ChangeObject[] diff( oldString: ValueT, newString: ValueT, options: DiffCallback | DiffOptionsWithCallback | DiffOptionsWithoutCallback = {} ): ChangeObject[] | undefined { - let callback: DiffCallback | undefined = undefined; + let callback: DiffCallback | undefined; if (typeof options === 'function') { callback = options; options = {}; - } else if ("callback" in options) { + } else if ('callback' in options) { callback = options.callback; } // Allow subclasses to massage the input prior to running @@ -76,7 +76,7 @@ export default class Diff< } } - let newLen = newTokens.length, oldLen = oldTokens.length; + const newLen = newTokens.length, oldLen = oldTokens.length; let editLength = 1; let maxEditLength = newLen + oldLen; if(options.maxEditLength != null) { @@ -85,7 +85,7 @@ export default class Diff< const maxExecutionTime = options.timeout ?? Infinity; const abortAfterTimestamp = Date.now() + maxExecutionTime; - let bestPath = [{ oldPos: -1, lastComponent: undefined }]; + const bestPath = [{ oldPos: -1, lastComponent: undefined }]; // Seed editLength = 0, i.e. the content starts with the same values let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options); @@ -121,7 +121,7 @@ export default class Diff< diagonalPath += 2 ) { let basePath; - let removePath = bestPath[diagonalPath - 1], + const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1]; if (removePath) { // No one else is going to attempt to use this value, clear it @@ -136,7 +136,7 @@ export default class Diff< canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen; } - let canRemove = removePath && removePath.oldPos + 1 < oldLen; + const canRemove = removePath && removePath.oldPos + 1 < oldLen; if (!canAdd && !canRemove) { // If this path is a terminal then prune // @ts-ignore @@ -190,7 +190,7 @@ export default class Diff< }()); } else { while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) { - let ret = execEditLength(); + const ret = execEditLength(); if (ret) { return ret; } @@ -205,7 +205,7 @@ export default class Diff< oldPosInc: number, options: DiffOptionsWithoutCallback ): Path { - let last = path.lastComponent; + const last = path.lastComponent; if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) { return { oldPos: path.oldPos + oldPosInc, @@ -259,7 +259,7 @@ export default class Diff< } protected removeEmpty(array: TokenT[]): TokenT[] { - let ret: TokenT[] = []; + const ret: TokenT[] = []; for (let i = 0; i < array.length; i++) { if (array[i]) { ret.push(array[i]); @@ -286,7 +286,7 @@ export default class Diff< protected postProcess( changeObjects: ChangeObject[], - options: DiffOptionsWithoutCallback, + options: DiffOptionsWithoutCallback ): ChangeObject[] { return changeObjects; } @@ -294,7 +294,7 @@ export default class Diff< protected get useLongestToken(): boolean { return false; } - + private buildValues( lastComponent: DraftChangeObject | undefined, newTokens: TokenT[], @@ -318,12 +318,12 @@ export default class Diff< oldPos = 0; for (; componentPos < componentLen; componentPos++) { - let component = components[componentPos]; + const component = components[componentPos]; if (!component.removed) { if (!component.added && this.useLongestToken) { let value = newTokens.slice(newPos, newPos + component.count); value = value.map(function(value, i) { - let oldValue = oldTokens[oldPos + i]; + const oldValue = oldTokens[oldPos + i]; return (oldValue as string).length > (value as string).length ? oldValue : value; }); @@ -345,5 +345,5 @@ export default class Diff< return components as ChangeObject[]; } -}; +} diff --git a/src/diff/character.ts b/src/diff/character.ts index 6eb410895..24866c05d 100644 --- a/src/diff/character.ts +++ b/src/diff/character.ts @@ -1,4 +1,4 @@ -import Diff from './base' +import Diff from './base'; import {CallbackOption, DiffOptionsWithoutCallback, DiffCallback, ChangeObject, DiffCharsOptions} from '../types'; class CharacterDiff extends Diff {} diff --git a/src/diff/css.ts b/src/diff/css.ts index 90f2c367e..eda81cfa6 100644 --- a/src/diff/css.ts +++ b/src/diff/css.ts @@ -1,4 +1,4 @@ -import Diff from './base' +import Diff from './base'; import { CallbackOption, ChangeObject, DiffCallback, DiffCssOptions } from '../types'; class CssDiff extends Diff { diff --git a/src/diff/json.ts b/src/diff/json.ts index 8bbdb8fc1..ece03ebda 100644 --- a/src/diff/json.ts +++ b/src/diff/json.ts @@ -1,4 +1,4 @@ -import Diff from './base' +import Diff from './base'; import { CallbackOption, ChangeObject, DiffCallback, DiffJsonOptions } from '../types'; import {lineDiff} from './line'; @@ -9,17 +9,17 @@ class JsonDiff extends Diff { return true; } - protected tokenize = lineDiff.tokenize + protected tokenize = lineDiff.tokenize; protected castInput(value: string, options: DiffJsonOptions) { const {undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v} = options; return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); - }; + } protected equals(left: string, right: string, options: DiffJsonOptions) { return super.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); - }; + } } const jsonDiff = new JsonDiff(); diff --git a/src/diff/line.ts b/src/diff/line.ts index 8ed005d4d..d908c8778 100644 --- a/src/diff/line.ts +++ b/src/diff/line.ts @@ -1,4 +1,4 @@ -import Diff from './base' +import Diff from './base'; import { CallbackOption, ChangeObject, DiffCallback, DiffLinesOptions } from '../types'; import {generateOptions} from '../util/params'; @@ -11,7 +11,7 @@ class LineDiff extends Diff { value = value.replace(/\r\n/g, '\n'); } - let retLines = [], + const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/); // Ignore the final empty token that occurs if the string ends with a new line @@ -21,7 +21,7 @@ class LineDiff extends Diff { // Merge the content and line separators into single tokens for (let i = 0; i < linesAndNewlines.length; i++) { - let line = linesAndNewlines[i]; + const line = linesAndNewlines[i]; if (i % 2 && !options.newlineIsToken) { retLines[retLines.length - 1] += line; @@ -31,7 +31,7 @@ class LineDiff extends Diff { } return retLines; - }; + } protected equals(left: string, right: string, options: DiffLinesOptions) { // If we're ignoring whitespace, we need to normalise lines by stripping @@ -57,7 +57,7 @@ class LineDiff extends Diff { } } return super.equals(left, right, options); - }; + } } export const lineDiff = new LineDiff(); diff --git a/src/diff/sentence.ts b/src/diff/sentence.ts index 0d4fd1dc0..5ba5a37ba 100644 --- a/src/diff/sentence.ts +++ b/src/diff/sentence.ts @@ -1,4 +1,4 @@ -import Diff from './base' +import Diff from './base'; import { CallbackOption, ChangeObject, DiffCallback, DiffSentencesOptions } from '../types'; class SentenceDiff extends Diff { diff --git a/src/diff/word.ts b/src/diff/word.ts index 655414805..79b889be0 100644 --- a/src/diff/word.ts +++ b/src/diff/word.ts @@ -1,4 +1,4 @@ -import Diff from './base' +import Diff from './base'; import { CallbackOption, ChangeObject, DiffCallback, DiffWordsOptions } from '../types'; import { longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap, leadingWs, trailingWs } from '../util/string'; diff --git a/src/patch/apply.ts b/src/patch/apply.ts index fc9b9c8b6..083492f3d 100644 --- a/src/patch/apply.ts +++ b/src/patch/apply.ts @@ -125,7 +125,7 @@ function applyStructuredPatch( let nConsecutiveOldContextLines = 0; let nextContextLineMustMatch = false; for (; hunkLinesI < hunkLines.length; hunkLinesI++) { - let hunkLine = hunkLines[hunkLinesI], + const hunkLine = hunkLines[hunkLinesI], operation = (hunkLine.length > 0 ? hunkLine[0] : ' '), content = (hunkLine.length > 0 ? hunkLine.substr(1) : hunkLine); @@ -231,11 +231,11 @@ function applyStructuredPatch( for (let i = 0; i < hunks.length; i++) { const hunk = hunks[i]; let hunkResult; - let maxLine = lines.length - hunk.oldLines + fuzzFactor; + const maxLine = lines.length - hunk.oldLines + fuzzFactor; let toPos; for (let maxErrors = 0; maxErrors <= fuzzFactor; maxErrors++) { toPos = hunk.oldStart + prevHunkOffset - 1; - let iterator = distanceIterator(toPos, minLine, maxLine); + const iterator = distanceIterator(toPos, minLine, maxLine); for (; toPos !== undefined; toPos = iterator()) { hunkResult = applyHunk(hunk.lines, toPos, maxErrors); if (hunkResult) { @@ -293,7 +293,7 @@ export function applyPatches(uniDiff: string | StructuredPatch[], options): void let currentIndex = 0; function processIndex(): void { - let index = uniDiff[currentIndex++]; + const index = uniDiff[currentIndex++]; if (!index) { return options.complete(); } @@ -303,7 +303,7 @@ export function applyPatches(uniDiff: string | StructuredPatch[], options): void return options.complete(err); } - let updatedContent = applyPatch(data, index, options); + const updatedContent = applyPatch(data, index, options); options.patched(index, updatedContent, function(err) { if (err) { return options.complete(err); diff --git a/src/patch/line-endings.ts b/src/patch/line-endings.ts index 01ab5c6ff..f8c2fcb46 100644 --- a/src/patch/line-endings.ts +++ b/src/patch/line-endings.ts @@ -1,4 +1,4 @@ -import { StructuredPatch } from "../types"; +import { StructuredPatch } from '../types'; export function unixToWin(patch: StructuredPatch): StructuredPatch; export function unixToWin(patches: StructuredPatch[]): StructuredPatch[]; @@ -11,7 +11,7 @@ export function unixToWin(patch: StructuredPatch | StructuredPatch[]): Structure // refuse to compile, thinking that unixToWin could then return StructuredPatch[][] and the // result would be incompatible with the overload signatures. // See bug report at https://github.com/microsoft/TypeScript/issues/61398. - return patch.map(function (p) { return unixToWin(p) }); + return patch.map(function(p) { return unixToWin(p); }); } return { @@ -34,7 +34,7 @@ export function winToUnix(patch: StructuredPatch | StructuredPatch[]): Structure export function winToUnix(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { if (Array.isArray(patch)) { // (See comment above equivalent line in unixToWin) - return patch.map(function (p) { return winToUnix(p) }); + return patch.map(function(p) { return winToUnix(p); }); } return { diff --git a/src/patch/parse.ts b/src/patch/parse.ts index d3e014833..04574bc62 100755 --- a/src/patch/parse.ts +++ b/src/patch/parse.ts @@ -6,12 +6,12 @@ export function parsePatch(uniDiff: string): StructuredPatch[] { i = 0; function parseIndex() { - let index: Partial = {}; + const index: Partial = {}; list.push(index); // Parse diff metadata while (i < diffstr.length) { - let line = diffstr[i]; + const line = diffstr[i]; // File header found, end parsing diff metadata if ((/^(\-\-\-|\+\+\+|@@)\s/).test(line)) { @@ -19,7 +19,7 @@ export function parsePatch(uniDiff: string): StructuredPatch[] { } // Diff index - let header = (/^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/).exec(line); + const header = (/^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/).exec(line); if (header) { index.index = header[1]; } @@ -36,7 +36,7 @@ export function parsePatch(uniDiff: string): StructuredPatch[] { index.hunks = []; while (i < diffstr.length) { - let line = diffstr[i]; + const line = diffstr[i]; if ((/^(Index:\s|diff\s|\-\-\-\s|\+\+\+\s|===================================================================)/).test(line)) { break; } else if ((/^@@/).test(line)) { @@ -54,7 +54,7 @@ export function parsePatch(uniDiff: string): StructuredPatch[] { function parseFileHeader(index) { const fileHeader = (/^(---|\+\+\+)\s+(.*)\r?$/).exec(diffstr[i]); if (fileHeader) { - let keyPrefix = fileHeader[1] === '---' ? 'old' : 'new'; + const keyPrefix = fileHeader[1] === '---' ? 'old' : 'new'; const data = fileHeader[2].split('\t', 2); let fileName = data[0].replace(/\\\\/g, '\\'); if ((/^".*"$/).test(fileName)) { @@ -70,11 +70,11 @@ export function parsePatch(uniDiff: string): StructuredPatch[] { // Parses a hunk // This assumes that we are at the start of a hunk. function parseHunk() { - let chunkHeaderIndex = i, + const chunkHeaderIndex = i, chunkHeaderLine = diffstr[i++], chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); - let hunk = { + const hunk = { oldStart: +chunkHeader[1], oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2], newStart: +chunkHeader[3], @@ -99,7 +99,7 @@ export function parsePatch(uniDiff: string): StructuredPatch[] { i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || diffstr[i]?.startsWith('\\')); i++ ) { - let operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0]; + const operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0]; if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') { hunk.lines.push(diffstr[i]); diff --git a/src/patch/reverse.ts b/src/patch/reverse.ts index 611bba97b..8feaafeb3 100644 --- a/src/patch/reverse.ts +++ b/src/patch/reverse.ts @@ -1,4 +1,4 @@ -import { StructuredPatch } from "../types"; +import { StructuredPatch } from '../types'; export function reversePatch(structuredPatch: StructuredPatch): StructuredPatch; export function reversePatch(structuredPatch: StructuredPatch[]): StructuredPatch[]; @@ -6,7 +6,7 @@ export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[ export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { if (Array.isArray(structuredPatch)) { // (See comment in unixToWin for why we need the pointless-looking anonymous function here) - return structuredPatch.map(function (patch) { return reversePatch(patch); }).reverse(); + return structuredPatch.map(function(patch) { return reversePatch(patch); }).reverse(); } return { diff --git a/src/util/params.ts b/src/util/params.ts index 36978341c..423a27495 100644 --- a/src/util/params.ts +++ b/src/util/params.ts @@ -8,7 +8,7 @@ export function generateOptions( if (typeof options === 'function') { (defaults as DiffOptionsWithCallback).callback = options; } else if (options) { - for (let name in options) { + for (const name in options) { /* istanbul ignore else */ if (options.hasOwnProperty(name)) { defaults[name] = options[name]; diff --git a/src/util/string.ts b/src/util/string.ts index 55dce220c..9be0603ec 100644 --- a/src/util/string.ts +++ b/src/util/string.ts @@ -66,7 +66,7 @@ function overlapCount(a: string, b: string): number { // Create a back-reference for each index // that should be followed in case of a mismatch. // We only need B to make these references: - let map = Array(endB); + const map = Array(endB); let k = 0; // Index that lags behind j map[0] = 0; for (let j = 1; j < endB; j++) { From 457e40a1341f4f0284cdd01f63399e65ca6c5430 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 13:57:05 +0000 Subject: [PATCH 32/46] Tweak some indentation that eslint broke --- src/diff/base.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diff/base.ts b/src/diff/base.ts index 2a10fbded..4dd276703 100644 --- a/src/diff/base.ts +++ b/src/diff/base.ts @@ -122,7 +122,7 @@ export default class Diff< ) { let basePath; const removePath = bestPath[diagonalPath - 1], - addPath = bestPath[diagonalPath + 1]; + addPath = bestPath[diagonalPath + 1]; if (removePath) { // No one else is going to attempt to use this value, clear it // @ts-ignore From 2f07bc3a715e085b570a79c215566c46aee400f2 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 14:08:56 +0000 Subject: [PATCH 33/46] Fix a couple of linting errors --- src/convert/dmp.ts | 4 ++-- src/diff/array.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/convert/dmp.ts b/src/convert/dmp.ts index a0563c72c..05e82d285 100644 --- a/src/convert/dmp.ts +++ b/src/convert/dmp.ts @@ -4,8 +4,8 @@ type DmpOperation = 1 | 0 | -1; // See: http://code.google.com/p/google-diff-match-patch/wiki/API export function convertChangesToDMP(changes: ChangeObject[]): [DmpOperation, ValueT][] { - let ret = [], - change, + const ret = []; + let change, operation; for (let i = 0; i < changes.length; i++) { change = changes[i]; diff --git a/src/diff/array.ts b/src/diff/array.ts index dda50e0f8..35b993f91 100644 --- a/src/diff/array.ts +++ b/src/diff/array.ts @@ -1,5 +1,5 @@ import Diff from './base'; -import {CallbackOption, DiffOptionsWithoutCallback, DiffCallback, ChangeObject, DiffArraysOptions} from '../types'; +import {CallbackOption, DiffCallback, ChangeObject, DiffArraysOptions} from '../types'; class ArrayDiff extends Diff> { protected tokenize(value: Array) { From 9c865af5f516300821ad00abcc29ce2c5ea462f8 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 14:19:45 +0000 Subject: [PATCH 34/46] Allow explicit 'any' I'm using it and it's convenient. In some places like diffArrays where we allow arrays of arbitrary type, I don't even know how to NOT use it. --- eslint.config.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index 66d04fba1..770010515 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -169,6 +169,9 @@ export default tseslint.config( "spaced-comment": [2, "always"], "wrap-regex": 1, "no-var": 2, + + // Typescript + "@typescript-eslint/no-explicit-any": 0, }, }, { From 7a13d9fa5ad7f7830a4bd398396fb41630354618 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 14:30:22 +0000 Subject: [PATCH 35/46] Eliminate needless explicit respecification of rules that eslint.configs.recommended already enables --- eslint.config.mjs | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 770010515..180b89460 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -25,39 +25,20 @@ export default tseslint.config( // Possible Errors // //-----------------// "comma-dangle": [2, "never"], - "no-cond-assign": [2, "except-parens"], "no-console": 1, // Allow for debugging - "no-constant-condition": 2, - "no-control-regex": 2, "no-debugger": 1, // Allow for debugging - "no-dupe-args": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-empty": 2, - "no-empty-character-class": 2, - "no-ex-assign": 2, - "no-extra-boolean-cast": 2, "no-extra-parens": [2, "functions"], "no-extra-semi": 2, - "no-func-assign": 2, - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, "no-negated-in-lhs": 2, - "no-obj-calls": 2, - "no-regex-spaces": 2, "no-unreachable": 1, // Optimizer and coverage will handle/highlight this and can be useful for debugging - "use-isnan": 2, - "valid-typeof": 2, // Best Practices // //----------------// curly: 2, "default-case": 1, - "dot-notation": [2, { allowKeywords: false, }], - "guard-for-in": 1, "no-alert": 2, "no-caller": 2, @@ -65,7 +46,6 @@ export default tseslint.config( "no-eval": 2, "no-extend-native": 2, "no-extra-bind": 2, - "no-fallthrough": 2, "no-floating-decimal": 2, "no-implied-eval": 2, "no-iterator": 2, @@ -77,11 +57,9 @@ export default tseslint.config( "no-new": 2, "no-new-func": 2, "no-new-wrappers": 2, - "no-octal": 2, "no-octal-escape": 2, "no-process-env": 2, "no-proto": 2, - "no-redeclare": 2, "no-return-assign": 2, "no-script-url": 2, "no-self-compare": 2, @@ -89,23 +67,14 @@ export default tseslint.config( "no-throw-literal": 2, "no-unused-expressions": 2, "no-warning-comments": 1, - "no-with": 2, radix: 2, "wrap-iife": 2, // Variables // //-----------// "no-catch-shadow": 2, - "no-delete-var": 2, "no-label-var": 2, - "no-undef": 2, "no-undef-init": 2, - - "no-unused-vars": [2, { - vars: "all", - args: "after-used", - }], - "no-use-before-define": [2, "nofunc"], // Node.js // @@ -116,24 +85,19 @@ export default tseslint.config( "brace-style": [2, "1tbs", { allowSingleLine: true, }], - camelcase: 2, - "comma-spacing": [2, { before: false, after: true, }], - "comma-style": [2, "last"], "consistent-this": [1, "self"], "eol-last": 2, "func-style": [2, "declaration"], - "key-spacing": [2, { beforeColon: false, afterColon: true, }], - "new-cap": 2, "new-parens": 2, "no-array-constructor": 2, @@ -143,26 +107,20 @@ export default tseslint.config( "no-new-object": 2, "no-spaced-func": 2, "no-trailing-spaces": 2, - "quote-props": [2, "as-needed", { keywords: true, }], - quotes: [2, "single", "avoid-escape"], semi: 2, - "semi-spacing": [2, { before: false, after: true, }], - "space-before-blocks": [2, "always"], - "space-before-function-paren": [2, { anonymous: "never", named: "never", }], - "space-in-parens": [2, "never"], "space-infix-ops": 2, "space-unary-ops": 2, From 1b6cf7ef6bb18833fc22b1308adc14064971339c Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 16:21:45 +0000 Subject: [PATCH 36/46] Fix another eslint config bug --- eslint.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index 180b89460..e8e0970c4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -142,6 +142,7 @@ export default tseslint.config( }, rules: { "no-unused-expressions": 0, // Needs disabling to support Chai `.to.be.undefined` etc syntax + "@typescript-eslint/no-unused-expressions": 0, // (as above) }, } ); \ No newline at end of file From 4809714f9e435e8461dfa19554cfa1e5ecf30c63 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 16:26:56 +0000 Subject: [PATCH 37/46] Start using arrow functions and thereby resolve a https://typescript-eslint.io/rules/no-this-alias/ error I wasn't using them before because I wasn't sure if our build process would turn them into normal functions for compatability with old JS environments; it turns out it does, so we're fine. --- src/diff/base.ts | 15 +++++++-------- src/patch/line-endings.ts | 4 ++-- src/patch/reverse.ts | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/diff/base.ts b/src/diff/base.ts index 4dd276703..0e4245f4f 100644 --- a/src/diff/base.ts +++ b/src/diff/base.ts @@ -65,9 +65,8 @@ export default class Diff< options: DiffOptionsWithoutCallback | DiffOptionsWithCallback, callback: DiffCallback | undefined ): ChangeObject[] | undefined { - const self = this; - function done(value) { - value = self.postProcess(value, options); + const done = (value) => { + value = this.postProcess(value, options); if (callback) { setTimeout(function() { callback(value); }, 0); return true; @@ -114,7 +113,7 @@ export default class Diff< let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity; // Main worker method. checks all permutations of a given edit length for acceptance. - function execEditLength() { + const execEditLength = () => { for ( let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); @@ -148,16 +147,16 @@ export default class Diff< // path whose position in the old string is the farthest from the origin // and does not pass the bounds of the diff graph if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) { - basePath = self.addToPath(addPath, true, false, 0, options); + basePath = this.addToPath(addPath, true, false, 0, options); } else { - basePath = self.addToPath(removePath, false, true, 1, options); + basePath = this.addToPath(removePath, false, true, 1, options); } - newPos = self.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options); + newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options); if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) { // If we have hit the end of both strings, then we are done - return done(self.buildValues(basePath.lastComponent, newTokens, oldTokens)); + return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)); } else { bestPath[diagonalPath] = basePath; if (basePath.oldPos + 1 >= oldLen) { diff --git a/src/patch/line-endings.ts b/src/patch/line-endings.ts index f8c2fcb46..64f65e024 100644 --- a/src/patch/line-endings.ts +++ b/src/patch/line-endings.ts @@ -11,7 +11,7 @@ export function unixToWin(patch: StructuredPatch | StructuredPatch[]): Structure // refuse to compile, thinking that unixToWin could then return StructuredPatch[][] and the // result would be incompatible with the overload signatures. // See bug report at https://github.com/microsoft/TypeScript/issues/61398. - return patch.map(function(p) { return unixToWin(p); }); + return patch.map(p => unixToWin(p)); } return { @@ -34,7 +34,7 @@ export function winToUnix(patch: StructuredPatch | StructuredPatch[]): Structure export function winToUnix(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { if (Array.isArray(patch)) { // (See comment above equivalent line in unixToWin) - return patch.map(function(p) { return winToUnix(p); }); + return patch.map(p => winToUnix(p)); } return { diff --git a/src/patch/reverse.ts b/src/patch/reverse.ts index 8feaafeb3..e56affe54 100644 --- a/src/patch/reverse.ts +++ b/src/patch/reverse.ts @@ -6,7 +6,7 @@ export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[ export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { if (Array.isArray(structuredPatch)) { // (See comment in unixToWin for why we need the pointless-looking anonymous function here) - return structuredPatch.map(function(patch) { return reversePatch(patch); }).reverse(); + return structuredPatch.map(patch => reversePatch(patch)).reverse(); } return { From fa3cb017fd2397bf33d9e3be25b4436333a68f73 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 16:30:19 +0000 Subject: [PATCH 38/46] Fix some linting errors about pointless escape sequences --- test/patch/create.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/patch/create.js b/test/patch/create.js index 1d1772ab8..0a43b18e3 100644 --- a/test/patch/create.js +++ b/test/patch/create.js @@ -686,7 +686,7 @@ describe('patch/create', function() { + '+line\n' + '\\ No newline at end of file\n'; - const diffResult = createPatch('testFileName', 'line \n\ line', 'line\n\line', undefined, undefined, {ignoreWhitespace: false}); + const diffResult = createPatch('testFileName', 'line \n line', 'line\nline', undefined, undefined, {ignoreWhitespace: false}); expect(diffResult).to.equal(expectedResult); }); @@ -697,7 +697,7 @@ describe('patch/create', function() { + '--- testFileName\n' + '+++ testFileName\n'; - const diffResult = createPatch('testFileName', 'line \n\ line', 'line\n\line', undefined, undefined, {ignoreWhitespace: true}); + const diffResult = createPatch('testFileName', 'line \n line', 'line\nline', undefined, undefined, {ignoreWhitespace: true}); expect(diffResult).to.equal(expectedResult); }); }); @@ -778,9 +778,9 @@ describe('patch/create', function() { + '--- foo\n' + '+++ bar\n' + '@@ -1,2 +1,2 @@\n' - + '\-line\n' - + '\+line\r\n' - + '\ line\n' + + '-line\n' + + '+line\r\n' + + ' line\n' + '\\ No newline at end of file\n'; expect(createTwoFilesPatch( 'foo', From 36580d067abc7cda3231f10b37cf08abdffac142 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 16:31:22 +0000 Subject: [PATCH 39/46] Fix more linting errors about pointless escape sequences --- test/diff/json.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/diff/json.js b/test/diff/json.js index e7aae0bec..4cddcb324 100644 --- a/test/diff/json.js +++ b/test/diff/json.js @@ -144,8 +144,8 @@ describe('diff/json', function() { {a: /foo/} )).to.eql([ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"a\": 123\n', added: false, removed: true }, - { count: 1, value: ' \"a\": {}\n', added: true, removed: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 1, value: ' "a": {}\n', added: true, removed: false }, { count: 1, value: '}', removed: false, added: false } ]); @@ -155,8 +155,8 @@ describe('diff/json', function() { {stringifyReplacer: (k, v) => v instanceof RegExp ? v.toString() : v} )).to.eql([ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"a\": 123\n', added: false, removed: true }, - { count: 1, value: ' \"a\": "/foo/gi"\n', added: true, removed: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 1, value: ' "a": "/foo/gi"\n', added: true, removed: false }, { count: 1, value: '}', removed: false, added: false } ]); @@ -166,8 +166,8 @@ describe('diff/json', function() { {stringifyReplacer: (k, v) => v instanceof Error ? `${v.name}: ${v.message}` : v} )).to.eql([ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"a\": 123\n', added: false, removed: true }, - { count: 1, value: ' \"a\": "Error: ohaider"\n', added: true, removed: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 1, value: ' "a": "Error: ohaider"\n', added: true, removed: false }, { count: 1, value: '}', removed: false, added: false } ]); @@ -177,8 +177,8 @@ describe('diff/json', function() { {stringifyReplacer: (k, v) => v instanceof Error ? `${v.name}: ${v.message}` : v} )).to.eql([ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"a\": 123\n', added: false, removed: true }, - { count: 3, value: ' \"a\": [\n "Error: ohaider"\n ]\n', added: true, removed: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 3, value: ' "a": [\n "Error: ohaider"\n ]\n', added: true, removed: false }, { count: 1, value: '}', removed: false, added: false } ]); }); @@ -193,8 +193,8 @@ describe('diff/json', function() { }).not.to['throw'](); expect(diff).to.eql([ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"a\": 123\n', removed: true, added: false }, - { count: 1, value: ' \"b\": 456\n', removed: false, added: true }, + { count: 1, value: ' "a": 123\n', removed: true, added: false }, + { count: 1, value: ' "b": 456\n', removed: false, added: true }, { count: 1, value: '}', removed: false, added: false } ]); }); From 9f93ce073808af1566167e97e8deabbcdb2cd9d0 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 16:33:36 +0000 Subject: [PATCH 40/46] Eliminate a util made redundant by Object.keys, and fix a linting error in the process --- test/diff/json.js | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/test/diff/json.js b/test/diff/json.js index 4cddcb324..3587d84fe 100644 --- a/test/diff/json.js +++ b/test/diff/json.js @@ -117,25 +117,25 @@ describe('diff/json', function() { describe('#canonicalize', function() { it('should put the keys in canonical order', function() { - expect(getKeys(canonicalize({b: 456, a: 123}))).to.eql(['a', 'b']); + expect(Object.keys(canonicalize({b: 456, a: 123}))).to.eql(['a', 'b']); }); it('should dive into nested objects', function() { const canonicalObj = canonicalize({b: 456, a: {d: 123, c: 456}}); - expect(getKeys(canonicalObj.a)).to.eql(['c', 'd']); + expect(Object.keys(canonicalObj.a)).to.eql(['c', 'd']); }); it('should dive into nested arrays', function() { const canonicalObj = canonicalize({b: 456, a: [789, {d: 123, c: 456}]}); - expect(getKeys(canonicalObj.a[1])).to.eql(['c', 'd']); + expect(Object.keys(canonicalObj.a[1])).to.eql(['c', 'd']); }); it('should handle circular references correctly', function() { const obj = {b: 456}; obj.a = obj; const canonicalObj = canonicalize(obj); - expect(getKeys(canonicalObj)).to.eql(['a', 'b']); - expect(getKeys(canonicalObj.a)).to.eql(['a', 'b']); + expect(Object.keys(canonicalObj)).to.eql(['a', 'b']); + expect(Object.keys(canonicalObj.a)).to.eql(['a', 'b']); }); it('should accept a custom JSON.stringify() replacer function', function() { @@ -200,13 +200,3 @@ describe('diff/json', function() { }); }); }); - -function getKeys(obj) { - const keys = []; - for (let key in obj) { - if (obj.hasOwnProperty(key)) { - keys.push(key); - } - } - return keys; -} From 91885c86ab0f9c2fbad5c0446bac6718d09daf6a Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 16:36:11 +0000 Subject: [PATCH 41/46] Fix a no-prototype-builtins linting error --- src/util/params.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/params.ts b/src/util/params.ts index 423a27495..89bf6e24b 100644 --- a/src/util/params.ts +++ b/src/util/params.ts @@ -10,7 +10,7 @@ export function generateOptions( } else if (options) { for (const name in options) { /* istanbul ignore else */ - if (options.hasOwnProperty(name)) { + if (Object.prototype.hasOwnProperty.call(options, name)) { defaults[name] = options[name]; } } From 5e6adc709ae5bfc23a90d24f5a327044c2e28e23 Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 16:53:25 +0000 Subject: [PATCH 42/46] Disable no-use-before-define for TypeScript code, where it's broken --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index e8e0970c4..b2a7932d8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -75,7 +75,6 @@ export default tseslint.config( "no-catch-shadow": 2, "no-label-var": 2, "no-undef-init": 2, - "no-use-before-define": [2, "nofunc"], // Node.js // //---------// @@ -143,6 +142,7 @@ export default tseslint.config( rules: { "no-unused-expressions": 0, // Needs disabling to support Chai `.to.be.undefined` etc syntax "@typescript-eslint/no-unused-expressions": 0, // (as above) + "no-use-before-define": [2, "nofunc"], // Useful rule but broken for TypeScript code }, } ); \ No newline at end of file From 3fe6e5717fc6b66432a11e8560588954861e03fe Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 17:00:07 +0000 Subject: [PATCH 43/46] Liberalise more rules --- eslint.config.mjs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index b2a7932d8..357400d35 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -127,8 +127,17 @@ export default tseslint.config( "wrap-regex": 1, "no-var": 2, - // Typescript - "@typescript-eslint/no-explicit-any": 0, + // Typescript // + //------------// + "@typescript-eslint/no-explicit-any": 0, // Very strict rule, incompatible with our code + + // We use these intentionally - e.g. + // export interface DiffCssOptions extends CommonDiffOptions {} + // for the options argument to diffCss which currently takes no options beyond the ones + // common to all diffFoo functions. Doing this allows consistency (one options interface per + // diffFoo function) and future-proofs against the API having to change in future if we add a + // non-common option to one of these functions. + "@typescript-eslint/no-empty-object-type": [2, {allowInterfaces: 'with-single-extends'}], }, }, { From ed5aaa0982f5abdb4b40f4c7954a37d24098016f Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 17:33:41 +0000 Subject: [PATCH 44/46] Fix lint errors in parse.ts --- src/patch/parse.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/patch/parse.ts b/src/patch/parse.ts index 04574bc62..965ccba26 100755 --- a/src/patch/parse.ts +++ b/src/patch/parse.ts @@ -1,9 +1,9 @@ import { StructuredPatch } from '../types'; export function parsePatch(uniDiff: string): StructuredPatch[] { - let diffstr = uniDiff.split(/\n/), - list: Partial[] = [], - i = 0; + const diffstr = uniDiff.split(/\n/), + list: Partial[] = []; + let i = 0; function parseIndex() { const index: Partial = {}; @@ -14,7 +14,7 @@ export function parsePatch(uniDiff: string): StructuredPatch[] { const line = diffstr[i]; // File header found, end parsing diff metadata - if ((/^(\-\-\-|\+\+\+|@@)\s/).test(line)) { + if ((/^(---|\+\+\+|@@)\s/).test(line)) { break; } @@ -37,7 +37,7 @@ export function parsePatch(uniDiff: string): StructuredPatch[] { while (i < diffstr.length) { const line = diffstr[i]; - if ((/^(Index:\s|diff\s|\-\-\-\s|\+\+\+\s|===================================================================)/).test(line)) { + if ((/^(Index:\s|diff\s|---\s|\+\+\+\s|===================================================================)/).test(line)) { break; } else if ((/^@@/).test(line)) { index.hunks.push(parseHunk()); From 3a52b9ec4ec02e564259e3fdeabc376c3bf5055e Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 17:35:14 +0000 Subject: [PATCH 45/46] Fix lint errors in apply.ts --- src/patch/apply.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/patch/apply.ts b/src/patch/apply.ts index 083492f3d..2d9ed4cd4 100644 --- a/src/patch/apply.ts +++ b/src/patch/apply.ts @@ -45,12 +45,11 @@ function applyStructuredPatch( } // Apply the diff to the input - let lines = source.split('\n'), - hunks = patch.hunks, - - compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), - fuzzFactor = options.fuzzFactor || 0, - minLine = 0; + const lines = source.split('\n'), + hunks = patch.hunks, + compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), + fuzzFactor = options.fuzzFactor || 0; + let minLine = 0; if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) { throw new Error('fuzzFactor must be a non-negative integer'); From 173bc0358d8357d072d34e17e4971ab969a3897e Mon Sep 17 00:00:00 2001 From: Mark Amery Date: Fri, 14 Mar 2025 17:45:45 +0000 Subject: [PATCH 46/46] Fix remaining linting errors --- src/diff/base.ts | 23 ++++++++++++----------- src/diff/character.ts | 2 +- src/diff/json.ts | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/diff/base.ts b/src/diff/base.ts index 0e4245f4f..68bd8f212 100644 --- a/src/diff/base.ts +++ b/src/diff/base.ts @@ -73,7 +73,7 @@ export default class Diff< } else { return value; } - } + }; const newLen = newTokens.length, oldLen = oldTokens.length; let editLength = 1; @@ -123,8 +123,7 @@ export default class Diff< const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1]; if (removePath) { - // No one else is going to attempt to use this value, clear it - // @ts-ignore + // @ts-expect-error (Nothing will ever read this element; we're doing this to free memory) bestPath[diagonalPath - 1] = undefined; } @@ -137,8 +136,7 @@ export default class Diff< const canRemove = removePath && removePath.oldPos + 1 < oldLen; if (!canAdd && !canRemove) { - // If this path is a terminal then prune - // @ts-ignore + // @ts-expect-error (Nothing will ever read this element; we're doing this to free memory) bestPath[diagonalPath] = undefined; continue; } @@ -169,7 +167,7 @@ export default class Diff< } editLength++; - } + }; // Performs the length of edit iteration. Is a bit fugly as this has to support the // sync and async mode which is never fun. Loops over execEditLength until a value @@ -225,12 +223,12 @@ export default class Diff< diagonalPath: number, options: DiffOptionsWithoutCallback ): number { - let newLen = newTokens.length, - oldLen = oldTokens.length, - oldPos = basePath.oldPos, + const newLen = newTokens.length, + oldLen = oldTokens.length; + let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, - commonCount = 0; + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) { newPos++; oldPos++; @@ -267,10 +265,12 @@ export default class Diff< return ret; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars protected castInput(value: ValueT, options: DiffOptionsWithoutCallback): ValueT { return value; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars protected tokenize(value: ValueT, options: DiffOptionsWithoutCallback): TokenT[] { return Array.from(value); } @@ -285,6 +285,7 @@ export default class Diff< protected postProcess( changeObjects: ChangeObject[], + // eslint-disable-next-line @typescript-eslint/no-unused-vars options: DiffOptionsWithoutCallback ): ChangeObject[] { return changeObjects; @@ -311,8 +312,8 @@ export default class Diff< } components.reverse(); + const componentLen = components.length; let componentPos = 0, - componentLen = components.length, newPos = 0, oldPos = 0; diff --git a/src/diff/character.ts b/src/diff/character.ts index 24866c05d..67d70dcad 100644 --- a/src/diff/character.ts +++ b/src/diff/character.ts @@ -1,5 +1,5 @@ import Diff from './base'; -import {CallbackOption, DiffOptionsWithoutCallback, DiffCallback, ChangeObject, DiffCharsOptions} from '../types'; +import {CallbackOption, DiffCallback, ChangeObject, DiffCharsOptions} from '../types'; class CharacterDiff extends Diff {} diff --git a/src/diff/json.ts b/src/diff/json.ts index ece03ebda..e36850449 100644 --- a/src/diff/json.ts +++ b/src/diff/json.ts @@ -76,8 +76,8 @@ export function canonicalize(obj: any, stack: Array | null, replacementStac stack.push(obj); canonicalizedObj = {}; replacementStack.push(canonicalizedObj); - let sortedKeys = [], - key; + const sortedKeys = []; + let key; for (key in obj) { /* istanbul ignore else */ if (Object.prototype.hasOwnProperty.call(obj, key)) {