@tsslint/cli 1.3.6 → 1.4.1
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 +321 -218
- package/lib/cache.d.ts +2 -2
- package/lib/cache.js +4 -4
- package/lib/languagePlugins.d.ts +2 -0
- package/lib/languagePlugins.js +78 -0
- package/lib/worker.d.ts +22 -0
- package/lib/worker.js +304 -0
- package/package.json +9 -4
package/index.js
CHANGED
|
@@ -3,147 +3,313 @@ 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");
|
|
11
|
+
const languagePlugins = require("./lib/languagePlugins.js");
|
|
9
12
|
const _reset = '\x1b[0m';
|
|
10
13
|
const purple = (s) => '\x1b[35m' + s + _reset;
|
|
11
14
|
const darkGray = (s) => '\x1b[90m' + s + _reset;
|
|
12
15
|
const lightRed = (s) => '\x1b[91m' + s + _reset;
|
|
13
16
|
const lightGreen = (s) => '\x1b[92m' + s + _reset;
|
|
14
17
|
const lightYellow = (s) => '\x1b[93m' + s + _reset;
|
|
18
|
+
let threads = 1;
|
|
19
|
+
if (process.argv.includes('--threads')) {
|
|
20
|
+
const threadsIndex = process.argv.indexOf('--threads');
|
|
21
|
+
const threadsArg = process.argv[threadsIndex + 1];
|
|
22
|
+
if (!threadsArg || threadsArg.startsWith('-')) {
|
|
23
|
+
console.error(lightRed(`Missing argument for --threads.`));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
threads = Math.min(os.availableParallelism(), Number(threadsArg));
|
|
27
|
+
}
|
|
28
|
+
class Project {
|
|
29
|
+
constructor(tsconfigOption, languages) {
|
|
30
|
+
this.languages = languages;
|
|
31
|
+
this.workers = [];
|
|
32
|
+
this.fileNames = [];
|
|
33
|
+
this.options = {};
|
|
34
|
+
this.currentFileIndex = 0;
|
|
35
|
+
this.cache = {};
|
|
36
|
+
try {
|
|
37
|
+
this.tsconfig = require.resolve(tsconfigOption, { paths: [process.cwd()] });
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
console.error(lightRed(`No such file: ${tsconfigOption}`));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async init(
|
|
45
|
+
// @ts-expect-error
|
|
46
|
+
clack) {
|
|
47
|
+
this.configFile = ts.findConfigFile(path.dirname(this.tsconfig), ts.sys.fileExists, 'tsslint.config.ts');
|
|
48
|
+
if (!this.configFile) {
|
|
49
|
+
clack.log.error(`${purple('[project]')} ${path.relative(process.cwd(), this.tsconfig)} ${darkGray('(No tsslint.config.ts found)')}`);
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
const commonLine = await parseCommonLine(this.tsconfig, this.languages);
|
|
53
|
+
this.fileNames = commonLine.fileNames;
|
|
54
|
+
this.options = commonLine.options;
|
|
55
|
+
if (!this.fileNames.length) {
|
|
56
|
+
clack.log.warn(`${purple('[project]')} ${path.relative(process.cwd(), this.tsconfig)} ${darkGray('(No included files)')}`);
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
clack.log.info(`${purple('[project]')} ${path.relative(process.cwd(), this.tsconfig)} ${darkGray(`(${this.fileNames.length})`)}`);
|
|
60
|
+
if (!process.argv.includes('--force')) {
|
|
61
|
+
this.cache = cache.loadCache(this.tsconfig, this.configFile, ts.sys.createHash);
|
|
62
|
+
}
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
15
66
|
(async () => {
|
|
16
|
-
|
|
17
|
-
let projectVersion = 0;
|
|
18
|
-
let typeRootsVersion = 0;
|
|
19
|
-
let parsed;
|
|
67
|
+
const builtConfigs = new Map();
|
|
20
68
|
const clack = await import('@clack/prompts');
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
69
|
+
const processFiles = new Set();
|
|
70
|
+
let projects = [];
|
|
71
|
+
let spinner = clack.spinner();
|
|
72
|
+
let lastSpinnerUpdate = Date.now();
|
|
73
|
+
let hasFix = false;
|
|
74
|
+
let allFilesNum = 0;
|
|
75
|
+
let processed = 0;
|
|
76
|
+
let excluded = 0;
|
|
77
|
+
let passed = 0;
|
|
78
|
+
let errors = 0;
|
|
79
|
+
let warnings = 0;
|
|
80
|
+
let cached = 0;
|
|
81
|
+
const tsconfigAndLanguages = new Map();
|
|
82
|
+
if (!process.argv.includes('--project')
|
|
83
|
+
&& !process.argv.includes('--projects')
|
|
84
|
+
&& !process.argv.includes('--vue-project')
|
|
85
|
+
&& !process.argv.includes('--vue-projects')
|
|
86
|
+
&& !process.argv.includes('--mdx-project')
|
|
87
|
+
&& !process.argv.includes('--mdx-projects')
|
|
88
|
+
&& !process.argv.includes('--astro-project')
|
|
89
|
+
&& !process.argv.includes('--astro-projects')) {
|
|
90
|
+
const languages = await clack.multiselect({
|
|
91
|
+
required: false,
|
|
92
|
+
message: 'Select frameworks (optional)',
|
|
93
|
+
options: [{
|
|
94
|
+
label: 'Vue',
|
|
95
|
+
value: 'vue',
|
|
96
|
+
}, {
|
|
97
|
+
label: 'MDX',
|
|
98
|
+
value: 'mdx',
|
|
99
|
+
}, {
|
|
100
|
+
label: 'Astro',
|
|
101
|
+
value: 'astro',
|
|
102
|
+
}],
|
|
103
|
+
});
|
|
104
|
+
if (clack.isCancel(languages)) {
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
const tsconfigOptions = glob.sync('**/{tsconfig.json,jsconfig.json}');
|
|
108
|
+
let options = await Promise.all(tsconfigOptions.map(async (tsconfigOption) => {
|
|
109
|
+
const tsconfig = require.resolve(tsconfigOption.startsWith('.') ? tsconfigOption : `./${tsconfigOption}`, { paths: [process.cwd()] });
|
|
110
|
+
try {
|
|
111
|
+
const commonLine = await parseCommonLine(tsconfig, languages);
|
|
112
|
+
return {
|
|
113
|
+
label: path.relative(process.cwd(), tsconfig) + ` (${commonLine.fileNames.length})`,
|
|
114
|
+
value: tsconfigOption,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
}));
|
|
121
|
+
options = options.filter(option => !!option);
|
|
122
|
+
if (!options.length) {
|
|
123
|
+
clack.log.error(lightRed('No projects found.'));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
const selectedTsconfigs = await clack.multiselect({
|
|
127
|
+
message: 'Select one or multiple projects',
|
|
128
|
+
// @ts-expect-error
|
|
129
|
+
options,
|
|
130
|
+
});
|
|
131
|
+
if (clack.isCancel(selectedTsconfigs)) {
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
let command = 'tsslint';
|
|
135
|
+
if (!languages.length) {
|
|
136
|
+
if (selectedTsconfigs.length === 1) {
|
|
137
|
+
command += ' --project ' + selectedTsconfigs[0];
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
command += ' --projects ' + selectedTsconfigs.join(' ');
|
|
47
141
|
}
|
|
48
|
-
return snapshots.get(fileName);
|
|
49
|
-
},
|
|
50
|
-
getDefaultLibFileName(options) {
|
|
51
|
-
return ts.getDefaultLibFilePath(options);
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
const languageService = ts.createLanguageService(languageServiceHost);
|
|
55
|
-
if (process.argv.includes('--project')) {
|
|
56
|
-
const projectIndex = process.argv.indexOf('--project');
|
|
57
|
-
let tsconfig = process.argv[projectIndex + 1];
|
|
58
|
-
if (tsconfig.startsWith('-') || !tsconfig) {
|
|
59
|
-
clack.log.error(lightRed(`Missing argument for --project.`));
|
|
60
142
|
}
|
|
61
143
|
else {
|
|
144
|
+
for (const language of languages) {
|
|
145
|
+
if (selectedTsconfigs.length === 1) {
|
|
146
|
+
command += ` --${language}-project ` + selectedTsconfigs[0];
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
command += ` --${language}-projects ` + selectedTsconfigs.join(' ');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
clack.log.info(`Running: ${purple(command)}`);
|
|
154
|
+
for (let tsconfig of selectedTsconfigs) {
|
|
62
155
|
if (!tsconfig.startsWith('.')) {
|
|
63
156
|
tsconfig = `./${tsconfig}`;
|
|
64
157
|
}
|
|
65
|
-
|
|
158
|
+
tsconfigAndLanguages.set(tsconfig, languages);
|
|
66
159
|
}
|
|
67
160
|
}
|
|
68
|
-
else
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
161
|
+
else {
|
|
162
|
+
const options = [
|
|
163
|
+
{
|
|
164
|
+
projectFlag: '--project',
|
|
165
|
+
projectsFlag: '--projects',
|
|
166
|
+
language: undefined,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
projectFlag: '--vue-project',
|
|
170
|
+
projectsFlag: '--vue-projects',
|
|
171
|
+
language: 'vue',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
projectFlag: '--mdx-project',
|
|
175
|
+
projectsFlag: '--mdx-projects',
|
|
176
|
+
language: 'mdx',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
projectFlag: '--astro-project',
|
|
180
|
+
projectsFlag: '--astro-projects',
|
|
181
|
+
language: 'astro',
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
for (const { projectFlag, projectsFlag, language } of options) {
|
|
185
|
+
if (process.argv.includes(projectFlag)) {
|
|
186
|
+
const projectIndex = process.argv.indexOf(projectFlag);
|
|
187
|
+
let tsconfig = process.argv[projectIndex + 1];
|
|
188
|
+
if (!tsconfig || tsconfig.startsWith('-')) {
|
|
189
|
+
clack.log.error(lightRed(`Missing argument for ${projectFlag}.`));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
77
192
|
if (!tsconfig.startsWith('.')) {
|
|
78
193
|
tsconfig = `./${tsconfig}`;
|
|
79
194
|
}
|
|
80
|
-
|
|
195
|
+
if (!tsconfigAndLanguages.has(tsconfig)) {
|
|
196
|
+
tsconfigAndLanguages.set(tsconfig, []);
|
|
197
|
+
}
|
|
198
|
+
if (language) {
|
|
199
|
+
tsconfigAndLanguages.get(tsconfig).push(language);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (process.argv.includes(projectsFlag)) {
|
|
203
|
+
const projectsIndex = process.argv.indexOf(projectsFlag);
|
|
204
|
+
let foundArg = false;
|
|
205
|
+
for (let i = projectsIndex + 1; i < process.argv.length; i++) {
|
|
206
|
+
if (process.argv[i].startsWith('-')) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
foundArg = true;
|
|
210
|
+
const searchGlob = process.argv[i];
|
|
211
|
+
const tsconfigs = glob.sync(searchGlob);
|
|
212
|
+
if (!tsconfigs.length) {
|
|
213
|
+
clack.log.error(lightRed(`No projects found for ${projectsFlag} ${searchGlob}.`));
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
for (let tsconfig of tsconfigs) {
|
|
217
|
+
if (!tsconfig.startsWith('.')) {
|
|
218
|
+
tsconfig = `./${tsconfig}`;
|
|
219
|
+
}
|
|
220
|
+
if (!tsconfigAndLanguages.has(tsconfig)) {
|
|
221
|
+
tsconfigAndLanguages.set(tsconfig, []);
|
|
222
|
+
}
|
|
223
|
+
if (language) {
|
|
224
|
+
tsconfigAndLanguages.get(tsconfig).push(language);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (!foundArg) {
|
|
229
|
+
clack.log.error(lightRed(`Missing argument for ${projectsFlag}.`));
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
81
232
|
}
|
|
82
233
|
}
|
|
83
234
|
}
|
|
235
|
+
for (const [tsconfig, languages] of tsconfigAndLanguages) {
|
|
236
|
+
projects.push(await new Project(tsconfig, languages).init(clack));
|
|
237
|
+
}
|
|
238
|
+
spinner.start();
|
|
239
|
+
projects = projects.filter(project => !!project.configFile);
|
|
240
|
+
projects = projects.filter(project => !!project.fileNames.length);
|
|
241
|
+
for (const project of projects) {
|
|
242
|
+
project.builtConfig = await getBuiltConfig(project.configFile);
|
|
243
|
+
}
|
|
244
|
+
projects = projects.filter(project => !!project.builtConfig);
|
|
245
|
+
for (const project of projects) {
|
|
246
|
+
allFilesNum += project.fileNames.length;
|
|
247
|
+
}
|
|
248
|
+
if (allFilesNum === 0) {
|
|
249
|
+
spinner.stop(lightYellow('No input files.'));
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
if (threads === 1) {
|
|
253
|
+
await startWorker(worker.createLocal());
|
|
254
|
+
}
|
|
84
255
|
else {
|
|
85
|
-
|
|
86
|
-
|
|
256
|
+
await Promise.all(new Array(threads).fill(0).map(() => {
|
|
257
|
+
return startWorker(worker.create());
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
spinner.stop(darkGray(cached
|
|
261
|
+
? `Processed ${processed} files with cache. (Use --force to ignore cache.)`
|
|
262
|
+
: `Processed ${processed} files.`));
|
|
263
|
+
const data = [
|
|
264
|
+
[passed, 'passed', lightGreen],
|
|
265
|
+
[errors, 'errors', lightRed],
|
|
266
|
+
[warnings, 'warnings', lightYellow],
|
|
267
|
+
[excluded, 'excluded', darkGray],
|
|
268
|
+
];
|
|
269
|
+
let summary = data
|
|
270
|
+
.filter(([count]) => count)
|
|
271
|
+
.map(([count, label, color]) => color(`${count} ${label}`))
|
|
272
|
+
.join(darkGray(' | '));
|
|
273
|
+
if (hasFix) {
|
|
274
|
+
summary += darkGray(` (Use --fix to apply automatic fixes.)`);
|
|
87
275
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
276
|
+
else if (errors || warnings) {
|
|
277
|
+
summary += darkGray(` (No fixes available.)`);
|
|
278
|
+
}
|
|
279
|
+
clack.outro(summary);
|
|
280
|
+
process.exit(errors ? 1 : 0);
|
|
281
|
+
async function startWorker(linterWorker) {
|
|
282
|
+
const unfinishedProjects = projects.filter(project => project.currentFileIndex < project.fileNames.length);
|
|
283
|
+
if (!unfinishedProjects.length) {
|
|
95
284
|
return;
|
|
96
285
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
286
|
+
// Select a project that has not has a worker yet
|
|
287
|
+
let project = unfinishedProjects.find(project => !project.workers.length);
|
|
288
|
+
if (!project) {
|
|
289
|
+
// Choose a project with the most files left per worker
|
|
290
|
+
project = unfinishedProjects.sort((a, b) => {
|
|
291
|
+
const aFilesPerWorker = (a.fileNames.length - a.currentFileIndex) / a.workers.length;
|
|
292
|
+
const bFilesPerWorker = (b.fileNames.length - b.currentFileIndex) / b.workers.length;
|
|
293
|
+
return bFilesPerWorker - aFilesPerWorker;
|
|
294
|
+
})[0];
|
|
101
295
|
}
|
|
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) {
|
|
296
|
+
project.workers.push(linterWorker);
|
|
297
|
+
const setupSuccess = await linterWorker.setup(project.tsconfig, project.languages, project.configFile, project.builtConfig, project.fileNames, project.options);
|
|
298
|
+
if (!setupSuccess) {
|
|
299
|
+
projects = projects.filter(p => p !== project);
|
|
300
|
+
startWorker(linterWorker);
|
|
113
301
|
return;
|
|
114
302
|
}
|
|
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)}`));
|
|
303
|
+
while (project.currentFileIndex < project.fileNames.length) {
|
|
304
|
+
const i = project.currentFileIndex++;
|
|
305
|
+
const fileName = project.fileNames[i];
|
|
306
|
+
const fileMtime = fs.statSync(fileName).mtimeMs;
|
|
307
|
+
addProcessFile(fileName);
|
|
308
|
+
if (Date.now() - lastSpinnerUpdate > 100) {
|
|
309
|
+
lastSpinnerUpdate = Date.now();
|
|
143
310
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
144
311
|
}
|
|
145
|
-
|
|
146
|
-
let fileCache = lintCache[fileName];
|
|
312
|
+
let fileCache = project.cache[fileName];
|
|
147
313
|
if (fileCache) {
|
|
148
314
|
if (fileCache[0] !== fileMtime) {
|
|
149
315
|
fileCache[0] = fileMtime;
|
|
@@ -157,48 +323,17 @@ const lightYellow = (s) => '\x1b[93m' + s + _reset;
|
|
|
157
323
|
}
|
|
158
324
|
}
|
|
159
325
|
else {
|
|
160
|
-
|
|
326
|
+
project.cache[fileName] = fileCache = [fileMtime, {}, [], [], {}];
|
|
161
327
|
}
|
|
162
328
|
let diagnostics;
|
|
163
329
|
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
|
-
}
|
|
330
|
+
diagnostics = await linterWorker.lintAndFix(fileName, fileCache);
|
|
195
331
|
}
|
|
196
332
|
else {
|
|
197
|
-
diagnostics =
|
|
333
|
+
diagnostics = await linterWorker.lint(fileName, fileCache);
|
|
198
334
|
}
|
|
199
335
|
if (diagnostics.length) {
|
|
200
|
-
hasFix ||=
|
|
201
|
-
hasError ||= diagnostics.some(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error);
|
|
336
|
+
hasFix ||= Object.values(fileCache[1]).some(fixes => fixes > 0) || await linterWorker.hasCodeFixes(fileName);
|
|
202
337
|
for (const diagnostic of diagnostics) {
|
|
203
338
|
if (diagnostic.category === ts.DiagnosticCategory.Suggestion) {
|
|
204
339
|
continue;
|
|
@@ -209,96 +344,64 @@ const lightYellow = (s) => '\x1b[93m' + s + _reset;
|
|
|
209
344
|
getNewLine: () => ts.sys.newLine,
|
|
210
345
|
});
|
|
211
346
|
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;
|
|
347
|
+
if (diagnostic.category === ts.DiagnosticCategory.Error) {
|
|
348
|
+
errors++;
|
|
349
|
+
log(output, 1);
|
|
350
|
+
}
|
|
351
|
+
else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
|
|
352
|
+
warnings++;
|
|
353
|
+
log(output, 2);
|
|
225
354
|
}
|
|
226
355
|
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
|
-
}
|
|
356
|
+
log(output);
|
|
238
357
|
}
|
|
239
358
|
}
|
|
240
359
|
}
|
|
241
|
-
else if (!
|
|
360
|
+
else if (!(await linterWorker.hasRules(fileName, fileCache[4]))) {
|
|
242
361
|
excluded++;
|
|
243
362
|
}
|
|
244
363
|
else {
|
|
245
364
|
passed++;
|
|
246
365
|
}
|
|
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.)`));
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
lintSpinner.stop(darkGray(`Processed ${parsed.fileNames.length} files.`));
|
|
258
|
-
}
|
|
259
|
-
const data = [
|
|
260
|
-
[passed, 'passed', lightGreen],
|
|
261
|
-
[errors, 'errors', lightRed],
|
|
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.)`);
|
|
366
|
+
processed++;
|
|
367
|
+
removeProcessFile(fileName);
|
|
271
368
|
}
|
|
272
|
-
|
|
273
|
-
|
|
369
|
+
cache.saveCache(project.tsconfig, project.configFile, project.cache, ts.sys.createHash);
|
|
370
|
+
await startWorker(linterWorker);
|
|
371
|
+
}
|
|
372
|
+
async function getBuiltConfig(configFile) {
|
|
373
|
+
if (!builtConfigs.has(configFile)) {
|
|
374
|
+
builtConfigs.set(configFile, core.buildConfig(configFile, ts.sys.createHash, spinner, (s, code) => log(darkGray(s), code)));
|
|
274
375
|
}
|
|
275
|
-
|
|
276
|
-
|
|
376
|
+
return await builtConfigs.get(configFile);
|
|
377
|
+
}
|
|
378
|
+
function addProcessFile(fileName) {
|
|
379
|
+
processFiles.add(fileName);
|
|
380
|
+
updateSpinner();
|
|
277
381
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
382
|
+
function removeProcessFile(fileName) {
|
|
383
|
+
processFiles.delete(fileName);
|
|
384
|
+
updateSpinner();
|
|
385
|
+
}
|
|
386
|
+
function updateSpinner() {
|
|
387
|
+
if (processFiles.size === 1) {
|
|
388
|
+
const fileName = processFiles.values().next().value;
|
|
389
|
+
spinner.message(`[${processed + processFiles.size}/${allFilesNum}] ${path.relative(process.cwd(), fileName)}`);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
spinner.message(`[${processed + processFiles.size}/${allFilesNum}] Processing ${processFiles.size} files`);
|
|
283
393
|
}
|
|
284
|
-
return await clack.text({
|
|
285
|
-
message: 'Select the project. (Use --project or --projects to skip this prompt.)',
|
|
286
|
-
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.',
|
|
287
|
-
defaultValue: shortTsconfig,
|
|
288
|
-
validate(value) {
|
|
289
|
-
value ||= shortTsconfig;
|
|
290
|
-
try {
|
|
291
|
-
require.resolve(value, { paths: [process.cwd()] });
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
return 'No such file.';
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
});
|
|
298
394
|
}
|
|
299
|
-
function
|
|
300
|
-
|
|
301
|
-
|
|
395
|
+
function log(msg, code) {
|
|
396
|
+
spinner.stop(msg, code);
|
|
397
|
+
spinner = clack.spinner();
|
|
398
|
+
spinner.start();
|
|
302
399
|
}
|
|
303
400
|
})();
|
|
401
|
+
async function parseCommonLine(tsconfig, languages) {
|
|
402
|
+
const jsonConfigFile = ts.readJsonConfigFile(tsconfig, ts.sys.readFile);
|
|
403
|
+
const plugins = await languagePlugins.load(tsconfig, languages);
|
|
404
|
+
const extraFileExtensions = plugins.flatMap(plugin => plugin.typescript?.extraFileExtensions ?? []).flat();
|
|
405
|
+
return ts.parseJsonSourceFileConfigFileContent(jsonConfigFile, ts.sys, path.dirname(tsconfig), {}, tsconfig, undefined, extraFileExtensions);
|
|
406
|
+
}
|
|
304
407
|
//# 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
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.load = load;
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const ts = require("typescript");
|
|
6
|
+
const cache = new Map();
|
|
7
|
+
async function load(tsconfig, languages) {
|
|
8
|
+
if (cache.has(tsconfig)) {
|
|
9
|
+
return cache.get(tsconfig);
|
|
10
|
+
}
|
|
11
|
+
const plugins = [];
|
|
12
|
+
if (languages.includes('vue')) {
|
|
13
|
+
let vue;
|
|
14
|
+
let vueTscPkgPath;
|
|
15
|
+
if (findPackageJson('@vue/language-core')) {
|
|
16
|
+
vue = require('@vue/language-core');
|
|
17
|
+
}
|
|
18
|
+
else if (vueTscPkgPath = findPackageJson('vue-tsc')) {
|
|
19
|
+
const vueTscPath = path.dirname(vueTscPkgPath);
|
|
20
|
+
vue = require(require.resolve('@vue/language-core', { paths: [vueTscPath] }));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const pkg = ts.findConfigFile(path.dirname(tsconfig), ts.sys.fileExists, 'package.json');
|
|
24
|
+
if (pkg) {
|
|
25
|
+
throw new Error('Please install @vue/language-core or vue-tsc to ' + path.relative(process.cwd(), pkg));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
throw new Error('Please install @vue/language-core or vue-tsc for ' + path.relative(process.cwd(), tsconfig));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const commonLine = vue.createParsedCommandLine(ts, ts.sys, tsconfig);
|
|
32
|
+
const vueLanguagePlugin = vue.createVueLanguagePlugin(ts, commonLine.options, commonLine.vueOptions, fileName => fileName);
|
|
33
|
+
plugins.push(vueLanguagePlugin);
|
|
34
|
+
}
|
|
35
|
+
if (languages.includes('mdx')) {
|
|
36
|
+
let mdx;
|
|
37
|
+
try {
|
|
38
|
+
mdx = await import(require.resolve('@mdx-js/language-service', { paths: [path.dirname(tsconfig)] }));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
const pkg = ts.findConfigFile(path.dirname(tsconfig), ts.sys.fileExists, 'package.json');
|
|
42
|
+
if (pkg) {
|
|
43
|
+
throw new Error('Please install @mdx-js/language-service to ' + path.relative(process.cwd(), pkg));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
throw new Error('Please install @mdx-js/language-service for ' + path.relative(process.cwd(), tsconfig));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const mdxLanguagePlugin = mdx.createMdxLanguagePlugin();
|
|
50
|
+
plugins.push(mdxLanguagePlugin);
|
|
51
|
+
}
|
|
52
|
+
if (languages.includes('astro')) {
|
|
53
|
+
let astro;
|
|
54
|
+
try {
|
|
55
|
+
astro = require(require.resolve('@astrojs/ts-plugin/dist/language.js', { paths: [path.dirname(tsconfig)] }));
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const pkg = ts.findConfigFile(path.dirname(tsconfig), ts.sys.fileExists, 'package.json');
|
|
59
|
+
if (pkg) {
|
|
60
|
+
throw new Error('Please install @astrojs/ts-plugin to ' + path.relative(process.cwd(), pkg));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
throw new Error('Please install @astrojs/ts-plugin for ' + path.relative(process.cwd(), tsconfig));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const astroLanguagePlugin = astro.getLanguagePlugin();
|
|
67
|
+
plugins.push(astroLanguagePlugin);
|
|
68
|
+
}
|
|
69
|
+
cache.set(tsconfig, plugins);
|
|
70
|
+
return plugins;
|
|
71
|
+
function findPackageJson(pkgName) {
|
|
72
|
+
try {
|
|
73
|
+
return require.resolve(`${pkgName}/package.json`, { paths: [path.dirname(tsconfig)] });
|
|
74
|
+
}
|
|
75
|
+
catch { }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=languagePlugins.js.map
|
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, languages: 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>): boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function create(): {
|
|
11
|
+
setup(tsconfig: string, languages: 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, languages: 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,304 @@
|
|
|
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
|
+
const languagePlugins = require("./languagePlugins.js");
|
|
11
|
+
const language_core_1 = require("@volar/language-core");
|
|
12
|
+
const typescript_1 = require("@volar/typescript");
|
|
13
|
+
const transform_1 = require("@volar/typescript/lib/node/transform");
|
|
14
|
+
let projectVersion = 0;
|
|
15
|
+
let typeRootsVersion = 0;
|
|
16
|
+
let options = {};
|
|
17
|
+
let fileNames = [];
|
|
18
|
+
let language;
|
|
19
|
+
let linter;
|
|
20
|
+
let linterLanguageService;
|
|
21
|
+
const snapshots = new Map();
|
|
22
|
+
const versions = new Map();
|
|
23
|
+
const originalHost = {
|
|
24
|
+
...ts.sys,
|
|
25
|
+
useCaseSensitiveFileNames() {
|
|
26
|
+
return ts.sys.useCaseSensitiveFileNames;
|
|
27
|
+
},
|
|
28
|
+
getProjectVersion() {
|
|
29
|
+
return projectVersion.toString();
|
|
30
|
+
},
|
|
31
|
+
getTypeRootsVersion() {
|
|
32
|
+
return typeRootsVersion;
|
|
33
|
+
},
|
|
34
|
+
getCompilationSettings() {
|
|
35
|
+
return options;
|
|
36
|
+
},
|
|
37
|
+
getScriptFileNames() {
|
|
38
|
+
return fileNames;
|
|
39
|
+
},
|
|
40
|
+
getScriptVersion(fileName) {
|
|
41
|
+
return versions.get(fileName)?.toString() ?? '0';
|
|
42
|
+
},
|
|
43
|
+
getScriptSnapshot(fileName) {
|
|
44
|
+
if (!snapshots.has(fileName)) {
|
|
45
|
+
snapshots.set(fileName, ts.ScriptSnapshot.fromString(ts.sys.readFile(fileName)));
|
|
46
|
+
}
|
|
47
|
+
return snapshots.get(fileName);
|
|
48
|
+
},
|
|
49
|
+
getDefaultLibFileName(options) {
|
|
50
|
+
return ts.getDefaultLibFilePath(options);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const linterHost = { ...originalHost };
|
|
54
|
+
const originalService = ts.createLanguageService(linterHost);
|
|
55
|
+
function createLocal() {
|
|
56
|
+
return {
|
|
57
|
+
setup(...args) {
|
|
58
|
+
return setup(...args);
|
|
59
|
+
},
|
|
60
|
+
lint(...args) {
|
|
61
|
+
return lint(...args)[0];
|
|
62
|
+
},
|
|
63
|
+
lintAndFix(...args) {
|
|
64
|
+
return lintAndFix(...args)[0];
|
|
65
|
+
},
|
|
66
|
+
hasCodeFixes(...args) {
|
|
67
|
+
return hasCodeFixes(...args);
|
|
68
|
+
},
|
|
69
|
+
hasRules(...args) {
|
|
70
|
+
return hasRules(...args)[0];
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function create() {
|
|
75
|
+
const worker = new worker_threads.Worker(__filename);
|
|
76
|
+
return {
|
|
77
|
+
setup(...args) {
|
|
78
|
+
return sendRequest(setup, ...args);
|
|
79
|
+
},
|
|
80
|
+
async lint(...args) {
|
|
81
|
+
const [res, newCache] = await sendRequest(lint, ...args);
|
|
82
|
+
Object.assign(args[1], newCache); // Sync the cache
|
|
83
|
+
return res;
|
|
84
|
+
},
|
|
85
|
+
async lintAndFix(...args) {
|
|
86
|
+
const [res, newCache] = await sendRequest(lintAndFix, ...args);
|
|
87
|
+
Object.assign(args[1], newCache); // Sync the cache
|
|
88
|
+
return res;
|
|
89
|
+
},
|
|
90
|
+
hasCodeFixes(...args) {
|
|
91
|
+
return sendRequest(hasCodeFixes, ...args);
|
|
92
|
+
},
|
|
93
|
+
async hasRules(...args) {
|
|
94
|
+
const [res, newCache] = await sendRequest(hasRules, ...args);
|
|
95
|
+
Object.assign(args[1], newCache); // Sync the cache
|
|
96
|
+
return res;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
function sendRequest(t, ...args) {
|
|
100
|
+
return new Promise(resolve => {
|
|
101
|
+
worker.once('message', json => {
|
|
102
|
+
resolve(JSON.parse(json));
|
|
103
|
+
});
|
|
104
|
+
worker.postMessage(JSON.stringify([t.name, ...args]));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
worker_threads.parentPort?.on('message', async (json) => {
|
|
109
|
+
const data = JSON.parse(json);
|
|
110
|
+
const result = await handlers[data[0]](...data.slice(1));
|
|
111
|
+
worker_threads.parentPort.postMessage(JSON.stringify(result));
|
|
112
|
+
});
|
|
113
|
+
const handlers = {
|
|
114
|
+
setup,
|
|
115
|
+
lint,
|
|
116
|
+
lintAndFix,
|
|
117
|
+
hasCodeFixes,
|
|
118
|
+
hasRules,
|
|
119
|
+
};
|
|
120
|
+
async function setup(tsconfig, languages, configFile, builtConfig, _fileNames, _options) {
|
|
121
|
+
const clack = await import('@clack/prompts');
|
|
122
|
+
let config;
|
|
123
|
+
try {
|
|
124
|
+
config = (await import(url.pathToFileURL(builtConfig).toString())).default;
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
if (err instanceof Error) {
|
|
128
|
+
clack.log.error(err.stack ?? err.message);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
clack.log.error(String(err));
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
for (let key in linterHost) {
|
|
136
|
+
if (!(key in originalHost)) {
|
|
137
|
+
// @ts-ignore
|
|
138
|
+
delete linterHost[key];
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// @ts-ignore
|
|
142
|
+
linterHost[key] = originalHost[key];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
linterLanguageService = originalService;
|
|
146
|
+
language = undefined;
|
|
147
|
+
const plugins = await languagePlugins.load(tsconfig, languages);
|
|
148
|
+
if (plugins.length) {
|
|
149
|
+
const { getScriptSnapshot } = originalHost;
|
|
150
|
+
language = (0, language_core_1.createLanguage)([
|
|
151
|
+
...plugins,
|
|
152
|
+
{ getLanguageId: fileName => (0, typescript_1.resolveFileLanguageId)(fileName) },
|
|
153
|
+
], new language_core_1.FileMap(ts.sys.useCaseSensitiveFileNames), fileName => {
|
|
154
|
+
const snapshot = getScriptSnapshot(fileName);
|
|
155
|
+
if (snapshot) {
|
|
156
|
+
language.scripts.set(fileName, snapshot);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
(0, typescript_1.decorateLanguageServiceHost)(ts, language, linterHost);
|
|
160
|
+
const proxy = (0, typescript_1.createProxyLanguageService)(linterLanguageService);
|
|
161
|
+
proxy.initialize(language);
|
|
162
|
+
linterLanguageService = proxy.proxy;
|
|
163
|
+
}
|
|
164
|
+
projectVersion++;
|
|
165
|
+
typeRootsVersion++;
|
|
166
|
+
fileNames = _fileNames;
|
|
167
|
+
options = _options;
|
|
168
|
+
linter = core.createLinter({
|
|
169
|
+
configFile,
|
|
170
|
+
languageService: linterLanguageService,
|
|
171
|
+
languageServiceHost: linterHost,
|
|
172
|
+
typescript: ts,
|
|
173
|
+
tsconfig: ts.server.toNormalizedPath(tsconfig),
|
|
174
|
+
}, config, 'cli', clack);
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
function lintAndFix(fileName, fileCache) {
|
|
178
|
+
let retry = 1;
|
|
179
|
+
let shouldRetry = true;
|
|
180
|
+
let newSnapshot;
|
|
181
|
+
let diagnostics;
|
|
182
|
+
while (shouldRetry && retry--) {
|
|
183
|
+
if (Object.values(fileCache[1]).some(fixes => fixes > 0)) {
|
|
184
|
+
// Reset the cache if there are any fixes applied.
|
|
185
|
+
fileCache[1] = {};
|
|
186
|
+
fileCache[2].length = 0;
|
|
187
|
+
fileCache[3].length = 0;
|
|
188
|
+
}
|
|
189
|
+
diagnostics = linter.lint(fileName, fileCache);
|
|
190
|
+
let fixes = linter
|
|
191
|
+
.getCodeFixes(fileName, 0, Number.MAX_VALUE, diagnostics, fileCache[4])
|
|
192
|
+
.filter(fix => fix.fixId === 'tsslint');
|
|
193
|
+
if (language) {
|
|
194
|
+
fixes = fixes.map(fix => {
|
|
195
|
+
fix.changes = (0, transform_1.transformFileTextChanges)(language, fix.changes, false, language_core_1.isCodeActionsEnabled);
|
|
196
|
+
return fix;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const textChanges = core.combineCodeFixes(fileName, fixes);
|
|
200
|
+
if (textChanges.length) {
|
|
201
|
+
const oldSnapshot = snapshots.get(fileName);
|
|
202
|
+
newSnapshot = core.applyTextChanges(oldSnapshot, textChanges);
|
|
203
|
+
snapshots.set(fileName, newSnapshot);
|
|
204
|
+
versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
|
|
205
|
+
projectVersion++;
|
|
206
|
+
shouldRetry = true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (newSnapshot) {
|
|
210
|
+
ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
|
|
211
|
+
fileCache[0] = fs.statSync(fileName).mtimeMs;
|
|
212
|
+
fileCache[1] = {};
|
|
213
|
+
fileCache[2].length = 0;
|
|
214
|
+
fileCache[3].length = 0;
|
|
215
|
+
}
|
|
216
|
+
if (shouldRetry) {
|
|
217
|
+
diagnostics = linter.lint(fileName, fileCache);
|
|
218
|
+
}
|
|
219
|
+
if (language) {
|
|
220
|
+
diagnostics = diagnostics
|
|
221
|
+
.map(d => (0, transform_1.transformDiagnostic)(language, d, originalService.getCurrentProgram(), false))
|
|
222
|
+
.filter(d => !!d);
|
|
223
|
+
diagnostics = diagnostics.map(diagnostic => ({
|
|
224
|
+
...diagnostic,
|
|
225
|
+
file: {
|
|
226
|
+
fileName: diagnostic.file.fileName,
|
|
227
|
+
text: getFileText(diagnostic.file.fileName),
|
|
228
|
+
},
|
|
229
|
+
relatedInformation: diagnostic.relatedInformation?.map(info => ({
|
|
230
|
+
...info,
|
|
231
|
+
file: info.file ? {
|
|
232
|
+
fileName: info.file.fileName,
|
|
233
|
+
text: getFileText(info.file.fileName),
|
|
234
|
+
} : undefined,
|
|
235
|
+
})),
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
diagnostics = diagnostics.map(diagnostic => ({
|
|
240
|
+
...diagnostic,
|
|
241
|
+
file: {
|
|
242
|
+
fileName: diagnostic.file.fileName,
|
|
243
|
+
text: diagnostic.file.text,
|
|
244
|
+
},
|
|
245
|
+
relatedInformation: diagnostic.relatedInformation?.map(info => ({
|
|
246
|
+
...info,
|
|
247
|
+
file: info.file ? {
|
|
248
|
+
fileName: info.file.fileName,
|
|
249
|
+
text: info.file.text,
|
|
250
|
+
} : undefined,
|
|
251
|
+
})),
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
return [diagnostics, fileCache];
|
|
255
|
+
}
|
|
256
|
+
function lint(fileName, fileCache) {
|
|
257
|
+
let diagnostics = linter.lint(fileName, fileCache);
|
|
258
|
+
if (language) {
|
|
259
|
+
diagnostics = diagnostics
|
|
260
|
+
.map(d => (0, transform_1.transformDiagnostic)(language, d, originalService.getCurrentProgram(), false))
|
|
261
|
+
.filter(d => !!d);
|
|
262
|
+
diagnostics = diagnostics.map(diagnostic => ({
|
|
263
|
+
...diagnostic,
|
|
264
|
+
file: {
|
|
265
|
+
fileName: diagnostic.file.fileName,
|
|
266
|
+
text: getFileText(diagnostic.file.fileName),
|
|
267
|
+
},
|
|
268
|
+
relatedInformation: diagnostic.relatedInformation?.map(info => ({
|
|
269
|
+
...info,
|
|
270
|
+
file: info.file ? {
|
|
271
|
+
fileName: info.file.fileName,
|
|
272
|
+
text: getFileText(info.file.fileName),
|
|
273
|
+
} : undefined,
|
|
274
|
+
})),
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
diagnostics = diagnostics.map(diagnostic => ({
|
|
279
|
+
...diagnostic,
|
|
280
|
+
file: {
|
|
281
|
+
fileName: diagnostic.file.fileName,
|
|
282
|
+
text: diagnostic.file.text,
|
|
283
|
+
},
|
|
284
|
+
relatedInformation: diagnostic.relatedInformation?.map(info => ({
|
|
285
|
+
...info,
|
|
286
|
+
file: info.file ? {
|
|
287
|
+
fileName: info.file.fileName,
|
|
288
|
+
text: info.file.text,
|
|
289
|
+
} : undefined,
|
|
290
|
+
})),
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
return [diagnostics, fileCache];
|
|
294
|
+
}
|
|
295
|
+
function getFileText(fileName) {
|
|
296
|
+
return originalHost.getScriptSnapshot(fileName).getText(0, Number.MAX_VALUE);
|
|
297
|
+
}
|
|
298
|
+
function hasCodeFixes(fileName) {
|
|
299
|
+
return linter.hasCodeFixes(fileName);
|
|
300
|
+
}
|
|
301
|
+
function hasRules(fileName, minimatchCache) {
|
|
302
|
+
return [Object.keys(linter.getRules(fileName, minimatchCache)).length > 0, minimatchCache];
|
|
303
|
+
}
|
|
304
|
+
//# 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.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tsslint": "./bin/tsslint.js"
|
|
@@ -16,12 +16,17 @@
|
|
|
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.1",
|
|
20
|
+
"@tsslint/core": "1.4.1",
|
|
21
|
+
"@volar/language-core": "~2.4.0",
|
|
22
|
+
"@volar/typescript": "~2.4.0",
|
|
21
23
|
"glob": "^10.4.1"
|
|
22
24
|
},
|
|
23
25
|
"peerDependencies": {
|
|
24
26
|
"typescript": "*"
|
|
25
27
|
},
|
|
26
|
-
"
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@vue/language-core": "latest"
|
|
30
|
+
},
|
|
31
|
+
"gitHead": "54f42ec9414029a356fa19a762260f03392563fa"
|
|
27
32
|
}
|