Skip to content

Commit 62de5c8

Browse files
feat(cli): cache mode (#29)
1 parent 0e15620 commit 62de5c8

File tree

6 files changed

+309
-116
lines changed

6 files changed

+309
-116
lines changed

packages/cli/index.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import ts = require('typescript');
22
import path = require('path');
33
import type config = require('@tsslint/config');
44
import core = require('@tsslint/core');
5+
import cache = require('./lib/cache');
56
import glob = require('glob');
7+
import fs = require('fs');
68

79
(async () => {
810

@@ -79,13 +81,16 @@ import glob = require('glob');
7981
const tsconfig = await getTsconfigPath(tsconfigOption);
8082
const configFile = ts.findConfigFile(path.dirname(tsconfig), ts.sys.fileExists, 'tsslint.config.ts');
8183

82-
log.step(`Project: ${path.relative(process.cwd(), tsconfig)} (${parseCommonLine(tsconfig).fileNames.length} files)`);
83-
8484
if (!configFile) {
85+
log.step(`Project: ${path.relative(process.cwd(), tsconfig)}`);
8586
log.error('No tsslint.config.ts file found!');
8687
return;
8788
}
8889

90+
parsed = parseCommonLine(tsconfig);
91+
92+
log.step(`Project: ${path.relative(process.cwd(), tsconfig)} (${parsed.fileNames.length} files)`);
93+
8994
if (!configs.has(configFile)) {
9095
try {
9196
configs.set(configFile, await core.buildConfigFile(configFile, ts.sys.createHash, {
@@ -103,24 +108,47 @@ import glob = require('glob');
103108
return;
104109
}
105110

106-
parsed = parseCommonLine(tsconfig);
107111
if (!parsed.fileNames) {
108112
throw new Error('No input files found in tsconfig!');
109113
}
110114
projectVersion++;
111115
typeRootsVersion++;
112116

113-
const linter = core.createLinter({
117+
const lintCache = process.argv.includes('--force')
118+
? {}
119+
: cache.loadCache(configFile, ts.sys.createHash);
120+
const projectContext: config.ProjectContext = {
114121
configFile,
115122
languageService,
116123
languageServiceHost,
117124
typescript: ts,
118125
tsconfig: ts.server.toNormalizedPath(tsconfig),
119-
}, tsslintConfig, false);
126+
};
127+
const linter = core.createLinter(projectContext, tsslintConfig, 'cli');
120128

121129
let hasFix = false;
130+
let cached = 0;
122131

123132
for (const fileName of parsed.fileNames) {
133+
134+
const fileMtime = fs.statSync(fileName).mtimeMs;
135+
let fileCache = lintCache[fileName];
136+
if (fileCache) {
137+
if (fileCache[0] !== fileMtime) {
138+
fileCache[0] = fileMtime;
139+
fileCache[1] = {};
140+
fileCache[2].length = 0;
141+
fileCache[3].length = 0;
142+
fileCache[4] = {};
143+
}
144+
else {
145+
cached++;
146+
}
147+
}
148+
else {
149+
lintCache[fileName] = fileCache = [fileMtime, {}, [], [], {}];
150+
}
151+
124152
if (process.argv.includes('--fix')) {
125153

126154
let retry = 3;
@@ -130,8 +158,14 @@ import glob = require('glob');
130158
while (shouldRetry && retry) {
131159
shouldRetry = false;
132160
retry--;
133-
const diagnostics = linter.lint(fileName);
134-
const fixes = linter.getCodeFixes(fileName, 0, Number.MAX_VALUE, diagnostics);
161+
if (Object.values(fileCache[1]).some(fixes => fixes > 0)) {
162+
// Reset the cache if there are any fixes applied.
163+
fileCache[1] = {};
164+
fileCache[2].length = 0;
165+
fileCache[3].length = 0;
166+
}
167+
const diagnostics = linter.lint(fileName, fileCache);
168+
const fixes = linter.getCodeFixes(fileName, 0, Number.MAX_VALUE, diagnostics, fileCache);
135169
const textChanges = core.combineCodeFixes(fileName, fixes);
136170
if (textChanges.length) {
137171
const oldSnapshot = snapshots.get(fileName)!;
@@ -145,10 +179,11 @@ import glob = require('glob');
145179

146180
if (newSnapshot) {
147181
ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
182+
fileCache[0] = fs.statSync(fileName).mtimeMs;
148183
}
149184
}
150185
else {
151-
const diagnostics = linter.lint(fileName);
186+
const diagnostics = linter.lint(fileName, fileCache);
152187
for (const diagnostic of diagnostics) {
153188
if (diagnostic.category === ts.DiagnosticCategory.Suggestion) {
154189
continue;
@@ -176,6 +211,12 @@ import glob = require('glob');
176211
}
177212
}
178213

214+
cache.saveCache(configFile, lintCache, ts.sys.createHash);
215+
216+
if (cached) {
217+
log.info(`Linted ${parsed.fileNames.length - cached} files. (Cached ${cached} files result, use --force to re-lint all files.)`);
218+
}
219+
179220
if (hasFix) {
180221
log.info(`Use --fix to apply fixes.`);
181222
}

packages/cli/lib/cache.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import core = require('@tsslint/core');
2+
import path = require('path');
3+
import fs = require('fs');
4+
5+
export type CacheData = Record<string /* fileName */, core.FileLintCache>;
6+
7+
export function loadCache(
8+
configFilePath: string,
9+
createHash: (path: string) => string = btoa
10+
): CacheData {
11+
const outDir = core.getDotTsslintPath(configFilePath);
12+
const cacheFileName = createHash(path.relative(outDir, configFilePath)) + '.cache.json';
13+
const cacheFilePath = path.join(outDir, cacheFileName);
14+
const cacheFileStat = fs.statSync(cacheFilePath, { throwIfNoEntry: false });
15+
const configFileStat = fs.statSync(configFilePath, { throwIfNoEntry: false });
16+
if (cacheFileStat?.isFile() && cacheFileStat.mtimeMs > (configFileStat?.mtimeMs ?? 0)) {
17+
try {
18+
return require(cacheFilePath);
19+
} catch {
20+
return {};
21+
}
22+
}
23+
return {};
24+
}
25+
26+
export function saveCache(
27+
configFilePath: string,
28+
cache: CacheData,
29+
createHash: (path: string) => string = btoa
30+
): void {
31+
const outDir = core.getDotTsslintPath(configFilePath);
32+
const cacheFileName = createHash(path.relative(outDir, configFilePath)) + '.cache.json';
33+
const cacheFilePath = path.join(outDir, cacheFileName);
34+
fs.writeFileSync(cacheFilePath, JSON.stringify(cache));
35+
}

0 commit comments

Comments
 (0)