Skip to content

Commit f2192de

Browse files
authored
Merge pull request #3 from DaVarga/master
Fixing performance issues caused by exessive use of spawn_child
2 parents 2b66df4 + 24b5716 commit f2192de

File tree

5 files changed

+106
-10
lines changed

5 files changed

+106
-10
lines changed

src/__fixtures__/diff.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,10 @@ index cb3c131..874b8f9 100644
4545
@@ -17,2 +16,0 @@ import { a } from "../components/a";
4646
-import { b } from "../context/b";
4747
-import { c } from "../context/c";`;
48+
49+
export const filenamesAB = `a/dirty.js
50+
b/dirty.js
51+
`;
52+
53+
export const filenamesA = `a/dirty.js
54+
`

src/__snapshots__/index.test.ts.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,25 @@ Object {
6262
"processors": Object {
6363
"diff": Object {
6464
"postprocess": [Function],
65+
"preprocess": [Function],
6566
"supportsAutofix": true,
6667
},
6768
"staged": Object {
6869
"postprocess": [Function],
70+
"preprocess": [Function],
6971
"supportsAutofix": true,
7072
},
7173
},
7274
},
7375
"processors": Object {
7476
"diff": Object {
7577
"postprocess": [Function],
78+
"preprocess": [Function],
7679
"supportsAutofix": true,
7780
},
7881
"staged": Object {
7982
"postprocess": [Function],
83+
"preprocess": [Function],
8084
"supportsAutofix": true,
8185
},
8286
},

src/git.test.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as child_process from "child_process";
22
import { mocked } from "ts-jest/utils";
3-
import { getDiffForFile, getRangesForDiff } from "./git";
4-
import { hunks, includingOnlyRemovals } from "./__fixtures__/diff";
3+
import { getDiffFileList, getDiffForFile, getRangesForDiff } from "./git";
4+
import { hunks, includingOnlyRemovals, filenamesAB } from "./__fixtures__/diff";
5+
import path from "path";
56

67
jest.mock("child_process");
78

@@ -25,4 +26,28 @@ describe("git", () => {
2526
expect(diffFromFile).toContain("diff --git");
2627
expect(diffFromFile).toContain("@@");
2728
});
29+
30+
it("should hit the cached diff of a file", () => {
31+
jest.mock("child_process").resetAllMocks();
32+
mockedChildProcess.execSync.mockReturnValue(Buffer.from(hunks));
33+
34+
const diffFromFileA = getDiffForFile("./mockfileCache.js");
35+
const diffFromFileB = getDiffForFile("./mockfileCache.js");
36+
expect(mockedChildProcess.execSync).toHaveBeenCalledTimes(1);
37+
expect(diffFromFileA).toEqual(diffFromFileB);
38+
39+
getDiffForFile("./mockfileMiss.js");
40+
expect(mockedChildProcess.execSync).toHaveBeenCalledTimes(2);
41+
});
42+
43+
it("should get the list of staged files", () => {
44+
mockedChildProcess.execSync.mockReturnValue(Buffer.from(filenamesAB));
45+
46+
const fileList = getDiffFileList();
47+
48+
expect(mockedChildProcess.execSync).toHaveBeenCalled();
49+
expect(fileList).toEqual(
50+
["a/dirty.js", "b/dirty.js"].map((p) => path.resolve(p))
51+
);
52+
});
2853
});

src/git.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,41 @@ import { Range } from "./Range";
55
const sanitizeFilePath = (filePath: string) =>
66
JSON.stringify(path.resolve(filePath));
77

8-
const getDiffForFile = (filePath: string, staged = false): string =>
9-
child_process
8+
const diffCacheKey = (filePath: string, staged: boolean): string =>
9+
JSON.stringify([path.resolve(filePath), staged]);
10+
11+
const diffCache = new Map<string, string>();
12+
13+
const getDiffForFile = (filePath: string, staged = false): string => {
14+
// Get diff entry from cache
15+
const cachedDiff = diffCache.get(diffCacheKey(filePath, staged));
16+
if (cachedDiff) {
17+
// If entry is not falsy return it
18+
return cachedDiff;
19+
} else {
20+
// Otherwise spawn child_process set it in cache and return it
21+
const diff = child_process
22+
.execSync(
23+
`git diff --diff-filter=ACM --unified=0 HEAD ${
24+
staged ? "--staged" : ""
25+
} -- ${sanitizeFilePath(filePath)}`
26+
)
27+
.toString();
28+
diffCache.set(diffCacheKey(filePath, staged), diff);
29+
return diff;
30+
}
31+
};
32+
33+
const getDiffFileList = (staged = false): string[] => {
34+
return child_process
1035
.execSync(
11-
`git diff --diff-filter=ACM --unified=0 HEAD ${
12-
staged ? " --staged" : ""
13-
} -- ${sanitizeFilePath(filePath)}`
36+
`git diff --diff-filter=ACM HEAD ${staged ? "--staged" : ""} --name-only`
1437
)
15-
.toString();
38+
.toString()
39+
.split("\n")
40+
.filter((filePath) => filePath.length)
41+
.map((filePath) => path.resolve(filePath));
42+
};
1643

1744
const isHunkHeader = (input: string) => {
1845
const hunkHeaderRE = new RegExp(/^@@ .* @@/g);
@@ -57,5 +84,5 @@ const getRangesForDiff = (diff: string): Range[] => {
5784
.filter(removeNullRanges);
5885
};
5986

60-
export { getDiffForFile, getRangesForDiff };
87+
export { getDiffForFile, getRangesForDiff, getDiffFileList };
6188
export type { Range };

src/processors.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
1-
import { getDiffForFile, getRangesForDiff, Range } from "./git";
1+
import {
2+
getDiffForFile,
3+
getRangesForDiff,
4+
getDiffFileList,
5+
Range,
6+
} from "./git";
27
import { Linter } from "eslint";
38

49
const STAGED = true;
510

611
const isLineWithinRange = (line: number) => (range: Range) =>
712
range.isWithinRange(line);
13+
let fileList: string[];
814

915
const diff = {
16+
preprocess: (
17+
text: string,
18+
filename: string
19+
): { text: string; filename: string }[] => {
20+
if (
21+
(fileList ? fileList : (fileList = getDiffFileList())).includes(filename)
22+
) {
23+
return [{ text, filename }];
24+
} else {
25+
return [];
26+
}
27+
},
28+
1029
postprocess: (
1130
messages: Linter.LintMessage[][],
1231
filename: string
@@ -35,6 +54,20 @@ const diffConfig = {
3554
};
3655

3756
const staged = {
57+
preprocess: (
58+
text: string,
59+
filename: string
60+
): { text: string; filename: string }[] => {
61+
if (
62+
(fileList ? fileList : (fileList = getDiffFileList(true))).includes(
63+
filename
64+
)
65+
) {
66+
return [{ text, filename }];
67+
} else {
68+
return [];
69+
}
70+
},
3871
postprocess: (
3972
messages: Linter.LintMessage[][],
4073
filename: string

0 commit comments

Comments
 (0)