getAlignedDiffs.js 7.1 KB

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