@tsslint/cli 1.3.5 → 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 +217 -185
- 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,139 +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");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
10
|
+
const os = require("os");
|
|
11
|
+
const _reset = '\x1b[0m';
|
|
12
|
+
const purple = (s) => '\x1b[35m' + s + _reset;
|
|
13
|
+
const darkGray = (s) => '\x1b[90m' + s + _reset;
|
|
14
|
+
const lightRed = (s) => '\x1b[91m' + s + _reset;
|
|
15
|
+
const lightGreen = (s) => '\x1b[92m' + s + _reset;
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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;
|
|
53
|
+
}
|
|
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);
|
|
47
57
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
77
|
+
let tsconfig = process.argv[projectIndex + 1];
|
|
78
|
+
if (!tsconfig || tsconfig.startsWith('-')) {
|
|
79
|
+
console.error(lightRed(`Missing argument for --project.`));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
if (!tsconfig.startsWith('.')) {
|
|
83
|
+
tsconfig = `./${tsconfig}`;
|
|
84
|
+
}
|
|
85
|
+
projects.push(new Project(tsconfig));
|
|
59
86
|
}
|
|
60
87
|
else if (process.argv.includes('--projects')) {
|
|
61
88
|
const projectsIndex = process.argv.indexOf('--projects');
|
|
89
|
+
let foundArg = false;
|
|
62
90
|
for (let i = projectsIndex + 1; i < process.argv.length; i++) {
|
|
63
91
|
if (process.argv[i].startsWith('-')) {
|
|
64
92
|
break;
|
|
65
93
|
}
|
|
94
|
+
foundArg = true;
|
|
66
95
|
const searchGlob = process.argv[i];
|
|
67
96
|
const tsconfigs = glob.sync(searchGlob);
|
|
68
97
|
for (let tsconfig of tsconfigs) {
|
|
69
98
|
if (!tsconfig.startsWith('.')) {
|
|
70
99
|
tsconfig = `./${tsconfig}`;
|
|
71
100
|
}
|
|
72
|
-
|
|
101
|
+
projects.push(new Project(tsconfig));
|
|
73
102
|
}
|
|
74
103
|
}
|
|
104
|
+
if (!foundArg) {
|
|
105
|
+
console.error(lightRed(`Missing argument for --projects.`));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
75
108
|
}
|
|
76
109
|
else {
|
|
77
|
-
await
|
|
110
|
+
const tsconfig = await askTSConfig();
|
|
111
|
+
projects.push(new Project(tsconfig));
|
|
78
112
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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.)`);
|
|
152
|
+
}
|
|
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) {
|
|
91
158
|
return;
|
|
92
159
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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);
|
|
104
175
|
return;
|
|
105
176
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
: cache.loadCache(configFile, ts.sys.createHash);
|
|
114
|
-
const projectContext = {
|
|
115
|
-
configFile,
|
|
116
|
-
languageService,
|
|
117
|
-
languageServiceHost,
|
|
118
|
-
typescript: ts,
|
|
119
|
-
tsconfig: ts.server.toNormalizedPath(tsconfig),
|
|
120
|
-
};
|
|
121
|
-
const linter = core.createLinter(projectContext, tsslintConfig, 'cli', clack);
|
|
122
|
-
const lintSpinner = clack.spinner();
|
|
123
|
-
let hasFix = false;
|
|
124
|
-
let passed = 0;
|
|
125
|
-
let errors = 0;
|
|
126
|
-
let warnings = 0;
|
|
127
|
-
let cached = 0;
|
|
128
|
-
let t = Date.now();
|
|
129
|
-
lintSpinner.start();
|
|
130
|
-
for (let i = 0; i < parsed.fileNames.length; i++) {
|
|
131
|
-
const fileName = parsed.fileNames[i];
|
|
132
|
-
if (Date.now() - t > 100) {
|
|
133
|
-
t = Date.now();
|
|
134
|
-
lintSpinner.message(`${gray}[${i + 1}/${parsed.fileNames.length}] ${path.relative(process.cwd(), fileName)}${reset}`);
|
|
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();
|
|
135
184
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
136
185
|
}
|
|
137
|
-
|
|
138
|
-
let fileCache = lintCache[fileName];
|
|
186
|
+
let fileCache = project.cache[fileName];
|
|
139
187
|
if (fileCache) {
|
|
140
188
|
if (fileCache[0] !== fileMtime) {
|
|
141
189
|
fileCache[0] = fileMtime;
|
|
@@ -149,40 +197,17 @@ const reset = '\x1b[0m';
|
|
|
149
197
|
}
|
|
150
198
|
}
|
|
151
199
|
else {
|
|
152
|
-
|
|
200
|
+
project.cache[fileName] = fileCache = [fileMtime, {}, [], [], {}];
|
|
153
201
|
}
|
|
202
|
+
let diagnostics;
|
|
154
203
|
if (process.argv.includes('--fix')) {
|
|
155
|
-
|
|
156
|
-
let shouldRetry = true;
|
|
157
|
-
let newSnapshot;
|
|
158
|
-
while (shouldRetry && retry) {
|
|
159
|
-
shouldRetry = false;
|
|
160
|
-
retry--;
|
|
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);
|
|
169
|
-
const textChanges = core.combineCodeFixes(fileName, fixes);
|
|
170
|
-
if (textChanges.length) {
|
|
171
|
-
const oldSnapshot = snapshots.get(fileName);
|
|
172
|
-
newSnapshot = core.applyTextChanges(oldSnapshot, textChanges);
|
|
173
|
-
snapshots.set(fileName, newSnapshot);
|
|
174
|
-
versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
|
|
175
|
-
projectVersion++;
|
|
176
|
-
shouldRetry = true;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (newSnapshot) {
|
|
180
|
-
ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
|
|
181
|
-
fileCache[0] = fs.statSync(fileName).mtimeMs;
|
|
182
|
-
}
|
|
204
|
+
diagnostics = await linterWorker.lintAndFix(fileName, fileCache);
|
|
183
205
|
}
|
|
184
206
|
else {
|
|
185
|
-
|
|
207
|
+
diagnostics = await linterWorker.lint(fileName, fileCache);
|
|
208
|
+
}
|
|
209
|
+
if (diagnostics.length) {
|
|
210
|
+
hasFix ||= await linterWorker.hasCodeFixes(fileName);
|
|
186
211
|
for (const diagnostic of diagnostics) {
|
|
187
212
|
if (diagnostic.category === ts.DiagnosticCategory.Suggestion) {
|
|
188
213
|
continue;
|
|
@@ -192,77 +217,84 @@ const reset = '\x1b[0m';
|
|
|
192
217
|
getCanonicalFileName: ts.sys.useCaseSensitiveFileNames ? x => x : x => x.toLowerCase(),
|
|
193
218
|
getNewLine: () => ts.sys.newLine,
|
|
194
219
|
});
|
|
195
|
-
output = output.replace(`TS${diagnostic.code}`,
|
|
220
|
+
output = output.replace(`TS${diagnostic.code}`, String(diagnostic.code));
|
|
196
221
|
if (diagnostic.category === ts.DiagnosticCategory.Error) {
|
|
197
222
|
errors++;
|
|
198
|
-
|
|
223
|
+
log(output, 1);
|
|
199
224
|
}
|
|
200
225
|
else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
|
|
201
226
|
warnings++;
|
|
202
|
-
|
|
227
|
+
log(output, 2);
|
|
203
228
|
}
|
|
204
229
|
else {
|
|
205
|
-
|
|
230
|
+
log(output);
|
|
206
231
|
}
|
|
207
232
|
}
|
|
208
|
-
if (diagnostics.length) {
|
|
209
|
-
hasFix ||= linter.hasCodeFixes(fileName);
|
|
210
|
-
hasError ||= diagnostics.some(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error);
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
passed++;
|
|
214
|
-
}
|
|
215
233
|
}
|
|
234
|
+
else if (!(await linterWorker.hasRules(fileName, fileCache[4]))) {
|
|
235
|
+
excluded++;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
passed++;
|
|
239
|
+
}
|
|
240
|
+
processed++;
|
|
241
|
+
removeProcessFile(fileName);
|
|
216
242
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (hasFix) {
|
|
224
|
-
clack.log.message(`${gray}Use --fix to apply fixes.${reset}`);
|
|
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)));
|
|
225
249
|
}
|
|
226
|
-
|
|
227
|
-
[passed, 'passed', green],
|
|
228
|
-
[errors, 'errors', red],
|
|
229
|
-
[warnings, 'warnings', yellow],
|
|
230
|
-
];
|
|
231
|
-
const summary = data
|
|
232
|
-
.filter(([count]) => count)
|
|
233
|
-
.map(([count, label, color]) => `${color}${count} ${label}${reset}`)
|
|
234
|
-
.join(` ${gray}|${reset} `);
|
|
235
|
-
clack.outro(summary);
|
|
236
|
-
cache.saveCache(configFile, lintCache, ts.sys.createHash);
|
|
250
|
+
return await builtConfigs.get(configFile);
|
|
237
251
|
}
|
|
238
|
-
async function
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
shortTsconfig = `./${shortTsconfig}`;
|
|
244
|
-
}
|
|
245
|
-
tsconfig = await clack.text({
|
|
246
|
-
message: 'Select the tsconfig project. (Use --project or --projects to skip this prompt.)',
|
|
247
|
-
placeholder: shortTsconfig ? `${shortTsconfig} (${parseCommonLine(tsconfig).fileNames.length} files)` : 'No tsconfig.json/jsconfig.json found, please enter the path to the tsconfig.json/jsconfig.json file.',
|
|
248
|
-
defaultValue: shortTsconfig,
|
|
249
|
-
validate(value) {
|
|
250
|
-
value ||= shortTsconfig;
|
|
251
|
-
try {
|
|
252
|
-
require.resolve(value, { paths: [process.cwd()] });
|
|
253
|
-
}
|
|
254
|
-
catch {
|
|
255
|
-
return `File not found!`;
|
|
256
|
-
}
|
|
257
|
-
},
|
|
258
|
-
});
|
|
252
|
+
async function askTSConfig() {
|
|
253
|
+
const presetConfig = ts.findConfigFile(process.cwd(), ts.sys.fileExists);
|
|
254
|
+
let shortTsconfig = presetConfig ? path.relative(process.cwd(), presetConfig) : undefined;
|
|
255
|
+
if (!shortTsconfig?.startsWith('.')) {
|
|
256
|
+
shortTsconfig = `./${shortTsconfig}`;
|
|
259
257
|
}
|
|
260
|
-
|
|
261
|
-
|
|
258
|
+
return await clack.text({
|
|
259
|
+
message: 'Select the project. (Use --project or --projects to skip this prompt.)',
|
|
260
|
+
placeholder: shortTsconfig ? `${shortTsconfig} (${parseCommonLine(presetConfig).fileNames.length} files)` : 'No tsconfig.json/jsconfig.json found, please enter the path to the tsconfig.json/jsconfig.json file.',
|
|
261
|
+
defaultValue: shortTsconfig,
|
|
262
|
+
validate(value) {
|
|
263
|
+
value ||= shortTsconfig;
|
|
264
|
+
try {
|
|
265
|
+
require.resolve(value, { paths: [process.cwd()] });
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return 'No such file.';
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
});
|
|
262
272
|
}
|
|
263
273
|
function parseCommonLine(tsconfig) {
|
|
264
274
|
const jsonConfigFile = ts.readJsonConfigFile(tsconfig, ts.sys.readFile);
|
|
265
275
|
return ts.parseJsonSourceFileConfigFileContent(jsonConfigFile, ts.sys, path.dirname(tsconfig), {}, tsconfig);
|
|
266
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
|
+
}
|
|
267
299
|
})();
|
|
268
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
|
}
|