@tsslint/cli 1.5.0 → 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 CHANGED
@@ -169,10 +169,6 @@ class Project {
169
169
  }
170
170
  }
171
171
  else {
172
- const projectsFlag = process.argv.find(arg => arg.endsWith('-projects'));
173
- if (projectsFlag) {
174
- clack.log.warn(lightYellow(`Please use ${projectsFlag.slice(0, -1)} instead of ${projectsFlag} starting from version 1.5.0.`));
175
- }
176
172
  const options = [
177
173
  {
178
174
  projectFlags: ['--project', '--projects'],
@@ -243,14 +239,9 @@ class Project {
243
239
  spinner.stop(lightYellow('No input files.'));
244
240
  process.exit(1);
245
241
  }
246
- if (threads === 1) {
247
- await startWorker(worker.createLocal());
248
- }
249
- else {
250
- await Promise.all(new Array(threads).fill(0).map(() => {
251
- return startWorker(worker.create());
252
- }));
253
- }
242
+ await Promise.all(new Array(threads).fill(0).map(() => {
243
+ return startWorker(worker.create());
244
+ }));
254
245
  spinner.stop(cached
255
246
  ? darkGray(`Processed ${processed} files with cache. (Use `) + cyan(`--force`) + darkGray(` to ignore cache.)`)
256
247
  : darkGray(`Processed ${processed} files.`));
@@ -260,6 +251,14 @@ class Project {
260
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.`));
261
252
  }
262
253
  }
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.`));
261
+ }
263
262
  const data = [
264
263
  [passed, 'passed', lightGreen],
265
264
  [errors, 'errors', lightRed],
@@ -303,7 +302,10 @@ class Project {
303
302
  while (project.currentFileIndex < project.fileNames.length) {
304
303
  const i = project.currentFileIndex++;
305
304
  const fileName = project.fileNames[i];
306
- const fileMtime = fs.statSync(fileName).mtimeMs;
305
+ const fileStat = fs.statSync(fileName, { throwIfNoEntry: false });
306
+ if (!fileStat) {
307
+ continue;
308
+ }
307
309
  addProcessFile(fileName);
308
310
  if (Date.now() - lastSpinnerUpdate > 100) {
309
311
  lastSpinnerUpdate = Date.now();
@@ -311,8 +313,8 @@ class Project {
311
313
  }
312
314
  let fileCache = project.cache[fileName];
313
315
  if (fileCache) {
314
- if (fileCache[0] !== fileMtime) {
315
- fileCache[0] = fileMtime;
316
+ if (fileCache[0] !== fileStat.mtimeMs) {
317
+ fileCache[0] = fileStat.mtimeMs;
316
318
  fileCache[1] = {};
317
319
  fileCache[2] = {};
318
320
  }
@@ -321,7 +323,7 @@ class Project {
321
323
  }
322
324
  }
323
325
  else {
324
- project.cache[fileName] = fileCache = [fileMtime, {}, {}, false];
326
+ project.cache[fileName] = fileCache = [fileStat.mtimeMs, {}, {}, false];
325
327
  }
326
328
  let diagnostics = await linterWorker.lint(fileName, process.argv.includes('--fix'), fileCache);
327
329
  diagnostics = diagnostics.filter(diagnostic => diagnostic.category !== ts.DiagnosticCategory.Suggestion);
@@ -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,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
@@ -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
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsslint/cli",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "tsslint": "./bin/tsslint.js"
@@ -16,8 +16,8 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@clack/prompts": "^0.8.2",
19
- "@tsslint/config": "1.5.0",
20
- "@tsslint/core": "1.5.0",
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
23
  "glob": "^10.4.1",
@@ -29,5 +29,5 @@
29
29
  "devDependencies": {
30
30
  "@vue/language-core": "latest"
31
31
  },
32
- "gitHead": "6585740b4ecbc8251163923b4e8c7978fe9cb12c"
32
+ "gitHead": "a0c7b85e3ba032d5c9a92f69efbba51a9f6ce06e"
33
33
  }