From 8d4acbdd9c9f36d078bb1262aa733e8201047a09 Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Sun, 31 Dec 2023 19:26:50 +0000 Subject: [PATCH 01/12] refactor(assert): prepare for noUncheckedIndexedAccess --- assert/_diff.ts | 70 +++++++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index c89ac4ce0eda..314f429849ed 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -35,13 +35,13 @@ const COMMON = 2; const ADDED = 3; function createCommon(A: T[], B: T[], reverse?: boolean): T[] { - const common = []; + const common: T[] = []; if (A.length === 0 || B.length === 0) return []; for (let i = 0; i < Math.min(A.length, B.length); i += 1) { if ( A[reverse ? A.length - i - 1 : i] === B[reverse ? B.length - i - 1 : i] ) { - common.push(A[reverse ? A.length - i - 1 : i]); + common.push(A[reverse ? A.length - i - 1 : i]!); } else { return common; } @@ -95,6 +95,14 @@ export function diff(A: T[], B: T[]): Array> { { length: size }, () => ({ y: -1, id: -1 }), ); + function getPoint(index: number) { + const point = fp[index]; + if (!point) { + throw Error(`Missing FarthestPoint at index ${index}`); + } + return point; + } + /** * INFO: * This buffer is used to save memory and improve performance. @@ -119,28 +127,28 @@ export function diff(A: T[], B: T[]): Array> { }> { const M = A.length; const N = B.length; - const result = []; + const result: { type: DiffType; value: T }[] = []; let a = M - 1; let b = N - 1; let j = routes[current.id]; let type = routes[current.id + diffTypesPtrOffset]; while (true) { if (!j && !type) break; - const prev = j; + const prev = j!; if (type === REMOVED) { result.unshift({ type: swapped ? DiffType.removed : DiffType.added, - value: B[b], + value: B[b]!, }); b -= 1; } else if (type === ADDED) { result.unshift({ type: swapped ? DiffType.added : DiffType.removed, - value: A[a], + value: A[a]!, }); a -= 1; } else { - result.unshift({ type: DiffType.common, value: A[a] }); + result.unshift({ type: DiffType.common, value: A[a]! }); a -= 1; b -= 1; } @@ -201,13 +209,13 @@ export function diff(A: T[], B: T[]): Array> { return fp; } - while (fp[delta + offset].y < N) { + while (getPoint(delta + offset).y < N) { p = p + 1; for (let k = -p; k < delta; ++k) { fp[k + offset] = snake( k, - fp[k - 1 + offset], - fp[k + 1 + offset], + getPoint(k - 1 + offset), + getPoint(k + 1 + offset), offset, A, B, @@ -216,8 +224,8 @@ export function diff(A: T[], B: T[]): Array> { for (let k = delta + p; k > delta; --k) { fp[k + offset] = snake( k, - fp[k - 1 + offset], - fp[k + 1 + offset], + getPoint(k - 1 + offset), + getPoint(k + 1 + offset), offset, A, B, @@ -225,8 +233,8 @@ export function diff(A: T[], B: T[]): Array> { } fp[delta + offset] = snake( delta, - fp[delta - 1 + offset], - fp[delta + 1 + offset], + getPoint(delta - 1 + offset), + getPoint(delta + 1 + offset), offset, A, B, @@ -236,7 +244,7 @@ export function diff(A: T[], B: T[]): Array> { ...prefixCommon.map( (c): DiffResult => ({ type: DiffType.common, value: c }), ), - ...backTrace(A, B, fp[delta + offset], swapped), + ...backTrace(A, B, getPoint(delta + offset), swapped), ...suffixCommon.map( (c): DiffResult => ({ type: DiffType.common, value: c }), ), @@ -274,11 +282,16 @@ export function diffstr(A: string, B: string) { // Join boundary splits that we do not consider to be boundaries and merge empty strings surrounded by word chars for (let i = 0; i < tokens.length - 1; i++) { + const token = tokens[i]; + const tokenPlusTwo = tokens[i + 2]; if ( - !tokens[i + 1] && tokens[i + 2] && words.test(tokens[i]) && - words.test(tokens[i + 2]) + !tokens[i + 1] && + token && + tokenPlusTwo && + words.test(token) && + words.test(tokenPlusTwo) ) { - tokens[i] += tokens[i + 2]; + tokens[i] += tokenPlusTwo; tokens.splice(i + 1, 2); i--; } @@ -286,7 +299,7 @@ export function diffstr(A: string, B: string) { return tokens.filter((token) => token); } else { // Split string on new lines symbols - const tokens = [], lines = string.split(/(\n|\r\n)/); + const tokens: string[] = [], lines = string.split(/(\n|\r\n)/); // Ignore final empty token when text ends with a newline if (!lines[lines.length - 1]) { @@ -294,11 +307,11 @@ export function diffstr(A: string, B: string) { } // Merge the content and line separators into single tokens - for (let i = 0; i < lines.length; i++) { + for (const [i, line] of lines.entries()) { if (i % 2) { - tokens[tokens.length - 1] += lines[i]; + tokens[tokens.length - 1] += line; } else { - tokens.push(lines[i]); + tokens.push(line); } } return tokens; @@ -314,13 +327,14 @@ export function diffstr(A: string, B: string) { return tokens.filter(({ type }) => type === line.type || type === DiffType.common ).map((result, i, t) => { + const token = t[i - 1]; if ( - (result.type === DiffType.common) && (t[i - 1]) && - (t[i - 1]?.type === t[i + 1]?.type) && /\s+/.test(result.value) + (result.type === DiffType.common) && token && + (token.type === t[i + 1]?.type) && /\s+/.test(result.value) ) { return { ...result, - type: t[i - 1].type, + type: token.type, }; } return result; @@ -356,7 +370,7 @@ export function diffstr(A: string, B: string) { const tokenized = [ tokenize(a.value, { wordDiff: true }), tokenize(b?.value ?? "", { wordDiff: true }), - ] as string[][]; + ] as [string[], string[]]; if (hasMoreRemovedLines) tokenized.reverse(); tokens = diff(tokenized[0], tokenized[1]); if ( @@ -443,3 +457,7 @@ export function buildMessage( return messages; } + +Deno.bench("_diff", () => { + diff(["abc", "c"], ["abc", "bcd", "c"]); +}); From 30298f84f34f3222a1d7aec0d59deb3b5af5660c Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Sun, 31 Dec 2023 19:31:35 +0000 Subject: [PATCH 02/12] remove bad bench --- assert/_diff.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index 314f429849ed..fd351c37b906 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -457,7 +457,3 @@ export function buildMessage( return messages; } - -Deno.bench("_diff", () => { - diff(["abc", "c"], ["abc", "bcd", "c"]); -}); From bac997fab48e008aae439183fe948e5afa3ef300 Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Sun, 31 Dec 2023 20:16:27 +0000 Subject: [PATCH 03/12] use more undefined where expected --- assert/_diff.ts | 51 ++++++++++++++++++++++++++----------------------- deno.json | 1 + 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index fd351c37b906..89557c13f7ad 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -95,13 +95,6 @@ export function diff(A: T[], B: T[]): Array> { { length: size }, () => ({ y: -1, id: -1 }), ); - function getPoint(index: number) { - const point = fp[index]; - if (!point) { - throw Error(`Missing FarthestPoint at index ${index}`); - } - return point; - } /** * INFO: @@ -159,37 +152,38 @@ export function diff(A: T[], B: T[]): Array> { } function createFP( - slide: FarthestPoint, - down: FarthestPoint, + slide: FarthestPoint | undefined, + down: FarthestPoint | undefined, k: number, M: number, ): FarthestPoint { if (slide && slide.y === -1 && down && down.y === -1) { return { y: 0, id: 0 }; } - if ( - (down && down.y === -1) || + const isAdding = (down?.y === -1) || k === M || - (slide && slide.y) > (down && down.y) + 1 - ) { + (slide?.y || 0) > (down?.y || 0) + 1; + if (slide && isAdding) { const prev = slide.id; ptr++; routes[ptr] = prev; routes[ptr + diffTypesPtrOffset] = ADDED; return { y: slide.y, id: ptr }; - } else { + } else if (down && !isAdding) { const prev = down.id; ptr++; routes[ptr] = prev; routes[ptr + diffTypesPtrOffset] = REMOVED; return { y: down.y + 1, id: ptr }; + } else { + throw new Error("Unexpected FarthestPoint"); } } function snake( k: number, - slide: FarthestPoint, - down: FarthestPoint, + slide: FarthestPoint | undefined, + down: FarthestPoint | undefined, _offset: number, A: T[], B: T[], @@ -209,13 +203,21 @@ export function diff(A: T[], B: T[]): Array> { return fp; } - while (getPoint(delta + offset).y < N) { + function ensureDefined(item: T | undefined): T { + if (!item) { + throw Error("durr"); + } + return item; + } + + let currentFP: FarthestPoint = ensureDefined(fp[delta + offset]); + while (currentFP && currentFP.y < N) { p = p + 1; for (let k = -p; k < delta; ++k) { fp[k + offset] = snake( k, - getPoint(k - 1 + offset), - getPoint(k + 1 + offset), + fp[k - 1 + offset], + fp[k + 1 + offset], offset, A, B, @@ -224,8 +226,8 @@ export function diff(A: T[], B: T[]): Array> { for (let k = delta + p; k > delta; --k) { fp[k + offset] = snake( k, - getPoint(k - 1 + offset), - getPoint(k + 1 + offset), + fp[k - 1 + offset], + fp[k + 1 + offset], offset, A, B, @@ -233,18 +235,19 @@ export function diff(A: T[], B: T[]): Array> { } fp[delta + offset] = snake( delta, - getPoint(delta - 1 + offset), - getPoint(delta + 1 + offset), + fp[delta - 1 + offset], + fp[delta + 1 + offset], offset, A, B, ); + currentFP = ensureDefined(fp[delta + offset]); } return [ ...prefixCommon.map( (c): DiffResult => ({ type: DiffType.common, value: c }), ), - ...backTrace(A, B, getPoint(delta + offset), swapped), + ...backTrace(A, B, currentFP, swapped), ...suffixCommon.map( (c): DiffResult => ({ type: DiffType.common, value: c }), ), diff --git a/deno.json b/deno.json index 371d60e8e37d..9f292bb5720e 100644 --- a/deno.json +++ b/deno.json @@ -2,6 +2,7 @@ "compilerOptions": { "strict": true, "useUnknownInCatchVariables": true, + "noUncheckedIndexedAccess": true, "noImplicitOverride": true }, "imports": { From ac23b928cdead9c0453a3a476cb10f8bd8916399 Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Sun, 31 Dec 2023 20:16:43 +0000 Subject: [PATCH 04/12] no deno.json change --- deno.json | 1 - 1 file changed, 1 deletion(-) diff --git a/deno.json b/deno.json index 9f292bb5720e..371d60e8e37d 100644 --- a/deno.json +++ b/deno.json @@ -2,7 +2,6 @@ "compilerOptions": { "strict": true, "useUnknownInCatchVariables": true, - "noUncheckedIndexedAccess": true, "noImplicitOverride": true }, "imports": { From 0b6ea8755a6cc5d6934a3d46e3814f7a948ea13e Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Sun, 31 Dec 2023 20:17:48 +0000 Subject: [PATCH 05/12] better error message --- assert/_diff.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index 89557c13f7ad..c11c04f5bb0a 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -176,7 +176,7 @@ export function diff(A: T[], B: T[]): Array> { routes[ptr + diffTypesPtrOffset] = REMOVED; return { y: down.y + 1, id: ptr }; } else { - throw new Error("Unexpected FarthestPoint"); + throw new Error("Unexpected missing FarthestPoint"); } } @@ -205,7 +205,7 @@ export function diff(A: T[], B: T[]): Array> { function ensureDefined(item: T | undefined): T { if (!item) { - throw Error("durr"); + throw Error("Unexpected missing FarthestPoint"); } return item; } From 3fe10904e2e767a1c788bd055e12b749860f9668 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Tue, 2 Jan 2024 07:58:14 +1100 Subject: [PATCH 06/12] tweak --- assert/_diff.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index c11c04f5bb0a..00c913d19f0a 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -38,10 +38,10 @@ function createCommon(A: T[], B: T[], reverse?: boolean): T[] { const common: T[] = []; if (A.length === 0 || B.length === 0) return []; for (let i = 0; i < Math.min(A.length, B.length); i += 1) { - if ( - A[reverse ? A.length - i - 1 : i] === B[reverse ? B.length - i - 1 : i] - ) { - common.push(A[reverse ? A.length - i - 1 : i]!); + const a = reverse ? A[A.length - i - 1] : A[i]; + const b = reverse ? B[B.length - i - 1] : B[i]; + if (a !== undefined && a === b) { + common.push(a); } else { return common; } From 99dce77855dac4c31650070ddb922970843012a5 Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Wed, 3 Jan 2024 13:13:39 +0000 Subject: [PATCH 07/12] One line per const declaration Co-authored-by: Asher Gomez --- assert/_diff.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index 0398f870ab55..d6565fe021ae 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -302,7 +302,8 @@ export function diffstr(A: string, B: string) { return tokens.filter((token) => token); } else { // Split string on new lines symbols - const tokens: string[] = [], lines = string.split(/(\n|\r\n)/); + const tokens: string[] = []; + const lines = string.split(/(\n|\r\n)/); // Ignore final empty token when text ends with a newline if (!lines[lines.length - 1]) { From 99893d0eb7a3ba494300831f0d3a82a2b2ec3641 Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Wed, 3 Jan 2024 13:14:22 +0000 Subject: [PATCH 08/12] Replace undefined type with optional parameter Co-authored-by: Asher Gomez --- assert/_diff.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index d6565fe021ae..194aea817a6c 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -152,8 +152,8 @@ export function diff(A: T[], B: T[]): Array> { } function createFP( - slide: FarthestPoint | undefined, - down: FarthestPoint | undefined, + slide?: FarthestPoint, + down?: FarthestPoint, k: number, M: number, ): FarthestPoint { From 94df1b6da72d64b7c60f9a6068715c453e3d1d42 Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Wed, 3 Jan 2024 13:14:46 +0000 Subject: [PATCH 09/12] Replace undefined type with optional parameter again Co-authored-by: Asher Gomez --- assert/_diff.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index 194aea817a6c..1a1c4f4bcb71 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -182,8 +182,8 @@ export function diff(A: T[], B: T[]): Array> { function snake( k: number, - slide: FarthestPoint | undefined, - down: FarthestPoint | undefined, + slide?: FarthestPoint, + down?: FarthestPoint, _offset: number, A: T[], B: T[], From f852d18c06ba5c8796ac1b1217d1fc5f2c6aecfe Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Wed, 3 Jan 2024 13:15:24 +0000 Subject: [PATCH 10/12] Pass type to ensureDefined Co-authored-by: Asher Gomez --- assert/_diff.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index 1a1c4f4bcb71..0c89afc94038 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -210,7 +210,7 @@ export function diff(A: T[], B: T[]): Array> { return item; } - let currentFP: FarthestPoint = ensureDefined(fp[delta + offset]); + let currentFP = ensureDefined(fp[delta + offset]); while (currentFP && currentFP.y < N) { p = p + 1; for (let k = -p; k < delta; ++k) { From da21e49241ef0edb2ff6048ee27877c7172c0ffc Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Wed, 3 Jan 2024 13:20:04 +0000 Subject: [PATCH 11/12] ensureDefined moved to top level --- assert/_diff.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index 0c89afc94038..aebef60de744 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -49,6 +49,13 @@ function createCommon(A: T[], B: T[], reverse?: boolean): T[] { return common; } +function ensureDefined(item?: T): T { + if (item === undefined) { + throw Error("Unexpected missing FarthestPoint"); + } + return item; +} + /** * Renders the differences between the actual and expected values * @param A Actual value @@ -203,13 +210,6 @@ export function diff(A: T[], B: T[]): Array> { return fp; } - function ensureDefined(item: T | undefined): T { - if (!item) { - throw Error("Unexpected missing FarthestPoint"); - } - return item; - } - let currentFP = ensureDefined(fp[delta + offset]); while (currentFP && currentFP.y < N) { p = p + 1; From 8aedb7c6f386922fa94fd0fbfb2bab0ab670e83f Mon Sep 17 00:00:00 2001 From: Simon Holloway Date: Wed, 3 Jan 2024 13:28:58 +0000 Subject: [PATCH 12/12] slide/down FarthestPoint changed the optional param back to or undefined --- assert/_diff.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assert/_diff.ts b/assert/_diff.ts index aebef60de744..f75f0ff87142 100644 --- a/assert/_diff.ts +++ b/assert/_diff.ts @@ -159,8 +159,8 @@ export function diff(A: T[], B: T[]): Array> { } function createFP( - slide?: FarthestPoint, - down?: FarthestPoint, + slide: FarthestPoint | undefined, + down: FarthestPoint | undefined, k: number, M: number, ): FarthestPoint { @@ -189,8 +189,8 @@ export function diff(A: T[], B: T[]): Array> { function snake( k: number, - slide?: FarthestPoint, - down?: FarthestPoint, + slide: FarthestPoint | undefined, + down: FarthestPoint | undefined, _offset: number, A: T[], B: T[],