| 'use strict'; |
| |
| Object.defineProperty(exports, '__esModule', { |
| value: true |
| }); |
| exports.default = void 0; |
| var _cleanupSemantic = require('./cleanupSemantic'); |
| /** |
| * Copyright (c) Meta Platforms, Inc. and affiliates. |
| * |
| * 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; |