'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 };