getAlignedDiffs.js
  1  'use strict';
  2  
  3  Object.defineProperty(exports, '__esModule', {
  4    value: true
  5  });
  6  exports.default = void 0;
  7  
  8  var _cleanupSemantic = require('./cleanupSemantic');
  9  
 10  function _defineProperty(obj, key, value) {
 11    if (key in obj) {
 12      Object.defineProperty(obj, key, {
 13        value: value,
 14        enumerable: true,
 15        configurable: true,
 16        writable: true
 17      });
 18    } else {
 19      obj[key] = value;
 20    }
 21    return obj;
 22  }
 23  
 24  // Given change op and array of diffs, return concatenated string:
 25  // * include common strings
 26  // * include change strings which have argument op with changeColor
 27  // * exclude change strings which have opposite op
 28  const concatenateRelevantDiffs = (op, diffs, changeColor) =>
 29    diffs.reduce(
 30      (reduced, diff) =>
 31        reduced +
 32        (diff[0] === _cleanupSemantic.DIFF_EQUAL
 33          ? diff[1]
 34          : diff[0] === op && diff[1].length !== 0 // empty if change is newline
 35          ? changeColor(diff[1])
 36          : ''),
 37      ''
 38    ); // Encapsulate change lines until either a common newline or the end.
 39  
 40  class ChangeBuffer {
 41    // incomplete line
 42    // complete lines
 43    constructor(op, changeColor) {
 44      _defineProperty(this, 'op', void 0);
 45  
 46      _defineProperty(this, 'line', void 0);
 47  
 48      _defineProperty(this, 'lines', void 0);
 49  
 50      _defineProperty(this, 'changeColor', void 0);
 51  
 52      this.op = op;
 53      this.line = [];
 54      this.lines = [];
 55      this.changeColor = changeColor;
 56    }
 57  
 58    pushSubstring(substring) {
 59      this.pushDiff(new _cleanupSemantic.Diff(this.op, substring));
 60    }
 61  
 62    pushLine() {
 63      // Assume call only if line has at least one diff,
 64      // therefore an empty line must have a diff which has an empty string.
 65      // If line has multiple diffs, then assume it has a common diff,
 66      // therefore change diffs have change color;
 67      // otherwise then it has line color only.
 68      this.lines.push(
 69        this.line.length !== 1
 70          ? new _cleanupSemantic.Diff(
 71              this.op,
 72              concatenateRelevantDiffs(this.op, this.line, this.changeColor)
 73            )
 74          : this.line[0][0] === this.op
 75          ? this.line[0] // can use instance
 76          : new _cleanupSemantic.Diff(this.op, this.line[0][1]) // was common diff
 77      );
 78      this.line.length = 0;
 79    }
 80  
 81    isLineEmpty() {
 82      return this.line.length === 0;
 83    } // Minor input to buffer.
 84  
 85    pushDiff(diff) {
 86      this.line.push(diff);
 87    } // Main input to buffer.
 88  
 89    align(diff) {
 90      const string = diff[1];
 91  
 92      if (string.includes('\n')) {
 93        const substrings = string.split('\n');
 94        const iLast = substrings.length - 1;
 95        substrings.forEach((substring, i) => {
 96          if (i < iLast) {
 97            // The first substring completes the current change line.
 98            // A middle substring is a change line.
 99            this.pushSubstring(substring);
100            this.pushLine();
101          } else if (substring.length !== 0) {
102            // The last substring starts a change line, if it is not empty.
103            // Important: This non-empty condition also automatically omits
104            // the newline appended to the end of expected and received strings.
105            this.pushSubstring(substring);
106          }
107        });
108      } else {
109        // Append non-multiline string to current change line.
110        this.pushDiff(diff);
111      }
112    } // Output from buffer.
113  
114    moveLinesTo(lines) {
115      if (!this.isLineEmpty()) {
116        this.pushLine();
117      }
118  
119      lines.push(...this.lines);
120      this.lines.length = 0;
121    }
122  } // Encapsulate common and change lines.
123  
124  class CommonBuffer {
125    constructor(deleteBuffer, insertBuffer) {
126      _defineProperty(this, 'deleteBuffer', void 0);
127  
128      _defineProperty(this, 'insertBuffer', void 0);
129  
130      _defineProperty(this, 'lines', void 0);
131  
132      this.deleteBuffer = deleteBuffer;
133      this.insertBuffer = insertBuffer;
134      this.lines = [];
135    }
136  
137    pushDiffCommonLine(diff) {
138      this.lines.push(diff);
139    }
140  
141    pushDiffChangeLines(diff) {
142      const isDiffEmpty = diff[1].length === 0; // An empty diff string is redundant, unless a change line is empty.
143  
144      if (!isDiffEmpty || this.deleteBuffer.isLineEmpty()) {
145        this.deleteBuffer.pushDiff(diff);
146      }
147  
148      if (!isDiffEmpty || this.insertBuffer.isLineEmpty()) {
149        this.insertBuffer.pushDiff(diff);
150      }
151    }
152  
153    flushChangeLines() {
154      this.deleteBuffer.moveLinesTo(this.lines);
155      this.insertBuffer.moveLinesTo(this.lines);
156    } // Input to buffer.
157  
158    align(diff) {
159      const op = diff[0];
160      const string = diff[1];
161  
162      if (string.includes('\n')) {
163        const substrings = string.split('\n');
164        const iLast = substrings.length - 1;
165        substrings.forEach((substring, i) => {
166          if (i === 0) {
167            const subdiff = new _cleanupSemantic.Diff(op, substring);
168  
169            if (
170              this.deleteBuffer.isLineEmpty() &&
171              this.insertBuffer.isLineEmpty()
172            ) {
173              // If both current change lines are empty,
174              // then the first substring is a common line.
175              this.flushChangeLines();
176              this.pushDiffCommonLine(subdiff);
177            } else {
178              // If either current change line is non-empty,
179              // then the first substring completes the change lines.
180              this.pushDiffChangeLines(subdiff);
181              this.flushChangeLines();
182            }
183          } else if (i < iLast) {
184            // A middle substring is a common line.
185            this.pushDiffCommonLine(new _cleanupSemantic.Diff(op, substring));
186          } else if (substring.length !== 0) {
187            // The last substring starts a change line, if it is not empty.
188            // Important: This non-empty condition also automatically omits
189            // the newline appended to the end of expected and received strings.
190            this.pushDiffChangeLines(new _cleanupSemantic.Diff(op, substring));
191          }
192        });
193      } else {
194        // Append non-multiline string to current change lines.
195        // Important: It cannot be at the end following empty change lines,
196        // because newline appended to the end of expected and received strings.
197        this.pushDiffChangeLines(diff);
198      }
199    } // Output from buffer.
200  
201    getLines() {
202      this.flushChangeLines();
203      return this.lines;
204    }
205  } // Given diffs from expected and received strings,
206  // return new array of diffs split or joined into lines.
207  //
208  // To correctly align a change line at the end, the algorithm:
209  // * assumes that a newline was appended to the strings
210  // * omits the last newline from the output array
211  //
212  // Assume the function is not called:
213  // * if either expected or received is empty string
214  // * if neither expected nor received is multiline string
215  
216  const getAlignedDiffs = (diffs, changeColor) => {
217    const deleteBuffer = new ChangeBuffer(
218      _cleanupSemantic.DIFF_DELETE,
219      changeColor
220    );
221    const insertBuffer = new ChangeBuffer(
222      _cleanupSemantic.DIFF_INSERT,
223      changeColor
224    );
225    const commonBuffer = new CommonBuffer(deleteBuffer, insertBuffer);
226    diffs.forEach(diff => {
227      switch (diff[0]) {
228        case _cleanupSemantic.DIFF_DELETE:
229          deleteBuffer.align(diff);
230          break;
231  
232        case _cleanupSemantic.DIFF_INSERT:
233          insertBuffer.align(diff);
234          break;
235  
236        default:
237          commonBuffer.align(diff);
238      }
239    });
240    return commonBuffer.getLines();
241  };
242  
243  var _default = getAlignedDiffs;
244  exports.default = _default;