@tsslint/cli 1.4.6 → 1.5.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 +144 -101
- package/lib/cli-options.d.ts +9 -0
- package/lib/cli-options.js +55 -0
- package/lib/formatting.d.ts +9 -0
- package/lib/formatting.js +60 -0
- package/lib/logger.d.ts +25 -0
- package/lib/logger.js +42 -0
- package/lib/project.d.ts +17 -0
- package/lib/project.js +83 -0
- package/lib/runner.d.ts +31 -0
- package/lib/runner.js +151 -0
- package/lib/worker.d.ts +19 -9
- package/lib/worker.js +159 -62
- package/package.json +6 -5
package/index.js
CHANGED
|
@@ -9,8 +9,10 @@ const glob = require("glob");
|
|
|
9
9
|
const fs = require("fs");
|
|
10
10
|
const os = require("os");
|
|
11
11
|
const languagePlugins = require("./lib/languagePlugins.js");
|
|
12
|
+
const formatting_js_1 = require("./lib/formatting.js");
|
|
12
13
|
const _reset = '\x1b[0m';
|
|
13
14
|
const purple = (s) => '\x1b[35m' + s + _reset;
|
|
15
|
+
const cyan = (s) => '\x1b[36m' + s + _reset;
|
|
14
16
|
const darkGray = (s) => '\x1b[90m' + s + _reset;
|
|
15
17
|
const lightRed = (s) => '\x1b[91m' + s + _reset;
|
|
16
18
|
const lightGreen = (s) => '\x1b[92m' + s + _reset;
|
|
@@ -31,20 +33,14 @@ if (process.argv.includes('--threads')) {
|
|
|
31
33
|
threads = Math.min(os.availableParallelism(), Number(threadsArg));
|
|
32
34
|
}
|
|
33
35
|
class Project {
|
|
34
|
-
constructor(
|
|
36
|
+
constructor(tsconfig, languages) {
|
|
37
|
+
this.tsconfig = tsconfig;
|
|
35
38
|
this.languages = languages;
|
|
36
39
|
this.workers = [];
|
|
37
40
|
this.fileNames = [];
|
|
38
41
|
this.options = {};
|
|
39
42
|
this.currentFileIndex = 0;
|
|
40
43
|
this.cache = {};
|
|
41
|
-
try {
|
|
42
|
-
this.tsconfig = require.resolve(tsconfigOption, { paths: [process.cwd()] });
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
console.error(lightRed(`No such file: ${tsconfigOption}`));
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
44
|
}
|
|
49
45
|
async init(
|
|
50
46
|
// @ts-expect-error
|
|
@@ -88,6 +84,8 @@ class Project {
|
|
|
88
84
|
const builtConfigs = new Map();
|
|
89
85
|
const clack = await import('@clack/prompts');
|
|
90
86
|
const processFiles = new Set();
|
|
87
|
+
const tsconfigAndLanguages = new Map();
|
|
88
|
+
const formattingSettings = getFormattingSettings();
|
|
91
89
|
let projects = [];
|
|
92
90
|
let spinner = clack.spinner();
|
|
93
91
|
let lastSpinnerUpdate = Date.now();
|
|
@@ -99,15 +97,16 @@ class Project {
|
|
|
99
97
|
let errors = 0;
|
|
100
98
|
let warnings = 0;
|
|
101
99
|
let cached = 0;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
if (![
|
|
101
|
+
'--project',
|
|
102
|
+
'--projects',
|
|
103
|
+
'--vue-project',
|
|
104
|
+
'--vue-projects',
|
|
105
|
+
'--mdx-project',
|
|
106
|
+
'--mdx-projects',
|
|
107
|
+
'--astro-project',
|
|
108
|
+
'--astro-projects',
|
|
109
|
+
].some(flag => process.argv.includes(flag))) {
|
|
111
110
|
const languages = await clack.multiselect({
|
|
112
111
|
required: false,
|
|
113
112
|
message: 'Select frameworks (optional)',
|
|
@@ -154,21 +153,11 @@ class Project {
|
|
|
154
153
|
}
|
|
155
154
|
let command = 'tsslint';
|
|
156
155
|
if (!languages.length) {
|
|
157
|
-
|
|
158
|
-
command += ' --project ' + selectedTsconfigs[0];
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
command += ' --projects ' + selectedTsconfigs.join(' ');
|
|
162
|
-
}
|
|
156
|
+
command += ' --project ' + selectedTsconfigs.join(' ');
|
|
163
157
|
}
|
|
164
158
|
else {
|
|
165
159
|
for (const language of languages) {
|
|
166
|
-
|
|
167
|
-
command += ` --${language}-project ` + selectedTsconfigs[0];
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
command += ` --${language}-projects ` + selectedTsconfigs.join(' ');
|
|
171
|
-
}
|
|
160
|
+
command += ` --${language}-project ` + selectedTsconfigs.join(' ');
|
|
172
161
|
}
|
|
173
162
|
}
|
|
174
163
|
clack.log.info(`Running: ${purple(command)}`);
|
|
@@ -182,75 +171,55 @@ class Project {
|
|
|
182
171
|
else {
|
|
183
172
|
const options = [
|
|
184
173
|
{
|
|
185
|
-
|
|
186
|
-
projectsFlag: '--projects',
|
|
174
|
+
projectFlags: ['--project', '--projects'],
|
|
187
175
|
language: undefined,
|
|
188
176
|
},
|
|
189
177
|
{
|
|
190
|
-
|
|
191
|
-
projectsFlag: '--vue-projects',
|
|
178
|
+
projectFlags: ['--vue-project', '--vue-projects'],
|
|
192
179
|
language: 'vue',
|
|
193
180
|
},
|
|
194
181
|
{
|
|
195
|
-
|
|
182
|
+
projectFlags: ['--mdx-project', '--mdx-projects'],
|
|
196
183
|
projectsFlag: '--mdx-projects',
|
|
197
184
|
language: 'mdx',
|
|
198
185
|
},
|
|
199
186
|
{
|
|
200
|
-
|
|
201
|
-
projectsFlag: '--astro-projects',
|
|
187
|
+
projectFlags: ['--astro-project', '--astro-projects'],
|
|
202
188
|
language: 'astro',
|
|
203
189
|
},
|
|
204
190
|
];
|
|
205
|
-
for (const {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
if (!tsconfigAndLanguages.has(tsconfig)) {
|
|
217
|
-
tsconfigAndLanguages.set(tsconfig, []);
|
|
191
|
+
for (const { projectFlags, language } of options) {
|
|
192
|
+
const projectFlag = projectFlags.find(flag => process.argv.includes(flag));
|
|
193
|
+
if (!projectFlag) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
let foundArg = false;
|
|
197
|
+
const projectsIndex = process.argv.indexOf(projectFlag);
|
|
198
|
+
for (let i = projectsIndex + 1; i < process.argv.length; i++) {
|
|
199
|
+
if (process.argv[i].startsWith('-')) {
|
|
200
|
+
break;
|
|
218
201
|
}
|
|
219
|
-
|
|
220
|
-
|
|
202
|
+
foundArg = true;
|
|
203
|
+
const searchGlob = process.argv[i];
|
|
204
|
+
const tsconfigs = glob.sync(searchGlob);
|
|
205
|
+
if (!tsconfigs.length) {
|
|
206
|
+
clack.log.error(lightRed(`No projects found for ${projectFlag} ${searchGlob}.`));
|
|
207
|
+
process.exit(1);
|
|
221
208
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
for (let i = projectsIndex + 1; i < process.argv.length; i++) {
|
|
227
|
-
if (process.argv[i].startsWith('-')) {
|
|
228
|
-
break;
|
|
209
|
+
for (let tsconfig of tsconfigs) {
|
|
210
|
+
tsconfig = resolvePath(tsconfig);
|
|
211
|
+
if (!tsconfigAndLanguages.has(tsconfig)) {
|
|
212
|
+
tsconfigAndLanguages.set(tsconfig, []);
|
|
229
213
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const tsconfigs = glob.sync(searchGlob);
|
|
233
|
-
if (!tsconfigs.length) {
|
|
234
|
-
clack.log.error(lightRed(`No projects found for ${projectsFlag} ${searchGlob}.`));
|
|
235
|
-
process.exit(1);
|
|
214
|
+
if (language) {
|
|
215
|
+
tsconfigAndLanguages.get(tsconfig).push(language);
|
|
236
216
|
}
|
|
237
|
-
for (let tsconfig of tsconfigs) {
|
|
238
|
-
if (!tsconfig.startsWith('.')) {
|
|
239
|
-
tsconfig = `./${tsconfig}`;
|
|
240
|
-
}
|
|
241
|
-
if (!tsconfigAndLanguages.has(tsconfig)) {
|
|
242
|
-
tsconfigAndLanguages.set(tsconfig, []);
|
|
243
|
-
}
|
|
244
|
-
if (language) {
|
|
245
|
-
tsconfigAndLanguages.get(tsconfig).push(language);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
if (!foundArg) {
|
|
250
|
-
clack.log.error(lightRed(`Missing argument for ${projectsFlag}.`));
|
|
251
|
-
process.exit(1);
|
|
252
217
|
}
|
|
253
218
|
}
|
|
219
|
+
if (!foundArg) {
|
|
220
|
+
clack.log.error(lightRed(`Missing argument for ${projectFlag}.`));
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
254
223
|
}
|
|
255
224
|
}
|
|
256
225
|
for (const [tsconfig, languages] of tsconfigAndLanguages) {
|
|
@@ -270,17 +239,26 @@ class Project {
|
|
|
270
239
|
spinner.stop(lightYellow('No input files.'));
|
|
271
240
|
process.exit(1);
|
|
272
241
|
}
|
|
273
|
-
|
|
274
|
-
|
|
242
|
+
await Promise.all(new Array(threads).fill(0).map(() => {
|
|
243
|
+
return startWorker(worker.create());
|
|
244
|
+
}));
|
|
245
|
+
spinner.stop(cached
|
|
246
|
+
? darkGray(`Processed ${processed} files with cache. (Use `) + cyan(`--force`) + darkGray(` to ignore cache.)`)
|
|
247
|
+
: darkGray(`Processed ${processed} files.`));
|
|
248
|
+
if (process.argv.includes('--fix') && !formattingSettings) {
|
|
249
|
+
const vscodeSettings = ts.findConfigFile(process.cwd(), ts.sys.fileExists, '.vscode/settings.json');
|
|
250
|
+
if (vscodeSettings) {
|
|
251
|
+
clack.log.message(darkGray(`Found available editor settings, you can use `) + cyan(`--vscode-settings ${path.relative(process.cwd(), vscodeSettings)}`) + darkGray(` to enable code format.`));
|
|
252
|
+
}
|
|
275
253
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
254
|
+
const projectsFlag = process.argv.find(arg => arg.endsWith('-projects'));
|
|
255
|
+
if (projectsFlag) {
|
|
256
|
+
clack.log.warn(darkGray(`Please use `)
|
|
257
|
+
+ cyan(`${projectsFlag.slice(0, -1)}`)
|
|
258
|
+
+ darkGray(` instead of `)
|
|
259
|
+
+ cyan(`${projectsFlag}`)
|
|
260
|
+
+ darkGray(` starting from version 1.5.0.`));
|
|
280
261
|
}
|
|
281
|
-
spinner.stop(darkGray(cached
|
|
282
|
-
? `Processed ${processed} files with cache. (Use --force to ignore cache.)`
|
|
283
|
-
: `Processed ${processed} files.`));
|
|
284
262
|
const data = [
|
|
285
263
|
[passed, 'passed', lightGreen],
|
|
286
264
|
[errors, 'errors', lightRed],
|
|
@@ -292,7 +270,7 @@ class Project {
|
|
|
292
270
|
.map(([count, label, color]) => color(`${count} ${label}`))
|
|
293
271
|
.join(darkGray(' | '));
|
|
294
272
|
if (hasFix) {
|
|
295
|
-
summary += darkGray(` (Use
|
|
273
|
+
summary += darkGray(` (Use `) + cyan(`--fix`) + darkGray(` to apply automatic fixes.)`);
|
|
296
274
|
}
|
|
297
275
|
else if (errors || warnings) {
|
|
298
276
|
summary += darkGray(` (No fixes available.)`);
|
|
@@ -315,7 +293,7 @@ class Project {
|
|
|
315
293
|
})[0];
|
|
316
294
|
}
|
|
317
295
|
project.workers.push(linterWorker);
|
|
318
|
-
const setupSuccess = await linterWorker.setup(project.tsconfig, project.languages, project.configFile, project.builtConfig, project.fileNames, project.options);
|
|
296
|
+
const setupSuccess = await linterWorker.setup(project.tsconfig, project.languages, project.configFile, project.builtConfig, project.fileNames, project.options, formattingSettings);
|
|
319
297
|
if (!setupSuccess) {
|
|
320
298
|
projects = projects.filter(p => p !== project);
|
|
321
299
|
startWorker(linterWorker);
|
|
@@ -324,7 +302,10 @@ class Project {
|
|
|
324
302
|
while (project.currentFileIndex < project.fileNames.length) {
|
|
325
303
|
const i = project.currentFileIndex++;
|
|
326
304
|
const fileName = project.fileNames[i];
|
|
327
|
-
const
|
|
305
|
+
const fileStat = fs.statSync(fileName, { throwIfNoEntry: false });
|
|
306
|
+
if (!fileStat) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
328
309
|
addProcessFile(fileName);
|
|
329
310
|
if (Date.now() - lastSpinnerUpdate > 100) {
|
|
330
311
|
lastSpinnerUpdate = Date.now();
|
|
@@ -332,8 +313,8 @@ class Project {
|
|
|
332
313
|
}
|
|
333
314
|
let fileCache = project.cache[fileName];
|
|
334
315
|
if (fileCache) {
|
|
335
|
-
if (fileCache[0] !==
|
|
336
|
-
fileCache[0] =
|
|
316
|
+
if (fileCache[0] !== fileStat.mtimeMs) {
|
|
317
|
+
fileCache[0] = fileStat.mtimeMs;
|
|
337
318
|
fileCache[1] = {};
|
|
338
319
|
fileCache[2] = {};
|
|
339
320
|
}
|
|
@@ -342,15 +323,9 @@ class Project {
|
|
|
342
323
|
}
|
|
343
324
|
}
|
|
344
325
|
else {
|
|
345
|
-
project.cache[fileName] = fileCache = [
|
|
346
|
-
}
|
|
347
|
-
let diagnostics;
|
|
348
|
-
if (process.argv.includes('--fix')) {
|
|
349
|
-
diagnostics = await linterWorker.lintAndFix(fileName, fileCache);
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
diagnostics = await linterWorker.lint(fileName, fileCache);
|
|
326
|
+
project.cache[fileName] = fileCache = [fileStat.mtimeMs, {}, {}, false];
|
|
353
327
|
}
|
|
328
|
+
let diagnostics = await linterWorker.lint(fileName, process.argv.includes('--fix'), fileCache);
|
|
354
329
|
diagnostics = diagnostics.filter(diagnostic => diagnostic.category !== ts.DiagnosticCategory.Suggestion);
|
|
355
330
|
if (diagnostics.length) {
|
|
356
331
|
hasFix ||= await linterWorker.hasCodeFixes(fileName);
|
|
@@ -388,6 +363,60 @@ class Project {
|
|
|
388
363
|
cache.saveCache(project.tsconfig, project.configFile, project.cache, ts.sys.createHash);
|
|
389
364
|
await startWorker(linterWorker);
|
|
390
365
|
}
|
|
366
|
+
function getFormattingSettings() {
|
|
367
|
+
let formattingSettings;
|
|
368
|
+
if (process.argv.includes('--vscode-settings')) {
|
|
369
|
+
formattingSettings = {
|
|
370
|
+
typescript: {},
|
|
371
|
+
javascript: {},
|
|
372
|
+
vue: {},
|
|
373
|
+
};
|
|
374
|
+
for (const section of ['typescript', 'javascript']) {
|
|
375
|
+
formattingSettings[section] = {
|
|
376
|
+
...ts.getDefaultFormatCodeSettings('\n'),
|
|
377
|
+
indentStyle: ts.IndentStyle.Smart,
|
|
378
|
+
newLineCharacter: '\n',
|
|
379
|
+
insertSpaceAfterCommaDelimiter: true,
|
|
380
|
+
insertSpaceAfterConstructor: false,
|
|
381
|
+
insertSpaceAfterSemicolonInForStatements: true,
|
|
382
|
+
insertSpaceBeforeAndAfterBinaryOperators: true,
|
|
383
|
+
insertSpaceAfterKeywordsInControlFlowStatements: true,
|
|
384
|
+
insertSpaceAfterFunctionKeywordForAnonymousFunctions: true,
|
|
385
|
+
insertSpaceBeforeFunctionParenthesis: false,
|
|
386
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
|
387
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
|
388
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
|
389
|
+
insertSpaceAfterOpeningAndBeforeClosingEmptyBraces: true,
|
|
390
|
+
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
|
391
|
+
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
|
|
392
|
+
insertSpaceAfterTypeAssertion: false,
|
|
393
|
+
placeOpenBraceOnNewLineForFunctions: false,
|
|
394
|
+
placeOpenBraceOnNewLineForControlBlocks: false,
|
|
395
|
+
semicolons: ts.SemicolonPreference.Ignore,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
let vscodeSettingsConfig = process.argv[process.argv.indexOf('--vscode-settings') + 1];
|
|
399
|
+
if (!vscodeSettingsConfig || vscodeSettingsConfig.startsWith('-')) {
|
|
400
|
+
clack.log.error(lightRed(`Missing argument for --vscode-settings.`));
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
const vscodeSettingsFile = resolvePath(vscodeSettingsConfig);
|
|
404
|
+
const vscodeSettings = (0, formatting_js_1.getVSCodeFormattingSettings)(vscodeSettingsFile);
|
|
405
|
+
formattingSettings.typescript = {
|
|
406
|
+
...formattingSettings.typescript,
|
|
407
|
+
...vscodeSettings.typescript,
|
|
408
|
+
};
|
|
409
|
+
formattingSettings.javascript = {
|
|
410
|
+
...formattingSettings.javascript,
|
|
411
|
+
...vscodeSettings.javascript,
|
|
412
|
+
};
|
|
413
|
+
formattingSettings.vue = {
|
|
414
|
+
...formattingSettings.vue,
|
|
415
|
+
...vscodeSettings.vue,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
return formattingSettings;
|
|
419
|
+
}
|
|
391
420
|
async function getBuiltConfig(configFile) {
|
|
392
421
|
if (!builtConfigs.has(configFile)) {
|
|
393
422
|
builtConfigs.set(configFile, core.buildConfig(configFile, ts.sys.createHash, spinner, (s, code) => log(darkGray(s), code)));
|
|
@@ -416,6 +445,20 @@ class Project {
|
|
|
416
445
|
spinner = clack.spinner();
|
|
417
446
|
spinner.start();
|
|
418
447
|
}
|
|
448
|
+
function resolvePath(p) {
|
|
449
|
+
if (!path.isAbsolute(p)
|
|
450
|
+
&& !p.startsWith('./')
|
|
451
|
+
&& !p.startsWith('../')) {
|
|
452
|
+
p = `./${p}`;
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
return require.resolve(p, { paths: [process.cwd()] });
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
clack.log.error(lightRed(`No such file: ${p}`));
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
419
462
|
})();
|
|
420
463
|
async function parseCommonLine(tsconfig, languages) {
|
|
421
464
|
const jsonConfigFile = ts.readJsonConfigFile(tsconfig, ts.sys.readFile);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface CliOptions {
|
|
2
|
+
threads: number;
|
|
3
|
+
force: boolean;
|
|
4
|
+
fix: boolean;
|
|
5
|
+
vscodeSettings?: string;
|
|
6
|
+
projects: Map<string, string[]>;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseProjectOptions(argv: string[], options: CliOptions): void;
|
|
9
|
+
export declare function parseCliOptions(argv: string[]): CliOptions;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseProjectOptions = parseProjectOptions;
|
|
4
|
+
exports.parseCliOptions = parseCliOptions;
|
|
5
|
+
const os = require("os");
|
|
6
|
+
function parseProjectOptions(argv, options) {
|
|
7
|
+
const projectFlags = [
|
|
8
|
+
{ flags: ['--project', '--projects'], language: undefined },
|
|
9
|
+
{ flags: ['--vue-project', '--vue-projects'], language: 'vue' },
|
|
10
|
+
{ flags: ['--mdx-project', '--mdx-projects'], language: 'mdx' },
|
|
11
|
+
{ flags: ['--astro-project', '--astro-projects'], language: 'astro' }
|
|
12
|
+
];
|
|
13
|
+
for (const { flags, language } of projectFlags) {
|
|
14
|
+
const flag = flags.find(f => argv.includes(f));
|
|
15
|
+
if (!flag)
|
|
16
|
+
continue;
|
|
17
|
+
const flagIndex = argv.indexOf(flag);
|
|
18
|
+
for (let i = flagIndex + 1; i < argv.length && !argv[i].startsWith('-'); i++) {
|
|
19
|
+
const tsconfig = argv[i];
|
|
20
|
+
if (!options.projects.has(tsconfig)) {
|
|
21
|
+
options.projects.set(tsconfig, []);
|
|
22
|
+
}
|
|
23
|
+
if (language) {
|
|
24
|
+
options.projects.get(tsconfig).push(language);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function parseCliOptions(argv) {
|
|
30
|
+
const options = {
|
|
31
|
+
threads: 1,
|
|
32
|
+
force: argv.includes('--force'),
|
|
33
|
+
fix: argv.includes('--fix'),
|
|
34
|
+
projects: new Map()
|
|
35
|
+
};
|
|
36
|
+
if (argv.includes('--threads')) {
|
|
37
|
+
const threadsIndex = argv.indexOf('--threads');
|
|
38
|
+
const threadsArg = argv[threadsIndex + 1];
|
|
39
|
+
if (!threadsArg || threadsArg.startsWith('-')) {
|
|
40
|
+
throw new Error('Missing argument for --threads');
|
|
41
|
+
}
|
|
42
|
+
options.threads = Math.min(os.availableParallelism(), Number(threadsArg));
|
|
43
|
+
}
|
|
44
|
+
if (argv.includes('--vscode-settings')) {
|
|
45
|
+
const settingsIndex = argv.indexOf('--vscode-settings');
|
|
46
|
+
const settingsPath = argv[settingsIndex + 1];
|
|
47
|
+
if (!settingsPath || settingsPath.startsWith('-')) {
|
|
48
|
+
throw new Error('Missing argument for --vscode-settings');
|
|
49
|
+
}
|
|
50
|
+
options.vscodeSettings = settingsPath;
|
|
51
|
+
}
|
|
52
|
+
parseProjectOptions(argv, options);
|
|
53
|
+
return options;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=cli-options.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type * as ts from 'typescript';
|
|
2
|
+
export declare function getVSCodeFormattingSettings(settingsFile: string): {
|
|
3
|
+
javascript: ts.FormatCodeSettings;
|
|
4
|
+
typescript: ts.FormatCodeSettings;
|
|
5
|
+
vue: {
|
|
6
|
+
'script.initialIndent'?: boolean;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export declare function computeInitialIndent(content: string, i: number, baseTabSize?: number): number;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getVSCodeFormattingSettings = getVSCodeFormattingSettings;
|
|
4
|
+
exports.computeInitialIndent = computeInitialIndent;
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const json5 = require("json5");
|
|
7
|
+
function getVSCodeFormattingSettings(settingsFile) {
|
|
8
|
+
const jsonc = fs.readFileSync(settingsFile, 'utf8');
|
|
9
|
+
const editorSettings = json5.parse(jsonc);
|
|
10
|
+
const jsSettings = {};
|
|
11
|
+
const tsSettings = {};
|
|
12
|
+
const vueSettings = {};
|
|
13
|
+
if ('editor.insertSpaces' in editorSettings) {
|
|
14
|
+
jsSettings.convertTabsToSpaces = !!editorSettings['editor.insertSpaces'];
|
|
15
|
+
tsSettings.convertTabsToSpaces = !!editorSettings['editor.insertSpaces'];
|
|
16
|
+
}
|
|
17
|
+
if ('editor.tabSize' in editorSettings) {
|
|
18
|
+
jsSettings.tabSize = editorSettings['editor.tabSize'];
|
|
19
|
+
tsSettings.tabSize = editorSettings['editor.tabSize'];
|
|
20
|
+
}
|
|
21
|
+
for (const key in editorSettings) {
|
|
22
|
+
if (key.startsWith('javascript.format.')) {
|
|
23
|
+
const settingKey = key.slice('javascript.format.'.length);
|
|
24
|
+
// @ts-expect-error
|
|
25
|
+
jsSettings[settingKey] = editorSettings[key];
|
|
26
|
+
}
|
|
27
|
+
else if (key.startsWith('typescript.format.')) {
|
|
28
|
+
const settingKey = key.slice('typescript.format.'.length);
|
|
29
|
+
// @ts-expect-error
|
|
30
|
+
tsSettings[settingKey] = editorSettings[key];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if ('vue.format.script.initialIndent' in editorSettings) {
|
|
34
|
+
vueSettings['script.initialIndent'] = !!editorSettings['vue.format.script.initialIndent'];
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
javascript: jsSettings,
|
|
38
|
+
typescript: tsSettings,
|
|
39
|
+
vue: vueSettings,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function computeInitialIndent(content, i, baseTabSize) {
|
|
43
|
+
let nChars = 0;
|
|
44
|
+
const tabSize = baseTabSize || 4;
|
|
45
|
+
while (i < content.length) {
|
|
46
|
+
const ch = content.charAt(i);
|
|
47
|
+
if (ch === ' ') {
|
|
48
|
+
nChars++;
|
|
49
|
+
}
|
|
50
|
+
else if (ch === '\t') {
|
|
51
|
+
nChars += tabSize;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
i++;
|
|
57
|
+
}
|
|
58
|
+
return Math.floor(nChars / tabSize);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=formatting.js.map
|
package/lib/logger.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface SpinnerHandle {
|
|
2
|
+
start(text?: string): void;
|
|
3
|
+
stop(text?: string): void;
|
|
4
|
+
}
|
|
5
|
+
export declare const purple: (s: string) => string;
|
|
6
|
+
export declare const cyan: (s: string) => string;
|
|
7
|
+
export declare const darkGray: (s: string) => string;
|
|
8
|
+
export declare const lightRed: (s: string) => string;
|
|
9
|
+
export declare const lightGreen: (s: string) => string;
|
|
10
|
+
export declare const lightYellow: (s: string) => string;
|
|
11
|
+
export declare const colors: {
|
|
12
|
+
ts: (s: string) => string;
|
|
13
|
+
vue: (s: string) => string;
|
|
14
|
+
mdx: (s: string) => string;
|
|
15
|
+
astro: (s: string) => string;
|
|
16
|
+
};
|
|
17
|
+
export declare class Logger {
|
|
18
|
+
private createSpinner;
|
|
19
|
+
private spinnerHandle?;
|
|
20
|
+
constructor(createSpinner: () => SpinnerHandle);
|
|
21
|
+
startSpinner(message: string): void;
|
|
22
|
+
error(msg: string): void;
|
|
23
|
+
warn(msg: string): void;
|
|
24
|
+
info(msg: string): void;
|
|
25
|
+
}
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Logger = exports.colors = exports.lightYellow = exports.lightGreen = exports.lightRed = exports.darkGray = exports.cyan = exports.purple = void 0;
|
|
4
|
+
const _reset = '\x1b[0m';
|
|
5
|
+
const purple = (s) => '\x1b[35m' + s + _reset;
|
|
6
|
+
exports.purple = purple;
|
|
7
|
+
const cyan = (s) => '\x1b[36m' + s + _reset;
|
|
8
|
+
exports.cyan = cyan;
|
|
9
|
+
const darkGray = (s) => '\x1b[90m' + s + _reset;
|
|
10
|
+
exports.darkGray = darkGray;
|
|
11
|
+
const lightRed = (s) => '\x1b[91m' + s + _reset;
|
|
12
|
+
exports.lightRed = lightRed;
|
|
13
|
+
const lightGreen = (s) => '\x1b[92m' + s + _reset;
|
|
14
|
+
exports.lightGreen = lightGreen;
|
|
15
|
+
const lightYellow = (s) => '\x1b[93m' + s + _reset;
|
|
16
|
+
exports.lightYellow = lightYellow;
|
|
17
|
+
exports.colors = {
|
|
18
|
+
ts: (s) => '\x1b[34m' + s + _reset,
|
|
19
|
+
vue: (s) => '\x1b[32m' + s + _reset,
|
|
20
|
+
mdx: (s) => '\x1b[33m' + s + _reset,
|
|
21
|
+
astro: (s) => '\x1b[38;5;209m' + s + _reset
|
|
22
|
+
};
|
|
23
|
+
class Logger {
|
|
24
|
+
constructor(createSpinner) {
|
|
25
|
+
this.createSpinner = createSpinner;
|
|
26
|
+
}
|
|
27
|
+
startSpinner(message) {
|
|
28
|
+
this.spinnerHandle = this.createSpinner();
|
|
29
|
+
this.spinnerHandle.start(message);
|
|
30
|
+
}
|
|
31
|
+
error(msg) {
|
|
32
|
+
this.spinnerHandle?.stop((0, exports.lightRed)(msg));
|
|
33
|
+
}
|
|
34
|
+
warn(msg) {
|
|
35
|
+
this.spinnerHandle?.stop((0, exports.lightYellow)(msg));
|
|
36
|
+
}
|
|
37
|
+
info(msg) {
|
|
38
|
+
this.spinnerHandle?.stop(msg);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.Logger = Logger;
|
|
42
|
+
//# sourceMappingURL=logger.js.map
|
package/lib/project.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import ts = require('typescript');
|
|
2
|
+
import cache = require('./cache.js');
|
|
3
|
+
import worker = require('./worker.js');
|
|
4
|
+
export declare class Project {
|
|
5
|
+
tsconfig: string;
|
|
6
|
+
languages: string[];
|
|
7
|
+
workers: ReturnType<typeof worker.create>[];
|
|
8
|
+
fileNames: string[];
|
|
9
|
+
options: ts.CompilerOptions;
|
|
10
|
+
configFile: string | undefined;
|
|
11
|
+
currentFileIndex: number;
|
|
12
|
+
builtConfig: string | undefined;
|
|
13
|
+
cache: cache.CacheData;
|
|
14
|
+
constructor(tsconfig: string, languages: string[]);
|
|
15
|
+
init(clack: typeof import('@clack/prompts')): Promise<this>;
|
|
16
|
+
private getProjectLabels;
|
|
17
|
+
}
|
package/lib/project.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Project = void 0;
|
|
4
|
+
const ts = require("typescript");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const core = require("@tsslint/core");
|
|
7
|
+
const cache = require("./cache.js");
|
|
8
|
+
const languagePlugins = require("./languagePlugins.js");
|
|
9
|
+
const logger_js_1 = require("./logger.js");
|
|
10
|
+
class Project {
|
|
11
|
+
constructor(tsconfig, languages) {
|
|
12
|
+
this.tsconfig = tsconfig;
|
|
13
|
+
this.languages = languages;
|
|
14
|
+
this.workers = [];
|
|
15
|
+
this.fileNames = [];
|
|
16
|
+
this.options = {};
|
|
17
|
+
this.currentFileIndex = 0;
|
|
18
|
+
this.cache = {};
|
|
19
|
+
}
|
|
20
|
+
async init(clack) {
|
|
21
|
+
this.configFile = ts.findConfigFile(path.dirname(this.tsconfig), ts.sys.fileExists, 'tsslint.config.ts');
|
|
22
|
+
const labels = this.getProjectLabels();
|
|
23
|
+
const label = labels.join((0, logger_js_1.darkGray)(' | '));
|
|
24
|
+
if (!this.configFile) {
|
|
25
|
+
clack.log.error(`${label} ${path.relative(process.cwd(), this.tsconfig)} ${(0, logger_js_1.darkGray)('(No tsslint.config.ts found)')}`);
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
const spinner = clack.spinner();
|
|
29
|
+
try {
|
|
30
|
+
this.builtConfig = await core.buildConfig(this.configFile, ts.sys.createHash, spinner, (s, code) => clack.log.error((0, logger_js_1.darkGray)(s)));
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
spinner.stop();
|
|
34
|
+
if (err instanceof Error) {
|
|
35
|
+
clack.log.error(err.stack ?? err.message);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
clack.log.error(String(err));
|
|
39
|
+
}
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
if (!this.builtConfig) {
|
|
43
|
+
spinner.stop();
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
const commonLine = await parseCommonLine(this.tsconfig, this.languages);
|
|
47
|
+
this.fileNames = commonLine.fileNames;
|
|
48
|
+
this.options = commonLine.options;
|
|
49
|
+
if (!this.fileNames.length) {
|
|
50
|
+
clack.log.warn(`${label} ${path.relative(process.cwd(), this.tsconfig)} ${(0, logger_js_1.darkGray)('(No included files)')}`);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
clack.log.info(`${label} ${path.relative(process.cwd(), this.tsconfig)} ${(0, logger_js_1.darkGray)(`(${this.fileNames.length})`)}`);
|
|
54
|
+
if (!process.argv.includes('--force')) {
|
|
55
|
+
this.cache = cache.loadCache(this.tsconfig, this.configFile, ts.sys.createHash);
|
|
56
|
+
}
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
getProjectLabels() {
|
|
60
|
+
if (this.languages.length === 0) {
|
|
61
|
+
return [logger_js_1.colors.ts('TS')];
|
|
62
|
+
}
|
|
63
|
+
const labels = [];
|
|
64
|
+
if (this.languages.includes('vue')) {
|
|
65
|
+
labels.push(logger_js_1.colors.vue('Vue'));
|
|
66
|
+
}
|
|
67
|
+
if (this.languages.includes('mdx')) {
|
|
68
|
+
labels.push(logger_js_1.colors.mdx('MDX'));
|
|
69
|
+
}
|
|
70
|
+
if (this.languages.includes('astro')) {
|
|
71
|
+
labels.push(logger_js_1.colors.astro('Astro'));
|
|
72
|
+
}
|
|
73
|
+
return labels;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.Project = Project;
|
|
77
|
+
async function parseCommonLine(tsconfig, languages) {
|
|
78
|
+
const jsonConfigFile = ts.readJsonConfigFile(tsconfig, ts.sys.readFile);
|
|
79
|
+
const plugins = await languagePlugins.load(tsconfig, languages);
|
|
80
|
+
const extraFileExtensions = plugins.flatMap(plugin => plugin.typescript?.extraFileExtensions ?? []).flat();
|
|
81
|
+
return ts.parseJsonSourceFileConfigFileContent(jsonConfigFile, ts.sys, path.dirname(tsconfig), {}, tsconfig, undefined, extraFileExtensions);
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=project.js.map
|
package/lib/runner.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Project } from './project';
|
|
2
|
+
import type { CliOptions } from './cli-options';
|
|
3
|
+
import type { Logger } from './logger';
|
|
4
|
+
export interface RunnerStats {
|
|
5
|
+
processed: number;
|
|
6
|
+
excluded: number;
|
|
7
|
+
passed: number;
|
|
8
|
+
errors: number;
|
|
9
|
+
warnings: number;
|
|
10
|
+
cached: number;
|
|
11
|
+
hasFix: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class Runner {
|
|
14
|
+
private projects;
|
|
15
|
+
private options;
|
|
16
|
+
private logger;
|
|
17
|
+
private stats;
|
|
18
|
+
private processFiles;
|
|
19
|
+
private lastSpinnerUpdate;
|
|
20
|
+
private readonly allFilesNum;
|
|
21
|
+
constructor(projects: Project[], options: CliOptions, logger: Logger);
|
|
22
|
+
run(): Promise<RunnerStats>;
|
|
23
|
+
private startWorker;
|
|
24
|
+
private selectProject;
|
|
25
|
+
private processProject;
|
|
26
|
+
private processFile;
|
|
27
|
+
private addProcessFile;
|
|
28
|
+
private removeProcessFile;
|
|
29
|
+
private updateSpinner;
|
|
30
|
+
private updateStats;
|
|
31
|
+
}
|
package/lib/runner.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Runner = void 0;
|
|
4
|
+
const worker = require("./worker");
|
|
5
|
+
const cache = require("./cache");
|
|
6
|
+
const ts = require("typescript");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const formatting_1 = require("./formatting");
|
|
9
|
+
const logger_1 = require("./logger");
|
|
10
|
+
class Runner {
|
|
11
|
+
constructor(projects, options, logger) {
|
|
12
|
+
this.projects = projects;
|
|
13
|
+
this.options = options;
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
this.stats = {
|
|
16
|
+
processed: 0,
|
|
17
|
+
excluded: 0,
|
|
18
|
+
passed: 0,
|
|
19
|
+
errors: 0,
|
|
20
|
+
warnings: 0,
|
|
21
|
+
cached: 0,
|
|
22
|
+
hasFix: false
|
|
23
|
+
};
|
|
24
|
+
this.processFiles = new Set();
|
|
25
|
+
this.lastSpinnerUpdate = Date.now();
|
|
26
|
+
this.allFilesNum = projects.reduce((sum, p) => sum + p.fileNames.length, 0);
|
|
27
|
+
}
|
|
28
|
+
async run() {
|
|
29
|
+
if (this.allFilesNum === 0) {
|
|
30
|
+
this.logger.warn('No input files.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
if (this.options.threads === 1) {
|
|
34
|
+
await this.startWorker(worker.createLocal());
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
await Promise.all(new Array(this.options.threads)
|
|
38
|
+
.fill(0)
|
|
39
|
+
.map(() => this.startWorker(worker.create())));
|
|
40
|
+
}
|
|
41
|
+
return this.stats;
|
|
42
|
+
}
|
|
43
|
+
async startWorker(linterWorker) {
|
|
44
|
+
const unfinishedProjects = this.projects.filter(project => project.currentFileIndex < project.fileNames.length);
|
|
45
|
+
if (!unfinishedProjects.length)
|
|
46
|
+
return;
|
|
47
|
+
const project = this.selectProject(unfinishedProjects);
|
|
48
|
+
project.workers.push(linterWorker);
|
|
49
|
+
const formattingSettings = this.options.vscodeSettings
|
|
50
|
+
? (0, formatting_1.getVSCodeFormattingSettings)(this.options.vscodeSettings)
|
|
51
|
+
: undefined;
|
|
52
|
+
const setupSuccess = await linterWorker.setup(project.tsconfig, project.languages, project.configFile, project.builtConfig, project.fileNames, project.options, formattingSettings);
|
|
53
|
+
if (!setupSuccess) {
|
|
54
|
+
this.projects = this.projects.filter(p => p !== project);
|
|
55
|
+
await this.startWorker(linterWorker);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await this.processProject(project, linterWorker);
|
|
59
|
+
await this.startWorker(linterWorker);
|
|
60
|
+
}
|
|
61
|
+
selectProject(unfinishedProjects) {
|
|
62
|
+
// First try to find a project without workers
|
|
63
|
+
const projectWithoutWorker = unfinishedProjects.find(p => !p.workers.length);
|
|
64
|
+
if (projectWithoutWorker) {
|
|
65
|
+
return projectWithoutWorker;
|
|
66
|
+
}
|
|
67
|
+
// Otherwise choose project with most files left per worker
|
|
68
|
+
return unfinishedProjects.sort((a, b) => {
|
|
69
|
+
const aFilesPerWorker = (a.fileNames.length - a.currentFileIndex) / (a.workers.length || 1);
|
|
70
|
+
const bFilesPerWorker = (b.fileNames.length - b.currentFileIndex) / (b.workers.length || 1);
|
|
71
|
+
return bFilesPerWorker - aFilesPerWorker;
|
|
72
|
+
})[0];
|
|
73
|
+
}
|
|
74
|
+
async processProject(project, linterWorker) {
|
|
75
|
+
while (project.currentFileIndex < project.fileNames.length) {
|
|
76
|
+
await this.processFile(project, project.currentFileIndex++, linterWorker);
|
|
77
|
+
}
|
|
78
|
+
cache.saveCache(project.tsconfig, project.configFile, project.cache, ts.sys.createHash);
|
|
79
|
+
}
|
|
80
|
+
async processFile(project, fileIndex, linterWorker) {
|
|
81
|
+
const fileName = project.fileNames[fileIndex];
|
|
82
|
+
const fileStat = ts.sys.getModifiedTime(fileName);
|
|
83
|
+
this.addProcessFile(fileName);
|
|
84
|
+
if (Date.now() - this.lastSpinnerUpdate > 100) {
|
|
85
|
+
this.lastSpinnerUpdate = Date.now();
|
|
86
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
87
|
+
this.updateSpinner();
|
|
88
|
+
}
|
|
89
|
+
let fileCache = project.cache[fileName];
|
|
90
|
+
if (fileCache) {
|
|
91
|
+
if (fileCache[0] !== fileStat.getTime()) {
|
|
92
|
+
fileCache[0] = fileStat.getTime();
|
|
93
|
+
fileCache[1] = {};
|
|
94
|
+
fileCache[2] = {};
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.stats.cached++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
project.cache[fileName] = fileCache = [fileStat.getTime(), {}, {}, false];
|
|
102
|
+
}
|
|
103
|
+
const diagnostics = await linterWorker.lint(fileName, this.options.fix, fileCache);
|
|
104
|
+
if (diagnostics.length === 0) {
|
|
105
|
+
const [hasRules] = await linterWorker.hasRules(fileName, fileCache[2]);
|
|
106
|
+
if (!hasRules) {
|
|
107
|
+
this.stats.excluded++;
|
|
108
|
+
this.removeProcessFile(fileName);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
this.stats.hasFix ||= await linterWorker.hasCodeFixes(fileName);
|
|
113
|
+
this.updateStats(diagnostics);
|
|
114
|
+
this.removeProcessFile(fileName);
|
|
115
|
+
}
|
|
116
|
+
addProcessFile(fileName) {
|
|
117
|
+
this.processFiles.add(fileName);
|
|
118
|
+
this.updateSpinner();
|
|
119
|
+
}
|
|
120
|
+
removeProcessFile(fileName) {
|
|
121
|
+
this.processFiles.delete(fileName);
|
|
122
|
+
this.updateSpinner();
|
|
123
|
+
}
|
|
124
|
+
updateSpinner() {
|
|
125
|
+
if (this.processFiles.size === 1) {
|
|
126
|
+
const fileName = Array.from(this.processFiles)[0];
|
|
127
|
+
this.logger.startSpinner((0, logger_1.darkGray)(`[${this.stats.processed + this.processFiles.size}/${this.allFilesNum}] ${path.relative(process.cwd(), fileName)}`));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this.logger.startSpinner((0, logger_1.darkGray)(`[${this.stats.processed + this.processFiles.size}/${this.allFilesNum}] Processing ${this.processFiles.size} files`));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
updateStats(diagnostics) {
|
|
134
|
+
if (diagnostics.length === 0) {
|
|
135
|
+
this.stats.passed++;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
for (const diagnostic of diagnostics) {
|
|
139
|
+
if (diagnostic.category === ts.DiagnosticCategory.Error) {
|
|
140
|
+
this.stats.errors++;
|
|
141
|
+
}
|
|
142
|
+
else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
|
|
143
|
+
this.stats.warnings++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
this.stats.processed++;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.Runner = Runner;
|
|
151
|
+
//# sourceMappingURL=runner.js.map
|
package/lib/worker.d.ts
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
import ts = require('typescript');
|
|
2
2
|
import core = require('@tsslint/core');
|
|
3
|
+
import { type getVSCodeFormattingSettings } from './formatting.js';
|
|
3
4
|
export declare function createLocal(): {
|
|
4
|
-
setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions, _fmtSettings: {
|
|
6
|
+
javascript: ts.FormatCodeSettings;
|
|
7
|
+
typescript: ts.FormatCodeSettings;
|
|
8
|
+
vue: {
|
|
9
|
+
'script.initialIndent'?: boolean;
|
|
10
|
+
};
|
|
11
|
+
} | undefined): Promise<boolean>;
|
|
12
|
+
lint(fileName: string, fix: boolean, fileCache: core.FileLintCache): ts.DiagnosticWithLocation[];
|
|
7
13
|
hasCodeFixes(fileName: string): boolean;
|
|
8
14
|
hasRules(fileName: string, minimatchCache: Record<string, boolean>): boolean;
|
|
9
15
|
};
|
|
10
16
|
export declare function create(): {
|
|
11
|
-
setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions, _fmtSettings: {
|
|
18
|
+
javascript: ts.FormatCodeSettings;
|
|
19
|
+
typescript: ts.FormatCodeSettings;
|
|
20
|
+
vue: {
|
|
21
|
+
'script.initialIndent'?: boolean;
|
|
22
|
+
};
|
|
23
|
+
} | undefined): Promise<boolean>;
|
|
24
|
+
lint(fileName: string, fix: boolean, fileCache: core.FileLintCache): Promise<ts.DiagnosticWithLocation[]>;
|
|
14
25
|
hasCodeFixes(fileName: string): Promise<boolean>;
|
|
15
26
|
hasRules(fileName: string, minimatchCache: Record<string, boolean>): Promise<boolean>;
|
|
16
27
|
};
|
|
17
|
-
declare function setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions): Promise<boolean>;
|
|
18
|
-
declare function
|
|
19
|
-
declare function lint(fileName: string, fileCache: core.FileLintCache): readonly [ts.DiagnosticWithLocation[], core.FileLintCache];
|
|
28
|
+
declare function setup(tsconfig: string, languages: string[], configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions, _fmtSettings: ReturnType<typeof getVSCodeFormattingSettings> | undefined): Promise<boolean>;
|
|
29
|
+
declare function lint(fileName: string, fix: boolean, fileCache: core.FileLintCache): readonly [ts.DiagnosticWithLocation[], core.FileLintCache];
|
|
20
30
|
declare function hasCodeFixes(fileName: string): boolean;
|
|
21
31
|
declare function hasRules(fileName: string, minimatchCache: core.FileLintCache[2]): readonly [boolean, Record<string, boolean>];
|
|
22
32
|
export {};
|
package/lib/worker.js
CHANGED
|
@@ -6,11 +6,14 @@ const ts = require("typescript");
|
|
|
6
6
|
const core = require("@tsslint/core");
|
|
7
7
|
const url = require("url");
|
|
8
8
|
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
9
10
|
const worker_threads = require("worker_threads");
|
|
10
11
|
const languagePlugins = require("./languagePlugins.js");
|
|
11
12
|
const language_core_1 = require("@volar/language-core");
|
|
12
13
|
const typescript_1 = require("@volar/typescript");
|
|
13
14
|
const transform_1 = require("@volar/typescript/lib/node/transform");
|
|
15
|
+
const transform_js_1 = require("@volar/typescript/lib/node/transform.js");
|
|
16
|
+
const formatting_js_1 = require("./formatting.js");
|
|
14
17
|
let projectVersion = 0;
|
|
15
18
|
let typeRootsVersion = 0;
|
|
16
19
|
let options = {};
|
|
@@ -19,6 +22,42 @@ let language;
|
|
|
19
22
|
let linter;
|
|
20
23
|
let linterLanguageService;
|
|
21
24
|
let linterSyntaxOnlyLanguageService;
|
|
25
|
+
let fmtSettings;
|
|
26
|
+
const formatLanguageService = ts.createLanguageService({
|
|
27
|
+
...ts.sys,
|
|
28
|
+
getCompilationSettings() {
|
|
29
|
+
return options;
|
|
30
|
+
},
|
|
31
|
+
getScriptFileNames() {
|
|
32
|
+
return [];
|
|
33
|
+
},
|
|
34
|
+
getScriptVersion() {
|
|
35
|
+
return '0';
|
|
36
|
+
},
|
|
37
|
+
getScriptSnapshot() {
|
|
38
|
+
return formattingSnapshot;
|
|
39
|
+
},
|
|
40
|
+
getScriptKind() {
|
|
41
|
+
return formattingScriptKind;
|
|
42
|
+
},
|
|
43
|
+
useCaseSensitiveFileNames() {
|
|
44
|
+
return ts.sys.useCaseSensitiveFileNames;
|
|
45
|
+
},
|
|
46
|
+
getCurrentDirectory() {
|
|
47
|
+
return ts.sys.getCurrentDirectory();
|
|
48
|
+
},
|
|
49
|
+
getDefaultLibFileName() {
|
|
50
|
+
return ts.getDefaultLibFilePath(options);
|
|
51
|
+
},
|
|
52
|
+
}, undefined, true);
|
|
53
|
+
let formattingSnapshot;
|
|
54
|
+
let formattingScriptKind;
|
|
55
|
+
let formattingIndex = 0;
|
|
56
|
+
function formatVirtualScript(kind, settings, snapshot) {
|
|
57
|
+
formattingSnapshot = snapshot;
|
|
58
|
+
formattingScriptKind = kind;
|
|
59
|
+
return formatLanguageService.getFormattingEditsForDocument(`${formattingIndex++}.txt`, settings);
|
|
60
|
+
}
|
|
22
61
|
const snapshots = new Map();
|
|
23
62
|
const versions = new Map();
|
|
24
63
|
const originalHost = {
|
|
@@ -78,9 +117,6 @@ function createLocal() {
|
|
|
78
117
|
lint(...args) {
|
|
79
118
|
return lint(...args)[0];
|
|
80
119
|
},
|
|
81
|
-
lintAndFix(...args) {
|
|
82
|
-
return lintAndFix(...args)[0];
|
|
83
|
-
},
|
|
84
120
|
hasCodeFixes(...args) {
|
|
85
121
|
return hasCodeFixes(...args);
|
|
86
122
|
},
|
|
@@ -100,11 +136,6 @@ function create() {
|
|
|
100
136
|
Object.assign(args[1], newCache); // Sync the cache
|
|
101
137
|
return res;
|
|
102
138
|
},
|
|
103
|
-
async lintAndFix(...args) {
|
|
104
|
-
const [res, newCache] = await sendRequest(lintAndFix, ...args);
|
|
105
|
-
Object.assign(args[1], newCache); // Sync the cache
|
|
106
|
-
return res;
|
|
107
|
-
},
|
|
108
139
|
hasCodeFixes(...args) {
|
|
109
140
|
return sendRequest(hasCodeFixes, ...args);
|
|
110
141
|
},
|
|
@@ -131,11 +162,10 @@ worker_threads.parentPort?.on('message', async (json) => {
|
|
|
131
162
|
const handlers = {
|
|
132
163
|
setup,
|
|
133
164
|
lint,
|
|
134
|
-
lintAndFix,
|
|
135
165
|
hasCodeFixes,
|
|
136
166
|
hasRules,
|
|
137
167
|
};
|
|
138
|
-
async function setup(tsconfig, languages, configFile, builtConfig, _fileNames, _options) {
|
|
168
|
+
async function setup(tsconfig, languages, configFile, builtConfig, _fileNames, _options, _fmtSettings) {
|
|
139
169
|
const clack = await import('@clack/prompts');
|
|
140
170
|
let config;
|
|
141
171
|
try {
|
|
@@ -192,27 +222,26 @@ async function setup(tsconfig, languages, configFile, builtConfig, _fileNames, _
|
|
|
192
222
|
allowNonTsExtensions: true,
|
|
193
223
|
}
|
|
194
224
|
: _options;
|
|
225
|
+
fmtSettings = _fmtSettings;
|
|
195
226
|
linter = core.createLinter({
|
|
196
|
-
configFile,
|
|
197
227
|
languageService: linterLanguageService,
|
|
198
228
|
languageServiceHost: linterHost,
|
|
199
229
|
typescript: ts,
|
|
200
|
-
|
|
201
|
-
}, config, 'cli', linterSyntaxOnlyLanguageService);
|
|
230
|
+
}, path.dirname(configFile), config, 'cli', linterSyntaxOnlyLanguageService);
|
|
202
231
|
return true;
|
|
203
232
|
}
|
|
204
|
-
function
|
|
205
|
-
let retry = 1;
|
|
206
|
-
let shouldRetry = true;
|
|
233
|
+
function lint(fileName, fix, fileCache) {
|
|
207
234
|
let newSnapshot;
|
|
208
235
|
let diagnostics;
|
|
209
|
-
|
|
236
|
+
let shouldCheck = true;
|
|
237
|
+
if (fix) {
|
|
210
238
|
if (Object.values(fileCache[1]).some(([hasFix]) => hasFix)) {
|
|
211
239
|
// Reset the cache if there are any fixes applied.
|
|
212
240
|
fileCache[1] = {};
|
|
213
241
|
fileCache[2] = {};
|
|
214
242
|
}
|
|
215
243
|
diagnostics = linter.lint(fileName, fileCache);
|
|
244
|
+
shouldCheck = false;
|
|
216
245
|
let fixes = linter
|
|
217
246
|
.getCodeFixes(fileName, 0, Number.MAX_VALUE, diagnostics, fileCache[2])
|
|
218
247
|
.filter(fix => fix.fixId === 'tsslint');
|
|
@@ -224,21 +253,128 @@ function lintAndFix(fileName, fileCache) {
|
|
|
224
253
|
}
|
|
225
254
|
const textChanges = core.combineCodeFixes(fileName, fixes);
|
|
226
255
|
if (textChanges.length) {
|
|
256
|
+
fileCache[3] = false;
|
|
227
257
|
const oldSnapshot = snapshots.get(fileName);
|
|
228
258
|
newSnapshot = core.applyTextChanges(oldSnapshot, textChanges);
|
|
229
259
|
snapshots.set(fileName, newSnapshot);
|
|
230
260
|
versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
|
|
231
261
|
projectVersion++;
|
|
232
|
-
|
|
262
|
+
}
|
|
263
|
+
if (!fileCache[3] && fmtSettings) {
|
|
264
|
+
fileCache[3] = true;
|
|
265
|
+
let script = language?.scripts.get(fileName);
|
|
266
|
+
let linterEdits = [];
|
|
267
|
+
let serviceEdits = [];
|
|
268
|
+
if (script?.generated) {
|
|
269
|
+
for (const code of (0, language_core_1.forEachEmbeddedCode)(script.generated.root)) {
|
|
270
|
+
if ((code.languageId === 'javascript'
|
|
271
|
+
|| code.languageId === 'typescript'
|
|
272
|
+
|| code.languageId === 'javascriptreact'
|
|
273
|
+
|| code.languageId === 'typescriptreact')
|
|
274
|
+
&& code.mappings.some(mapping => (0, language_core_1.isFormattingEnabled)(mapping.data))) {
|
|
275
|
+
const scriptKind = code.languageId === 'javascript' ? ts.ScriptKind.JS
|
|
276
|
+
: code.languageId === 'javascriptreact' ? ts.ScriptKind.JSX
|
|
277
|
+
: code.languageId === 'typescript' ? ts.ScriptKind.TS
|
|
278
|
+
: ts.ScriptKind.TSX;
|
|
279
|
+
const sourceFile = ts.createSourceFile(fileName, code.snapshot.getText(0, code.snapshot.getLength()), ts.ScriptTarget.Latest, true, scriptKind);
|
|
280
|
+
linterEdits.push(...linter
|
|
281
|
+
.format(sourceFile, fileCache[2])
|
|
282
|
+
.map(edit => {
|
|
283
|
+
return (0, transform_js_1.transformTextChange)(script, language, {
|
|
284
|
+
code,
|
|
285
|
+
extension: '',
|
|
286
|
+
scriptKind: scriptKind,
|
|
287
|
+
preventLeadingOffset: true,
|
|
288
|
+
}, edit, false, language_core_1.isFormattingEnabled)?.[1];
|
|
289
|
+
})
|
|
290
|
+
.filter(edit => !!edit));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
const sourceFile = originalSyntaxOnlyService.getNonBoundSourceFile(fileName);
|
|
296
|
+
linterEdits = linter.format(sourceFile, fileCache[2]);
|
|
297
|
+
}
|
|
298
|
+
if (linterEdits.length) {
|
|
299
|
+
const oldSnapshot = snapshots.get(fileName);
|
|
300
|
+
newSnapshot = core.applyTextChanges(oldSnapshot, linterEdits);
|
|
301
|
+
snapshots.set(fileName, newSnapshot);
|
|
302
|
+
versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
|
|
303
|
+
projectVersion++;
|
|
304
|
+
script = language?.scripts.get(fileName);
|
|
305
|
+
}
|
|
306
|
+
if (script?.generated) {
|
|
307
|
+
let sourceFile;
|
|
308
|
+
for (const code of (0, language_core_1.forEachEmbeddedCode)(script.generated.root)) {
|
|
309
|
+
if ((code.languageId === 'javascript'
|
|
310
|
+
|| code.languageId === 'typescript'
|
|
311
|
+
|| code.languageId === 'javascriptreact'
|
|
312
|
+
|| code.languageId === 'typescriptreact')
|
|
313
|
+
&& code.mappings.some(mapping => (0, language_core_1.isFormattingEnabled)(mapping.data))) {
|
|
314
|
+
const scriptKind = code.languageId === 'javascript' ? ts.ScriptKind.JS
|
|
315
|
+
: code.languageId === 'javascriptreact' ? ts.ScriptKind.JSX
|
|
316
|
+
: code.languageId === 'typescript' ? ts.ScriptKind.TS
|
|
317
|
+
: ts.ScriptKind.TSX;
|
|
318
|
+
let settings = scriptKind === ts.ScriptKind.JS || scriptKind === ts.ScriptKind.JSX
|
|
319
|
+
? fmtSettings.javascript
|
|
320
|
+
: fmtSettings.typescript;
|
|
321
|
+
if (settings.tabSize !== undefined) {
|
|
322
|
+
const firstMapping = code.mappings[0];
|
|
323
|
+
sourceFile ??= ts.createSourceFile(fileName, script.snapshot.getText(0, script.snapshot.getLength()), ts.ScriptTarget.Latest, true, ts.ScriptKind.Deferred);
|
|
324
|
+
const line = sourceFile.getLineAndCharacterOfPosition(firstMapping.sourceOffsets[0]).line;
|
|
325
|
+
const offset = sourceFile.getPositionOfLineAndCharacter(line, 0);
|
|
326
|
+
let initialIndentLevel = (0, formatting_js_1.computeInitialIndent)(script.snapshot.getText(0, script.snapshot.getLength()), offset, settings.tabSize);
|
|
327
|
+
if (script.languageId === 'vue'
|
|
328
|
+
&& fmtSettings.vue['script.initialIndent']
|
|
329
|
+
&& (code.id === 'script_raw'
|
|
330
|
+
|| code.id === 'scriptsetup_raw')) {
|
|
331
|
+
initialIndentLevel++;
|
|
332
|
+
}
|
|
333
|
+
settings = {
|
|
334
|
+
...settings,
|
|
335
|
+
baseIndentSize: initialIndentLevel * settings.tabSize,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
serviceEdits.push(...formatVirtualScript(scriptKind, settings, code.snapshot)
|
|
339
|
+
.map(edit => {
|
|
340
|
+
return (0, transform_js_1.transformTextChange)(script, language, {
|
|
341
|
+
code,
|
|
342
|
+
extension: '',
|
|
343
|
+
scriptKind: scriptKind,
|
|
344
|
+
preventLeadingOffset: true,
|
|
345
|
+
}, edit, false, language_core_1.isFormattingEnabled)?.[1];
|
|
346
|
+
})
|
|
347
|
+
.filter(edit => !!edit));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
const scriptKind = linterHost.getScriptKind(fileName);
|
|
353
|
+
const settings = scriptKind === ts.ScriptKind.JS || scriptKind === ts.ScriptKind.JSX ? fmtSettings.javascript : fmtSettings.typescript;
|
|
354
|
+
serviceEdits = linterLanguageService.getFormattingEditsForDocument(fileName, settings);
|
|
355
|
+
}
|
|
356
|
+
if (serviceEdits.length) {
|
|
357
|
+
const oldSnapshot = snapshots.get(fileName);
|
|
358
|
+
newSnapshot = core.applyTextChanges(oldSnapshot, serviceEdits);
|
|
359
|
+
snapshots.set(fileName, newSnapshot);
|
|
360
|
+
versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
|
|
361
|
+
projectVersion++;
|
|
362
|
+
}
|
|
233
363
|
}
|
|
234
364
|
}
|
|
235
365
|
if (newSnapshot) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
366
|
+
const newText = newSnapshot.getText(0, newSnapshot.getLength());
|
|
367
|
+
const oldText = ts.sys.readFile(fileName);
|
|
368
|
+
if (newText !== oldText) {
|
|
369
|
+
ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
|
|
370
|
+
fileCache[0] = fs.statSync(fileName).mtimeMs;
|
|
371
|
+
fileCache[1] = {};
|
|
372
|
+
fileCache[2] = {};
|
|
373
|
+
fileCache[3] = false;
|
|
374
|
+
shouldCheck = true;
|
|
375
|
+
}
|
|
240
376
|
}
|
|
241
|
-
if (
|
|
377
|
+
if (shouldCheck) {
|
|
242
378
|
diagnostics = linter.lint(fileName, fileCache);
|
|
243
379
|
}
|
|
244
380
|
if (language) {
|
|
@@ -278,45 +414,6 @@ function lintAndFix(fileName, fileCache) {
|
|
|
278
414
|
}
|
|
279
415
|
return [diagnostics, fileCache];
|
|
280
416
|
}
|
|
281
|
-
function lint(fileName, fileCache) {
|
|
282
|
-
let diagnostics = linter.lint(fileName, fileCache);
|
|
283
|
-
if (language) {
|
|
284
|
-
diagnostics = diagnostics
|
|
285
|
-
.map(d => (0, transform_1.transformDiagnostic)(language, d, originalService.getCurrentProgram(), false))
|
|
286
|
-
.filter(d => !!d);
|
|
287
|
-
diagnostics = diagnostics.map(diagnostic => ({
|
|
288
|
-
...diagnostic,
|
|
289
|
-
file: {
|
|
290
|
-
fileName: diagnostic.file.fileName,
|
|
291
|
-
text: getFileText(diagnostic.file.fileName),
|
|
292
|
-
},
|
|
293
|
-
relatedInformation: diagnostic.relatedInformation?.map(info => ({
|
|
294
|
-
...info,
|
|
295
|
-
file: info.file ? {
|
|
296
|
-
fileName: info.file.fileName,
|
|
297
|
-
text: getFileText(info.file.fileName),
|
|
298
|
-
} : undefined,
|
|
299
|
-
})),
|
|
300
|
-
}));
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
diagnostics = diagnostics.map(diagnostic => ({
|
|
304
|
-
...diagnostic,
|
|
305
|
-
file: {
|
|
306
|
-
fileName: diagnostic.file.fileName,
|
|
307
|
-
text: diagnostic.file.text,
|
|
308
|
-
},
|
|
309
|
-
relatedInformation: diagnostic.relatedInformation?.map(info => ({
|
|
310
|
-
...info,
|
|
311
|
-
file: info.file ? {
|
|
312
|
-
fileName: info.file.fileName,
|
|
313
|
-
text: info.file.text,
|
|
314
|
-
} : undefined,
|
|
315
|
-
})),
|
|
316
|
-
}));
|
|
317
|
-
}
|
|
318
|
-
return [diagnostics, fileCache];
|
|
319
|
-
}
|
|
320
417
|
function getFileText(fileName) {
|
|
321
418
|
return originalHost.getScriptSnapshot(fileName).getText(0, Number.MAX_VALUE);
|
|
322
419
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsslint/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tsslint": "./bin/tsslint.js"
|
|
@@ -16,11 +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.5.1",
|
|
20
|
+
"@tsslint/core": "1.5.1",
|
|
21
21
|
"@volar/language-core": "~2.4.0",
|
|
22
22
|
"@volar/typescript": "~2.4.0",
|
|
23
|
-
"glob": "^10.4.1"
|
|
23
|
+
"glob": "^10.4.1",
|
|
24
|
+
"json5": "^2.2.3"
|
|
24
25
|
},
|
|
25
26
|
"peerDependencies": {
|
|
26
27
|
"typescript": "*"
|
|
@@ -28,5 +29,5 @@
|
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@vue/language-core": "latest"
|
|
30
31
|
},
|
|
31
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "a0c7b85e3ba032d5c9a92f69efbba51a9f6ce06e"
|
|
32
33
|
}
|