| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- import { diffLines } from '../diff/line.js';
- export function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
- let 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.
- const 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(diffLines(oldStr, newStr, optionsObj));
- }
- else {
- const { callback } = optionsObj;
- diffLines(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => {
- const 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(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; });
- }
- const hunks = [];
- let oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1;
- for (let i = 0; i < diff.length; i++) {
- const 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) {
- const 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 (const line of lines) {
- 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 (const line of contextLines(lines)) {
- curRange.push(line);
- }
- }
- else {
- // end the range and output
- const contextSize = Math.min(lines.length, context);
- for (const line of contextLines(lines.slice(0, contextSize))) {
- curRange.push(line);
- }
- const 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 (const hunk of hunks) {
- for (let 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`)
- */
- export function formatPatch(patch) {
- if (Array.isArray(patch)) {
- return patch.map(formatPatch).join('\n');
- }
- const 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 (let i = 0; i < patch.hunks.length; i++) {
- const 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 (const line of hunk.lines) {
- ret.push(line);
- }
- }
- return ret.join('\n') + '\n';
- }
- export 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)) {
- const patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);
- if (!patchObj) {
- return;
- }
- return formatPatch(patchObj);
- }
- else {
- const { callback } = options;
- structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, Object.assign(Object.assign({}, options), { callback: patchObj => {
- if (!patchObj) {
- callback(undefined);
- }
- else {
- callback(formatPatch(patchObj));
- }
- } }));
- }
- }
- export 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) {
- const hasTrailingNl = text.endsWith('\n');
- const result = text.split('\n').map(line => line + '\n');
- if (hasTrailingNl) {
- result.pop();
- }
- else {
- result.push(result.pop().slice(0, -1));
- }
- return result;
- }
|