forked from M3-Academy/challenge-algorithms-v2.0
224 lines
6.8 KiB
JavaScript
224 lines
6.8 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
Object.defineProperty(exports, '__esModule', {
|
||
|
value: true
|
||
|
});
|
||
|
exports.default = void 0;
|
||
|
var _cleanupSemantic = require('./cleanupSemantic');
|
||
|
/**
|
||
|
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||
|
*
|
||
|
* This source code is licensed under the MIT license found in the
|
||
|
* LICENSE file in the root directory of this source tree.
|
||
|
*/
|
||
|
|
||
|
// Given change op and array of diffs, return concatenated string:
|
||
|
// * include common strings
|
||
|
// * include change strings which have argument op with changeColor
|
||
|
// * exclude change strings which have opposite op
|
||
|
const concatenateRelevantDiffs = (op, diffs, changeColor) =>
|
||
|
diffs.reduce(
|
||
|
(reduced, diff) =>
|
||
|
reduced +
|
||
|
(diff[0] === _cleanupSemantic.DIFF_EQUAL
|
||
|
? diff[1]
|
||
|
: diff[0] === op && diff[1].length !== 0 // empty if change is newline
|
||
|
? changeColor(diff[1])
|
||
|
: ''),
|
||
|
''
|
||
|
);
|
||
|
|
||
|
// Encapsulate change lines until either a common newline or the end.
|
||
|
class ChangeBuffer {
|
||
|
op;
|
||
|
line; // incomplete line
|
||
|
lines; // complete lines
|
||
|
changeColor;
|
||
|
constructor(op, changeColor) {
|
||
|
this.op = op;
|
||
|
this.line = [];
|
||
|
this.lines = [];
|
||
|
this.changeColor = changeColor;
|
||
|
}
|
||
|
pushSubstring(substring) {
|
||
|
this.pushDiff(new _cleanupSemantic.Diff(this.op, substring));
|
||
|
}
|
||
|
pushLine() {
|
||
|
// Assume call only if line has at least one diff,
|
||
|
// therefore an empty line must have a diff which has an empty string.
|
||
|
|
||
|
// If line has multiple diffs, then assume it has a common diff,
|
||
|
// therefore change diffs have change color;
|
||
|
// otherwise then it has line color only.
|
||
|
this.lines.push(
|
||
|
this.line.length !== 1
|
||
|
? new _cleanupSemantic.Diff(
|
||
|
this.op,
|
||
|
concatenateRelevantDiffs(this.op, this.line, this.changeColor)
|
||
|
)
|
||
|
: this.line[0][0] === this.op
|
||
|
? this.line[0] // can use instance
|
||
|
: new _cleanupSemantic.Diff(this.op, this.line[0][1]) // was common diff
|
||
|
);
|
||
|
|
||
|
this.line.length = 0;
|
||
|
}
|
||
|
isLineEmpty() {
|
||
|
return this.line.length === 0;
|
||
|
}
|
||
|
|
||
|
// Minor input to buffer.
|
||
|
pushDiff(diff) {
|
||
|
this.line.push(diff);
|
||
|
}
|
||
|
|
||
|
// Main input to buffer.
|
||
|
align(diff) {
|
||
|
const string = diff[1];
|
||
|
if (string.includes('\n')) {
|
||
|
const substrings = string.split('\n');
|
||
|
const iLast = substrings.length - 1;
|
||
|
substrings.forEach((substring, i) => {
|
||
|
if (i < iLast) {
|
||
|
// The first substring completes the current change line.
|
||
|
// A middle substring is a change line.
|
||
|
this.pushSubstring(substring);
|
||
|
this.pushLine();
|
||
|
} else if (substring.length !== 0) {
|
||
|
// The last substring starts a change line, if it is not empty.
|
||
|
// Important: This non-empty condition also automatically omits
|
||
|
// the newline appended to the end of expected and received strings.
|
||
|
this.pushSubstring(substring);
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
// Append non-multiline string to current change line.
|
||
|
this.pushDiff(diff);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Output from buffer.
|
||
|
moveLinesTo(lines) {
|
||
|
if (!this.isLineEmpty()) {
|
||
|
this.pushLine();
|
||
|
}
|
||
|
lines.push(...this.lines);
|
||
|
this.lines.length = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Encapsulate common and change lines.
|
||
|
class CommonBuffer {
|
||
|
deleteBuffer;
|
||
|
insertBuffer;
|
||
|
lines;
|
||
|
constructor(deleteBuffer, insertBuffer) {
|
||
|
this.deleteBuffer = deleteBuffer;
|
||
|
this.insertBuffer = insertBuffer;
|
||
|
this.lines = [];
|
||
|
}
|
||
|
pushDiffCommonLine(diff) {
|
||
|
this.lines.push(diff);
|
||
|
}
|
||
|
pushDiffChangeLines(diff) {
|
||
|
const isDiffEmpty = diff[1].length === 0;
|
||
|
|
||
|
// An empty diff string is redundant, unless a change line is empty.
|
||
|
if (!isDiffEmpty || this.deleteBuffer.isLineEmpty()) {
|
||
|
this.deleteBuffer.pushDiff(diff);
|
||
|
}
|
||
|
if (!isDiffEmpty || this.insertBuffer.isLineEmpty()) {
|
||
|
this.insertBuffer.pushDiff(diff);
|
||
|
}
|
||
|
}
|
||
|
flushChangeLines() {
|
||
|
this.deleteBuffer.moveLinesTo(this.lines);
|
||
|
this.insertBuffer.moveLinesTo(this.lines);
|
||
|
}
|
||
|
|
||
|
// Input to buffer.
|
||
|
align(diff) {
|
||
|
const op = diff[0];
|
||
|
const string = diff[1];
|
||
|
if (string.includes('\n')) {
|
||
|
const substrings = string.split('\n');
|
||
|
const iLast = substrings.length - 1;
|
||
|
substrings.forEach((substring, i) => {
|
||
|
if (i === 0) {
|
||
|
const subdiff = new _cleanupSemantic.Diff(op, substring);
|
||
|
if (
|
||
|
this.deleteBuffer.isLineEmpty() &&
|
||
|
this.insertBuffer.isLineEmpty()
|
||
|
) {
|
||
|
// If both current change lines are empty,
|
||
|
// then the first substring is a common line.
|
||
|
this.flushChangeLines();
|
||
|
this.pushDiffCommonLine(subdiff);
|
||
|
} else {
|
||
|
// If either current change line is non-empty,
|
||
|
// then the first substring completes the change lines.
|
||
|
this.pushDiffChangeLines(subdiff);
|
||
|
this.flushChangeLines();
|
||
|
}
|
||
|
} else if (i < iLast) {
|
||
|
// A middle substring is a common line.
|
||
|
this.pushDiffCommonLine(new _cleanupSemantic.Diff(op, substring));
|
||
|
} else if (substring.length !== 0) {
|
||
|
// The last substring starts a change line, if it is not empty.
|
||
|
// Important: This non-empty condition also automatically omits
|
||
|
// the newline appended to the end of expected and received strings.
|
||
|
this.pushDiffChangeLines(new _cleanupSemantic.Diff(op, substring));
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
// Append non-multiline string to current change lines.
|
||
|
// Important: It cannot be at the end following empty change lines,
|
||
|
// because newline appended to the end of expected and received strings.
|
||
|
this.pushDiffChangeLines(diff);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Output from buffer.
|
||
|
getLines() {
|
||
|
this.flushChangeLines();
|
||
|
return this.lines;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Given diffs from expected and received strings,
|
||
|
// return new array of diffs split or joined into lines.
|
||
|
//
|
||
|
// To correctly align a change line at the end, the algorithm:
|
||
|
// * assumes that a newline was appended to the strings
|
||
|
// * omits the last newline from the output array
|
||
|
//
|
||
|
// Assume the function is not called:
|
||
|
// * if either expected or received is empty string
|
||
|
// * if neither expected nor received is multiline string
|
||
|
const getAlignedDiffs = (diffs, changeColor) => {
|
||
|
const deleteBuffer = new ChangeBuffer(
|
||
|
_cleanupSemantic.DIFF_DELETE,
|
||
|
changeColor
|
||
|
);
|
||
|
const insertBuffer = new ChangeBuffer(
|
||
|
_cleanupSemantic.DIFF_INSERT,
|
||
|
changeColor
|
||
|
);
|
||
|
const commonBuffer = new CommonBuffer(deleteBuffer, insertBuffer);
|
||
|
diffs.forEach(diff => {
|
||
|
switch (diff[0]) {
|
||
|
case _cleanupSemantic.DIFF_DELETE:
|
||
|
deleteBuffer.align(diff);
|
||
|
break;
|
||
|
case _cleanupSemantic.DIFF_INSERT:
|
||
|
insertBuffer.align(diff);
|
||
|
break;
|
||
|
default:
|
||
|
commonBuffer.align(diff);
|
||
|
}
|
||
|
});
|
||
|
return commonBuffer.getLines();
|
||
|
};
|
||
|
var _default = getAlignedDiffs;
|
||
|
exports.default = _default;
|