@tsslint/core 1.4.3 → 1.4.5

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.
Files changed (3) hide show
  1. package/index.d.ts +9 -8
  2. package/index.js +150 -192
  3. package/package.json +3 -3
package/index.d.ts CHANGED
@@ -1,23 +1,24 @@
1
1
  export * from './lib/build';
2
2
  export * from './lib/watch';
3
- import type { Config, ProjectContext, Rules } from '@tsslint/types';
3
+ import type { Config, ProjectContext, Rule, Rules } from '@tsslint/types';
4
4
  import type * as ts from 'typescript';
5
5
  export type FileLintCache = [
6
6
  mtime: number,
7
- ruleFixes: Record<string, number>,
8
- result: ts.DiagnosticWithLocation[],
9
- resolvedResult: ts.DiagnosticWithLocation[],
7
+ lintResult: Record<string, [
8
+ hasFix: boolean,
9
+ diagnostics: ts.DiagnosticWithLocation[]
10
+ ]>,
10
11
  minimatchResult: Record<string, boolean>
11
12
  ];
12
13
  export type Linter = ReturnType<typeof createLinter>;
13
- export declare function createLinter(ctx: ProjectContext, config: Config | Config[], mode: 'cli' | 'typescript-plugin'): {
14
+ export declare function createLinter(ctx: ProjectContext, config: Config | Config[], mode: 'cli' | 'typescript-plugin', syntaxOnlyLanguageService?: ts.LanguageService): {
14
15
  lint(fileName: string, cache?: FileLintCache): ts.DiagnosticWithLocation[];
15
16
  hasCodeFixes(fileName: string): boolean;
16
- getCodeFixes(fileName: string, start: number, end: number, diagnostics?: ts.Diagnostic[], minimatchCache?: FileLintCache[4]): ts.CodeFixAction[];
17
+ getCodeFixes(fileName: string, start: number, end: number, diagnostics?: ts.Diagnostic[], minimatchCache?: FileLintCache[2]): ts.CodeFixAction[];
17
18
  getRefactors(fileName: string, start: number, end: number): ts.RefactorActionInfo[];
18
19
  getRefactorEdits(fileName: string, actionName: string): ts.FileTextChanges[] | undefined;
19
- getRules: (fileName: string, minimatchCache: undefined | FileLintCache[4]) => Rules;
20
- getConfigs: (fileName: string, minimatchCache: undefined | FileLintCache[4]) => {
20
+ getRules: (fileName: string, minimatchCache: undefined | FileLintCache[2]) => Record<string, Rule>;
21
+ getConfigs: (fileName: string, minimatchCache: undefined | FileLintCache[2]) => {
21
22
  include: string[];
22
23
  exclude: string[];
23
24
  rules: Rules;
package/index.js CHANGED
@@ -23,25 +23,11 @@ __exportStar(require("./lib/watch"), exports);
23
23
  const ErrorStackParser = require("error-stack-parser");
24
24
  const path = require("path");
25
25
  const minimatch = require("minimatch");
26
- const typeAwareModeChange = new Error('enable type-aware mode');
27
- function createLinter(ctx, config, mode) {
28
- let languageServiceUsage = 0;
26
+ function createLinter(ctx, config, mode, syntaxOnlyLanguageService) {
29
27
  const ts = ctx.typescript;
30
- const languageService = new Proxy(ctx.languageService, {
31
- get(target, key, receiver) {
32
- if (!languageServiceUsage) {
33
- languageServiceUsage++;
34
- throw typeAwareModeChange;
35
- }
36
- languageServiceUsage++;
37
- return Reflect.get(target, key, receiver);
38
- },
39
- });
40
28
  const fileRules = new Map();
41
29
  const fileConfigs = new Map();
42
- const fileFixes = new Map();
43
- const fileRefactors = new Map();
44
- const snapshot2SourceFile = new WeakMap();
30
+ const lintResults = new Map();
45
31
  const basePath = path.dirname(ctx.configFile);
46
32
  const configs = (Array.isArray(config) ? config : [config])
47
33
  .map(config => ({
@@ -51,63 +37,98 @@ function createLinter(ctx, config, mode) {
51
37
  plugins: (config.plugins ?? []).map(plugin => plugin(ctx)),
52
38
  }));
53
39
  const normalizedPath = new Map();
40
+ const rule2Mode = new Map();
41
+ const getNonBoundSourceFile = syntaxOnlyLanguageService.getNonBoundSourceFile;
42
+ let shouldEnableTypeAware = false;
54
43
  return {
55
44
  lint(fileName, cache) {
56
- let cacheableDiagnostics = [];
57
- let uncacheableDiagnostics = [];
58
45
  let currentRuleId;
59
- let currentIssues = 0;
60
- let currentFixes = 0;
61
- let currentRefactors = 0;
62
- let currentRuleLanguageServiceUsage = 0;
63
- let sourceFile;
64
- let hasUncacheResult = false;
65
- const rules = getFileRules(fileName, cache?.[4]);
66
- const rulesContext = {
67
- ...ctx,
68
- languageService,
69
- get sourceFile() {
70
- return sourceFile ??= getSourceFile(fileName);
71
- },
72
- reportError,
73
- reportWarning,
74
- reportSuggestion,
75
- };
76
- const token = ctx.languageServiceHost.getCancellationToken?.();
77
- const fixes = getFileFixes(fileName);
78
- const refactors = getFileRefactors(fileName);
79
- const cachedRules = new Map();
80
- if (cache) {
81
- for (const ruleId in cache[1]) {
82
- cachedRules.set(ruleId, cache[1][ruleId]);
46
+ let shouldRetry = false;
47
+ const rules = getFileRules(fileName, cache?.[2]);
48
+ const typeAwareMode = !getNonBoundSourceFile
49
+ || shouldEnableTypeAware && !Object.keys(rules).some(ruleId => !rule2Mode.has(ruleId));
50
+ const rulesContext = typeAwareMode
51
+ ? {
52
+ ...ctx,
53
+ sourceFile: ctx.languageService.getProgram().getSourceFile(fileName),
54
+ reportError,
55
+ reportWarning,
56
+ reportSuggestion,
83
57
  }
84
- }
85
- fixes.clear();
86
- refactors.length = 0;
87
- if (!runRules(rules)) {
88
- return this.lint(fileName, cache);
89
- }
90
- const configs = getFileConfigs(fileName, cache?.[4]);
91
- if (cache) {
92
- for (const [ruleId, fixes] of cachedRules) {
93
- cache[1][ruleId] = fixes;
58
+ : {
59
+ ...ctx,
60
+ languageService: syntaxOnlyLanguageService,
61
+ sourceFile: getNonBoundSourceFile(fileName),
62
+ reportError,
63
+ reportWarning,
64
+ reportSuggestion,
65
+ };
66
+ const token = ctx.languageServiceHost.getCancellationToken?.();
67
+ const configs = getFileConfigs(fileName, cache?.[2]);
68
+ lintResults.set(fileName, [rulesContext.sourceFile, new Map(), []]);
69
+ const lintResult = lintResults.get(fileName);
70
+ for (const [ruleId, rule] of Object.entries(rules)) {
71
+ if (token?.isCancellationRequested()) {
72
+ break;
94
73
  }
95
- }
96
- let diagnostics;
97
- if (hasUncacheResult) {
98
- diagnostics = [
99
- ...(cacheableDiagnostics.length
100
- ? cacheableDiagnostics
101
- : (cache?.[2] ?? []).map(data => ({
102
- ...data,
74
+ currentRuleId = ruleId;
75
+ const ruleCache = cache?.[1][currentRuleId];
76
+ if (ruleCache) {
77
+ let lintResult = lintResults.get(fileName);
78
+ if (!lintResult) {
79
+ lintResults.set(fileName, lintResult = [rulesContext.sourceFile, new Map(), []]);
80
+ }
81
+ for (const cacheDiagnostic of ruleCache[1]) {
82
+ lintResult[1].set({
83
+ ...cacheDiagnostic,
103
84
  file: rulesContext.sourceFile,
104
- relatedInformation: data.relatedInformation?.map(info => ({
85
+ relatedInformation: cacheDiagnostic.relatedInformation?.map(info => ({
105
86
  ...info,
106
- file: info.file ? getSourceFile(info.file.fileName) : undefined,
87
+ file: info.file ? syntaxOnlyLanguageService.getNonBoundSourceFile(info.file.fileName) : undefined,
107
88
  })),
108
- }))),
109
- ...uncacheableDiagnostics,
110
- ];
89
+ }, []);
90
+ }
91
+ if (!typeAwareMode) {
92
+ rule2Mode.set(currentRuleId, false);
93
+ }
94
+ continue;
95
+ }
96
+ try {
97
+ rule(rulesContext);
98
+ if (!typeAwareMode) {
99
+ rule2Mode.set(currentRuleId, false);
100
+ }
101
+ }
102
+ catch (err) {
103
+ if (!typeAwareMode) {
104
+ // console.log(`Rule "${currentRuleId}" is type aware.`);
105
+ rule2Mode.set(currentRuleId, true);
106
+ shouldRetry = true;
107
+ }
108
+ else if (err instanceof Error) {
109
+ report(ts.DiagnosticCategory.Error, err.stack ?? err.message, 0, 0, 0, err);
110
+ }
111
+ else {
112
+ report(ts.DiagnosticCategory.Error, String(err), 0, 0, false);
113
+ }
114
+ }
115
+ if (cache && !rule2Mode.get(currentRuleId)) {
116
+ cache[1][currentRuleId] ??= [false, []];
117
+ for (const [_, fixes] of lintResult[1]) {
118
+ if (fixes.length) {
119
+ cache[1][currentRuleId][0] = true;
120
+ break;
121
+ }
122
+ }
123
+ }
124
+ }
125
+ if (shouldRetry) {
126
+ // Retry
127
+ shouldEnableTypeAware = true;
128
+ return this.lint(fileName, cache);
129
+ }
130
+ let diagnostics = [...lintResult[1].keys()];
131
+ try {
111
132
  for (const { plugins } of configs) {
112
133
  for (const { resolveDiagnostics } of plugins) {
113
134
  if (resolveDiagnostics) {
@@ -115,77 +136,24 @@ function createLinter(ctx, config, mode) {
115
136
  }
116
137
  }
117
138
  }
118
- if (cache) {
119
- cache[3] = diagnostics.map(data => ({
120
- ...data,
121
- file: undefined,
122
- relatedInformation: data.relatedInformation?.map(info => ({
123
- ...info,
124
- file: info.file ? { fileName: info.file.fileName } : undefined,
125
- })),
126
- }));
127
- }
128
139
  }
129
- else {
130
- diagnostics = (cache?.[3] ?? []).map(data => ({
131
- ...data,
132
- file: rulesContext.sourceFile,
133
- relatedInformation: data.relatedInformation?.map(info => ({
134
- ...info,
135
- file: info.file ? getSourceFile(info.file.fileName) : undefined,
136
- })),
137
- }));
140
+ catch (error) {
141
+ if (!typeAwareMode) {
142
+ // Retry
143
+ shouldEnableTypeAware = true;
144
+ return this.lint(fileName, cache);
145
+ }
146
+ throw error;
138
147
  }
148
+ // Remove fixes and refactors that removed by resolveDiagnostics
139
149
  const diagnosticSet = new Set(diagnostics);
140
- for (const diagnostic of [...fixes.keys()]) {
150
+ for (const diagnostic of [...lintResult[1].keys()]) {
141
151
  if (!diagnosticSet.has(diagnostic)) {
142
- fixes.delete(diagnostic);
152
+ lintResult[1].delete(diagnostic);
143
153
  }
144
154
  }
145
- fileRefactors.set(fileName, refactors.filter(refactor => diagnosticSet.has(refactor.diagnostic)));
155
+ lintResult[2] = lintResult[2].filter(refactor => diagnosticSet.has(refactor.diagnostic));
146
156
  return diagnostics;
147
- function runRules(rules, paths = []) {
148
- for (const [path, rule] of Object.entries(rules)) {
149
- if (token?.isCancellationRequested()) {
150
- break;
151
- }
152
- if (typeof rule === 'object') {
153
- if (!runRules(rule, [...paths, path])) {
154
- return false;
155
- }
156
- continue;
157
- }
158
- currentRuleLanguageServiceUsage = languageServiceUsage;
159
- currentRuleId = [...paths, path].join('/');
160
- currentIssues = 0;
161
- currentFixes = 0;
162
- currentRefactors = 0;
163
- if (cachedRules.has(currentRuleId)) {
164
- continue;
165
- }
166
- hasUncacheResult = true;
167
- try {
168
- rule(rulesContext);
169
- }
170
- catch (err) {
171
- if (err === typeAwareModeChange) {
172
- // logger?.log.message(`Type-aware mode enabled by ${currentRuleId} rule.`);
173
- return false;
174
- }
175
- else if (err instanceof Error) {
176
- report(ts.DiagnosticCategory.Error, err.stack ?? err.message, 0, 0, 0, err);
177
- }
178
- else {
179
- report(ts.DiagnosticCategory.Error, String(err), 0, 0, false);
180
- }
181
- }
182
- if (cache && currentRuleLanguageServiceUsage === languageServiceUsage) {
183
- cachedRules.set(currentRuleId, currentFixes);
184
- }
185
- }
186
- return true;
187
- }
188
- ;
189
157
  function reportError(message, start, end, stackOffset) {
190
158
  return report(ts.DiagnosticCategory.Error, message, start, end, stackOffset);
191
159
  }
@@ -206,9 +174,9 @@ function createLinter(ctx, config, mode) {
206
174
  source: 'tsslint',
207
175
  relatedInformation: [],
208
176
  };
209
- const cacheable = currentRuleLanguageServiceUsage === languageServiceUsage;
210
- if (cache && cacheable) {
211
- cache[2].push({
177
+ if (cache && !rule2Mode.get(currentRuleId)) {
178
+ cache[1][currentRuleId] ??= [false, []];
179
+ cache[1][currentRuleId][1].push({
212
180
  ...error,
213
181
  file: undefined,
214
182
  relatedInformation: error.relatedInformation?.map(info => ({
@@ -224,9 +192,14 @@ function createLinter(ctx, config, mode) {
224
192
  error.relatedInformation.push(relatedInfo);
225
193
  }
226
194
  }
227
- fixes.set(error, []);
228
- (cacheable ? cacheableDiagnostics : uncacheableDiagnostics).push(error);
229
- currentIssues++;
195
+ let lintResult = lintResults.get(fileName);
196
+ if (!lintResult) {
197
+ lintResults.set(fileName, lintResult = [rulesContext.sourceFile, new Map(), []]);
198
+ }
199
+ const diagnostic2Fixes = lintResult[1];
200
+ const refactors = lintResult[2];
201
+ diagnostic2Fixes.set(error, []);
202
+ const fixes = diagnostic2Fixes.get(error);
230
203
  return {
231
204
  withDeprecated() {
232
205
  error.reportsDeprecated = true;
@@ -237,12 +210,10 @@ function createLinter(ctx, config, mode) {
237
210
  return this;
238
211
  },
239
212
  withFix(title, getEdits) {
240
- currentFixes++;
241
- fixes.get(error).push(({ title, getEdits }));
213
+ fixes.push(({ title, getEdits }));
242
214
  return this;
243
215
  },
244
216
  withRefactor(title, getEdits) {
245
- currentRefactors++;
246
217
  refactors.push(({
247
218
  diagnostic: error,
248
219
  title,
@@ -254,19 +225,26 @@ function createLinter(ctx, config, mode) {
254
225
  }
255
226
  },
256
227
  hasCodeFixes(fileName) {
257
- const fixesMap = getFileFixes(fileName);
258
- for (const [_diagnostic, actions] of fixesMap) {
259
- if (actions.length) {
228
+ const lintResult = lintResults.get(fileName);
229
+ if (!lintResult) {
230
+ return false;
231
+ }
232
+ for (const [_, fixes] of lintResult[1]) {
233
+ if (fixes.length) {
260
234
  return true;
261
235
  }
262
236
  }
263
237
  return false;
264
238
  },
265
239
  getCodeFixes(fileName, start, end, diagnostics, minimatchCache) {
240
+ const lintResult = lintResults.get(fileName);
241
+ if (!lintResult) {
242
+ return [];
243
+ }
244
+ const sourceFile = lintResult[0];
266
245
  const configs = getFileConfigs(fileName, minimatchCache);
267
- const fixesMap = getFileFixes(fileName);
268
246
  const result = [];
269
- for (const [diagnostic, actions] of fixesMap) {
247
+ for (const [diagnostic, actions] of lintResult[1]) {
270
248
  if (diagnostics?.length && !diagnostics.includes(diagnostic)) {
271
249
  continue;
272
250
  }
@@ -289,7 +267,7 @@ function createLinter(ctx, config, mode) {
289
267
  for (const { plugins } of configs) {
290
268
  for (const { resolveCodeFixes } of plugins) {
291
269
  if (resolveCodeFixes) {
292
- codeFixes = resolveCodeFixes(getSourceFile(fileName), diagnostic, codeFixes);
270
+ codeFixes = resolveCodeFixes(sourceFile, diagnostic, codeFixes);
293
271
  }
294
272
  }
295
273
  }
@@ -299,10 +277,13 @@ function createLinter(ctx, config, mode) {
299
277
  return result;
300
278
  },
301
279
  getRefactors(fileName, start, end) {
302
- const refactors = getFileRefactors(fileName);
280
+ const lintResult = lintResults.get(fileName);
281
+ if (!lintResult) {
282
+ return [];
283
+ }
303
284
  const result = [];
304
- for (let i = 0; i < refactors.length; i++) {
305
- const refactor = refactors[i];
285
+ for (let i = 0; i < lintResult[2].length; i++) {
286
+ const refactor = lintResult[2][i];
306
287
  const diagStart = refactor.diagnostic.start;
307
288
  const diagEnd = diagStart + refactor.diagnostic.length;
308
289
  if ((diagStart >= start && diagStart <= end) ||
@@ -320,9 +301,12 @@ function createLinter(ctx, config, mode) {
320
301
  },
321
302
  getRefactorEdits(fileName, actionName) {
322
303
  if (actionName.startsWith('tsslint:')) {
304
+ const lintResult = lintResults.get(fileName);
305
+ if (!lintResult) {
306
+ return [];
307
+ }
323
308
  const index = actionName.slice('tsslint:'.length);
324
- const actions = getFileRefactors(fileName);
325
- const refactor = actions[Number(index)];
309
+ const refactor = lintResult[2][Number(index)];
326
310
  if (refactor) {
327
311
  return refactor.getEdits();
328
312
  }
@@ -331,47 +315,33 @@ function createLinter(ctx, config, mode) {
331
315
  getRules: getFileRules,
332
316
  getConfigs: getFileConfigs,
333
317
  };
334
- function getSourceFile(fileName) {
335
- if (languageServiceUsage) {
336
- const sourceFile = ctx.languageService.getProgram().getSourceFile(fileName);
337
- if (sourceFile) {
338
- return sourceFile;
339
- }
340
- }
341
- else {
342
- const snapshot = ctx.languageServiceHost.getScriptSnapshot(fileName);
343
- if (snapshot) {
344
- if (!snapshot2SourceFile.has(snapshot)) {
345
- const sourceFile = ts.createSourceFile(fileName, snapshot.getText(0, snapshot.getLength()), ts.ScriptTarget.ESNext, true);
346
- snapshot2SourceFile.set(snapshot, sourceFile);
347
- return sourceFile;
348
- }
349
- return snapshot2SourceFile.get(snapshot);
350
- }
351
- }
352
- throw new Error('No source file');
353
- }
354
318
  function getFileRules(fileName, minimatchCache) {
355
- let result = fileRules.get(fileName);
356
- if (!result) {
357
- result = {};
319
+ let rules = fileRules.get(fileName);
320
+ if (!rules) {
321
+ rules = {};
358
322
  const configs = getFileConfigs(fileName, minimatchCache);
359
- for (const { rules } of configs) {
360
- result = {
361
- ...result,
362
- ...rules,
363
- };
323
+ for (const config of configs) {
324
+ collectRules(rules, config.rules, []);
364
325
  }
365
326
  for (const { plugins } of configs) {
366
327
  for (const { resolveRules } of plugins) {
367
328
  if (resolveRules) {
368
- result = resolveRules(fileName, result);
329
+ rules = resolveRules(fileName, rules);
369
330
  }
370
331
  }
371
332
  }
372
- fileRules.set(fileName, result);
333
+ fileRules.set(fileName, rules);
334
+ }
335
+ return rules;
336
+ }
337
+ function collectRules(record, rules, paths) {
338
+ for (const [path, rule] of Object.entries(rules)) {
339
+ if (typeof rule === 'object') {
340
+ collectRules(record, rule, [...paths, path]);
341
+ continue;
342
+ }
343
+ record[[...paths, path].join('/')] = rule;
373
344
  }
374
- return result;
375
345
  }
376
346
  function getFileConfigs(fileName, minimatchCache) {
377
347
  let result = fileConfigs.get(fileName);
@@ -406,18 +376,6 @@ function createLinter(ctx, config, mode) {
406
376
  }
407
377
  return result;
408
378
  }
409
- function getFileFixes(fileName) {
410
- if (!fileFixes.has(fileName)) {
411
- fileFixes.set(fileName, new Map());
412
- }
413
- return fileFixes.get(fileName);
414
- }
415
- function getFileRefactors(fileName) {
416
- if (!fileRefactors.has(fileName)) {
417
- fileRefactors.set(fileName, []);
418
- }
419
- return fileRefactors.get(fileName);
420
- }
421
379
  }
422
380
  const fsFiles = new Map();
423
381
  function createRelatedInformation(ts, err, stackOffset) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsslint/core",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "**/*.js",
@@ -12,7 +12,7 @@
12
12
  "directory": "packages/core"
13
13
  },
14
14
  "dependencies": {
15
- "@tsslint/types": "1.4.3",
15
+ "@tsslint/types": "1.4.5",
16
16
  "error-stack-parser": "^2.1.4",
17
17
  "esbuild": ">=0.17.0",
18
18
  "minimatch": "^10.0.1"
@@ -23,5 +23,5 @@
23
23
  "scripts": {
24
24
  "postinstall": "node scripts/cleanCache.js"
25
25
  },
26
- "gitHead": "7cdb4a39c0af8ee4cebd430bb546c6b6e2ddc28b"
26
+ "gitHead": "e8f0b027ff4e15d2633efd15c08bf2054896cf1b"
27
27
  }