UNPKG

diff

Version:

A JavaScript text diff implementation.

224 lines (223 loc) 9.46 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.structuredPatch = structuredPatch; exports.formatPatch = formatPatch; exports.createTwoFilesPatch = createTwoFilesPatch; exports.createPatch = createPatch; var line_js_1 = require("../diff/line.js"); function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { var optionsObj; if (!options) { optionsObj = {}; } else if (typeof options === 'function') { optionsObj = { callback: options }; } else { optionsObj = options; } if (typeof optionsObj.context === 'undefined') { optionsObj.context = 4; } // We copy this into its own variable to placate TypeScript, which thinks // optionsObj.context might be undefined in the callbacks below. var context = optionsObj.context; // @ts-expect-error (runtime check for something that is correctly a static type error) if (optionsObj.newlineIsToken) { throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions'); } if (!optionsObj.callback) { return diffLinesResultToPatch((0, line_js_1.diffLines)(oldStr, newStr, optionsObj)); } else { var callback_1 = optionsObj.callback; (0, line_js_1.diffLines)(oldStr, newStr, __assign(__assign({}, optionsObj), { callback: function (diff) { var patch = diffLinesResultToPatch(diff); // TypeScript is unhappy without the cast because it does not understand that `patch` may // be undefined here only if `callback` is StructuredPatchCallbackAbortable: callback_1(patch); } })); } function diffLinesResultToPatch(diff) { // STEP 1: Build up the patch with no "\ No newline at end of file" lines and with the arrays // of lines containing trailing newline characters. We'll tidy up later... if (!diff) { return; } diff.push({ value: '', lines: [] }); // Append an empty value to make cleanup easier function contextLines(lines) { return lines.map(function (entry) { return ' ' + entry; }); } var hunks = []; var oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1; for (var i = 0; i < diff.length; i++) { var current = diff[i], lines = current.lines || splitLines(current.value); current.lines = lines; if (current.added || current.removed) { // If we have previous context, start with that if (!oldRangeStart) { var prev = diff[i - 1]; oldRangeStart = oldLine; newRangeStart = newLine; if (prev) { curRange = context > 0 ? contextLines(prev.lines.slice(-context)) : []; oldRangeStart -= curRange.length; newRangeStart -= curRange.length; } } // Output our changes for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) { var line = lines_1[_i]; curRange.push((current.added ? '+' : '-') + line); } // Track the updated file position if (current.added) { newLine += lines.length; } else { oldLine += lines.length; } } else { // Identical context lines. Track line changes if (oldRangeStart) { // Close out any changes that have been output (or join overlapping) if (lines.length <= context * 2 && i < diff.length - 2) { // Overlapping for (var _a = 0, _b = contextLines(lines); _a < _b.length; _a++) { var line = _b[_a]; curRange.push(line); } } else { // end the range and output var contextSize = Math.min(lines.length, context); for (var _c = 0, _d = contextLines(lines.slice(0, contextSize)); _c < _d.length; _c++) { var line = _d[_c]; curRange.push(line); } var hunk = { oldStart: oldRangeStart, oldLines: (oldLine - oldRangeStart + contextSize), newStart: newRangeStart, newLines: (newLine - newRangeStart + contextSize), lines: curRange }; hunks.push(hunk); oldRangeStart = 0; newRangeStart = 0; curRange = []; } } oldLine += lines.length; newLine += lines.length; } } // Step 2: eliminate the trailing `\n` from each line of each hunk, and, where needed, add // "\ No newline at end of file". for (var _e = 0, hunks_1 = hunks; _e < hunks_1.length; _e++) { var hunk = hunks_1[_e]; for (var i = 0; i < hunk.lines.length; i++) { if (hunk.lines[i].endsWith('\n')) { hunk.lines[i] = hunk.lines[i].slice(0, -1); } else { hunk.lines.splice(i + 1, 0, '\\ No newline at end of file'); i++; // Skip the line we just added, then continue iterating } } } return { oldFileName: oldFileName, newFileName: newFileName, oldHeader: oldHeader, newHeader: newHeader, hunks: hunks }; } } /** * creates a unified diff patch. * @param patch either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`) */ function formatPatch(patch) { if (Array.isArray(patch)) { return patch.map(formatPatch).join('\n'); } var ret = []; if (patch.oldFileName == patch.newFileName) { ret.push('Index: ' + patch.oldFileName); } ret.push('==================================================================='); ret.push('--- ' + patch.oldFileName + (typeof patch.oldHeader === 'undefined' ? '' : '\t' + patch.oldHeader)); ret.push('+++ ' + patch.newFileName + (typeof patch.newHeader === 'undefined' ? '' : '\t' + patch.newHeader)); for (var i = 0; i < patch.hunks.length; i++) { var hunk = patch.hunks[i]; // Unified Diff Format quirk: If the chunk size is 0, // the first number is one lower than one would expect. // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 if (hunk.oldLines === 0) { hunk.oldStart -= 1; } if (hunk.newLines === 0) { hunk.newStart -= 1; } ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@'); for (var _i = 0, _a = hunk.lines; _i < _a.length; _i++) { var line = _a[_i]; ret.push(line); } } return ret.join('\n') + '\n'; } function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { if (typeof options === 'function') { options = { callback: options }; } if (!(options === null || options === void 0 ? void 0 : options.callback)) { var patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); if (!patchObj) { return; } return formatPatch(patchObj); } else { var callback_2 = options.callback; structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, __assign(__assign({}, options), { callback: function (patchObj) { if (!patchObj) { callback_2(undefined); } else { callback_2(formatPatch(patchObj)); } } })); } } function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) { return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); } /** * Split `text` into an array of lines, including the trailing newline character (where present) */ function splitLines(text) { var hasTrailingNl = text.endsWith('\n'); var result = text.split('\n').map(function (line) { return line + '\n'; }); if (hasTrailingNl) { result.pop(); } else { result.push(result.pop().slice(0, -1)); } return result; }