diffLines.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _chalk = _interopRequireDefault(require('chalk'));
  7. var _diffSequences = _interopRequireDefault(require('diff-sequences'));
  8. var _constants = require('./constants');
  9. var _printDiffs = require('./printDiffs');
  10. function _interopRequireDefault(obj) {
  11. return obj && obj.__esModule ? obj : {default: obj};
  12. }
  13. /**
  14. * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  15. *
  16. * This source code is licensed under the MIT license found in the
  17. * LICENSE file in the root directory of this source tree.
  18. */
  19. const DIFF_CONTEXT_DEFAULT = 5;
  20. const fgDelete = _chalk.default.green;
  21. const fgInsert = _chalk.default.red;
  22. const fgCommon = _chalk.default.dim; // common lines (even indentation same)
  23. const fgIndent = _chalk.default.cyan; // common lines (only indentation different)
  24. const bgCommon = _chalk.default.bgYellow; // edge spaces in common line (even indentation same)
  25. const bgInverse = _chalk.default.inverse; // edge spaces in any other lines
  26. // ONLY trailing if expected value is snapshot or multiline string.
  27. const highlightTrailingSpaces = (line, bgColor) =>
  28. line.replace(/\s+$/, bgColor('$&')); // BOTH leading AND trailing if expected value is data structure.
  29. const highlightLeadingTrailingSpaces = (
  30. line,
  31. bgColor // If line consists of ALL spaces: highlight all of them.
  32. ) =>
  33. highlightTrailingSpaces(line, bgColor).replace(
  34. // If line has an ODD length of leading spaces: highlight only the LAST.
  35. /^(\s\s)*(\s)(?=[^\s])/,
  36. '$1' + bgColor('$2')
  37. );
  38. const getHighlightSpaces = bothEdges =>
  39. bothEdges ? highlightLeadingTrailingSpaces : highlightTrailingSpaces;
  40. // Given index interval in expected lines, put formatted delete lines.
  41. const formatDelete = (aStart, aEnd, aLinesUn, aLinesIn, put) => {
  42. const highlightSpaces = getHighlightSpaces(aLinesUn !== aLinesIn);
  43. for (let aIndex = aStart; aIndex !== aEnd; aIndex += 1) {
  44. const aLineUn = aLinesUn[aIndex];
  45. const aLineIn = aLinesIn[aIndex];
  46. const indentation = aLineIn.slice(0, aLineIn.length - aLineUn.length);
  47. put(fgDelete('- ' + indentation + highlightSpaces(aLineUn, bgInverse)));
  48. }
  49. }; // Given index interval in received lines, put formatted insert lines.
  50. const formatInsert = (bStart, bEnd, bLinesUn, bLinesIn, put) => {
  51. const highlightSpaces = getHighlightSpaces(bLinesUn !== bLinesIn);
  52. for (let bIndex = bStart; bIndex !== bEnd; bIndex += 1) {
  53. const bLineUn = bLinesUn[bIndex];
  54. const bLineIn = bLinesIn[bIndex];
  55. const indentation = bLineIn.slice(0, bLineIn.length - bLineUn.length);
  56. put(fgInsert('+ ' + indentation + highlightSpaces(bLineUn, bgInverse)));
  57. }
  58. }; // Given the number of items and starting indexes of a common subsequence,
  59. // put formatted common lines.
  60. const formatCommon = (
  61. nCommon,
  62. aCommon,
  63. bCommon,
  64. aLinesIn,
  65. bLinesUn,
  66. bLinesIn,
  67. put
  68. ) => {
  69. const highlightSpaces = getHighlightSpaces(bLinesUn !== bLinesIn);
  70. for (; nCommon !== 0; nCommon -= 1, aCommon += 1, bCommon += 1) {
  71. const bLineUn = bLinesUn[bCommon];
  72. const bLineIn = bLinesIn[bCommon];
  73. const bLineInLength = bLineIn.length; // For common lines, received indentation seems more intuitive.
  74. const indentation = bLineIn.slice(0, bLineInLength - bLineUn.length); // Color shows whether expected and received line has same indentation.
  75. const hasSameIndentation = aLinesIn[aCommon].length === bLineInLength;
  76. const fg = hasSameIndentation ? fgCommon : fgIndent;
  77. const bg = hasSameIndentation ? bgCommon : bgInverse;
  78. put(fg(' ' + indentation + highlightSpaces(bLineUn, bg)));
  79. }
  80. }; // jest --expand
  81. // Return formatted diff as joined string of all lines.
  82. const diffExpand = (aLinesUn, bLinesUn, aLinesIn, bLinesIn) => {
  83. const isCommon = (aIndex, bIndex) => aLinesUn[aIndex] === bLinesUn[bIndex];
  84. const array = [];
  85. const put = line => {
  86. array.push(line);
  87. };
  88. let aStart = 0;
  89. let bStart = 0;
  90. const foundSubsequence = (nCommon, aCommon, bCommon) => {
  91. formatDelete(aStart, aCommon, aLinesUn, aLinesIn, put);
  92. formatInsert(bStart, bCommon, bLinesUn, bLinesIn, put);
  93. formatCommon(nCommon, aCommon, bCommon, aLinesIn, bLinesUn, bLinesIn, put);
  94. aStart = aCommon + nCommon;
  95. bStart = bCommon + nCommon;
  96. };
  97. const aLength = aLinesUn.length;
  98. const bLength = bLinesUn.length;
  99. (0, _diffSequences.default)(aLength, bLength, isCommon, foundSubsequence); // After the last common subsequence, format remaining change lines.
  100. formatDelete(aStart, aLength, aLinesUn, aLinesIn, put);
  101. formatInsert(bStart, bLength, bLinesUn, bLinesIn, put);
  102. return array.join('\n');
  103. };
  104. const getContextLines = options =>
  105. options &&
  106. typeof options.contextLines === 'number' &&
  107. options.contextLines >= 0
  108. ? options.contextLines
  109. : DIFF_CONTEXT_DEFAULT; // jest --no-expand
  110. // Return joined string of formatted diff for all change lines,
  111. // but if some common lines are omitted because there are more than the context,
  112. // then a “patch mark” precedes each set of adjacent changed and common lines.
  113. const diffNoExpand = (
  114. aLinesUn,
  115. bLinesUn,
  116. aLinesIn,
  117. bLinesIn,
  118. nContextLines
  119. ) => {
  120. const isCommon = (aIndex, bIndex) => aLinesUn[aIndex] === bLinesUn[bIndex];
  121. let iPatchMark = 0; // index of placeholder line for patch mark
  122. const array = [''];
  123. const put = line => {
  124. array.push(line);
  125. };
  126. let isAtEnd = false;
  127. const aLength = aLinesUn.length;
  128. const bLength = bLinesUn.length;
  129. const nContextLines2 = nContextLines + nContextLines; // Initialize the first patch for changes at the start,
  130. // especially for edge case in which there is no common subsequence.
  131. let aStart = 0;
  132. let aEnd = 0;
  133. let bStart = 0;
  134. let bEnd = 0; // Given the number of items and starting indexes of each common subsequence,
  135. // format any preceding change lines, and then common context lines.
  136. const foundSubsequence = (nCommon, aStartCommon, bStartCommon) => {
  137. const aEndCommon = aStartCommon + nCommon;
  138. const bEndCommon = bStartCommon + nCommon;
  139. isAtEnd = aEndCommon === aLength && bEndCommon === bLength; // If common subsequence is at start, re-initialize the first patch.
  140. if (aStartCommon === 0 && bStartCommon === 0) {
  141. const nLines = nContextLines < nCommon ? nContextLines : nCommon;
  142. aStart = aEndCommon - nLines;
  143. bStart = bEndCommon - nLines;
  144. formatCommon(nLines, aStart, bStart, aLinesIn, bLinesUn, bLinesIn, put);
  145. aEnd = aEndCommon;
  146. bEnd = bEndCommon;
  147. return;
  148. } // Format preceding change lines.
  149. formatDelete(aEnd, aStartCommon, aLinesUn, aLinesIn, put);
  150. formatInsert(bEnd, bStartCommon, bLinesUn, bLinesIn, put);
  151. aEnd = aStartCommon;
  152. bEnd = bStartCommon; // If common subsequence is at end, then context follows preceding changes;
  153. // else context follows preceding changes AND precedes following changes.
  154. const maxContextLines = isAtEnd ? nContextLines : nContextLines2;
  155. if (nCommon <= maxContextLines) {
  156. // The patch includes all lines in the common subsequence.
  157. formatCommon(nCommon, aEnd, bEnd, aLinesIn, bLinesUn, bLinesIn, put);
  158. aEnd += nCommon;
  159. bEnd += nCommon;
  160. return;
  161. } // The patch ends because context is less than number of common lines.
  162. formatCommon(nContextLines, aEnd, bEnd, aLinesIn, bLinesUn, bLinesIn, put);
  163. aEnd += nContextLines;
  164. bEnd += nContextLines;
  165. array[iPatchMark] = (0, _printDiffs.createPatchMark)(
  166. aStart,
  167. aEnd,
  168. bStart,
  169. bEnd
  170. ); // If common subsequence is not at end, another patch follows it.
  171. if (!isAtEnd) {
  172. iPatchMark = array.length; // index of placeholder line
  173. array[iPatchMark] = '';
  174. const nLines = nContextLines < nCommon ? nContextLines : nCommon;
  175. aStart = aEndCommon - nLines;
  176. bStart = bEndCommon - nLines;
  177. formatCommon(nLines, aStart, bStart, aLinesIn, bLinesUn, bLinesIn, put);
  178. aEnd = aEndCommon;
  179. bEnd = bEndCommon;
  180. }
  181. };
  182. (0, _diffSequences.default)(aLength, bLength, isCommon, foundSubsequence); // If no common subsequence or last was not at end, format remaining change lines.
  183. if (!isAtEnd) {
  184. formatDelete(aEnd, aLength, aLinesUn, aLinesIn, put);
  185. formatInsert(bEnd, bLength, bLinesUn, bLinesIn, put);
  186. aEnd = aLength;
  187. bEnd = bLength;
  188. }
  189. if (aStart === 0 && aEnd === aLength && bStart === 0 && bEnd === bLength) {
  190. array.splice(0, 1); // delete placeholder line for patch mark
  191. } else {
  192. array[iPatchMark] = (0, _printDiffs.createPatchMark)(
  193. aStart,
  194. aEnd,
  195. bStart,
  196. bEnd
  197. );
  198. }
  199. return array.join('\n');
  200. };
  201. var _default = (a, b, options, original) => {
  202. if (a === b) {
  203. return _constants.NO_DIFF_MESSAGE;
  204. }
  205. let aLinesUn = a.split('\n');
  206. let bLinesUn = b.split('\n'); // Indentation is unknown if expected value is snapshot or multiline string.
  207. let aLinesIn = aLinesUn;
  208. let bLinesIn = bLinesUn;
  209. if (original) {
  210. // Indentation is known if expected value is data structure:
  211. // Compare lines without indentation and format lines with indentation.
  212. aLinesIn = original.a.split('\n');
  213. bLinesIn = original.b.split('\n');
  214. if (
  215. aLinesUn.length !== aLinesIn.length ||
  216. bLinesUn.length !== bLinesIn.length
  217. ) {
  218. // Fall back if unindented and indented lines are inconsistent.
  219. aLinesUn = aLinesIn;
  220. bLinesUn = bLinesIn;
  221. }
  222. }
  223. return (
  224. (0, _printDiffs.printAnnotation)(options) +
  225. (options && options.expand === false
  226. ? diffNoExpand(
  227. aLinesUn,
  228. bLinesUn,
  229. aLinesIn,
  230. bLinesIn,
  231. getContextLines(options)
  232. )
  233. : diffExpand(aLinesUn, bLinesUn, aLinesIn, bLinesIn))
  234. );
  235. };
  236. exports.default = _default;