123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- exports.default = diffStrings;
- var _chalk = require('chalk');
- var _chalk2 = _interopRequireDefault(_chalk);
- var _diff = require('diff');
- var _constants = require('./constants.js');
- function _interopRequireDefault(obj) {
- return obj && obj.__esModule ? obj : {default: obj};
- }
- const DIFF_CONTEXT_DEFAULT = 5; // removed | added | equal
- // Given diff digit, return array which consists of:
- // if compared line is removed or added: corresponding original line
- // if compared line is equal: original received and expected lines
- /**
- * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- *
- */
- // Given chunk, return diff character.
- const getDiffChar = chunk => (chunk.removed ? '-' : chunk.added ? '+' : ' ');
- // Given diff character in line of hunk or computed from properties of chunk.
- const getDiffDigit = char => (char === '-' ? -1 : char === '+' ? 1 : 0);
- // Color for text of line.
- const getColor = (digit, onlyIndentationChanged) => {
- if (digit === -1) {
- return _chalk2.default.green; // removed
- }
- if (digit === 1) {
- return _chalk2.default.red; // added
- }
- return onlyIndentationChanged ? _chalk2.default.cyan : _chalk2.default.dim;
- };
- // Do NOT color leading or trailing spaces if original lines are equal:
- // Background color for leading or trailing spaces.
- const getBgColor = (digit, onlyIndentationChanged) =>
- digit === 0 && !onlyIndentationChanged
- ? _chalk2.default.bgYellow
- : _chalk2.default.inverse;
- // ONLY trailing if expected value is snapshot or multiline string.
- const highlightTrailingSpaces = (line, bgColor) =>
- line.replace(/\s+$/, bgColor('$&'));
- // BOTH leading AND trailing if expected value is data structure.
- const highlightLeadingTrailingSpaces = (
- line,
- bgColor
- // If line consists of ALL spaces: highlight all of them.
- ) =>
- highlightTrailingSpaces(line, bgColor).replace(
- // If line has an ODD length of leading spaces: highlight only the LAST.
- /^(\s\s)*(\s)(?=[^\s])/,
- '$1' + bgColor('$2')
- );
- const getAnnotation = options =>
- _chalk2.default.green(
- '- ' + ((options && options.aAnnotation) || 'Expected')
- ) +
- '\n' +
- _chalk2.default.red('+ ' + ((options && options.bAnnotation) || 'Received')) +
- '\n\n';
- // Given string, return array of its lines.
- const splitIntoLines = string => {
- const lines = string.split('\n');
- if (lines.length !== 0 && lines[lines.length - 1] === '') {
- lines.pop();
- }
- return lines;
- };
- // Given diff character and compared line, return original line with colors.
- const formatLine = (char, lineCompared, getOriginal) => {
- const digit = getDiffDigit(char);
- if (getOriginal) {
- // Compared without indentation if expected value is data structure.
- const lineArray = getOriginal(digit);
- const lineOriginal = lineArray[0];
- const onlyIndentationChanged =
- digit === 0 && lineOriginal.length !== lineArray[1].length;
- return getColor(digit, onlyIndentationChanged)(
- char +
- ' ' +
- // Prepend indentation spaces from original to compared line.
- lineOriginal.slice(0, lineOriginal.length - lineCompared.length) +
- highlightLeadingTrailingSpaces(
- lineCompared,
- getBgColor(digit, onlyIndentationChanged)
- )
- );
- }
- // Format compared line when expected is snapshot or multiline string.
- return getColor(digit)(
- char + ' ' + highlightTrailingSpaces(lineCompared, getBgColor(digit))
- );
- };
- // Given original lines, return callback function
- // which given diff digit, returns array.
- const getterForChunks = original => {
- const linesExpected = splitIntoLines(original.a);
- const linesReceived = splitIntoLines(original.b);
- let iExpected = 0;
- let iReceived = 0;
- return digit => {
- if (digit === -1) {
- return [linesExpected[iExpected++]];
- }
- if (digit === 1) {
- return [linesReceived[iReceived++]];
- }
- // Because compared line is equal: original received and expected lines.
- return [linesReceived[iReceived++], linesExpected[iExpected++]];
- };
- };
- // jest --expand
- const formatChunks = (a, b, original) => {
- const chunks = (0, _diff.diffLines)(a, b);
- if (chunks.every(chunk => !chunk.removed && !chunk.added)) {
- return null;
- }
- const getOriginal = original && getterForChunks(original);
- return chunks
- .reduce((lines, chunk) => {
- const char = getDiffChar(chunk);
- splitIntoLines(chunk.value).forEach(line => {
- lines.push(formatLine(char, line, getOriginal));
- });
- return lines;
- }, [])
- .join('\n');
- };
- // Only show patch marks ("@@ ... @@") if the diff is big.
- // To determine this, we need to compare either the original string (a) to
- // `hunk.oldLines` or a new string to `hunk.newLines`.
- // If the `oldLinesCount` is greater than `hunk.oldLines`
- // we can be sure that at least 1 line has been "hidden".
- const shouldShowPatchMarks = (hunk, oldLinesCount) =>
- oldLinesCount > hunk.oldLines;
- const createPatchMark = hunk => {
- const markOld = `-${hunk.oldStart},${hunk.oldLines}`;
- const markNew = `+${hunk.newStart},${hunk.newLines}`;
- return _chalk2.default.yellow(`@@ ${markOld} ${markNew} @@`);
- };
- // Given original lines, return callback function which given indexes for hunk,
- // returns another callback function which given diff digit, returns array.
- const getterForHunks = original => {
- const linesExpected = splitIntoLines(original.a);
- const linesReceived = splitIntoLines(original.b);
- return (iExpected, iReceived) => digit => {
- if (digit === -1) {
- return [linesExpected[iExpected++]];
- }
- if (digit === 1) {
- return [linesReceived[iReceived++]];
- }
- // Because compared line is equal: original received and expected lines.
- return [linesReceived[iReceived++], linesExpected[iExpected++]];
- };
- };
- // jest --no-expand
- const formatHunks = (a, b, contextLines, original) => {
- const options = {
- context:
- typeof contextLines === 'number' && contextLines >= 0
- ? contextLines
- : DIFF_CONTEXT_DEFAULT
- };
- var _structuredPatch = (0, _diff.structuredPatch)(
- '',
- '',
- a,
- b,
- '',
- '',
- options
- );
- const hunks = _structuredPatch.hunks;
- if (hunks.length === 0) {
- return null;
- }
- const getter = original && getterForHunks(original);
- const oldLinesCount = (a.match(/\n/g) || []).length;
- return hunks
- .reduce((lines, hunk) => {
- if (shouldShowPatchMarks(hunk, oldLinesCount)) {
- lines.push(createPatchMark(hunk));
- }
- // Hunk properties are one-based but index args are zero-based.
- const getOriginal =
- getter && getter(hunk.oldStart - 1, hunk.newStart - 1);
- hunk.lines.forEach(line => {
- lines.push(formatLine(line[0], line.slice(1), getOriginal));
- });
- return lines;
- }, [])
- .join('\n');
- };
- function diffStrings(a, b, options, original) {
- // Because `formatHunks` and `formatChunks` ignore one trailing newline,
- // always append newline to strings:
- a += '\n';
- b += '\n';
- // `diff` uses the Myers LCS diff algorithm which runs in O(n+d^2) time
- // (where "d" is the edit distance) and can get very slow for large edit
- // distances. Mitigate the cost by switching to a lower-resolution diff
- // whenever linebreaks are involved.
- const result =
- options && options.expand === false
- ? formatHunks(a, b, options && options.contextLines, original)
- : formatChunks(a, b, original);
- return result === null
- ? _constants.NO_DIFF_MESSAGE
- : getAnnotation(options) + result;
- }
|