npm
Version:
a package manager for JavaScript
151 lines (150 loc) • 6.32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parsePatch = parsePatch;
/**
* Parses a patch into structured data, in the same structure returned by `structuredPatch`.
*
* @return a JSON object representation of the a patch, suitable for use with the `applyPatch` method.
*/
function parsePatch(uniDiff) {
var diffstr = uniDiff.split(/\n/), list = [];
var i = 0;
function parseIndex() {
var index = {};
list.push(index);
// Parse diff metadata
while (i < diffstr.length) {
var line = diffstr[i];
// File header found, end parsing diff metadata
if ((/^(---|\+\+\+|@@)\s/).test(line)) {
break;
}
// Try to parse the line as a diff header, like
// Index: README.md
// or
// diff -r 9117c6561b0b -r 273ce12ad8f1 .hgignore
// or
// Index: something with multiple words
// and extract the filename (or whatever else is used as an index name)
// from the end (i.e. 'README.md', '.hgignore', or
// 'something with multiple words' in the examples above).
//
// TODO: It seems awkward that we indiscriminately trim off trailing
// whitespace here. Theoretically, couldn't that be meaningful -
// e.g. if the patch represents a diff of a file whose name ends
// with a space? Seems wrong to nuke it.
// But this behaviour has been around since v2.2.1 in 2015, so if
// it's going to change, it should be done cautiously and in a new
// major release, for backwards-compat reasons.
// -- ExplodingCabbage
var headerMatch = (/^(?:Index:|diff(?: -r \w+)+)\s+/).exec(line);
if (headerMatch) {
index.index = line.substring(headerMatch[0].length).trim();
}
i++;
}
// Parse file headers if they are defined. Unified diff requires them, but
// there's no technical issues to have an isolated hunk without file header
parseFileHeader(index);
parseFileHeader(index);
// Parse hunks
index.hunks = [];
while (i < diffstr.length) {
var line = diffstr[i];
if ((/^(Index:\s|diff\s|---\s|\+\+\+\s|===================================================================)/).test(line)) {
break;
}
else if ((/^@@/).test(line)) {
index.hunks.push(parseHunk());
}
else if (line) {
throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(line));
}
else {
i++;
}
}
}
// Parses the --- and +++ headers, if none are found, no lines
// are consumed.
function parseFileHeader(index) {
var fileHeaderMatch = (/^(---|\+\+\+)\s+/).exec(diffstr[i]);
if (fileHeaderMatch) {
var prefix = fileHeaderMatch[1], data = diffstr[i].substring(3).trim().split('\t', 2), header = (data[1] || '').trim();
var fileName = data[0].replace(/\\\\/g, '\\');
if (fileName.startsWith('"') && fileName.endsWith('"')) {
fileName = fileName.substr(1, fileName.length - 2);
}
if (prefix === '---') {
index.oldFileName = fileName;
index.oldHeader = header;
}
else {
index.newFileName = fileName;
index.newHeader = header;
}
i++;
}
}
// Parses a hunk
// This assumes that we are at the start of a hunk.
function parseHunk() {
var _a;
var chunkHeaderIndex = i, chunkHeaderLine = diffstr[i++], chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
var hunk = {
oldStart: +chunkHeader[1],
oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2],
newStart: +chunkHeader[3],
newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4],
lines: []
};
// 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;
}
var addCount = 0, removeCount = 0;
for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || ((_a = diffstr[i]) === null || _a === void 0 ? void 0 : _a.startsWith('\\'))); i++) {
var operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0];
if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
hunk.lines.push(diffstr[i]);
if (operation === '+') {
addCount++;
}
else if (operation === '-') {
removeCount++;
}
else if (operation === ' ') {
addCount++;
removeCount++;
}
}
else {
throw new Error("Hunk at line ".concat(chunkHeaderIndex + 1, " contained invalid line ").concat(diffstr[i]));
}
}
// Handle the empty block count case
if (!addCount && hunk.newLines === 1) {
hunk.newLines = 0;
}
if (!removeCount && hunk.oldLines === 1) {
hunk.oldLines = 0;
}
// Perform sanity checking
if (addCount !== hunk.newLines) {
throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
}
if (removeCount !== hunk.oldLines) {
throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
}
return hunk;
}
while (i < diffstr.length) {
parseIndex();
}
return list;
}