nhb-toolbox
Version:
A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.
159 lines (158 loc) • 5.26 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.computeTextDiff = computeTextDiff;
exports.getCharacterDifferences = getCharacterDifferences;
const helpers_1 = require("./helpers");
function computeTextDiff(originalText, modifiedText) {
const originalLines = originalText.split('\n');
const modifiedLines = modifiedText.split('\n');
const originalLen = originalLines.length;
const modifiedLen = modifiedLines.length;
const lcs = Array(originalLen + 1)
.fill(null)
.map(() => Array(modifiedLen + 1).fill(0));
for (let i = 1; i <= originalLen; i++) {
for (let j = 1; j <= modifiedLen; j++) {
if (originalLines[i - 1] === modifiedLines[j - 1]) {
lcs[i][j] = lcs[i - 1][j - 1] + 1;
}
else {
lcs[i][j] = Math.max(lcs[i - 1][j], lcs[i][j - 1]);
}
}
}
const diffLines = [];
let i = originalLen;
let j = modifiedLen;
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && originalLines[i - 1] === modifiedLines[j - 1]) {
diffLines.unshift({
type: 'unchanged',
original: originalLines[i - 1],
modified: modifiedLines[j - 1],
originalLineNum: i,
modifiedLineNum: j,
});
i--;
j--;
}
else if (j > 0 && (i === 0 || lcs[i][j - 1] >= lcs[i - 1][j])) {
diffLines.unshift({
type: 'added',
modified: modifiedLines[j - 1],
modifiedLineNum: j,
});
j--;
}
else if (i > 0) {
diffLines.unshift({
type: 'removed',
original: originalLines[i - 1],
originalLineNum: i,
});
i--;
}
}
const processedLines = [];
const usedIndices = new Set();
const SIMILARITY_THRESHOLD = 0.6;
for (let idx = 0; idx < diffLines.length; idx++) {
if (usedIndices.has(idx))
continue;
const line = diffLines[idx];
if (line.type === 'removed') {
for (let jdx = idx + 1; jdx < diffLines.length; jdx++) {
if (usedIndices.has(jdx))
continue;
const nextLine = diffLines[jdx];
if (nextLine.type === 'added') {
const similarity = (0, helpers_1._calculateSimilarity)(line.original || '', nextLine.modified || '');
if (similarity >= SIMILARITY_THRESHOLD) {
processedLines.push({
type: 'modified',
original: line.original,
modified: nextLine.modified,
originalLineNum: line.originalLineNum,
modifiedLineNum: nextLine.modifiedLineNum,
});
usedIndices.add(idx);
usedIndices.add(jdx);
break;
}
}
}
if (!usedIndices.has(idx)) {
processedLines.push(line);
usedIndices.add(idx);
}
}
else if (line.type !== 'added' || !usedIndices.has(idx)) {
if (line.type !== 'added' || !usedIndices.has(idx)) {
processedLines.push(line);
usedIndices.add(idx);
}
}
}
let linesAdded = 0;
let linesRemoved = 0;
let linesChanged = 0;
let linesUnchanged = 0;
for (const line of processedLines) {
if (line.type === 'added') {
linesAdded++;
}
else if (line.type === 'removed') {
linesRemoved++;
}
else if (line.type === 'modified') {
linesChanged++;
}
else if (line.type === 'unchanged') {
linesUnchanged++;
}
}
return {
lines: processedLines,
stats: {
linesAdded,
linesRemoved,
linesChanged,
linesUnchanged,
},
};
}
function getCharacterDifferences(original, modified) {
const result = {
original: [],
modified: [],
};
if (!original && !modified)
return result;
if (!original) {
return {
original: [],
modified: modified.split('').map((text) => ({ text, highlighted: true })),
};
}
if (!modified) {
return {
original: original.split('').map((text) => ({ text, highlighted: true })),
modified: [],
};
}
const lcsTable = (0, helpers_1._buildCharLcsTable)(original, modified);
const [origMatched, modMatched] = (0, helpers_1._getLcsIndices)(original, modified, lcsTable);
for (let i = 0; i < original.length; i++) {
result.original.push({
text: original[i],
highlighted: !origMatched.has(i),
});
}
for (let j = 0; j < modified.length; j++) {
result.modified.push({
text: modified[j],
highlighted: !modMatched.has(j),
});
}
return result;
}