@tsslint/cli 1.3.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -3,139 +3,187 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const ts = require("typescript");
4
4
  const path = require("path");
5
5
  const core = require("@tsslint/core");
6
- const cache = require("./lib/cache");
6
+ const cache = require("./lib/cache.js");
7
+ const worker = require("./lib/worker.js");
7
8
  const glob = require("glob");
8
9
  const fs = require("fs");
9
- const gray = '\x1b[90m';
10
- const purple = '\x1b[35m';
11
- const green = '\x1b[32m';
12
- const red = '\x1b[31m';
13
- const yellow = '\x1b[33m';
14
- const reset = '\x1b[0m';
10
+ const os = require("os");
11
+ const _reset = '\x1b[0m';
12
+ const purple = (s) => '\x1b[35m' + s + _reset;
13
+ const darkGray = (s) => '\x1b[90m' + s + _reset;
14
+ const lightRed = (s) => '\x1b[91m' + s + _reset;
15
+ const lightGreen = (s) => '\x1b[92m' + s + _reset;
16
+ const lightYellow = (s) => '\x1b[93m' + s + _reset;
17
+ let threads = 1;
18
+ if (process.argv.includes('--threads')) {
19
+ const threadsIndex = process.argv.indexOf('--threads');
20
+ const threadsArg = process.argv[threadsIndex + 1];
21
+ if (!threadsArg || threadsArg.startsWith('-')) {
22
+ console.error(lightRed(`Missing argument for --threads.`));
23
+ process.exit(1);
24
+ }
25
+ threads = Math.min(os.availableParallelism(), Number(threadsArg));
26
+ }
15
27
  (async () => {
16
- let hasError = false;
17
- let projectVersion = 0;
18
- let typeRootsVersion = 0;
19
- let parsed;
20
- const clack = await import('@clack/prompts');
21
- const snapshots = new Map();
22
- const versions = new Map();
23
- const configs = new Map();
24
- const languageServiceHost = {
25
- ...ts.sys,
26
- useCaseSensitiveFileNames() {
27
- return ts.sys.useCaseSensitiveFileNames;
28
- },
29
- getProjectVersion() {
30
- return projectVersion.toString();
31
- },
32
- getTypeRootsVersion() {
33
- return typeRootsVersion;
34
- },
35
- getCompilationSettings() {
36
- return parsed.options;
37
- },
38
- getScriptFileNames() {
39
- return parsed.fileNames;
40
- },
41
- getScriptVersion(fileName) {
42
- return versions.get(fileName)?.toString() ?? '0';
43
- },
44
- getScriptSnapshot(fileName) {
45
- if (!snapshots.has(fileName)) {
46
- snapshots.set(fileName, ts.ScriptSnapshot.fromString(ts.sys.readFile(fileName)));
28
+ class Project {
29
+ constructor(tsconfigOption) {
30
+ this.workers = [];
31
+ this.fileNames = [];
32
+ this.options = {};
33
+ this.currentFileIndex = 0;
34
+ this.cache = {};
35
+ try {
36
+ this.tsconfig = require.resolve(tsconfigOption, { paths: [process.cwd()] });
37
+ }
38
+ catch {
39
+ console.error(lightRed(`No such file: ${tsconfigOption}`));
40
+ process.exit(1);
41
+ }
42
+ this.configFile = ts.findConfigFile(path.dirname(this.tsconfig), ts.sys.fileExists, 'tsslint.config.ts');
43
+ if (!this.configFile) {
44
+ log(`${purple('[project]')} ${path.relative(process.cwd(), this.tsconfig)} ${darkGray('(No tsslint.config.ts found)')}`);
45
+ return;
46
+ }
47
+ const commonLine = parseCommonLine(this.tsconfig);
48
+ this.fileNames = commonLine.fileNames;
49
+ this.options = commonLine.options;
50
+ if (!this.fileNames.length) {
51
+ log(`${purple('[project]')} ${path.relative(process.cwd(), this.tsconfig)} ${darkGray('(No included files)')}`);
52
+ return;
53
+ }
54
+ log(`${purple('[project]')} ${path.relative(process.cwd(), this.tsconfig)} ${darkGray(`(${this.fileNames.length})`)}`);
55
+ if (!process.argv.includes('--force')) {
56
+ this.cache = cache.loadCache(this.tsconfig, this.configFile, ts.sys.createHash);
47
57
  }
48
- return snapshots.get(fileName);
49
- },
50
- getDefaultLibFileName(options) {
51
- return ts.getDefaultLibFilePath(options);
52
- },
53
- };
54
- const languageService = ts.createLanguageService(languageServiceHost);
58
+ }
59
+ }
60
+ const builtConfigs = new Map();
61
+ const clack = await import('@clack/prompts');
62
+ const processFiles = new Set();
63
+ let projects = [];
64
+ let spinner = clack.spinner();
65
+ let lastSpinnerUpdate = Date.now();
66
+ let hasFix = false;
67
+ let allFilesNum = 0;
68
+ let processed = 0;
69
+ let excluded = 0;
70
+ let passed = 0;
71
+ let errors = 0;
72
+ let warnings = 0;
73
+ let cached = 0;
74
+ spinner.start();
55
75
  if (process.argv.includes('--project')) {
56
76
  const projectIndex = process.argv.indexOf('--project');
57
- const tsconfig = process.argv[projectIndex + 1];
58
- await projectWorker(tsconfig);
77
+ let tsconfig = process.argv[projectIndex + 1];
78
+ if (!tsconfig || tsconfig.startsWith('-')) {
79
+ console.error(lightRed(`Missing argument for --project.`));
80
+ process.exit(1);
81
+ }
82
+ if (!tsconfig.startsWith('.')) {
83
+ tsconfig = `./${tsconfig}`;
84
+ }
85
+ projects.push(new Project(tsconfig));
59
86
  }
60
87
  else if (process.argv.includes('--projects')) {
61
88
  const projectsIndex = process.argv.indexOf('--projects');
89
+ let foundArg = false;
62
90
  for (let i = projectsIndex + 1; i < process.argv.length; i++) {
63
91
  if (process.argv[i].startsWith('-')) {
64
92
  break;
65
93
  }
94
+ foundArg = true;
66
95
  const searchGlob = process.argv[i];
67
96
  const tsconfigs = glob.sync(searchGlob);
68
97
  for (let tsconfig of tsconfigs) {
69
98
  if (!tsconfig.startsWith('.')) {
70
99
  tsconfig = `./${tsconfig}`;
71
100
  }
72
- await projectWorker(tsconfig);
101
+ projects.push(new Project(tsconfig));
73
102
  }
74
103
  }
104
+ if (!foundArg) {
105
+ console.error(lightRed(`Missing argument for --projects.`));
106
+ process.exit(1);
107
+ }
75
108
  }
76
109
  else {
77
- await projectWorker();
110
+ const tsconfig = await askTSConfig();
111
+ projects.push(new Project(tsconfig));
78
112
  }
79
- process.exit(hasError ? 1 : 0);
80
- async function projectWorker(tsconfigOption) {
81
- const tsconfig = await getTsconfigPath(tsconfigOption);
82
- clack.intro(`${purple}[project]${reset} ${path.relative(process.cwd(), tsconfig)}`);
83
- parsed = parseCommonLine(tsconfig);
84
- if (!parsed.fileNames.length) {
85
- clack.outro(`${yellow}No input files found.${reset}`);
86
- return;
87
- }
88
- const configFile = ts.findConfigFile(path.dirname(tsconfig), ts.sys.fileExists, 'tsslint.config.ts');
89
- if (!configFile) {
90
- clack.outro(`${yellow}No tsslint.config.ts found!${reset}`);
113
+ projects = projects.filter(project => !!project.configFile);
114
+ projects = projects.filter(project => !!project.fileNames.length);
115
+ for (const project of projects) {
116
+ project.builtConfig = await getBuiltConfig(project.configFile);
117
+ }
118
+ projects = projects.filter(project => !!project.builtConfig);
119
+ for (const project of projects) {
120
+ allFilesNum += project.fileNames.length;
121
+ }
122
+ if (allFilesNum === 0) {
123
+ spinner.stop(lightYellow('No input files.'));
124
+ process.exit(1);
125
+ }
126
+ if (threads === 1) {
127
+ await startWorker(worker.createLocal());
128
+ }
129
+ else {
130
+ await Promise.all(new Array(threads).fill(0).map(() => {
131
+ return startWorker(worker.create());
132
+ }));
133
+ }
134
+ spinner.stop(darkGray(cached
135
+ ? `Processed ${processed} files with cache. (Use --force to ignore cache.)`
136
+ : `Processed ${processed} files.`));
137
+ const data = [
138
+ [passed, 'passed', lightGreen],
139
+ [errors, 'errors', lightRed],
140
+ [warnings, 'warnings', lightYellow],
141
+ [excluded, 'excluded', darkGray],
142
+ ];
143
+ let summary = data
144
+ .filter(([count]) => count)
145
+ .map(([count, label, color]) => color(`${count} ${label}`))
146
+ .join(darkGray(' | '));
147
+ if (hasFix) {
148
+ summary += darkGray(` (Use --fix to apply automatic fixes.)`);
149
+ }
150
+ else if (errors || warnings) {
151
+ summary += darkGray(` (No fixes available.)`);
152
+ }
153
+ clack.outro(summary);
154
+ process.exit(errors ? 1 : 0);
155
+ async function startWorker(linterWorker) {
156
+ const unfinishedProjects = projects.filter(project => project.currentFileIndex < project.fileNames.length);
157
+ if (!unfinishedProjects.length) {
91
158
  return;
92
159
  }
93
- if (!configs.has(configFile)) {
94
- try {
95
- configs.set(configFile, await core.buildConfigFile(configFile, ts.sys.createHash, clack));
96
- }
97
- catch (err) {
98
- configs.set(configFile, undefined);
99
- console.error(err);
100
- }
160
+ // Select a project that has not has a worker yet
161
+ let project = unfinishedProjects.find(project => !project.workers.length);
162
+ if (!project) {
163
+ // Choose a project with the most files left per worker
164
+ project = unfinishedProjects.sort((a, b) => {
165
+ const aFilesPerWorker = (a.fileNames.length - a.currentFileIndex) / a.workers.length;
166
+ const bFilesPerWorker = (b.fileNames.length - b.currentFileIndex) / b.workers.length;
167
+ return bFilesPerWorker - aFilesPerWorker;
168
+ })[0];
101
169
  }
102
- const tsslintConfig = configs.get(configFile);
103
- if (!tsslintConfig) {
170
+ project.workers.push(linterWorker);
171
+ const setupSuccess = await linterWorker.setup(project.tsconfig, project.configFile, project.builtConfig, project.fileNames, project.options);
172
+ if (!setupSuccess) {
173
+ projects = projects.filter(p => p !== project);
174
+ startWorker(linterWorker);
104
175
  return;
105
176
  }
106
- if (!parsed.fileNames) {
107
- throw new Error('No input files found in tsconfig!');
108
- }
109
- projectVersion++;
110
- typeRootsVersion++;
111
- const lintCache = process.argv.includes('--force')
112
- ? {}
113
- : cache.loadCache(configFile, ts.sys.createHash);
114
- const projectContext = {
115
- configFile,
116
- languageService,
117
- languageServiceHost,
118
- typescript: ts,
119
- tsconfig: ts.server.toNormalizedPath(tsconfig),
120
- };
121
- const linter = core.createLinter(projectContext, tsslintConfig, 'cli', clack);
122
- const lintSpinner = clack.spinner();
123
- let hasFix = false;
124
- let passed = 0;
125
- let errors = 0;
126
- let warnings = 0;
127
- let cached = 0;
128
- let t = Date.now();
129
- lintSpinner.start();
130
- for (let i = 0; i < parsed.fileNames.length; i++) {
131
- const fileName = parsed.fileNames[i];
132
- if (Date.now() - t > 100) {
133
- t = Date.now();
134
- lintSpinner.message(`${gray}[${i + 1}/${parsed.fileNames.length}] ${path.relative(process.cwd(), fileName)}${reset}`);
177
+ while (project.currentFileIndex < project.fileNames.length) {
178
+ const i = project.currentFileIndex++;
179
+ const fileName = project.fileNames[i];
180
+ const fileMtime = fs.statSync(fileName).mtimeMs;
181
+ addProcessFile(fileName);
182
+ if (Date.now() - lastSpinnerUpdate > 100) {
183
+ lastSpinnerUpdate = Date.now();
135
184
  await new Promise(resolve => setTimeout(resolve, 0));
136
185
  }
137
- const fileMtime = fs.statSync(fileName).mtimeMs;
138
- let fileCache = lintCache[fileName];
186
+ let fileCache = project.cache[fileName];
139
187
  if (fileCache) {
140
188
  if (fileCache[0] !== fileMtime) {
141
189
  fileCache[0] = fileMtime;
@@ -149,40 +197,17 @@ const reset = '\x1b[0m';
149
197
  }
150
198
  }
151
199
  else {
152
- lintCache[fileName] = fileCache = [fileMtime, {}, [], [], {}];
200
+ project.cache[fileName] = fileCache = [fileMtime, {}, [], [], {}];
153
201
  }
202
+ let diagnostics;
154
203
  if (process.argv.includes('--fix')) {
155
- let retry = 3;
156
- let shouldRetry = true;
157
- let newSnapshot;
158
- while (shouldRetry && retry) {
159
- shouldRetry = false;
160
- retry--;
161
- if (Object.values(fileCache[1]).some(fixes => fixes > 0)) {
162
- // Reset the cache if there are any fixes applied.
163
- fileCache[1] = {};
164
- fileCache[2].length = 0;
165
- fileCache[3].length = 0;
166
- }
167
- const diagnostics = linter.lint(fileName, fileCache);
168
- const fixes = linter.getCodeFixes(fileName, 0, Number.MAX_VALUE, diagnostics, fileCache);
169
- const textChanges = core.combineCodeFixes(fileName, fixes);
170
- if (textChanges.length) {
171
- const oldSnapshot = snapshots.get(fileName);
172
- newSnapshot = core.applyTextChanges(oldSnapshot, textChanges);
173
- snapshots.set(fileName, newSnapshot);
174
- versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
175
- projectVersion++;
176
- shouldRetry = true;
177
- }
178
- }
179
- if (newSnapshot) {
180
- ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
181
- fileCache[0] = fs.statSync(fileName).mtimeMs;
182
- }
204
+ diagnostics = await linterWorker.lintAndFix(fileName, fileCache);
183
205
  }
184
206
  else {
185
- const diagnostics = linter.lint(fileName, fileCache);
207
+ diagnostics = await linterWorker.lint(fileName, fileCache);
208
+ }
209
+ if (diagnostics.length) {
210
+ hasFix ||= await linterWorker.hasCodeFixes(fileName);
186
211
  for (const diagnostic of diagnostics) {
187
212
  if (diagnostic.category === ts.DiagnosticCategory.Suggestion) {
188
213
  continue;
@@ -192,77 +217,84 @@ const reset = '\x1b[0m';
192
217
  getCanonicalFileName: ts.sys.useCaseSensitiveFileNames ? x => x : x => x.toLowerCase(),
193
218
  getNewLine: () => ts.sys.newLine,
194
219
  });
195
- output = output.replace(`TS${diagnostic.code}`, `TSSLint(${diagnostic.code})`);
220
+ output = output.replace(`TS${diagnostic.code}`, String(diagnostic.code));
196
221
  if (diagnostic.category === ts.DiagnosticCategory.Error) {
197
222
  errors++;
198
- clack.log.error(output);
223
+ log(output, 1);
199
224
  }
200
225
  else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
201
226
  warnings++;
202
- clack.log.warn(output);
227
+ log(output, 2);
203
228
  }
204
229
  else {
205
- clack.log.info(output);
230
+ log(output);
206
231
  }
207
232
  }
208
- if (diagnostics.length) {
209
- hasFix ||= linter.hasCodeFixes(fileName);
210
- hasError ||= diagnostics.some(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error);
211
- }
212
- else {
213
- passed++;
214
- }
215
233
  }
234
+ else if (!(await linterWorker.hasRules(fileName, fileCache[4]))) {
235
+ excluded++;
236
+ }
237
+ else {
238
+ passed++;
239
+ }
240
+ processed++;
241
+ removeProcessFile(fileName);
216
242
  }
217
- if (cached) {
218
- lintSpinner.stop(`${gray}Checked ${parsed.fileNames.length} files with cache.${reset} ${gray}(Use --force to ignore cache.)${reset}`);
219
- }
220
- else {
221
- lintSpinner.stop(`${gray}Checked ${parsed.fileNames.length} files.${reset}`);
222
- }
223
- if (hasFix) {
224
- clack.log.message(`${gray}Use --fix to apply fixes.${reset}`);
243
+ cache.saveCache(project.tsconfig, project.configFile, project.cache, ts.sys.createHash);
244
+ await startWorker(linterWorker);
245
+ }
246
+ async function getBuiltConfig(configFile) {
247
+ if (!builtConfigs.has(configFile)) {
248
+ builtConfigs.set(configFile, core.buildConfig(configFile, ts.sys.createHash, spinner, (s, code) => log(darkGray(s), code)));
225
249
  }
226
- const data = [
227
- [passed, 'passed', green],
228
- [errors, 'errors', red],
229
- [warnings, 'warnings', yellow],
230
- ];
231
- const summary = data
232
- .filter(([count]) => count)
233
- .map(([count, label, color]) => `${color}${count} ${label}${reset}`)
234
- .join(` ${gray}|${reset} `);
235
- clack.outro(summary);
236
- cache.saveCache(configFile, lintCache, ts.sys.createHash);
250
+ return await builtConfigs.get(configFile);
237
251
  }
238
- async function getTsconfigPath(tsconfig) {
239
- if (!tsconfig) {
240
- tsconfig = ts.findConfigFile(process.cwd(), ts.sys.fileExists);
241
- let shortTsconfig = tsconfig ? path.relative(process.cwd(), tsconfig) : undefined;
242
- if (!shortTsconfig?.startsWith('.')) {
243
- shortTsconfig = `./${shortTsconfig}`;
244
- }
245
- tsconfig = await clack.text({
246
- message: 'Select the tsconfig project. (Use --project or --projects to skip this prompt.)',
247
- placeholder: shortTsconfig ? `${shortTsconfig} (${parseCommonLine(tsconfig).fileNames.length} files)` : 'No tsconfig.json/jsconfig.json found, please enter the path to the tsconfig.json/jsconfig.json file.',
248
- defaultValue: shortTsconfig,
249
- validate(value) {
250
- value ||= shortTsconfig;
251
- try {
252
- require.resolve(value, { paths: [process.cwd()] });
253
- }
254
- catch {
255
- return `File not found!`;
256
- }
257
- },
258
- });
252
+ async function askTSConfig() {
253
+ const presetConfig = ts.findConfigFile(process.cwd(), ts.sys.fileExists);
254
+ let shortTsconfig = presetConfig ? path.relative(process.cwd(), presetConfig) : undefined;
255
+ if (!shortTsconfig?.startsWith('.')) {
256
+ shortTsconfig = `./${shortTsconfig}`;
259
257
  }
260
- tsconfig = require.resolve(tsconfig, { paths: [process.cwd()] });
261
- return tsconfig;
258
+ return await clack.text({
259
+ message: 'Select the project. (Use --project or --projects to skip this prompt.)',
260
+ placeholder: shortTsconfig ? `${shortTsconfig} (${parseCommonLine(presetConfig).fileNames.length} files)` : 'No tsconfig.json/jsconfig.json found, please enter the path to the tsconfig.json/jsconfig.json file.',
261
+ defaultValue: shortTsconfig,
262
+ validate(value) {
263
+ value ||= shortTsconfig;
264
+ try {
265
+ require.resolve(value, { paths: [process.cwd()] });
266
+ }
267
+ catch {
268
+ return 'No such file.';
269
+ }
270
+ },
271
+ });
262
272
  }
263
273
  function parseCommonLine(tsconfig) {
264
274
  const jsonConfigFile = ts.readJsonConfigFile(tsconfig, ts.sys.readFile);
265
275
  return ts.parseJsonSourceFileConfigFileContent(jsonConfigFile, ts.sys, path.dirname(tsconfig), {}, tsconfig);
266
276
  }
277
+ function addProcessFile(fileName) {
278
+ processFiles.add(fileName);
279
+ updateSpinner();
280
+ }
281
+ function removeProcessFile(fileName) {
282
+ processFiles.delete(fileName);
283
+ updateSpinner();
284
+ }
285
+ function updateSpinner() {
286
+ if (processFiles.size === 1) {
287
+ const fileName = processFiles.values().next().value;
288
+ spinner.message(`[${processed + processFiles.size}/${allFilesNum}] ${path.relative(process.cwd(), fileName)}`);
289
+ }
290
+ else {
291
+ spinner.message(`[${processed + processFiles.size}/${allFilesNum}] Processing ${processFiles.size} files`);
292
+ }
293
+ }
294
+ function log(msg, code) {
295
+ spinner.stop(msg, code);
296
+ spinner = clack.spinner();
297
+ spinner.start();
298
+ }
267
299
  })();
268
300
  //# sourceMappingURL=index.js.map
package/lib/cache.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import core = require('@tsslint/core');
2
2
  export type CacheData = Record<string, core.FileLintCache>;
3
- export declare function loadCache(configFilePath: string, createHash?: (path: string) => string): CacheData;
4
- export declare function saveCache(configFilePath: string, cache: CacheData, createHash?: (path: string) => string): void;
3
+ export declare function loadCache(tsconfig: string, configFilePath: string, createHash?: (path: string) => string): CacheData;
4
+ export declare function saveCache(tsconfig: string, configFilePath: string, cache: CacheData, createHash?: (path: string) => string): void;
package/lib/cache.js CHANGED
@@ -5,9 +5,9 @@ exports.saveCache = saveCache;
5
5
  const core = require("@tsslint/core");
6
6
  const path = require("path");
7
7
  const fs = require("fs");
8
- function loadCache(configFilePath, createHash = btoa) {
8
+ function loadCache(tsconfig, configFilePath, createHash = btoa) {
9
9
  const outDir = core.getDotTsslintPath(configFilePath);
10
- const cacheFileName = createHash(path.relative(outDir, configFilePath)) + '.cache.json';
10
+ const cacheFileName = createHash(path.relative(outDir, configFilePath)) + '_' + createHash(path.relative(outDir, tsconfig)) + '.cache.json';
11
11
  const cacheFilePath = path.join(outDir, cacheFileName);
12
12
  const cacheFileStat = fs.statSync(cacheFilePath, { throwIfNoEntry: false });
13
13
  const configFileStat = fs.statSync(configFilePath, { throwIfNoEntry: false });
@@ -21,9 +21,9 @@ function loadCache(configFilePath, createHash = btoa) {
21
21
  }
22
22
  return {};
23
23
  }
24
- function saveCache(configFilePath, cache, createHash = btoa) {
24
+ function saveCache(tsconfig, configFilePath, cache, createHash = btoa) {
25
25
  const outDir = core.getDotTsslintPath(configFilePath);
26
- const cacheFileName = createHash(path.relative(outDir, configFilePath)) + '.cache.json';
26
+ const cacheFileName = createHash(path.relative(outDir, configFilePath)) + '_' + createHash(path.relative(outDir, tsconfig)) + '.cache.json';
27
27
  const cacheFilePath = path.join(outDir, cacheFileName);
28
28
  fs.writeFileSync(cacheFilePath, JSON.stringify(cache));
29
29
  }
@@ -0,0 +1,22 @@
1
+ import ts = require('typescript');
2
+ import core = require('@tsslint/core');
3
+ export declare function createLocal(): {
4
+ setup(tsconfig: string, configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions): Promise<boolean>;
5
+ lint(fileName: string, fileCache: core.FileLintCache): ts.DiagnosticWithLocation[];
6
+ lintAndFix(fileName: string, fileCache: core.FileLintCache): ts.DiagnosticWithLocation[];
7
+ hasCodeFixes(fileName: string): boolean;
8
+ hasRules(fileName: string, minimatchCache: Record<string, boolean>): Promise<boolean>;
9
+ };
10
+ export declare function create(): {
11
+ setup(tsconfig: string, configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions): Promise<boolean>;
12
+ lint(fileName: string, fileCache: core.FileLintCache): Promise<ts.DiagnosticWithLocation[]>;
13
+ lintAndFix(fileName: string, fileCache: core.FileLintCache): Promise<ts.DiagnosticWithLocation[]>;
14
+ hasCodeFixes(fileName: string): Promise<boolean>;
15
+ hasRules(fileName: string, minimatchCache: Record<string, boolean>): Promise<boolean>;
16
+ };
17
+ declare function setup(tsconfig: string, configFile: string, builtConfig: string, _fileNames: string[], _options: ts.CompilerOptions): Promise<boolean>;
18
+ declare function lintAndFix(fileName: string, fileCache: core.FileLintCache): readonly [ts.DiagnosticWithLocation[], core.FileLintCache];
19
+ declare function lint(fileName: string, fileCache: core.FileLintCache): readonly [ts.DiagnosticWithLocation[], core.FileLintCache];
20
+ declare function hasCodeFixes(fileName: string): boolean;
21
+ declare function hasRules(fileName: string, minimatchCache: core.FileLintCache[4]): readonly [boolean, Record<string, boolean>];
22
+ export {};
package/lib/worker.js ADDED
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLocal = createLocal;
4
+ exports.create = create;
5
+ const ts = require("typescript");
6
+ const core = require("@tsslint/core");
7
+ const url = require("url");
8
+ const fs = require("fs");
9
+ const worker_threads = require("worker_threads");
10
+ let projectVersion = 0;
11
+ let typeRootsVersion = 0;
12
+ let options = {};
13
+ let fileNames = [];
14
+ let linter;
15
+ const snapshots = new Map();
16
+ const versions = new Map();
17
+ const languageServiceHost = {
18
+ ...ts.sys,
19
+ useCaseSensitiveFileNames() {
20
+ return ts.sys.useCaseSensitiveFileNames;
21
+ },
22
+ getProjectVersion() {
23
+ return projectVersion.toString();
24
+ },
25
+ getTypeRootsVersion() {
26
+ return typeRootsVersion;
27
+ },
28
+ getCompilationSettings() {
29
+ return options;
30
+ },
31
+ getScriptFileNames() {
32
+ return fileNames;
33
+ },
34
+ getScriptVersion(fileName) {
35
+ return versions.get(fileName)?.toString() ?? '0';
36
+ },
37
+ getScriptSnapshot(fileName) {
38
+ if (!snapshots.has(fileName)) {
39
+ snapshots.set(fileName, ts.ScriptSnapshot.fromString(ts.sys.readFile(fileName)));
40
+ }
41
+ return snapshots.get(fileName);
42
+ },
43
+ getDefaultLibFileName(options) {
44
+ return ts.getDefaultLibFilePath(options);
45
+ },
46
+ };
47
+ const languageService = ts.createLanguageService(languageServiceHost);
48
+ function createLocal() {
49
+ return {
50
+ setup(...args) {
51
+ return setup(...args);
52
+ },
53
+ lint(...args) {
54
+ return lint(...args)[0];
55
+ },
56
+ lintAndFix(...args) {
57
+ return lintAndFix(...args)[0];
58
+ },
59
+ hasCodeFixes(...args) {
60
+ return hasCodeFixes(...args);
61
+ },
62
+ async hasRules(...args) {
63
+ return hasRules(...args)[0];
64
+ },
65
+ };
66
+ }
67
+ function create() {
68
+ const worker = new worker_threads.Worker(__filename);
69
+ return {
70
+ setup(...args) {
71
+ return sendRequest(setup, ...args);
72
+ },
73
+ async lint(...args) {
74
+ const [res, newCache] = await sendRequest(lint, ...args);
75
+ Object.assign(args[1], newCache); // Sync the cache
76
+ return res;
77
+ },
78
+ async lintAndFix(...args) {
79
+ const [res, newCache] = await sendRequest(lintAndFix, ...args);
80
+ Object.assign(args[1], newCache); // Sync the cache
81
+ return res;
82
+ },
83
+ hasCodeFixes(...args) {
84
+ return sendRequest(hasCodeFixes, ...args);
85
+ },
86
+ async hasRules(...args) {
87
+ const [res, newCache] = await sendRequest(hasRules, ...args);
88
+ Object.assign(args[1], newCache); // Sync the cache
89
+ return res;
90
+ },
91
+ };
92
+ function sendRequest(t, ...args) {
93
+ return new Promise(resolve => {
94
+ worker.once('message', json => {
95
+ resolve(JSON.parse(json));
96
+ });
97
+ worker.postMessage(JSON.stringify([t.name, ...args]));
98
+ });
99
+ }
100
+ }
101
+ worker_threads.parentPort?.on('message', async (json) => {
102
+ const data = JSON.parse(json);
103
+ const result = await handlers[data[0]](...data.slice(1));
104
+ worker_threads.parentPort.postMessage(JSON.stringify(result));
105
+ });
106
+ const handlers = {
107
+ setup,
108
+ lint,
109
+ lintAndFix,
110
+ hasCodeFixes,
111
+ hasRules,
112
+ };
113
+ async function setup(tsconfig, configFile, builtConfig, _fileNames, _options) {
114
+ const clack = await import('@clack/prompts');
115
+ let config;
116
+ try {
117
+ config = (await import(url.pathToFileURL(builtConfig).toString())).default;
118
+ }
119
+ catch (err) {
120
+ if (err instanceof Error) {
121
+ clack.log.error(err.stack ?? err.message);
122
+ }
123
+ else {
124
+ clack.log.error(String(err));
125
+ }
126
+ return false;
127
+ }
128
+ projectVersion++;
129
+ typeRootsVersion++;
130
+ fileNames = _fileNames;
131
+ options = _options;
132
+ linter = core.createLinter({
133
+ configFile,
134
+ languageService,
135
+ languageServiceHost,
136
+ typescript: ts,
137
+ tsconfig: ts.server.toNormalizedPath(tsconfig),
138
+ }, config, 'cli', clack);
139
+ return true;
140
+ }
141
+ function lintAndFix(fileName, fileCache) {
142
+ let retry = 3;
143
+ let shouldRetry = true;
144
+ let newSnapshot;
145
+ let diagnostics;
146
+ while (shouldRetry && retry--) {
147
+ if (Object.values(fileCache[1]).some(fixes => fixes > 0)) {
148
+ // Reset the cache if there are any fixes applied.
149
+ fileCache[1] = {};
150
+ fileCache[2].length = 0;
151
+ fileCache[3].length = 0;
152
+ }
153
+ diagnostics = linter.lint(fileName, fileCache);
154
+ const fixes = linter
155
+ .getCodeFixes(fileName, 0, Number.MAX_VALUE, diagnostics, fileCache[4])
156
+ .filter(fix => fix.fixId === 'tsslint');
157
+ const textChanges = core.combineCodeFixes(fileName, fixes);
158
+ if (textChanges.length) {
159
+ const oldSnapshot = snapshots.get(fileName);
160
+ newSnapshot = core.applyTextChanges(oldSnapshot, textChanges);
161
+ snapshots.set(fileName, newSnapshot);
162
+ versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
163
+ projectVersion++;
164
+ shouldRetry = true;
165
+ }
166
+ }
167
+ if (newSnapshot) {
168
+ ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
169
+ fileCache[0] = fs.statSync(fileName).mtimeMs;
170
+ fileCache[1] = {};
171
+ fileCache[2].length = 0;
172
+ fileCache[3].length = 0;
173
+ }
174
+ if (shouldRetry) {
175
+ diagnostics = linter.lint(fileName, fileCache);
176
+ }
177
+ return [
178
+ diagnostics.map(diagnostic => ({
179
+ ...diagnostic,
180
+ file: {
181
+ fileName: diagnostic.file.fileName,
182
+ text: diagnostic.file.text,
183
+ },
184
+ relatedInformation: diagnostic.relatedInformation?.map(info => ({
185
+ ...info,
186
+ file: info.file ? {
187
+ fileName: info.file.fileName,
188
+ text: info.file.text,
189
+ } : undefined,
190
+ })),
191
+ })),
192
+ fileCache,
193
+ ];
194
+ }
195
+ function lint(fileName, fileCache) {
196
+ return [
197
+ linter.lint(fileName, fileCache).map(diagnostic => ({
198
+ ...diagnostic,
199
+ file: {
200
+ fileName: diagnostic.file.fileName,
201
+ text: diagnostic.file.text,
202
+ },
203
+ relatedInformation: diagnostic.relatedInformation?.map(info => ({
204
+ ...info,
205
+ file: info.file ? {
206
+ fileName: info.file.fileName,
207
+ text: info.file.text,
208
+ } : undefined,
209
+ })),
210
+ })),
211
+ fileCache,
212
+ ];
213
+ }
214
+ function hasCodeFixes(fileName) {
215
+ return linter.hasCodeFixes(fileName);
216
+ }
217
+ function hasRules(fileName, minimatchCache) {
218
+ return [Object.keys(linter.getRules(fileName, minimatchCache)).length > 0, minimatchCache];
219
+ }
220
+ //# sourceMappingURL=worker.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsslint/cli",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "tsslint": "./bin/tsslint.js"
@@ -16,12 +16,12 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@clack/prompts": "^0.8.2",
19
- "@tsslint/config": "1.3.5",
20
- "@tsslint/core": "1.3.5",
19
+ "@tsslint/config": "1.4.0",
20
+ "@tsslint/core": "1.4.0",
21
21
  "glob": "^10.4.1"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "typescript": "*"
25
25
  },
26
- "gitHead": "6fd4e516735cdf1205a3fa0df91933de72f62476"
26
+ "gitHead": "9a3f7ce55f079eaaedfb61af9b72d8ba736f0123"
27
27
  }