UNPKG

12.7 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright 2013 Palantir Technologies, Inc.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18Object.defineProperty(exports, "__esModule", { value: true });
19var tslib_1 = require("tslib");
20var fs = require("fs");
21var path = require("path");
22var ts = require("typescript");
23var configuration_1 = require("./configuration");
24var enableDisableRules_1 = require("./enableDisableRules");
25var error_1 = require("./error");
26var formatterLoader_1 = require("./formatterLoader");
27var rule_1 = require("./language/rule/rule");
28var utils = require("./language/utils");
29var ruleLoader_1 = require("./ruleLoader");
30var utils_1 = require("./utils");
31/**
32 * Linter that can lint multiple files in consecutive runs.
33 */
34var Linter = /** @class */ (function () {
35 function Linter(options, program) {
36 this.options = options;
37 this.program = program;
38 this.failures = [];
39 this.fixes = [];
40 if (typeof options !== "object") {
41 throw new Error("Unknown Linter options type: " + typeof options);
42 }
43 if (options.configuration != undefined) {
44 throw new Error("ILinterOptions does not contain the property `configuration` as of version 4. " +
45 "Did you mean to pass the `IConfigurationFile` object to lint() ? ");
46 }
47 }
48 /**
49 * Creates a TypeScript program object from a tsconfig.json file path and optional project directory.
50 */
51 Linter.createProgram = function (configFile, projectDirectory) {
52 if (projectDirectory === void 0) { projectDirectory = path.dirname(configFile); }
53 var config = ts.readConfigFile(configFile, ts.sys.readFile);
54 if (config.error !== undefined) {
55 throw new error_1.FatalError(ts.formatDiagnostics([config.error], {
56 getCanonicalFileName: function (f) { return f; },
57 getCurrentDirectory: process.cwd,
58 getNewLine: function () { return "\n"; },
59 }));
60 }
61 var parseConfigHost = {
62 fileExists: fs.existsSync,
63 readDirectory: ts.sys.readDirectory,
64 readFile: function (file) { return fs.readFileSync(file, "utf8"); },
65 useCaseSensitiveFileNames: true,
66 };
67 var parsed = ts.parseJsonConfigFileContent(config.config, parseConfigHost, path.resolve(projectDirectory), { noEmit: true });
68 if (parsed.errors !== undefined) {
69 // ignore warnings and 'TS18003: No inputs were found in config file ...'
70 var errors = parsed.errors.filter(function (d) { return d.category === ts.DiagnosticCategory.Error && d.code !== 18003; });
71 if (errors.length !== 0) {
72 throw new error_1.FatalError(ts.formatDiagnostics(errors, {
73 getCanonicalFileName: function (f) { return f; },
74 getCurrentDirectory: process.cwd,
75 getNewLine: function () { return "\n"; },
76 }));
77 }
78 }
79 var host = ts.createCompilerHost(parsed.options, true);
80 var program = ts.createProgram(parsed.fileNames, parsed.options, host);
81 return program;
82 };
83 /**
84 * Returns a list of source file names from a TypeScript program. This includes all referenced
85 * files and excludes declaration (".d.ts") files.
86 */
87 Linter.getFileNames = function (program) {
88 return utils_1.mapDefined(program.getSourceFiles(), function (file) {
89 return file.fileName.endsWith(".d.ts") || program.isSourceFileFromExternalLibrary(file)
90 ? undefined
91 : file.fileName;
92 });
93 };
94 Linter.prototype.lint = function (fileName, source, configuration) {
95 if (configuration === void 0) { configuration = configuration_1.DEFAULT_CONFIG; }
96 if (configuration_1.isFileExcluded(fileName, configuration)) {
97 return;
98 }
99 var sourceFile = this.getSourceFile(fileName, source);
100 var isJs = /\.jsx?$/i.test(fileName);
101 var enabledRules = this.getEnabledRules(configuration, isJs);
102 var fileFailures = this.getAllFailures(sourceFile, enabledRules);
103 if (fileFailures.length === 0) {
104 // Usual case: no errors.
105 return;
106 }
107 if (this.options.fix && fileFailures.some(function (f) { return f.hasFix(); })) {
108 fileFailures = this.applyAllFixes(enabledRules, fileFailures, sourceFile, fileName);
109 }
110 // add rule severity to failures
111 var ruleSeverityMap = new Map(enabledRules.map(function (rule) { return [
112 rule.getOptions().ruleName,
113 rule.getOptions().ruleSeverity,
114 ]; }));
115 for (var _i = 0, fileFailures_1 = fileFailures; _i < fileFailures_1.length; _i++) {
116 var failure = fileFailures_1[_i];
117 var severity = ruleSeverityMap.get(failure.getRuleName());
118 if (severity === undefined) {
119 throw new Error("Severity for rule '" + failure.getRuleName() + "' not found");
120 }
121 failure.setRuleSeverity(severity);
122 }
123 this.failures = this.failures.concat(fileFailures);
124 };
125 Linter.prototype.getResult = function () {
126 var errors = this.failures.filter(function (failure) { return failure.getRuleSeverity() === "error"; });
127 var failures = this.options.quiet ? errors : this.failures;
128 var formatterName = this.options.formatter !== undefined ? this.options.formatter : "prose";
129 var Formatter = formatterLoader_1.findFormatter(formatterName, this.options.formattersDirectory);
130 if (Formatter === undefined) {
131 throw new Error("formatter '" + formatterName + "' not found");
132 }
133 var formatter = new Formatter();
134 var output = formatter.format(failures, this.fixes);
135 var errorCount = errors.length;
136 return {
137 errorCount: errorCount,
138 failures: failures,
139 fixes: this.fixes,
140 format: formatterName,
141 output: output,
142 warningCount: failures.length - errorCount,
143 };
144 };
145 Linter.prototype.getAllFailures = function (sourceFile, enabledRules) {
146 var _this = this;
147 var failures = utils_1.flatMap(enabledRules, function (rule) { return _this.applyRule(rule, sourceFile); });
148 return enableDisableRules_1.removeDisabledFailures(sourceFile, failures);
149 };
150 Linter.prototype.applyAllFixes = function (enabledRules, fileFailures, sourceFile, sourceFileName) {
151 // When fixing, we need to be careful as a fix in one rule may affect other rules.
152 // So fix each rule separately.
153 var source = sourceFile.text;
154 var _loop_1 = function (rule) {
155 var hasFixes = fileFailures.some(function (f) { return f.hasFix() && f.getRuleName() === rule.getOptions().ruleName; });
156 if (hasFixes) {
157 // Get new failures in case the file changed.
158 var updatedFailures = enableDisableRules_1.removeDisabledFailures(sourceFile, this_1.applyRule(rule, sourceFile));
159 var fixableFailures = updatedFailures.filter(function (f) { return f.hasFix(); });
160 this_1.fixes = this_1.fixes.concat(fixableFailures);
161 source = this_1.applyFixes(sourceFileName, source, fixableFailures);
162 sourceFile = this_1.getSourceFile(sourceFileName, source);
163 }
164 };
165 var this_1 = this;
166 for (var _i = 0, enabledRules_1 = enabledRules; _i < enabledRules_1.length; _i++) {
167 var rule = enabledRules_1[_i];
168 _loop_1(rule);
169 }
170 // If there were fixes, get the *new* list of failures.
171 return this.getAllFailures(sourceFile, enabledRules);
172 };
173 // Only "protected" because a test directly accesses it.
174 // tslint:disable-next-line member-ordering
175 Linter.prototype.applyFixes = function (sourceFilePath, source, fixableFailures) {
176 var _this = this;
177 var fixesByFile = createMultiMap(fixableFailures, function (f) { return [f.getFileName(), f.getFix()]; });
178 fixesByFile.forEach(function (fileFixes, filePath) {
179 var fileNewSource;
180 if (path.resolve(filePath) === path.resolve(sourceFilePath)) {
181 source = rule_1.Replacement.applyFixes(source, fileFixes);
182 fileNewSource = source;
183 }
184 else {
185 var oldSource = fs.readFileSync(filePath, "utf-8");
186 fileNewSource = rule_1.Replacement.applyFixes(oldSource, fileFixes);
187 }
188 fs.writeFileSync(filePath, fileNewSource);
189 _this.updateProgram(filePath);
190 });
191 return source;
192 };
193 Linter.prototype.updateProgram = function (sourceFilePath) {
194 if (this.program !== undefined &&
195 this.program.getSourceFile(sourceFilePath) !== undefined) {
196 var options = this.program.getCompilerOptions();
197 this.program = ts.createProgram(this.program.getRootFileNames(), options, ts.createCompilerHost(options, true), this.program);
198 }
199 };
200 Linter.prototype.applyRule = function (rule, sourceFile) {
201 try {
202 if (this.program !== undefined && rule_1.isTypedRule(rule)) {
203 return rule.applyWithProgram(sourceFile, this.program);
204 }
205 else {
206 return rule.apply(sourceFile);
207 }
208 }
209 catch (error) {
210 if (error_1.isError(error) && error.stack !== undefined) {
211 error_1.showRuleCrashWarning(error.stack, rule.getOptions().ruleName, sourceFile.fileName);
212 }
213 else {
214 error_1.showRuleCrashWarning(String(error), rule.getOptions().ruleName, sourceFile.fileName);
215 }
216 return [];
217 }
218 };
219 Linter.prototype.getEnabledRules = function (configuration, isJs) {
220 if (configuration === void 0) { configuration = configuration_1.DEFAULT_CONFIG; }
221 var ruleOptionsList = configuration_1.convertRuleOptions(isJs ? configuration.jsRules : configuration.rules);
222 var rulesDirectories = utils_1.arrayify(this.options.rulesDirectory).concat(utils_1.arrayify(configuration.rulesDirectory));
223 return ruleLoader_1.loadRules(ruleOptionsList, rulesDirectories, isJs);
224 };
225 Linter.prototype.getSourceFile = function (fileName, source) {
226 if (this.program !== undefined) {
227 var sourceFile = this.program.getSourceFile(fileName);
228 if (sourceFile === undefined) {
229 var INVALID_SOURCE_ERROR = utils_1.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n Invalid source file: ", ". Ensure that the files supplied to lint have a .ts, .tsx, .d.ts, .js or .jsx extension.\n "], ["\n Invalid source file: ", ". Ensure that the files supplied to lint have a .ts, .tsx, .d.ts, .js or .jsx extension.\n "])), fileName);
230 throw new error_1.FatalError(INVALID_SOURCE_ERROR);
231 }
232 return sourceFile;
233 }
234 else {
235 return utils.getSourceFile(fileName, source);
236 }
237 };
238 Linter.VERSION = "5.12.0";
239 Linter.findConfiguration = configuration_1.findConfiguration;
240 Linter.findConfigurationPath = configuration_1.findConfigurationPath;
241 Linter.getRulesDirectories = configuration_1.getRulesDirectories;
242 Linter.loadConfigurationFromPath = configuration_1.loadConfigurationFromPath;
243 return Linter;
244}());
245exports.Linter = Linter;
246function createMultiMap(inputs, getPair) {
247 var map = new Map();
248 for (var _i = 0, inputs_1 = inputs; _i < inputs_1.length; _i++) {
249 var input = inputs_1[_i];
250 var pair = getPair(input);
251 if (pair !== undefined) {
252 var k = pair[0], v = pair[1];
253 var vs = map.get(k);
254 if (vs !== undefined) {
255 vs.push(v);
256 }
257 else {
258 map.set(k, [v]);
259 }
260 }
261 }
262 return map;
263}
264var templateObject_1;