-
-
Notifications
You must be signed in to change notification settings - Fork 252
Expand file tree
/
Copy pathApiIncrementalChecker.ts
More file actions
150 lines (127 loc) · 4.52 KB
/
ApiIncrementalChecker.ts
File metadata and controls
150 lines (127 loc) · 4.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import * as ts from 'typescript';
import * as minimatch from 'minimatch';
import * as path from 'path';
import { IncrementalCheckerInterface } from './IncrementalCheckerInterface';
import { CancellationToken } from './CancellationToken';
import { NormalizedMessage } from './NormalizedMessage';
import { Configuration, Linter, LintResult } from 'tslint';
import { CompilerHost } from './CompilerHost';
import { FsHelper } from './FsHelper';
// Need some augmentation here - linterOptions.exclude is not (yet) part of the official
// types for tslint.
interface ConfigurationFile extends Configuration.IConfigurationFile {
linterOptions?: {
typeCheck?: boolean;
exclude?: string[];
};
}
export class ApiIncrementalChecker implements IncrementalCheckerInterface {
private linterConfig?: ConfigurationFile;
private readonly tsIncrementalCompiler: CompilerHost;
private linterExclusions: minimatch.IMinimatch[] = [];
private currentLintErrors = new Map<string, LintResult>();
private lastUpdatedFiles: string[] = [];
private lastRemovedFiles: string[] = [];
constructor(
programConfigFile: string,
compilerOptions: ts.CompilerOptions,
private linterConfigFile: string | false,
private linterAutoFix: boolean,
checkSyntacticErrors: boolean
) {
this.initLinterConfig();
this.tsIncrementalCompiler = new CompilerHost(
programConfigFile,
compilerOptions,
checkSyntacticErrors
);
}
private initLinterConfig() {
if (!this.linterConfig && this.linterConfigFile) {
this.linterConfig = ApiIncrementalChecker.loadLinterConfig(
this.linterConfigFile
);
if (
this.linterConfig.linterOptions &&
this.linterConfig.linterOptions.exclude
) {
// Pre-build minimatch patterns to avoid additional overhead later on.
// Note: Resolving the path is required to properly match against the full file paths,
// and also deals with potential cross-platform problems regarding path separators.
this.linterExclusions = this.linterConfig.linterOptions.exclude.map(
pattern => new minimatch.Minimatch(path.resolve(pattern))
);
}
}
}
private static loadLinterConfig(configFile: string): ConfigurationFile {
const tslint = require('tslint');
return tslint.Configuration.loadConfigurationFromPath(
configFile
) as ConfigurationFile;
}
private createLinter(program: ts.Program): Linter {
const tslint = require('tslint');
return new tslint.Linter({ fix: this.linterAutoFix }, program);
}
public hasLinter(): boolean {
return !!this.linterConfig;
}
public isFileExcluded(filePath: string): boolean {
return (
filePath.endsWith('.d.ts') ||
this.linterExclusions.some(matcher => matcher.match(filePath))
);
}
public nextIteration() {
// do nothing
}
public async getDiagnostics(_cancellationToken: CancellationToken) {
const diagnostics = await this.tsIncrementalCompiler.processChanges();
this.lastUpdatedFiles = diagnostics.updatedFiles;
this.lastRemovedFiles = diagnostics.removedFiles;
return NormalizedMessage.deduplicate(
diagnostics.results.map(NormalizedMessage.createFromDiagnostic)
);
}
public getLints(_cancellationToken: CancellationToken) {
if (!this.linterConfig) {
return [];
}
for (const updatedFile of this.lastUpdatedFiles) {
if (this.isFileExcluded(updatedFile)) {
continue;
}
try {
const linter = this.createLinter(
this.tsIncrementalCompiler.getProgram()
);
// const source = fs.readFileSync(updatedFile, 'utf-8');
linter.lint(updatedFile, undefined!, this.linterConfig);
const lints = linter.getResult();
this.currentLintErrors.set(updatedFile, lints);
} catch (e) {
if (
FsHelper.existsSync(updatedFile) &&
// check the error type due to file system lag
!(e instanceof Error) &&
!(e.constructor.name === 'FatalError') &&
!(e.message && e.message.trim().startsWith('Invalid source file'))
) {
// it's not because file doesn't exist - throw error
throw e;
}
}
for (const removedFile of this.lastRemovedFiles) {
this.currentLintErrors.delete(removedFile);
}
}
const allLints = [];
for (const [, value] of this.currentLintErrors) {
allLints.push(...value.failures);
}
return NormalizedMessage.deduplicate(
allLints.map(NormalizedMessage.createFromLint)
);
}
}