UNPKG

12.8 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const fs = require("fs");
4const path = require("path");
5const FilesRegister_1 = require("./FilesRegister");
6const FilesWatcher_1 = require("./FilesWatcher");
7const linterConfigHelpers_1 = require("./linterConfigHelpers");
8const WorkSet_1 = require("./WorkSet");
9const NormalizedMessage_1 = require("./NormalizedMessage");
10const resolution_1 = require("./resolution");
11const minimatch = require("minimatch");
12const VueProgram_1 = require("./VueProgram");
13const FsHelper_1 = require("./FsHelper");
14class IncrementalChecker {
15 constructor({ typescript, context, programConfigFile, compilerOptions, createNormalizedMessageFromDiagnostic, linterConfigFile, linterAutoFix, createNormalizedMessageFromRuleFailure, eslinter, watchPaths, workNumber = 0, workDivision = 1, vue = false, checkSyntacticErrors = false, resolveModuleName, resolveTypeReferenceDirective }) {
16 // it's shared between compilations
17 this.linterConfigs = {};
18 this.files = new FilesRegister_1.FilesRegister(() => ({
19 // data shape
20 source: undefined,
21 linted: false,
22 tslints: [],
23 eslints: []
24 }));
25 // Use empty array of exclusions in general to avoid having
26 // to check of its existence later on.
27 this.linterExclusions = [];
28 this.getLinterConfig = linterConfigHelpers_1.makeGetLinterConfig(this.linterConfigs, this.linterExclusions, this.context);
29 this.typescript = typescript;
30 this.context = context;
31 this.programConfigFile = programConfigFile;
32 this.compilerOptions = compilerOptions;
33 this.createNormalizedMessageFromDiagnostic = createNormalizedMessageFromDiagnostic;
34 this.linterConfigFile = linterConfigFile;
35 this.linterAutoFix = linterAutoFix;
36 this.createNormalizedMessageFromRuleFailure = createNormalizedMessageFromRuleFailure;
37 this.eslinter = eslinter;
38 this.watchPaths = watchPaths;
39 this.workNumber = workNumber;
40 this.workDivision = workDivision;
41 this.vue = vue;
42 this.checkSyntacticErrors = checkSyntacticErrors;
43 this.resolveModuleName = resolveModuleName;
44 this.resolveTypeReferenceDirective = resolveTypeReferenceDirective;
45 this.hasFixedConfig = typeof this.linterConfigFile === 'string';
46 }
47 static loadProgramConfig(typescript, configFile, compilerOptions) {
48 const tsconfig = typescript.readConfigFile(configFile, typescript.sys.readFile).config;
49 tsconfig.compilerOptions = tsconfig.compilerOptions || {};
50 tsconfig.compilerOptions = Object.assign({}, tsconfig.compilerOptions, compilerOptions);
51 const parsed = typescript.parseJsonConfigFileContent(tsconfig, typescript.sys, path.dirname(configFile));
52 return parsed;
53 }
54 static createProgram(typescript, programConfig, files, watcher, oldProgram, userResolveModuleName, userResolveTypeReferenceDirective) {
55 const host = typescript.createCompilerHost(programConfig.options);
56 const realGetSourceFile = host.getSourceFile;
57 const { resolveModuleName, resolveTypeReferenceDirective } = resolution_1.makeResolutionFunctions(userResolveModuleName, userResolveTypeReferenceDirective);
58 host.resolveModuleNames = (moduleNames, containingFile) => {
59 return moduleNames.map(moduleName => {
60 return resolveModuleName(typescript, moduleName, containingFile, programConfig.options, host).resolvedModule;
61 });
62 };
63 host.resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile) => {
64 return typeDirectiveNames.map(typeDirectiveName => {
65 return resolveTypeReferenceDirective(typescript, typeDirectiveName, containingFile, programConfig.options, host).resolvedTypeReferenceDirective;
66 });
67 };
68 host.getSourceFile = (filePath, languageVersion, onError) => {
69 // first check if watcher is watching file - if not - check it's mtime
70 if (!watcher.isWatchingFile(filePath)) {
71 try {
72 const stats = fs.statSync(filePath);
73 files.setMtime(filePath, stats.mtime.valueOf());
74 }
75 catch (e) {
76 // probably file does not exists
77 files.remove(filePath);
78 }
79 }
80 // get source file only if there is no source in files register
81 if (!files.has(filePath) || !files.getData(filePath).source) {
82 files.mutateData(filePath, data => {
83 data.source = realGetSourceFile(filePath, languageVersion, onError);
84 });
85 }
86 return files.getData(filePath).source;
87 };
88 return typescript.createProgram(programConfig.fileNames, programConfig.options, host, oldProgram // re-use old program
89 );
90 }
91 createLinter(program) {
92 // tslint:disable-next-line:no-implicit-dependencies
93 const tslint = require('tslint');
94 return new tslint.Linter({ fix: this.linterAutoFix }, program);
95 }
96 hasLinter() {
97 return !!this.linter;
98 }
99 hasEsLinter() {
100 return this.eslinter !== undefined;
101 }
102 static isFileExcluded(filePath, linterExclusions) {
103 return (filePath.endsWith('.d.ts') ||
104 linterExclusions.some(matcher => matcher.match(filePath)));
105 }
106 nextIteration() {
107 if (!this.watcher) {
108 const watchExtensions = this.vue
109 ? ['.ts', '.tsx', '.vue']
110 : ['.ts', '.tsx'];
111 this.watcher = new FilesWatcher_1.FilesWatcher(this.watchPaths, watchExtensions);
112 // connect watcher with register
113 this.watcher.on('change', (filePath, stats) => {
114 this.files.setMtime(filePath, stats.mtime.valueOf());
115 });
116 this.watcher.on('unlink', (filePath) => {
117 this.files.remove(filePath);
118 });
119 this.watcher.watch();
120 }
121 if (!this.linterConfig && this.hasFixedConfig) {
122 this.linterConfig = linterConfigHelpers_1.loadLinterConfig(this.linterConfigFile);
123 if (this.linterConfig.linterOptions &&
124 this.linterConfig.linterOptions.exclude) {
125 // Pre-build minimatch patterns to avoid additional overhead later on.
126 // Note: Resolving the path is required to properly match against the full file paths,
127 // and also deals with potential cross-platform problems regarding path separators.
128 this.linterExclusions = this.linterConfig.linterOptions.exclude.map(pattern => new minimatch.Minimatch(path.resolve(pattern)));
129 }
130 }
131 this.program = this.vue ? this.loadVueProgram() : this.loadDefaultProgram();
132 if (this.linterConfigFile) {
133 this.linter = this.createLinter(this.program);
134 }
135 }
136 loadVueProgram() {
137 this.programConfig =
138 this.programConfig ||
139 VueProgram_1.VueProgram.loadProgramConfig(this.typescript, this.programConfigFile, this.compilerOptions);
140 return VueProgram_1.VueProgram.createProgram(this.typescript, this.programConfig, path.dirname(this.programConfigFile), this.files, this.watcher, this.program, this.resolveModuleName, this.resolveTypeReferenceDirective);
141 }
142 loadDefaultProgram() {
143 this.programConfig =
144 this.programConfig ||
145 IncrementalChecker.loadProgramConfig(this.typescript, this.programConfigFile, this.compilerOptions);
146 return IncrementalChecker.createProgram(this.typescript, this.programConfig, this.files, this.watcher, this.program, this.resolveModuleName, this.resolveTypeReferenceDirective);
147 }
148 getDiagnostics(cancellationToken) {
149 const { program } = this;
150 if (!program) {
151 throw new Error('Invoked called before program initialized');
152 }
153 const diagnostics = [];
154 // select files to check (it's semantic check - we have to include all files :/)
155 const filesToCheck = program.getSourceFiles();
156 // calculate subset of work to do
157 const workSet = new WorkSet_1.WorkSet(filesToCheck, this.workNumber, this.workDivision);
158 // check given work set
159 workSet.forEach(sourceFile => {
160 if (cancellationToken) {
161 cancellationToken.throwIfCancellationRequested();
162 }
163 const diagnosticsToRegister = this
164 .checkSyntacticErrors
165 ? program
166 .getSemanticDiagnostics(sourceFile, cancellationToken)
167 .concat(program.getSyntacticDiagnostics(sourceFile, cancellationToken))
168 : program.getSemanticDiagnostics(sourceFile, cancellationToken);
169 diagnostics.push(...diagnosticsToRegister);
170 });
171 // normalize and deduplicate diagnostics
172 return Promise.resolve(NormalizedMessage_1.NormalizedMessage.deduplicate(diagnostics.map(this.createNormalizedMessageFromDiagnostic)));
173 }
174 getLints(cancellationToken) {
175 const { linter } = this;
176 if (!linter) {
177 throw new Error('Cannot get lints - checker has no linter.');
178 }
179 // select files to lint
180 const filesToLint = this.files
181 .keys()
182 .filter(filePath => !this.files.getData(filePath).linted &&
183 !IncrementalChecker.isFileExcluded(filePath, this.linterExclusions));
184 // calculate subset of work to do
185 const workSet = new WorkSet_1.WorkSet(filesToLint, this.workNumber, this.workDivision);
186 // lint given work set
187 workSet.forEach(fileName => {
188 cancellationToken.throwIfCancellationRequested();
189 const config = this.hasFixedConfig
190 ? this.linterConfig
191 : this.getLinterConfig(fileName);
192 if (!config) {
193 return;
194 }
195 try {
196 // Assertion: `.lint` second parameter can be undefined
197 linter.lint(fileName, undefined, config);
198 }
199 catch (e) {
200 FsHelper_1.throwIfIsInvalidSourceFileError(fileName, e);
201 }
202 });
203 // set lints in files register
204 linter.getResult().failures.forEach(lint => {
205 const filePath = lint.getFileName();
206 this.files.mutateData(filePath, data => {
207 data.linted = true;
208 data.tslints.push(lint);
209 });
210 });
211 // set all files as linted
212 this.files.keys().forEach(filePath => {
213 this.files.mutateData(filePath, data => {
214 data.linted = true;
215 });
216 });
217 // get all lints
218 const lints = this.files
219 .keys()
220 .reduce((innerLints, filePath) => innerLints.concat(this.files.getData(filePath).tslints), []);
221 // normalize and deduplicate lints
222 return NormalizedMessage_1.NormalizedMessage.deduplicate(lints.map(this.createNormalizedMessageFromRuleFailure));
223 }
224 getEsLints(cancellationToken) {
225 // select files to lint
226 const filesToLint = this.files
227 .keys()
228 .filter(filePath => !this.files.getData(filePath).linted &&
229 !IncrementalChecker.isFileExcluded(filePath, this.linterExclusions));
230 // calculate subset of work to do
231 const workSet = new WorkSet_1.WorkSet(filesToLint, this.workNumber, this.workDivision);
232 // lint given work set
233 const currentEsLintErrors = new Map();
234 workSet.forEach(fileName => {
235 cancellationToken.throwIfCancellationRequested();
236 const lints = this.eslinter.getLints(fileName);
237 if (lints !== undefined) {
238 currentEsLintErrors.set(fileName, lints);
239 }
240 });
241 // set lints in files register
242 for (const [filePath, lint] of currentEsLintErrors) {
243 this.files.mutateData(filePath, data => {
244 data.linted = true;
245 data.eslints.push(lint);
246 });
247 }
248 // set all files as linted
249 this.files.keys().forEach(filePath => {
250 this.files.mutateData(filePath, data => {
251 data.linted = true;
252 });
253 });
254 // get all lints
255 const allEsLintReports = this.files
256 .keys()
257 .reduce((innerLints, filePath) => innerLints.concat(this.files.getData(filePath).eslints), []);
258 return this.eslinter.getFormattedLints(allEsLintReports);
259 }
260}
261exports.IncrementalChecker = IncrementalChecker;
262//# sourceMappingURL=IncrementalChecker.js.map
\No newline at end of file