@tsslint/cli 1.5.0 → 1.5.2

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'],
@@ -260,6 +256,14 @@ class Project {
260
256
  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
257
  }
262
258
  }
259
+ const projectsFlag = process.argv.find(arg => arg.endsWith('-projects'));
260
+ if (projectsFlag) {
261
+ clack.log.warn(darkGray(`Please use `)
262
+ + cyan(`${projectsFlag.slice(0, -1)}`)
263
+ + darkGray(` instead of `)
264
+ + cyan(`${projectsFlag}`)
265
+ + darkGray(` starting from version 1.5.0.`));
266
+ }
263
267
  const data = [
264
268
  [passed, 'passed', lightGreen],
265
269
  [errors, 'errors', lightRed],
@@ -303,7 +307,10 @@ class Project {
303
307
  while (project.currentFileIndex < project.fileNames.length) {
304
308
  const i = project.currentFileIndex++;
305
309
  const fileName = project.fileNames[i];
306
- const fileMtime = fs.statSync(fileName).mtimeMs;
310
+ const fileStat = fs.statSync(fileName, { throwIfNoEntry: false });
311
+ if (!fileStat) {
312
+ continue;
313
+ }
307
314
  addProcessFile(fileName);
308
315
  if (Date.now() - lastSpinnerUpdate > 100) {
309
316
  lastSpinnerUpdate = Date.now();
@@ -311,8 +318,8 @@ class Project {
311
318
  }
312
319
  let fileCache = project.cache[fileName];
313
320
  if (fileCache) {
314
- if (fileCache[0] !== fileMtime) {
315
- fileCache[0] = fileMtime;
321
+ if (fileCache[0] !== fileStat.mtimeMs) {
322
+ fileCache[0] = fileStat.mtimeMs;
316
323
  fileCache[1] = {};
317
324
  fileCache[2] = {};
318
325
  }
@@ -321,7 +328,7 @@ class Project {
321
328
  }
322
329
  }
323
330
  else {
324
- project.cache[fileName] = fileCache = [fileMtime, {}, {}, false];
331
+ project.cache[fileName] = fileCache = [fileStat.mtimeMs, {}, {}, false];
325
332
  }
326
333
  let diagnostics = await linterWorker.lint(fileName, process.argv.includes('--fix'), fileCache);
327
334
  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.2",
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.2",
20
+ "@tsslint/core": "1.5.2",
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": "7d5ed919f5a6ac75b76319396ff2d43b5d9a8ad4"
33
33
  }