@tsslint/cli 1.3.6 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +184 -188
- package/lib/cache.d.ts +2 -2
- package/lib/cache.js +4 -4
- package/lib/worker.d.ts +22 -0
- package/lib/worker.js +220 -0
- package/package.json +4 -4
package/index.js
CHANGED
|
@@ -3,147 +3,187 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const ts = require("typescript");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const core = require("@tsslint/core");
|
|
6
|
-
const cache = require("./lib/cache");
|
|
6
|
+
const cache = require("./lib/cache.js");
|
|
7
|
+
const worker = require("./lib/worker.js");
|
|
7
8
|
const glob = require("glob");
|
|
8
9
|
const fs = require("fs");
|
|
10
|
+
const os = require("os");
|
|
9
11
|
const _reset = '\x1b[0m';
|
|
10
12
|
const purple = (s) => '\x1b[35m' + s + _reset;
|
|
11
13
|
const darkGray = (s) => '\x1b[90m' + s + _reset;
|
|
12
14
|
const lightRed = (s) => '\x1b[91m' + s + _reset;
|
|
13
15
|
const lightGreen = (s) => '\x1b[92m' + s + _reset;
|
|
14
16
|
const lightYellow = (s) => '\x1b[93m' + s + _reset;
|
|
17
|
+
let threads = 1;
|
|
18
|
+
if (process.argv.includes('--threads')) {
|
|
19
|
+
const threadsIndex = process.argv.indexOf('--threads');
|
|
20
|
+
const threadsArg = process.argv[threadsIndex + 1];
|
|
21
|
+
if (!threadsArg || threadsArg.startsWith('-')) {
|
|
22
|
+
console.error(lightRed(`Missing argument for --threads.`));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
threads = Math.min(os.availableParallelism(), Number(threadsArg));
|
|
26
|
+
}
|
|
15
27
|
(async () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
getScriptVersion(fileName) {
|
|
42
|
-
return versions.get(fileName)?.toString() ?? '0';
|
|
43
|
-
},
|
|
44
|
-
getScriptSnapshot(fileName) {
|
|
45
|
-
if (!snapshots.has(fileName)) {
|
|
46
|
-
snapshots.set(fileName, ts.ScriptSnapshot.fromString(ts.sys.readFile(fileName)));
|
|
28
|
+
class Project {
|
|
29
|
+
constructor(tsconfigOption) {
|
|
30
|
+
this.workers = [];
|
|
31
|
+
this.fileNames = [];
|
|
32
|
+
this.options = {};
|
|
33
|
+
this.currentFileIndex = 0;
|
|
34
|
+
this.cache = {};
|
|
35
|
+
try {
|
|
36
|
+
this.tsconfig = require.resolve(tsconfigOption, { paths: [process.cwd()] });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
console.error(lightRed(`No such file: ${tsconfigOption}`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
this.configFile = ts.findConfigFile(path.dirname(this.tsconfig), ts.sys.fileExists, 'tsslint.config.ts');
|
|
43
|
+
if (!this.configFile) {
|
|
44
|
+
log(`${purple('[project]')} ${path.relative(process.cwd(), this.tsconfig)} ${darkGray('(No tsslint.config.ts found)')}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const commonLine = parseCommonLine(this.tsconfig);
|
|
48
|
+
this.fileNames = commonLine.fileNames;
|
|
49
|
+
this.options = commonLine.options;
|
|
50
|
+
if (!this.fileNames.length) {
|
|
51
|
+
log(`${purple('[project]')} ${path.relative(process.cwd(), this.tsconfig)} ${darkGray('(No included files)')}`);
|
|
52
|
+
return;
|
|
47
53
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const
|
|
54
|
+
log(`${purple('[project]')} ${path.relative(process.cwd(), this.tsconfig)} ${darkGray(`(${this.fileNames.length})`)}`);
|
|
55
|
+
if (!process.argv.includes('--force')) {
|
|
56
|
+
this.cache = cache.loadCache(this.tsconfig, this.configFile, ts.sys.createHash);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const builtConfigs = new Map();
|
|
61
|
+
const clack = await import('@clack/prompts');
|
|
62
|
+
const processFiles = new Set();
|
|
63
|
+
let projects = [];
|
|
64
|
+
let spinner = clack.spinner();
|
|
65
|
+
let lastSpinnerUpdate = Date.now();
|
|
66
|
+
let hasFix = false;
|
|
67
|
+
let allFilesNum = 0;
|
|
68
|
+
let processed = 0;
|
|
69
|
+
let excluded = 0;
|
|
70
|
+
let passed = 0;
|
|
71
|
+
let errors = 0;
|
|
72
|
+
let warnings = 0;
|
|
73
|
+
let cached = 0;
|
|
74
|
+
spinner.start();
|
|
55
75
|
if (process.argv.includes('--project')) {
|
|
56
76
|
const projectIndex = process.argv.indexOf('--project');
|
|
57
77
|
let tsconfig = process.argv[projectIndex + 1];
|
|
58
|
-
if (tsconfig.startsWith('-')
|
|
59
|
-
|
|
78
|
+
if (!tsconfig || tsconfig.startsWith('-')) {
|
|
79
|
+
console.error(lightRed(`Missing argument for --project.`));
|
|
80
|
+
process.exit(1);
|
|
60
81
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
tsconfig = `./${tsconfig}`;
|
|
64
|
-
}
|
|
65
|
-
await projectWorker(tsconfig);
|
|
82
|
+
if (!tsconfig.startsWith('.')) {
|
|
83
|
+
tsconfig = `./${tsconfig}`;
|
|
66
84
|
}
|
|
85
|
+
projects.push(new Project(tsconfig));
|
|
67
86
|
}
|
|
68
87
|
else if (process.argv.includes('--projects')) {
|
|
69
88
|
const projectsIndex = process.argv.indexOf('--projects');
|
|
89
|
+
let foundArg = false;
|
|
70
90
|
for (let i = projectsIndex + 1; i < process.argv.length; i++) {
|
|
71
91
|
if (process.argv[i].startsWith('-')) {
|
|
72
92
|
break;
|
|
73
93
|
}
|
|
94
|
+
foundArg = true;
|
|
74
95
|
const searchGlob = process.argv[i];
|
|
75
96
|
const tsconfigs = glob.sync(searchGlob);
|
|
76
97
|
for (let tsconfig of tsconfigs) {
|
|
77
98
|
if (!tsconfig.startsWith('.')) {
|
|
78
99
|
tsconfig = `./${tsconfig}`;
|
|
79
100
|
}
|
|
80
|
-
|
|
101
|
+
projects.push(new Project(tsconfig));
|
|
81
102
|
}
|
|
82
103
|
}
|
|
104
|
+
if (!foundArg) {
|
|
105
|
+
console.error(lightRed(`Missing argument for --projects.`));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
83
108
|
}
|
|
84
109
|
else {
|
|
85
110
|
const tsconfig = await askTSConfig();
|
|
86
|
-
|
|
111
|
+
projects.push(new Project(tsconfig));
|
|
112
|
+
}
|
|
113
|
+
projects = projects.filter(project => !!project.configFile);
|
|
114
|
+
projects = projects.filter(project => !!project.fileNames.length);
|
|
115
|
+
for (const project of projects) {
|
|
116
|
+
project.builtConfig = await getBuiltConfig(project.configFile);
|
|
117
|
+
}
|
|
118
|
+
projects = projects.filter(project => !!project.builtConfig);
|
|
119
|
+
for (const project of projects) {
|
|
120
|
+
allFilesNum += project.fileNames.length;
|
|
121
|
+
}
|
|
122
|
+
if (allFilesNum === 0) {
|
|
123
|
+
spinner.stop(lightYellow('No input files.'));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
if (threads === 1) {
|
|
127
|
+
await startWorker(worker.createLocal());
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
await Promise.all(new Array(threads).fill(0).map(() => {
|
|
131
|
+
return startWorker(worker.create());
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
spinner.stop(darkGray(cached
|
|
135
|
+
? `Processed ${processed} files with cache. (Use --force to ignore cache.)`
|
|
136
|
+
: `Processed ${processed} files.`));
|
|
137
|
+
const data = [
|
|
138
|
+
[passed, 'passed', lightGreen],
|
|
139
|
+
[errors, 'errors', lightRed],
|
|
140
|
+
[warnings, 'warnings', lightYellow],
|
|
141
|
+
[excluded, 'excluded', darkGray],
|
|
142
|
+
];
|
|
143
|
+
let summary = data
|
|
144
|
+
.filter(([count]) => count)
|
|
145
|
+
.map(([count, label, color]) => color(`${count} ${label}`))
|
|
146
|
+
.join(darkGray(' | '));
|
|
147
|
+
if (hasFix) {
|
|
148
|
+
summary += darkGray(` (Use --fix to apply automatic fixes.)`);
|
|
149
|
+
}
|
|
150
|
+
else if (errors || warnings) {
|
|
151
|
+
summary += darkGray(` (No fixes available.)`);
|
|
87
152
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (!parsed.fileNames.length) {
|
|
94
|
-
clack.outro(lightYellow('No included files.'));
|
|
153
|
+
clack.outro(summary);
|
|
154
|
+
process.exit(errors ? 1 : 0);
|
|
155
|
+
async function startWorker(linterWorker) {
|
|
156
|
+
const unfinishedProjects = projects.filter(project => project.currentFileIndex < project.fileNames.length);
|
|
157
|
+
if (!unfinishedProjects.length) {
|
|
95
158
|
return;
|
|
96
159
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
160
|
+
// Select a project that has not has a worker yet
|
|
161
|
+
let project = unfinishedProjects.find(project => !project.workers.length);
|
|
162
|
+
if (!project) {
|
|
163
|
+
// Choose a project with the most files left per worker
|
|
164
|
+
project = unfinishedProjects.sort((a, b) => {
|
|
165
|
+
const aFilesPerWorker = (a.fileNames.length - a.currentFileIndex) / a.workers.length;
|
|
166
|
+
const bFilesPerWorker = (b.fileNames.length - b.currentFileIndex) / b.workers.length;
|
|
167
|
+
return bFilesPerWorker - aFilesPerWorker;
|
|
168
|
+
})[0];
|
|
101
169
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
configs.set(configFile, undefined);
|
|
108
|
-
console.error(err);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const tsslintConfig = configs.get(configFile);
|
|
112
|
-
if (!tsslintConfig) {
|
|
170
|
+
project.workers.push(linterWorker);
|
|
171
|
+
const setupSuccess = await linterWorker.setup(project.tsconfig, project.configFile, project.builtConfig, project.fileNames, project.options);
|
|
172
|
+
if (!setupSuccess) {
|
|
173
|
+
projects = projects.filter(p => p !== project);
|
|
174
|
+
startWorker(linterWorker);
|
|
113
175
|
return;
|
|
114
176
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
languageService,
|
|
123
|
-
languageServiceHost,
|
|
124
|
-
typescript: ts,
|
|
125
|
-
tsconfig: ts.server.toNormalizedPath(tsconfig),
|
|
126
|
-
};
|
|
127
|
-
const linter = core.createLinter(projectContext, tsslintConfig, 'cli', clack);
|
|
128
|
-
let lintSpinner = clack.spinner();
|
|
129
|
-
let hasFix = false;
|
|
130
|
-
let excluded = 0;
|
|
131
|
-
let passed = 0;
|
|
132
|
-
let errors = 0;
|
|
133
|
-
let warnings = 0;
|
|
134
|
-
let cached = 0;
|
|
135
|
-
let t = Date.now();
|
|
136
|
-
lintSpinner.start(darkGray(`[1/${parsed.fileNames.length}] ${path.relative(process.cwd(), parsed.fileNames[0])}`));
|
|
137
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
138
|
-
for (let i = 0; i < parsed.fileNames.length; i++) {
|
|
139
|
-
const fileName = parsed.fileNames[i];
|
|
140
|
-
if (Date.now() - t > 100) {
|
|
141
|
-
t = Date.now();
|
|
142
|
-
lintSpinner.message(darkGray(`[${i + 1}/${parsed.fileNames.length}] ${path.relative(process.cwd(), fileName)}`));
|
|
177
|
+
while (project.currentFileIndex < project.fileNames.length) {
|
|
178
|
+
const i = project.currentFileIndex++;
|
|
179
|
+
const fileName = project.fileNames[i];
|
|
180
|
+
const fileMtime = fs.statSync(fileName).mtimeMs;
|
|
181
|
+
addProcessFile(fileName);
|
|
182
|
+
if (Date.now() - lastSpinnerUpdate > 100) {
|
|
183
|
+
lastSpinnerUpdate = Date.now();
|
|
143
184
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
144
185
|
}
|
|
145
|
-
|
|
146
|
-
let fileCache = lintCache[fileName];
|
|
186
|
+
let fileCache = project.cache[fileName];
|
|
147
187
|
if (fileCache) {
|
|
148
188
|
if (fileCache[0] !== fileMtime) {
|
|
149
189
|
fileCache[0] = fileMtime;
|
|
@@ -157,48 +197,17 @@ const lightYellow = (s) => '\x1b[93m' + s + _reset;
|
|
|
157
197
|
}
|
|
158
198
|
}
|
|
159
199
|
else {
|
|
160
|
-
|
|
200
|
+
project.cache[fileName] = fileCache = [fileMtime, {}, [], [], {}];
|
|
161
201
|
}
|
|
162
202
|
let diagnostics;
|
|
163
203
|
if (process.argv.includes('--fix')) {
|
|
164
|
-
|
|
165
|
-
let shouldRetry = true;
|
|
166
|
-
let newSnapshot;
|
|
167
|
-
while (shouldRetry && retry) {
|
|
168
|
-
shouldRetry = false;
|
|
169
|
-
retry--;
|
|
170
|
-
if (Object.values(fileCache[1]).some(fixes => fixes > 0)) {
|
|
171
|
-
// Reset the cache if there are any fixes applied.
|
|
172
|
-
fileCache[1] = {};
|
|
173
|
-
fileCache[2].length = 0;
|
|
174
|
-
fileCache[3].length = 0;
|
|
175
|
-
}
|
|
176
|
-
diagnostics = linter.lint(fileName, fileCache);
|
|
177
|
-
const fixes = linter.getCodeFixes(fileName, 0, Number.MAX_VALUE, diagnostics, fileCache);
|
|
178
|
-
const textChanges = core.combineCodeFixes(fileName, fixes);
|
|
179
|
-
if (textChanges.length) {
|
|
180
|
-
const oldSnapshot = snapshots.get(fileName);
|
|
181
|
-
newSnapshot = core.applyTextChanges(oldSnapshot, textChanges);
|
|
182
|
-
snapshots.set(fileName, newSnapshot);
|
|
183
|
-
versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
|
|
184
|
-
projectVersion++;
|
|
185
|
-
shouldRetry = true;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (newSnapshot) {
|
|
189
|
-
ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
|
|
190
|
-
fileCache[0] = fs.statSync(fileName).mtimeMs;
|
|
191
|
-
}
|
|
192
|
-
if (shouldRetry) {
|
|
193
|
-
diagnostics = linter.lint(fileName, fileCache);
|
|
194
|
-
}
|
|
204
|
+
diagnostics = await linterWorker.lintAndFix(fileName, fileCache);
|
|
195
205
|
}
|
|
196
206
|
else {
|
|
197
|
-
diagnostics =
|
|
207
|
+
diagnostics = await linterWorker.lint(fileName, fileCache);
|
|
198
208
|
}
|
|
199
209
|
if (diagnostics.length) {
|
|
200
|
-
hasFix ||=
|
|
201
|
-
hasError ||= diagnostics.some(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error);
|
|
210
|
+
hasFix ||= await linterWorker.hasCodeFixes(fileName);
|
|
202
211
|
for (const diagnostic of diagnostics) {
|
|
203
212
|
if (diagnostic.category === ts.DiagnosticCategory.Suggestion) {
|
|
204
213
|
continue;
|
|
@@ -209,71 +218,36 @@ const lightYellow = (s) => '\x1b[93m' + s + _reset;
|
|
|
209
218
|
getNewLine: () => ts.sys.newLine,
|
|
210
219
|
});
|
|
211
220
|
output = output.replace(`TS${diagnostic.code}`, String(diagnostic.code));
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
lintSpinner.stop(output, 2);
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
lintSpinner.stop(output);
|
|
223
|
-
}
|
|
224
|
-
lintSpinner = undefined;
|
|
221
|
+
if (diagnostic.category === ts.DiagnosticCategory.Error) {
|
|
222
|
+
errors++;
|
|
223
|
+
log(output, 1);
|
|
224
|
+
}
|
|
225
|
+
else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
|
|
226
|
+
warnings++;
|
|
227
|
+
log(output, 2);
|
|
225
228
|
}
|
|
226
229
|
else {
|
|
227
|
-
|
|
228
|
-
errors++;
|
|
229
|
-
clack.log.error(output);
|
|
230
|
-
}
|
|
231
|
-
else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
|
|
232
|
-
warnings++;
|
|
233
|
-
clack.log.warning(output);
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
clack.log.info(output);
|
|
237
|
-
}
|
|
230
|
+
log(output);
|
|
238
231
|
}
|
|
239
232
|
}
|
|
240
233
|
}
|
|
241
|
-
else if (!
|
|
234
|
+
else if (!(await linterWorker.hasRules(fileName, fileCache[4]))) {
|
|
242
235
|
excluded++;
|
|
243
236
|
}
|
|
244
237
|
else {
|
|
245
238
|
passed++;
|
|
246
239
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
lintSpinner.start(darkGray(`[${i + 1}/${parsed.fileNames.length}] ${path.relative(process.cwd(), parsed.fileNames[i])}`));
|
|
250
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (cached) {
|
|
254
|
-
lintSpinner.stop(darkGray(`Processed ${parsed.fileNames.length} files with cache. (Use --force to ignore cache.)`));
|
|
240
|
+
processed++;
|
|
241
|
+
removeProcessFile(fileName);
|
|
255
242
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
[warnings, 'warnings', lightYellow],
|
|
263
|
-
[excluded, 'excluded', darkGray],
|
|
264
|
-
];
|
|
265
|
-
let summary = data
|
|
266
|
-
.filter(([count]) => count)
|
|
267
|
-
.map(([count, label, color]) => color(`${count} ${label}`))
|
|
268
|
-
.join(darkGray(' | '));
|
|
269
|
-
if (hasFix) {
|
|
270
|
-
summary += darkGray(` (Use --fix to apply automatic fixes.)`);
|
|
271
|
-
}
|
|
272
|
-
else if (errors || warnings) {
|
|
273
|
-
summary += darkGray(` (No fixes available.)`);
|
|
243
|
+
cache.saveCache(project.tsconfig, project.configFile, project.cache, ts.sys.createHash);
|
|
244
|
+
await startWorker(linterWorker);
|
|
245
|
+
}
|
|
246
|
+
async function getBuiltConfig(configFile) {
|
|
247
|
+
if (!builtConfigs.has(configFile)) {
|
|
248
|
+
builtConfigs.set(configFile, core.buildConfig(configFile, ts.sys.createHash, spinner, (s, code) => log(darkGray(s), code)));
|
|
274
249
|
}
|
|
275
|
-
|
|
276
|
-
cache.saveCache(configFile, lintCache, ts.sys.createHash);
|
|
250
|
+
return await builtConfigs.get(configFile);
|
|
277
251
|
}
|
|
278
252
|
async function askTSConfig() {
|
|
279
253
|
const presetConfig = ts.findConfigFile(process.cwd(), ts.sys.fileExists);
|
|
@@ -300,5 +274,27 @@ const lightYellow = (s) => '\x1b[93m' + s + _reset;
|
|
|
300
274
|
const jsonConfigFile = ts.readJsonConfigFile(tsconfig, ts.sys.readFile);
|
|
301
275
|
return ts.parseJsonSourceFileConfigFileContent(jsonConfigFile, ts.sys, path.dirname(tsconfig), {}, tsconfig);
|
|
302
276
|
}
|
|
277
|
+
function addProcessFile(fileName) {
|
|
278
|
+
processFiles.add(fileName);
|
|
279
|
+
updateSpinner();
|
|
280
|
+
}
|
|
281
|
+
function removeProcessFile(fileName) {
|
|
282
|
+
processFiles.delete(fileName);
|
|
283
|
+
updateSpinner();
|
|
284
|
+
}
|
|
285
|
+
function updateSpinner() {
|
|
286
|
+
if (processFiles.size === 1) {
|
|
287
|
+
const fileName = processFiles.values().next().value;
|
|
288
|
+
spinner.message(`[${processed + processFiles.size}/${allFilesNum}] ${path.relative(process.cwd(), fileName)}`);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
spinner.message(`[${processed + processFiles.size}/${allFilesNum}] Processing ${processFiles.size} files`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function log(msg, code) {
|
|
295
|
+
spinner.stop(msg, code);
|
|
296
|
+
spinner = clack.spinner();
|
|
297
|
+
spinner.start();
|
|
298
|
+
}
|
|
303
299
|
})();
|
|
304
300
|
//# sourceMappingURL=index.js.map
|
package/lib/cache.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import core = require('@tsslint/core');
|
|
2
2
|
export type CacheData = Record<string, core.FileLintCache>;
|
|
3
|
-
export declare function loadCache(configFilePath: string, createHash?: (path: string) => string): CacheData;
|
|
4
|
-
export declare function saveCache(configFilePath: string, cache: CacheData, createHash?: (path: string) => string): void;
|
|
3
|
+
export declare function loadCache(tsconfig: string, configFilePath: string, createHash?: (path: string) => string): CacheData;
|
|
4
|
+
export declare function saveCache(tsconfig: string, configFilePath: string, cache: CacheData, createHash?: (path: string) => string): void;
|
package/lib/cache.js
CHANGED
|
@@ -5,9 +5,9 @@ exports.saveCache = saveCache;
|
|
|
5
5
|
const core = require("@tsslint/core");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const fs = require("fs");
|
|
8
|
-
function loadCache(configFilePath, createHash = btoa) {
|
|
8
|
+
function loadCache(tsconfig, configFilePath, createHash = btoa) {
|
|
9
9
|
const outDir = core.getDotTsslintPath(configFilePath);
|
|
10
|
-
const cacheFileName = createHash(path.relative(outDir, configFilePath)) + '.cache.json';
|
|
10
|
+
const cacheFileName = createHash(path.relative(outDir, configFilePath)) + '_' + createHash(path.relative(outDir, tsconfig)) + '.cache.json';
|
|
11
11
|
const cacheFilePath = path.join(outDir, cacheFileName);
|
|
12
12
|
const cacheFileStat = fs.statSync(cacheFilePath, { throwIfNoEntry: false });
|
|
13
13
|
const configFileStat = fs.statSync(configFilePath, { throwIfNoEntry: false });
|
|
@@ -21,9 +21,9 @@ function loadCache(configFilePath, createHash = btoa) {
|
|
|
21
21
|
}
|
|
22
22
|
return {};
|
|
23
23
|
}
|
|
24
|
-
function saveCache(configFilePath, cache, createHash = btoa) {
|
|
24
|
+
function saveCache(tsconfig, configFilePath, cache, createHash = btoa) {
|
|
25
25
|
const outDir = core.getDotTsslintPath(configFilePath);
|
|
26
|
-
const cacheFileName = createHash(path.relative(outDir, configFilePath)) + '.cache.json';
|
|
26
|
+
const cacheFileName = createHash(path.relative(outDir, configFilePath)) + '_' + createHash(path.relative(outDir, tsconfig)) + '.cache.json';
|
|
27
27
|
const cacheFilePath = path.join(outDir, cacheFileName);
|
|
28
28
|
fs.writeFileSync(cacheFilePath, JSON.stringify(cache));
|
|
29
29
|
}
|
package/lib/worker.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import ts = require('typescript');
|
|
2
|
+
import core = require('@tsslint/core');
|
|
3
|
+
export declare function createLocal(): {
|
|
4
|
+
setup(tsconfig: string, configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions): Promise<boolean>;
|
|
5
|
+
lint(fileName: string, fileCache: core.FileLintCache): ts.DiagnosticWithLocation[];
|
|
6
|
+
lintAndFix(fileName: string, fileCache: core.FileLintCache): ts.DiagnosticWithLocation[];
|
|
7
|
+
hasCodeFixes(fileName: string): boolean;
|
|
8
|
+
hasRules(fileName: string, minimatchCache: Record<string, boolean>): Promise<boolean>;
|
|
9
|
+
};
|
|
10
|
+
export declare function create(): {
|
|
11
|
+
setup(tsconfig: string, configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions): Promise<boolean>;
|
|
12
|
+
lint(fileName: string, fileCache: core.FileLintCache): Promise<ts.DiagnosticWithLocation[]>;
|
|
13
|
+
lintAndFix(fileName: string, fileCache: core.FileLintCache): Promise<ts.DiagnosticWithLocation[]>;
|
|
14
|
+
hasCodeFixes(fileName: string): Promise<boolean>;
|
|
15
|
+
hasRules(fileName: string, minimatchCache: Record<string, boolean>): Promise<boolean>;
|
|
16
|
+
};
|
|
17
|
+
declare function setup(tsconfig: string, configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions): Promise<boolean>;
|
|
18
|
+
declare function lintAndFix(fileName: string, fileCache: core.FileLintCache): readonly [ts.DiagnosticWithLocation[], core.FileLintCache];
|
|
19
|
+
declare function lint(fileName: string, fileCache: core.FileLintCache): readonly [ts.DiagnosticWithLocation[], core.FileLintCache];
|
|
20
|
+
declare function hasCodeFixes(fileName: string): boolean;
|
|
21
|
+
declare function hasRules(fileName: string, minimatchCache: core.FileLintCache[4]): readonly [boolean, Record<string, boolean>];
|
|
22
|
+
export {};
|
package/lib/worker.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLocal = createLocal;
|
|
4
|
+
exports.create = create;
|
|
5
|
+
const ts = require("typescript");
|
|
6
|
+
const core = require("@tsslint/core");
|
|
7
|
+
const url = require("url");
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const worker_threads = require("worker_threads");
|
|
10
|
+
let projectVersion = 0;
|
|
11
|
+
let typeRootsVersion = 0;
|
|
12
|
+
let options = {};
|
|
13
|
+
let fileNames = [];
|
|
14
|
+
let linter;
|
|
15
|
+
const snapshots = new Map();
|
|
16
|
+
const versions = new Map();
|
|
17
|
+
const languageServiceHost = {
|
|
18
|
+
...ts.sys,
|
|
19
|
+
useCaseSensitiveFileNames() {
|
|
20
|
+
return ts.sys.useCaseSensitiveFileNames;
|
|
21
|
+
},
|
|
22
|
+
getProjectVersion() {
|
|
23
|
+
return projectVersion.toString();
|
|
24
|
+
},
|
|
25
|
+
getTypeRootsVersion() {
|
|
26
|
+
return typeRootsVersion;
|
|
27
|
+
},
|
|
28
|
+
getCompilationSettings() {
|
|
29
|
+
return options;
|
|
30
|
+
},
|
|
31
|
+
getScriptFileNames() {
|
|
32
|
+
return fileNames;
|
|
33
|
+
},
|
|
34
|
+
getScriptVersion(fileName) {
|
|
35
|
+
return versions.get(fileName)?.toString() ?? '0';
|
|
36
|
+
},
|
|
37
|
+
getScriptSnapshot(fileName) {
|
|
38
|
+
if (!snapshots.has(fileName)) {
|
|
39
|
+
snapshots.set(fileName, ts.ScriptSnapshot.fromString(ts.sys.readFile(fileName)));
|
|
40
|
+
}
|
|
41
|
+
return snapshots.get(fileName);
|
|
42
|
+
},
|
|
43
|
+
getDefaultLibFileName(options) {
|
|
44
|
+
return ts.getDefaultLibFilePath(options);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
const languageService = ts.createLanguageService(languageServiceHost);
|
|
48
|
+
function createLocal() {
|
|
49
|
+
return {
|
|
50
|
+
setup(...args) {
|
|
51
|
+
return setup(...args);
|
|
52
|
+
},
|
|
53
|
+
lint(...args) {
|
|
54
|
+
return lint(...args)[0];
|
|
55
|
+
},
|
|
56
|
+
lintAndFix(...args) {
|
|
57
|
+
return lintAndFix(...args)[0];
|
|
58
|
+
},
|
|
59
|
+
hasCodeFixes(...args) {
|
|
60
|
+
return hasCodeFixes(...args);
|
|
61
|
+
},
|
|
62
|
+
async hasRules(...args) {
|
|
63
|
+
return hasRules(...args)[0];
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function create() {
|
|
68
|
+
const worker = new worker_threads.Worker(__filename);
|
|
69
|
+
return {
|
|
70
|
+
setup(...args) {
|
|
71
|
+
return sendRequest(setup, ...args);
|
|
72
|
+
},
|
|
73
|
+
async lint(...args) {
|
|
74
|
+
const [res, newCache] = await sendRequest(lint, ...args);
|
|
75
|
+
Object.assign(args[1], newCache); // Sync the cache
|
|
76
|
+
return res;
|
|
77
|
+
},
|
|
78
|
+
async lintAndFix(...args) {
|
|
79
|
+
const [res, newCache] = await sendRequest(lintAndFix, ...args);
|
|
80
|
+
Object.assign(args[1], newCache); // Sync the cache
|
|
81
|
+
return res;
|
|
82
|
+
},
|
|
83
|
+
hasCodeFixes(...args) {
|
|
84
|
+
return sendRequest(hasCodeFixes, ...args);
|
|
85
|
+
},
|
|
86
|
+
async hasRules(...args) {
|
|
87
|
+
const [res, newCache] = await sendRequest(hasRules, ...args);
|
|
88
|
+
Object.assign(args[1], newCache); // Sync the cache
|
|
89
|
+
return res;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
function sendRequest(t, ...args) {
|
|
93
|
+
return new Promise(resolve => {
|
|
94
|
+
worker.once('message', json => {
|
|
95
|
+
resolve(JSON.parse(json));
|
|
96
|
+
});
|
|
97
|
+
worker.postMessage(JSON.stringify([t.name, ...args]));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
worker_threads.parentPort?.on('message', async (json) => {
|
|
102
|
+
const data = JSON.parse(json);
|
|
103
|
+
const result = await handlers[data[0]](...data.slice(1));
|
|
104
|
+
worker_threads.parentPort.postMessage(JSON.stringify(result));
|
|
105
|
+
});
|
|
106
|
+
const handlers = {
|
|
107
|
+
setup,
|
|
108
|
+
lint,
|
|
109
|
+
lintAndFix,
|
|
110
|
+
hasCodeFixes,
|
|
111
|
+
hasRules,
|
|
112
|
+
};
|
|
113
|
+
async function setup(tsconfig, configFile, builtConfig, _fileNames, _options) {
|
|
114
|
+
const clack = await import('@clack/prompts');
|
|
115
|
+
let config;
|
|
116
|
+
try {
|
|
117
|
+
config = (await import(url.pathToFileURL(builtConfig).toString())).default;
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (err instanceof Error) {
|
|
121
|
+
clack.log.error(err.stack ?? err.message);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
clack.log.error(String(err));
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
projectVersion++;
|
|
129
|
+
typeRootsVersion++;
|
|
130
|
+
fileNames = _fileNames;
|
|
131
|
+
options = _options;
|
|
132
|
+
linter = core.createLinter({
|
|
133
|
+
configFile,
|
|
134
|
+
languageService,
|
|
135
|
+
languageServiceHost,
|
|
136
|
+
typescript: ts,
|
|
137
|
+
tsconfig: ts.server.toNormalizedPath(tsconfig),
|
|
138
|
+
}, config, 'cli', clack);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
function lintAndFix(fileName, fileCache) {
|
|
142
|
+
let retry = 3;
|
|
143
|
+
let shouldRetry = true;
|
|
144
|
+
let newSnapshot;
|
|
145
|
+
let diagnostics;
|
|
146
|
+
while (shouldRetry && retry--) {
|
|
147
|
+
if (Object.values(fileCache[1]).some(fixes => fixes > 0)) {
|
|
148
|
+
// Reset the cache if there are any fixes applied.
|
|
149
|
+
fileCache[1] = {};
|
|
150
|
+
fileCache[2].length = 0;
|
|
151
|
+
fileCache[3].length = 0;
|
|
152
|
+
}
|
|
153
|
+
diagnostics = linter.lint(fileName, fileCache);
|
|
154
|
+
const fixes = linter
|
|
155
|
+
.getCodeFixes(fileName, 0, Number.MAX_VALUE, diagnostics, fileCache[4])
|
|
156
|
+
.filter(fix => fix.fixId === 'tsslint');
|
|
157
|
+
const textChanges = core.combineCodeFixes(fileName, fixes);
|
|
158
|
+
if (textChanges.length) {
|
|
159
|
+
const oldSnapshot = snapshots.get(fileName);
|
|
160
|
+
newSnapshot = core.applyTextChanges(oldSnapshot, textChanges);
|
|
161
|
+
snapshots.set(fileName, newSnapshot);
|
|
162
|
+
versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
|
|
163
|
+
projectVersion++;
|
|
164
|
+
shouldRetry = true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (newSnapshot) {
|
|
168
|
+
ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
|
|
169
|
+
fileCache[0] = fs.statSync(fileName).mtimeMs;
|
|
170
|
+
fileCache[1] = {};
|
|
171
|
+
fileCache[2].length = 0;
|
|
172
|
+
fileCache[3].length = 0;
|
|
173
|
+
}
|
|
174
|
+
if (shouldRetry) {
|
|
175
|
+
diagnostics = linter.lint(fileName, fileCache);
|
|
176
|
+
}
|
|
177
|
+
return [
|
|
178
|
+
diagnostics.map(diagnostic => ({
|
|
179
|
+
...diagnostic,
|
|
180
|
+
file: {
|
|
181
|
+
fileName: diagnostic.file.fileName,
|
|
182
|
+
text: diagnostic.file.text,
|
|
183
|
+
},
|
|
184
|
+
relatedInformation: diagnostic.relatedInformation?.map(info => ({
|
|
185
|
+
...info,
|
|
186
|
+
file: info.file ? {
|
|
187
|
+
fileName: info.file.fileName,
|
|
188
|
+
text: info.file.text,
|
|
189
|
+
} : undefined,
|
|
190
|
+
})),
|
|
191
|
+
})),
|
|
192
|
+
fileCache,
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
function lint(fileName, fileCache) {
|
|
196
|
+
return [
|
|
197
|
+
linter.lint(fileName, fileCache).map(diagnostic => ({
|
|
198
|
+
...diagnostic,
|
|
199
|
+
file: {
|
|
200
|
+
fileName: diagnostic.file.fileName,
|
|
201
|
+
text: diagnostic.file.text,
|
|
202
|
+
},
|
|
203
|
+
relatedInformation: diagnostic.relatedInformation?.map(info => ({
|
|
204
|
+
...info,
|
|
205
|
+
file: info.file ? {
|
|
206
|
+
fileName: info.file.fileName,
|
|
207
|
+
text: info.file.text,
|
|
208
|
+
} : undefined,
|
|
209
|
+
})),
|
|
210
|
+
})),
|
|
211
|
+
fileCache,
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
function hasCodeFixes(fileName) {
|
|
215
|
+
return linter.hasCodeFixes(fileName);
|
|
216
|
+
}
|
|
217
|
+
function hasRules(fileName, minimatchCache) {
|
|
218
|
+
return [Object.keys(linter.getRules(fileName, minimatchCache)).length > 0, minimatchCache];
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=worker.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsslint/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tsslint": "./bin/tsslint.js"
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@clack/prompts": "^0.8.2",
|
|
19
|
-
"@tsslint/config": "1.
|
|
20
|
-
"@tsslint/core": "1.
|
|
19
|
+
"@tsslint/config": "1.4.0",
|
|
20
|
+
"@tsslint/core": "1.4.0",
|
|
21
21
|
"glob": "^10.4.1"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"typescript": "*"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "9a3f7ce55f079eaaedfb61af9b72d8ba736f0123"
|
|
27
27
|
}
|