Commit 19eb7acd by lxyang

feat: 上传改版新增parse-diff组件源码

parent 29774858
src/ src/
node_modules/ node_modules/
dist/
fixtures/
.history/ .history/
\ No newline at end of file
{ {
"name": "babel-plugin-ry-istanbul", "name": "babel-plugin-ry-istanbul-web",
"version": "0.0.4-bat", "version": "0.0.3-bat",
"author": "Thai Pangsakulyanont @dtinth", "author": "Thai Pangsakulyanont @dtinth",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"description": "A babel plugin that adds istanbul instrumentation to ES6 code", "description": "A babel plugin that adds istanbul instrumentation to ES6 code",
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"istanbul-lib-coverage": "^1.0.0", "istanbul-lib-coverage": "^1.0.0",
"onchange": "^7.1.0", "onchange": "^7.1.0",
"parse-diff": "^0.9.0",
"ry-istanbul-web": "^0.0.1", "ry-istanbul-web": "^0.0.1",
"semver": "^6.3.0", "semver": "^6.3.0",
"test-exclude": "^6.0.0" "test-exclude": "^6.0.0"
...@@ -31,24 +30,28 @@ ...@@ -31,24 +30,28 @@
"@babel/core": "^7.7.5", "@babel/core": "^7.7.5",
"@babel/plugin-proposal-optional-chaining": "^7.16.7", "@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@babel/plugin-transform-modules-commonjs": "^7.7.5", "@babel/plugin-transform-modules-commonjs": "^7.7.5",
"@babel/preset-env": "^7.19.3",
"@babel/register": "^7.7.4", "@babel/register": "^7.7.4",
"chai": "^4.2.0",
"coveralls": "^3.0.9",
"cross-env": "^6.0.3",
"gaze": "^1.1.3",
"mocha": "^6.2.2",
"nyc": "^15.0.0",
"pmock": "^0.2.3",
"standard": "^14.3.1",
"babel-cli": "^6.3.17", "babel-cli": "^6.3.17",
"babel-plugin-istanbul": "^2.0.3", "babel-plugin-istanbul": "^2.0.3",
"babel-preset-es2015": "^6.3.13", "babel-preset-es2015": "^6.3.13",
"babel-register": "^6.16.3", "babel-register": "^6.16.3",
"chai": "^4.2.0",
"clone": "^2.0.0", "clone": "^2.0.0",
"coveralls": "^3.0.9",
"cross-env": "^6.0.3",
"documentation": "^4.0.0-beta9", "documentation": "^4.0.0-beta9",
"eslint": "^6.8.0",
"gaze": "^1.1.3",
"jest": "^29.1.2",
"js-yaml": "^3.3.1", "js-yaml": "^3.3.1",
"jshint": "^2.8.0", "jshint": "^2.8.0",
"mocha": "^6.2.2",
"nopt": "^3.0.6", "nopt": "^3.0.6",
"nyc": "^15.0.0",
"pmock": "^0.2.3",
"prettier": "^2.7.1",
"standard": "^14.3.1",
"standard-version": "^3.0.0" "standard-version": "^3.0.0"
}, },
"scripts": { "scripts": {
......
...@@ -6,7 +6,7 @@ import { declare } from '@babel/helper-plugin-utils' ...@@ -6,7 +6,7 @@ import { declare } from '@babel/helper-plugin-utils'
import { programVisitor } from 'istanbul-lib-instrument' import { programVisitor } from 'istanbul-lib-instrument'
import TestExclude from 'test-exclude' import TestExclude from 'test-exclude'
import schema from '@istanbuljs/schema' import schema from '@istanbuljs/schema'
import parseDiffData from 'parse-diff' import parseDiffData from './parse-diff'
function getRealpath(n) { function getRealpath(n) {
try { try {
...@@ -138,7 +138,7 @@ export default declare(api => { ...@@ -138,7 +138,7 @@ export default declare(api => {
} }
// instrumenttation (branch: git diff 跟那个分支对比, increment 是否开启增量代码检测) 拿配置到底是全量代码还是增量代码 // instrumenttation (branch: git diff 跟那个分支对比, increment 是否开启增量代码检测) 拿配置到底是全量代码还是增量代码
if (!IS_PITCHING_PILE_DATA) { if (!IS_PITCHING_PILE_DATA) {
return return false
} }
// const changeList = [] // const changeList = []
// 下面的是零时的 // 下面的是零时的
...@@ -151,6 +151,7 @@ export default declare(api => { ...@@ -151,6 +151,7 @@ export default declare(api => {
if (INCREMENT_DATA && BRANCH_DATA) { if (INCREMENT_DATA && BRANCH_DATA) {
const gitDiffCode = execSync(`git diff ${BRANCH_DATA}`) const gitDiffCode = execSync(`git diff ${BRANCH_DATA}`)
const diffData = parseDiffData(gitDiffCode.toString()) const diffData = parseDiffData(gitDiffCode.toString())
console.log(diffData, 'diffData')
diffScreen = diffData.find(item => { diffScreen = diffData.find(item => {
return realPath.indexOf(item.to) > -1 return realPath.indexOf(item.to) > -1
}) })
......
const parse = require("../index");
describe("diff parser", function () {
it("should parse null", () => {
expect(parse(null).length).toBe(0);
});
it("should parse empty string", () => {
expect(parse("").length).toBe(0);
});
it("should parse whitespace", () => {
expect(parse(" ").length).toBe(0);
});
it("should parse simple git-like diff", function () {
const diff = `\
diff --git a/file b/file
index 123..456 789
--- a/file
+++ b/file
@@ -1,2 +1,2 @@
- line1
+ line2\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.from).toBe("file");
expect(file.to).toBe("file");
expect(file.deletions).toBe(1);
expect(file.additions).toBe(1);
expect(file.chunks.length).toBe(1);
const chunk = file.chunks[0];
expect(chunk.content).toBe("@@ -1,2 +1,2 @@");
expect(chunk.changes.length).toBe(2);
expect(chunk.changes[0].content).toBe("- line1");
expect(chunk.changes[1].content).toBe("+ line2");
});
it("should parse simple git-like diff with file enclosed by double-quote", function () {
const diff = `\
diff --git "a/file1" "b/file2"
similarity index 100%
rename from "file1"
rename to "file2"\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.from).toBe("file1");
expect(file.to).toBe("file2");
expect(file.chunks.length).toBe(0);
});
it("should parse file names for changed binaries with spaces in their names", function () {
const diff = `\
diff --git a/Artsy_Tests/ReferenceImages/ARTopMenuViewControllerSpec/selects 'home' by default as ipad@2x.png b/Artsy_Tests/ReferenceImages/ARTopMenuViewControllerSpec/selects 'home' by default as ipad@2x.png
index fc72ba34b..ec373e9a4 100644
Binary files a/Artsy_Tests/ReferenceImages/ARTopMenuViewControllerSpec/selects 'home' by default as ipad@2x.png and b/Artsy_Tests/ReferenceImages/ARTopMenuViewControllerSpec/selects 'home' by default as ipad@2x.png differ\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.from).toBe(
"Artsy_Tests/ReferenceImages/ARTopMenuViewControllerSpec/selects 'home' by default as ipad@2x.png"
);
expect(file.to).toBe(
"Artsy_Tests/ReferenceImages/ARTopMenuViewControllerSpec/selects 'home' by default as ipad@2x.png"
);
});
it("should parse diff with new file mode line", function () {
const diff = `\
diff --git a/test b/test
new file mode 100644
index 0000000..db81be4
--- /dev/null
+++ b/test
@@ -0,0 +1,2 @@
+line1
+line2\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.new).toBeTruthy();
expect(file.from).toBe("/dev/null");
expect(file.to).toBe("test");
expect(file.chunks[0].content).toBe("@@ -0,0 +1,2 @@");
expect(file.chunks[0].changes.length).toBe(2);
expect(file.chunks[0].changes[0].content).toBe("+line1");
expect(file.chunks[0].changes[1].content).toBe("+line2");
});
it("should parse diff with deleted file mode line", function () {
const diff = `\
diff --git a/test b/test
deleted file mode 100644
index db81be4..0000000
--- b/test
+++ /dev/null
@@ -1,2 +0,0 @@
-line1
-line2\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.deleted).toBeTruthy();
expect(file.from).toBe("test");
expect(file.to).toBe("/dev/null");
expect(file.chunks[0].content).toBe("@@ -1,2 +0,0 @@");
expect(file.chunks[0].changes.length).toBe(2);
expect(file.chunks[0].changes[0].content).toBe("-line1");
expect(file.chunks[0].changes[1].content).toBe("-line2");
});
it("should parse diff with single line files", function () {
const diff = `\
diff --git a/file1 b/file1
deleted file mode 100644
index db81be4..0000000
--- b/file1
+++ /dev/null
@@ -1 +0,0 @@
-line1
diff --git a/file2 b/file2
new file mode 100644
index 0000000..db81be4
--- /dev/null
+++ b/file2
@@ -0,0 +1 @@
+line1\
`;
const files = parse(diff);
expect(files.length).toBe(2);
let file = files[0];
expect(file.deleted).toBeTruthy();
expect(file.from).toBe("file1");
expect(file.to).toBe("/dev/null");
expect(file.chunks[0].content).toBe("@@ -1 +0,0 @@");
expect(file.chunks[0].changes.length).toBe(1);
expect(file.chunks[0].changes[0].content).toBe("-line1");
expect(file.chunks[0].changes[0].type).toBe("del");
file = files[1];
expect(file.new).toBeTruthy();
expect(file.from).toBe("/dev/null");
expect(file.to).toBe("file2");
expect(file.chunks[0].content).toBe("@@ -0,0 +1 @@");
expect(file.chunks[0].oldStart).toBe(0);
expect(file.chunks[0].oldLines).toBe(0);
expect(file.chunks[0].newStart).toBe(1);
expect(file.chunks[0].newLines).toBe(1);
expect(file.chunks[0].changes.length).toBe(1);
expect(file.chunks[0].changes[0].content).toBe("+line1");
expect(file.chunks[0].changes[0].type).toBe("add");
});
it("should parse multiple files in diff", function () {
const diff = `\
diff --git a/file1 b/file1
index 123..456 789
--- a/file1
+++ b/file1
@@ -1,1 +1,1 @@
- line1
+ line2
diff --git a/file2 b/file2
index 123..456 789
--- a/file2
+++ b/file2
@@ -1,1 +1,1 @@
- line1
+ line2\
`;
const files = parse(diff);
expect(files.length).toBe(2);
let file = files[0];
expect(file.from).toBe("file1");
expect(file.to).toBe("file1");
expect(file.chunks[0].content).toBe("@@ -1,1 +1,1 @@");
expect(file.chunks[0].changes.length).toBe(2);
expect(file.chunks[0].changes[0].content).toBe("- line1");
expect(file.chunks[0].changes[1].content).toBe("+ line2");
file = files[1];
expect(file.from).toBe("file2");
expect(file.to).toBe("file2");
expect(file.chunks[0].content).toBe("@@ -1,1 +1,1 @@");
expect(file.chunks[0].changes.length).toBe(2);
expect(file.chunks[0].changes[0].content).toBe("- line1");
expect(file.chunks[0].changes[1].content).toBe("+ line2");
});
it("should parse diff with EOF flag", function () {
const diff = `\
diff --git a/file1 b/file1
index 123..456 789
--- a/file1
+++ b/file1
@@ -1,1 +1,1 @@
- line1
+ line2
\\ No newline at end of file\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.from).toBe("file1");
expect(file.to).toBe("file1");
const chunk = file.chunks[0];
expect(chunk.content).toBe("@@ -1,1 +1,1 @@");
expect(chunk.changes.length).toBe(3);
expect(chunk.changes[0].content).toBe("- line1");
expect(chunk.changes[1].content).toBe("+ line2");
expect(chunk.changes[2].type).toBe("add");
expect(chunk.changes[2].content).toBe("\\ No newline at end of file");
});
it("should parse gnu sample diff", function () {
const diff = `\
--- lao 2002-02-21 23:30:39.942229878 -0800
+++ tzu 2002-02-21 23:30:50.442260588 -0800
@@ -1,7 +1,6 @@
-The Way that can be told of is not the eternal Way;
-The name that can be named is not the eternal name.
The Nameless is the origin of Heaven and Earth;
-The Named is the mother of all things.
+The named is the mother of all things.
+
Therefore let there always be non-being,
so we may see their subtlety,
And let there always be being,
@@ -9,3 +8,6 @@
The two are the same,
But after they are produced,
they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.from).toBe("lao");
expect(file.to).toBe("tzu");
expect(file.chunks.length).toBe(2);
const chunk0 = file.chunks[0];
expect(chunk0.oldStart).toBe(1);
expect(chunk0.oldLines).toBe(7);
expect(chunk0.newStart).toBe(1);
expect(chunk0.newLines).toBe(6);
const chunk1 = file.chunks[1];
expect(chunk1.oldStart).toBe(9);
expect(chunk1.oldLines).toBe(3);
expect(chunk1.newStart).toBe(8);
expect(chunk1.newLines).toBe(6);
});
it("should parse hg diff output", function () {
const diff = `\
diff -r 514fc757521e lib/parsers.coffee
--- a/lib/parsers.coffee Thu Jul 09 00:56:36 2015 +0200
+++ b/lib/parsers.coffee Fri Jul 10 16:23:43 2015 +0200
@@ -43,6 +43,9 @@
files[file] = { added: added, deleted: deleted }
files
+ diff: (out) ->
+ files = {}
+
module.exports = Parsers
module.exports.version = (out) ->\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.chunks[0].content).toBe("@@ -43,6 +43,9 @@");
expect(file.from).toBe("lib/parsers.coffee");
expect(file.to).toBe("lib/parsers.coffee");
});
it("should parse svn diff output", function () {
const diff = `\
Index: new.txt
===================================================================
--- new.txt (revision 0)
+++ new.txt (working copy)
@@ -0,0 +1 @@
+test
Index: text.txt
===================================================================
--- text.txt (revision 6)
+++ text.txt (working copy)
@@ -1,7 +1,5 @@
-This part of the
-document has stayed the
-same from version to
-version. It shouldn't
+This is an important
+notice! It shouldn't
be shown if it doesn't
change. Otherwise, that
would not be helping to\
`;
const files = parse(diff);
expect(files.length).toBe(2);
const file = files[0];
expect(file.from).toBe("new.txt");
expect(file.to).toBe("new.txt");
expect(file.chunks[0].changes.length).toBe(1);
});
it("should parse file names for n new empty file", function () {
const diff = `\
diff --git a/newFile.txt b/newFile.txt
new file mode 100644
index 0000000..e6a2e28\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.from).toBe("/dev/null");
expect(file.to).toBe("newFile.txt");
});
it("should parse file names for a deleted file", function () {
const diff = `\
diff --git a/deletedFile.txt b/deletedFile.txt
deleted file mode 100644
index e6a2e28..0000000\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.from).toBe("deletedFile.txt");
expect(file.to).toBe("/dev/null");
});
it("should parse rename diff with space in path with no changes", function () {
const diff = `\
diff --git a/My Folder/File b/My Folder/a/File
similarity index 100%
rename from a/My Folder/File
rename to My Folder/a/File\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.from).toBe("My Folder/File");
expect(file.to).toBe("My Folder/a/File");
expect(file.chunks.length).toBe(0);
});
it("should parse rename diff with space in path with changes", function () {
const diff = `\
diff --git a/My Folder/File b/My Folder/a/File
similarity index 100%
rename from a/My Folder/File
rename to My Folder/a/File
@@ -1,2 +1,2 @@
- line1
+ line2\
`;
const files = parse(diff);
expect(files.length).toBe(1);
const file = files[0];
expect(file.from).toBe("My Folder/File");
expect(file.to).toBe("My Folder/a/File");
expect(file.chunks.length).toBe(1);
const chunk = file.chunks[0];
expect(chunk.content).toBe("@@ -1,2 +1,2 @@");
expect(chunk.changes.length).toBe(2);
expect(chunk.changes[0].content).toBe("- line1");
expect(chunk.changes[1].content).toBe("+ line2");
});
it("should parse diff with single line quote escaped file names", function () {
const diff = `
diff --git "a/file \\"space\\"" "b/file \\"space\\""
index 9daeafb..88bd214 100644
--- "a/file \\"space\\""
+++ "b/file \\"space\\""
@@ -1 +1 @@
-test
+test\n1234
`;
const files = parse(diff);
expect(files.length).toBe(1);
const [file] = files;
expect(file.from).toBe(`file \\"space\\"`);
expect(file.to).toBe(`file \\"space\\"`);
});
it("should parse files with additional '-' and '+'", function () {
const diff = `\
diff --git a/file1 b/file1
index 123..456 789
--- a/file1
+++ b/file1
@@ -1,2 +1,1 @@
- line11
--- line12
+ line21
diff --git a/file2 b/file2
index 123..456 789
--- a/file2
+++ b/file2
@@ -1,2 +1,1 @@
- line11
+++ line21
+ line22\
`;
const files = parse(diff);
expect(files.length).toBe(2);
const [file1, file2] = files;
expect(file1.from).toBe(`file1`);
expect(file1.to).toBe(`file1`);
expect(file1.chunks[0].changes.length).toBe(3);
expect(file2.from).toBe(`file2`);
expect(file2.to).toBe(`file2`);
expect(file2.chunks[0].changes.length).toBe(3);
});
});
declare function parseDiff(input?: string | null): parseDiff.File[];
declare namespace parseDiff {
export interface File {
chunks: Chunk[];
deletions: number;
additions: number;
from?: string;
to?: string;
index?: string[];
deleted?: true;
new?: true;
}
export interface Chunk {
content: string;
changes: Change[];
oldStart: number;
oldLines: number;
newStart: number;
newLines: number;
}
export interface NormalChange {
type: 'normal';
ln1: number;
ln2: number;
normal: true;
content: string;
}
export interface AddChange {
type: 'add';
add: true;
ln: number;
content: string;
}
export interface DeleteChange {
type: 'del';
del: true;
ln: number;
content: string;
}
export type ChangeType = 'normal' | 'add' | 'del';
export type Change = NormalChange | AddChange | DeleteChange;
}
export = parseDiff;
"use strict";module.exports=input=>{if(!input)return[];if(typeof input!=="string"||input.match(/^\s+$/))return[];const lines=input.split("\n");if(lines.length===0)return[];const files=[];let currentFile=null;let currentChunk=null;let deletedLineCounter=0;let addedLineCounter=0;let currentFileChanges=null;const normal=line=>{var _currentChunk;(_currentChunk=currentChunk)===null||_currentChunk===void 0?void 0:_currentChunk.changes.push({type:"normal",normal:true,ln1:deletedLineCounter++,ln2:addedLineCounter++,content:line});currentFileChanges.oldLines--;currentFileChanges.newLines--};const start=line=>{const[fromFileName,toFileName]=parseFiles(line)??[];currentFile={chunks:[],deletions:0,additions:0,from:fromFileName,to:toFileName};files.push(currentFile)};const restart=()=>{if(!currentFile||currentFile.chunks.length)start()};const newFile=()=>{restart();currentFile.new=true;currentFile.from="/dev/null"};const deletedFile=()=>{restart();currentFile.deleted=true;currentFile.to="/dev/null"};const index=line=>{restart();currentFile.index=line.split(" ").slice(1)};const fromFile=line=>{restart();currentFile.from=parseOldOrNewFile(line)};const toFile=line=>{restart();currentFile.to=parseOldOrNewFile(line)};const toNumOfLines=number=>+(number||1);const chunk=(line,match)=>{if(!currentFile)return;const[oldStart,oldNumLines,newStart,newNumLines]=match.slice(1);deletedLineCounter=+oldStart;addedLineCounter=+newStart;currentChunk={content:line,changes:[],oldStart:+oldStart,oldLines:toNumOfLines(oldNumLines),newStart:+newStart,newLines:toNumOfLines(newNumLines)};currentFileChanges={oldLines:toNumOfLines(oldNumLines),newLines:toNumOfLines(newNumLines)};currentFile.chunks.push(currentChunk)};const del=line=>{if(!currentChunk)return;currentChunk.changes.push({type:"del",del:true,ln:deletedLineCounter++,content:line});currentFile.deletions++;currentFileChanges.oldLines--};const add=line=>{if(!currentChunk)return;currentChunk.changes.push({type:"add",add:true,ln:addedLineCounter++,content:line});currentFile.additions++;currentFileChanges.newLines--};const eof=line=>{if(!currentChunk)return;const[mostRecentChange]=currentChunk.changes.slice(-1);currentChunk.changes.push({type:mostRecentChange.type,[mostRecentChange.type]:true,ln1:mostRecentChange.ln1,ln2:mostRecentChange.ln2,ln:mostRecentChange.ln,content:line})};const schemaHeaders=[[/^diff\s/,start],[/^new file mode \d+$/,newFile],[/^deleted file mode \d+$/,deletedFile],[/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/,index],[/^---\s/,fromFile],[/^\+\+\+\s/,toFile],[/^@@\s+-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/,chunk],[/^\\ No newline at end of file$/,eof]];const schemaContent=[[/^-/,del],[/^\+/,add],[/^\s+/,normal]];const parseContentLine=line=>{for(const[pattern,handler]of schemaContent){const match=line.match(pattern);if(match){handler(line,match);break}}if(currentFileChanges.oldLines===0&&currentFileChanges.newLines===0){currentFileChanges=null}};const parseHeaderLine=line=>{for(const[pattern,handler]of schemaHeaders){const match=line.match(pattern);if(match){handler(line,match);break}}};const parseLine=line=>{if(currentFileChanges){parseContentLine(line)}else{parseHeaderLine(line)}return};for(const line of lines)parseLine(line);return files};const fileNameDiffRegex=/a\/.*(?=["']? ["']?b\/)|b\/.*$/g;const gitFileHeaderRegex=/^(a|b)\//;const parseFiles=line=>{let fileNames=line===null||line===void 0?void 0:line.match(fileNameDiffRegex);return fileNames===null||fileNames===void 0?void 0:fileNames.map(fileName=>fileName.replace(gitFileHeaderRegex,"").replace(/("|')$/,""))};const qoutedFileNameRegex=/^\\?['"]|\\?['"]$/g;const parseOldOrNewFile=line=>{let fileName=leftTrimChars(line,"-+").trim();fileName=removeTimeStamp(fileName);return fileName.replace(qoutedFileNameRegex,"").replace(gitFileHeaderRegex,"")};const leftTrimChars=(string,trimmingChars)=>{string=makeString(string);if(!trimmingChars&&String.prototype.trimLeft)return string.trimLeft();let trimmingString=formTrimmingString(trimmingChars);return string.replace(new RegExp(`^${trimmingString}+`),"")};const timeStampRegex=/\t.*|\d{4}-\d\d-\d\d\s\d\d:\d\d:\d\d(.\d+)?\s(\+|-)\d\d\d\d/;const removeTimeStamp=string=>{const timeStamp=timeStampRegex.exec(string);if(timeStamp){string=string.substring(0,timeStamp.index).trim()}return string};const formTrimmingString=trimmingChars=>{if(trimmingChars===null||trimmingChars===undefined)return"\\s";else if(trimmingChars instanceof RegExp)return trimmingChars.source;return`[${makeString(trimmingChars).replace(/([.*+?^=!:${}()|[\]/\\])/g,"\\$1")}]`};const makeString=itemToConvert=>(itemToConvert??"")+"";
module.exports = (input) => {
if (!input) return [];
if (typeof input !== "string" || input.match(/^\s+$/)) return [];
const lines = input.split("\n");
if (lines.length === 0) return [];
const files = [];
let currentFile = null;
let currentChunk = null;
let deletedLineCounter = 0;
let addedLineCounter = 0;
let currentFileChanges = null;
const normal = (line) => {
currentChunk?.changes.push({
type: "normal",
normal: true,
ln1: deletedLineCounter++,
ln2: addedLineCounter++,
content: line,
});
currentFileChanges.oldLines--;
currentFileChanges.newLines--;
};
const start = (line) => {
const [fromFileName, toFileName] = parseFiles(line) ?? [];
currentFile = {
chunks: [],
deletions: 0,
additions: 0,
from: fromFileName,
to: toFileName,
};
files.push(currentFile);
};
const restart = () => {
if (!currentFile || currentFile.chunks.length) start();
};
const newFile = () => {
restart();
currentFile.new = true;
currentFile.from = "/dev/null";
};
const deletedFile = () => {
restart();
currentFile.deleted = true;
currentFile.to = "/dev/null";
};
const index = (line) => {
restart();
currentFile.index = line.split(" ").slice(1);
};
const fromFile = (line) => {
restart();
currentFile.from = parseOldOrNewFile(line);
};
const toFile = (line) => {
restart();
currentFile.to = parseOldOrNewFile(line);
};
const toNumOfLines = (number) => +(number || 1);
const chunk = (line, match) => {
if (!currentFile) return;
const [oldStart, oldNumLines, newStart, newNumLines] = match.slice(1);
deletedLineCounter = +oldStart;
addedLineCounter = +newStart;
currentChunk = {
content: line,
changes: [],
oldStart: +oldStart,
oldLines: toNumOfLines(oldNumLines),
newStart: +newStart,
newLines: toNumOfLines(newNumLines),
};
currentFileChanges = {
oldLines: toNumOfLines(oldNumLines),
newLines: toNumOfLines(newNumLines),
};
currentFile.chunks.push(currentChunk);
};
const del = (line) => {
if (!currentChunk) return;
currentChunk.changes.push({
type: "del",
del: true,
ln: deletedLineCounter++,
content: line,
});
currentFile.deletions++;
currentFileChanges.oldLines--;
};
const add = (line) => {
if (!currentChunk) return;
currentChunk.changes.push({
type: "add",
add: true,
ln: addedLineCounter++,
content: line,
});
currentFile.additions++;
currentFileChanges.newLines--;
};
const eof = (line) => {
if (!currentChunk) return;
const [mostRecentChange] = currentChunk.changes.slice(-1);
currentChunk.changes.push({
type: mostRecentChange.type,
[mostRecentChange.type]: true,
ln1: mostRecentChange.ln1,
ln2: mostRecentChange.ln2,
ln: mostRecentChange.ln,
content: line,
});
};
const schemaHeaders = [
[/^diff\s/, start],
[/^new file mode \d+$/, newFile],
[/^deleted file mode \d+$/, deletedFile],
[/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/, index],
[/^---\s/, fromFile],
[/^\+\+\+\s/, toFile],
[/^@@\s+-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk],
[/^\\ No newline at end of file$/, eof],
];
const schemaContent = [
[/^-/, del],
[/^\+/, add],
[/^\s+/, normal],
];
const parseContentLine = (line) => {
for (const [pattern, handler] of schemaContent) {
const match = line.match(pattern);
if (match) {
handler(line, match);
break;
}
}
if (
currentFileChanges.oldLines === 0 &&
currentFileChanges.newLines === 0
) {
currentFileChanges = null;
}
};
const parseHeaderLine = (line) => {
for (const [pattern, handler] of schemaHeaders) {
const match = line.match(pattern);
if (match) {
handler(line, match);
break;
}
}
};
const parseLine = (line) => {
if (currentFileChanges) {
parseContentLine(line);
} else {
parseHeaderLine(line);
}
return;
};
for (const line of lines) parseLine(line);
return files;
};
const fileNameDiffRegex = /a\/.*(?=["']? ["']?b\/)|b\/.*$/g;
const gitFileHeaderRegex = /^(a|b)\//;
const parseFiles = (line) => {
let fileNames = line?.match(fileNameDiffRegex);
return fileNames?.map((fileName) =>
fileName.replace(gitFileHeaderRegex, "").replace(/("|')$/, "")
);
};
const qoutedFileNameRegex = /^\\?['"]|\\?['"]$/g;
const parseOldOrNewFile = (line) => {
let fileName = leftTrimChars(line, "-+").trim();
fileName = removeTimeStamp(fileName);
return fileName
.replace(qoutedFileNameRegex, "")
.replace(gitFileHeaderRegex, "");
};
const leftTrimChars = (string, trimmingChars) => {
string = makeString(string);
if (!trimmingChars && String.prototype.trimLeft) return string.trimLeft();
let trimmingString = formTrimmingString(trimmingChars);
return string.replace(new RegExp(`^${trimmingString}+`), "");
};
const timeStampRegex =
/\t.*|\d{4}-\d\d-\d\d\s\d\d:\d\d:\d\d(.\d+)?\s(\+|-)\d\d\d\d/;
const removeTimeStamp = (string) => {
const timeStamp = timeStampRegex.exec(string);
if (timeStamp) {
string = string.substring(0, timeStamp.index).trim();
}
return string;
};
const formTrimmingString = (trimmingChars) => {
if (trimmingChars === null || trimmingChars === undefined) return "\\s";
else if (trimmingChars instanceof RegExp) return trimmingChars.source;
return `[${makeString(trimmingChars).replace(
/([.*+?^=!:${}()|[\]/\\])/g,
"\\$1"
)}]`;
};
const makeString = (itemToConvert) => (itemToConvert ?? "") + "";
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment