UNPKG

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