@tsslint/cli 1.3.6 → 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,147 +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");
10
+ const os = require("os");
9
11
  const _reset = '\x1b[0m';
10
12
  const purple = (s) => '\x1b[35m' + s + _reset;
11
13
  const darkGray = (s) => '\x1b[90m' + s + _reset;
12
14
  const lightRed = (s) => '\x1b[91m' + s + _reset;
13
15
  const lightGreen = (s) => '\x1b[92m' + s + _reset;
14
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;
47
53
  }
48
- return snapshots.get(fileName);
49
- },
50
- getDefaultLibFileName(options) {
51
- return ts.getDefaultLibFilePath(options);
52
- },
53
- };
54
- const languageService = ts.createLanguageService(languageServiceHost);
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);
57
+ }
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
77
  let tsconfig = process.argv[projectIndex + 1];
58
- if (tsconfig.startsWith('-') || !tsconfig) {
59
- clack.log.error(lightRed(`Missing argument for --project.`));
78
+ if (!tsconfig || tsconfig.startsWith('-')) {
79
+ console.error(lightRed(`Missing argument for --project.`));
80
+ process.exit(1);
60
81
  }
61
- else {
62
- if (!tsconfig.startsWith('.')) {
63
- tsconfig = `./${tsconfig}`;
64
- }
65
- await projectWorker(tsconfig);
82
+ if (!tsconfig.startsWith('.')) {
83
+ tsconfig = `./${tsconfig}`;
66
84
  }
85
+ projects.push(new Project(tsconfig));
67
86
  }
68
87
  else if (process.argv.includes('--projects')) {
69
88
  const projectsIndex = process.argv.indexOf('--projects');
89
+ let foundArg = false;
70
90
  for (let i = projectsIndex + 1; i < process.argv.length; i++) {
71
91
  if (process.argv[i].startsWith('-')) {
72
92
  break;
73
93
  }
94
+ foundArg = true;
74
95
  const searchGlob = process.argv[i];
75
96
  const tsconfigs = glob.sync(searchGlob);
76
97
  for (let tsconfig of tsconfigs) {
77
98
  if (!tsconfig.startsWith('.')) {
78
99
  tsconfig = `./${tsconfig}`;
79
100
  }
80
- await projectWorker(tsconfig);
101
+ projects.push(new Project(tsconfig));
81
102
  }
82
103
  }
104
+ if (!foundArg) {
105
+ console.error(lightRed(`Missing argument for --projects.`));
106
+ process.exit(1);
107
+ }
83
108
  }
84
109
  else {
85
110
  const tsconfig = await askTSConfig();
86
- await projectWorker(tsconfig);
111
+ projects.push(new Project(tsconfig));
112
+ }
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.)`);
87
152
  }
88
- process.exit(hasError ? 1 : 0);
89
- async function projectWorker(tsconfigOption) {
90
- const tsconfig = require.resolve(tsconfigOption, { paths: [process.cwd()] });
91
- clack.intro(`${purple('[project]')} ${path.relative(process.cwd(), tsconfig)}`);
92
- parsed = parseCommonLine(tsconfig);
93
- if (!parsed.fileNames.length) {
94
- clack.outro(lightYellow('No included files.'));
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) {
95
158
  return;
96
159
  }
97
- const configFile = ts.findConfigFile(path.dirname(tsconfig), ts.sys.fileExists, 'tsslint.config.ts');
98
- if (!configFile) {
99
- clack.outro(lightYellow('No tsslint.config.ts found.'));
100
- return;
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
- if (!configs.has(configFile)) {
103
- try {
104
- configs.set(configFile, await core.buildConfigFile(configFile, ts.sys.createHash, clack));
105
- }
106
- catch (err) {
107
- configs.set(configFile, undefined);
108
- console.error(err);
109
- }
110
- }
111
- const tsslintConfig = configs.get(configFile);
112
- 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);
113
175
  return;
114
176
  }
115
- projectVersion++;
116
- typeRootsVersion++;
117
- const lintCache = process.argv.includes('--force')
118
- ? {}
119
- : cache.loadCache(configFile, ts.sys.createHash);
120
- const projectContext = {
121
- configFile,
122
- languageService,
123
- languageServiceHost,
124
- typescript: ts,
125
- tsconfig: ts.server.toNormalizedPath(tsconfig),
126
- };
127
- const linter = core.createLinter(projectContext, tsslintConfig, 'cli', clack);
128
- let lintSpinner = clack.spinner();
129
- let hasFix = false;
130
- let excluded = 0;
131
- let passed = 0;
132
- let errors = 0;
133
- let warnings = 0;
134
- let cached = 0;
135
- let t = Date.now();
136
- lintSpinner.start(darkGray(`[1/${parsed.fileNames.length}] ${path.relative(process.cwd(), parsed.fileNames[0])}`));
137
- await new Promise(resolve => setTimeout(resolve, 100));
138
- for (let i = 0; i < parsed.fileNames.length; i++) {
139
- const fileName = parsed.fileNames[i];
140
- if (Date.now() - t > 100) {
141
- t = Date.now();
142
- lintSpinner.message(darkGray(`[${i + 1}/${parsed.fileNames.length}] ${path.relative(process.cwd(), fileName)}`));
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();
143
184
  await new Promise(resolve => setTimeout(resolve, 0));
144
185
  }
145
- const fileMtime = fs.statSync(fileName).mtimeMs;
146
- let fileCache = lintCache[fileName];
186
+ let fileCache = project.cache[fileName];
147
187
  if (fileCache) {
148
188
  if (fileCache[0] !== fileMtime) {
149
189
  fileCache[0] = fileMtime;
@@ -157,48 +197,17 @@ const lightYellow = (s) => '\x1b[93m' + s + _reset;
157
197
  }
158
198
  }
159
199
  else {
160
- lintCache[fileName] = fileCache = [fileMtime, {}, [], [], {}];
200
+ project.cache[fileName] = fileCache = [fileMtime, {}, [], [], {}];
161
201
  }
162
202
  let diagnostics;
163
203
  if (process.argv.includes('--fix')) {
164
- let retry = 3;
165
- let shouldRetry = true;
166
- let newSnapshot;
167
- while (shouldRetry && retry) {
168
- shouldRetry = false;
169
- retry--;
170
- if (Object.values(fileCache[1]).some(fixes => fixes > 0)) {
171
- // Reset the cache if there are any fixes applied.
172
- fileCache[1] = {};
173
- fileCache[2].length = 0;
174
- fileCache[3].length = 0;
175
- }
176
- diagnostics = linter.lint(fileName, fileCache);
177
- const fixes = linter.getCodeFixes(fileName, 0, Number.MAX_VALUE, diagnostics, fileCache);
178
- const textChanges = core.combineCodeFixes(fileName, fixes);
179
- if (textChanges.length) {
180
- const oldSnapshot = snapshots.get(fileName);
181
- newSnapshot = core.applyTextChanges(oldSnapshot, textChanges);
182
- snapshots.set(fileName, newSnapshot);
183
- versions.set(fileName, (versions.get(fileName) ?? 0) + 1);
184
- projectVersion++;
185
- shouldRetry = true;
186
- }
187
- }
188
- if (newSnapshot) {
189
- ts.sys.writeFile(fileName, newSnapshot.getText(0, newSnapshot.getLength()));
190
- fileCache[0] = fs.statSync(fileName).mtimeMs;
191
- }
192
- if (shouldRetry) {
193
- diagnostics = linter.lint(fileName, fileCache);
194
- }
204
+ diagnostics = await linterWorker.lintAndFix(fileName, fileCache);
195
205
  }
196
206
  else {
197
- diagnostics = linter.lint(fileName, fileCache);
207
+ diagnostics = await linterWorker.lint(fileName, fileCache);
198
208
  }
199
209
  if (diagnostics.length) {
200
- hasFix ||= linter.hasCodeFixes(fileName);
201
- hasError ||= diagnostics.some(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error);
210
+ hasFix ||= await linterWorker.hasCodeFixes(fileName);
202
211
  for (const diagnostic of diagnostics) {
203
212
  if (diagnostic.category === ts.DiagnosticCategory.Suggestion) {
204
213
  continue;
@@ -209,71 +218,36 @@ const lightYellow = (s) => '\x1b[93m' + s + _reset;
209
218
  getNewLine: () => ts.sys.newLine,
210
219
  });
211
220
  output = output.replace(`TS${diagnostic.code}`, String(diagnostic.code));
212
- if (lintSpinner) {
213
- if (diagnostic.category === ts.DiagnosticCategory.Error) {
214
- errors++;
215
- lintSpinner.stop(output, 1);
216
- }
217
- else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
218
- warnings++;
219
- lintSpinner.stop(output, 2);
220
- }
221
- else {
222
- lintSpinner.stop(output);
223
- }
224
- lintSpinner = undefined;
221
+ if (diagnostic.category === ts.DiagnosticCategory.Error) {
222
+ errors++;
223
+ log(output, 1);
224
+ }
225
+ else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
226
+ warnings++;
227
+ log(output, 2);
225
228
  }
226
229
  else {
227
- if (diagnostic.category === ts.DiagnosticCategory.Error) {
228
- errors++;
229
- clack.log.error(output);
230
- }
231
- else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
232
- warnings++;
233
- clack.log.warning(output);
234
- }
235
- else {
236
- clack.log.info(output);
237
- }
230
+ log(output);
238
231
  }
239
232
  }
240
233
  }
241
- else if (!Object.keys(linter.getRules(fileName, fileCache)).length) {
234
+ else if (!(await linterWorker.hasRules(fileName, fileCache[4]))) {
242
235
  excluded++;
243
236
  }
244
237
  else {
245
238
  passed++;
246
239
  }
247
- if (!lintSpinner) {
248
- lintSpinner = clack.spinner();
249
- lintSpinner.start(darkGray(`[${i + 1}/${parsed.fileNames.length}] ${path.relative(process.cwd(), parsed.fileNames[i])}`));
250
- await new Promise(resolve => setTimeout(resolve, 100));
251
- }
252
- }
253
- if (cached) {
254
- lintSpinner.stop(darkGray(`Processed ${parsed.fileNames.length} files with cache. (Use --force to ignore cache.)`));
240
+ processed++;
241
+ removeProcessFile(fileName);
255
242
  }
256
- else {
257
- lintSpinner.stop(darkGray(`Processed ${parsed.fileNames.length} files.`));
258
- }
259
- const data = [
260
- [passed, 'passed', lightGreen],
261
- [errors, 'errors', lightRed],
262
- [warnings, 'warnings', lightYellow],
263
- [excluded, 'excluded', darkGray],
264
- ];
265
- let summary = data
266
- .filter(([count]) => count)
267
- .map(([count, label, color]) => color(`${count} ${label}`))
268
- .join(darkGray(' | '));
269
- if (hasFix) {
270
- summary += darkGray(` (Use --fix to apply automatic fixes.)`);
271
- }
272
- else if (errors || warnings) {
273
- summary += darkGray(` (No fixes available.)`);
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)));
274
249
  }
275
- clack.outro(summary);
276
- cache.saveCache(configFile, lintCache, ts.sys.createHash);
250
+ return await builtConfigs.get(configFile);
277
251
  }
278
252
  async function askTSConfig() {
279
253
  const presetConfig = ts.findConfigFile(process.cwd(), ts.sys.fileExists);
@@ -300,5 +274,27 @@ const lightYellow = (s) => '\x1b[93m' + s + _reset;
300
274
  const jsonConfigFile = ts.readJsonConfigFile(tsconfig, ts.sys.readFile);
301
275
  return ts.parseJsonSourceFileConfigFileContent(jsonConfigFile, ts.sys, path.dirname(tsconfig), {}, tsconfig);
302
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
+ }
303
299
  })();
304
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.6",
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.6",
20
- "@tsslint/core": "1.3.6",
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": "d153aa87c92803b4c20fef1f5cf2799f1a599099"
26
+ "gitHead": "9a3f7ce55f079eaaedfb61af9b72d8ba736f0123"
27
27
  }