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;