diff
Version:
A JavaScript text diff implementation.
224 lines (223 loc) • 9.46 kB
JavaScript
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;
}
;