node/lib/internal/assert/myers_diff.js
Giovanni Bucci 6b42554342
Some checks are pending
Coverage Linux (without intl) / coverage-linux-without-intl (push) Waiting to run
Coverage Linux / coverage-linux (push) Waiting to run
Coverage Windows / coverage-windows (push) Waiting to run
Test and upload documentation to artifacts / build-docs (push) Waiting to run
Linters / lint-addon-docs (push) Waiting to run
Linters / lint-cpp (push) Waiting to run
Linters / format-cpp (push) Waiting to run
Linters / lint-js-and-md (push) Waiting to run
Linters / lint-py (push) Waiting to run
Linters / lint-yaml (push) Waiting to run
Linters / lint-sh (push) Waiting to run
Linters / lint-codeowners (push) Waiting to run
Linters / lint-pr-url (push) Waiting to run
Linters / lint-readme (push) Waiting to run
Notify on Push / Notify on Force Push on `main` (push) Waiting to run
Notify on Push / Notify on Push on `main` that lacks metadata (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
util: expose diff function used by the assertion errors
fix: https://github.com/nodejs/node/issues/51740
PR-URL: https://github.com/nodejs/node/pull/57462
Fixes: https://github.com/nodejs/node/issues/51740
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Pietro Marchini <pietro.marchini94@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
2025-03-19 23:59:07 +00:00

177 lines
5.0 KiB
JavaScript

'use strict';
const {
ArrayPrototypePush,
Int32Array,
StringPrototypeEndsWith,
} = primordials;
const colors = require('internal/util/colors');
const kNopLinesToCollapse = 5;
const kOperations = {
DELETE: -1,
NOP: 0,
INSERT: 1,
};
function areLinesEqual(actual, expected, checkCommaDisparity) {
if (actual === expected) {
return true;
}
if (checkCommaDisparity) {
return (actual + ',') === expected || actual === (expected + ',');
}
return false;
}
function myersDiff(actual, expected, checkCommaDisparity = false) {
const actualLength = actual.length;
const expectedLength = expected.length;
const max = actualLength + expectedLength;
const v = new Int32Array(2 * max + 1);
const trace = [];
for (let diffLevel = 0; diffLevel <= max; diffLevel++) {
ArrayPrototypePush(trace, new Int32Array(v)); // Clone the current state of `v`
for (let diagonalIndex = -diffLevel; diagonalIndex <= diffLevel; diagonalIndex += 2) {
const offset = diagonalIndex + max;
const previousOffset = v[offset - 1];
const nextOffset = v[offset + 1];
let x = diagonalIndex === -diffLevel || (diagonalIndex !== diffLevel && previousOffset < nextOffset) ?
nextOffset :
previousOffset + 1;
let y = x - diagonalIndex;
while (
x < actualLength &&
y < expectedLength &&
areLinesEqual(actual[x], expected[y], checkCommaDisparity)
) {
x++;
y++;
}
v[offset] = x;
if (x >= actualLength && y >= expectedLength) {
return backtrack(trace, actual, expected, checkCommaDisparity);
}
}
}
}
function backtrack(trace, actual, expected, checkCommaDisparity) {
const actualLength = actual.length;
const expectedLength = expected.length;
const max = actualLength + expectedLength;
let x = actualLength;
let y = expectedLength;
const result = [];
for (let diffLevel = trace.length - 1; diffLevel >= 0; diffLevel--) {
const v = trace[diffLevel];
const diagonalIndex = x - y;
const offset = diagonalIndex + max;
let prevDiagonalIndex;
if (
diagonalIndex === -diffLevel ||
(diagonalIndex !== diffLevel && v[offset - 1] < v[offset + 1])
) {
prevDiagonalIndex = diagonalIndex + 1;
} else {
prevDiagonalIndex = diagonalIndex - 1;
}
const prevX = v[prevDiagonalIndex + max];
const prevY = prevX - prevDiagonalIndex;
while (x > prevX && y > prevY) {
const actualItem = actual[x - 1];
const value = checkCommaDisparity && !StringPrototypeEndsWith(actualItem, ',') ? expected[y - 1] : actualItem;
ArrayPrototypePush(result, [ kOperations.NOP, value ]);
x--;
y--;
}
if (diffLevel > 0) {
if (x > prevX) {
ArrayPrototypePush(result, [ kOperations.INSERT, actual[--x] ]);
} else {
ArrayPrototypePush(result, [ kOperations.DELETE, expected[--y] ]);
}
}
}
return result;
}
function printSimpleMyersDiff(diff) {
let message = '';
for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) {
const { 0: operation, 1: value } = diff[diffIdx];
let color = colors.white;
if (operation === kOperations.INSERT) {
color = colors.green;
} else if (operation === kOperations.DELETE) {
color = colors.red;
}
message += `${color}${value}${colors.white}`;
}
return `\n${message}`;
}
function printMyersDiff(diff, operator) {
let message = '';
let skipped = false;
let nopCount = 0;
for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) {
const { 0: operation, 1: value } = diff[diffIdx];
const previousOperation = diffIdx < diff.length - 1 ? diff[diffIdx + 1][0] : null;
// Avoid grouping if only one line would have been grouped otherwise
if (previousOperation === kOperations.NOP && operation !== previousOperation) {
if (nopCount === kNopLinesToCollapse + 1) {
message += `${colors.white} ${diff[diffIdx + 1][1]}\n`;
} else if (nopCount === kNopLinesToCollapse + 2) {
message += `${colors.white} ${diff[diffIdx + 2][1]}\n`;
message += `${colors.white} ${diff[diffIdx + 1][1]}\n`;
} else if (nopCount >= kNopLinesToCollapse + 3) {
message += `${colors.blue}...${colors.white}\n`;
message += `${colors.white} ${diff[diffIdx + 1][1]}\n`;
skipped = true;
}
nopCount = 0;
}
if (operation === kOperations.INSERT) {
if (operator === 'partialDeepStrictEqual') {
message += `${colors.gray}${colors.hasColors ? ' ' : '+'} ${value}${colors.white}\n`;
} else {
message += `${colors.green}+${colors.white} ${value}\n`;
}
} else if (operation === kOperations.DELETE) {
message += `${colors.red}-${colors.white} ${value}\n`;
} else if (operation === kOperations.NOP) {
if (nopCount < kNopLinesToCollapse) {
message += `${colors.white} ${value}\n`;
}
nopCount++;
}
}
message = message.trimEnd();
return { message: `\n${message}`, skipped };
}
module.exports = { myersDiff, printMyersDiff, printSimpleMyersDiff };