123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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. */
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. var tslib_1 = require("tslib");
  20. var fs = require("fs");
  21. var path = require("path");
  22. var ts = require("typescript");
  23. var configuration_1 = require("./configuration");
  24. var enableDisableRules_1 = require("./enableDisableRules");
  25. var error_1 = require("./error");
  26. var formatterLoader_1 = require("./formatterLoader");
  27. var rule_1 = require("./language/rule/rule");
  28. var utils = require("./language/utils");
  29. var ruleLoader_1 = require("./ruleLoader");
  30. var utils_1 = require("./utils");
  31. /**
  32. * Linter that can lint multiple files in consecutive runs.
  33. */
  34. var 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. var sourceFile = this.getSourceFile(fileName, source);
  97. var isJs = /\.jsx?$/i.test(fileName);
  98. var enabledRules = this.getEnabledRules(configuration, isJs);
  99. var fileFailures = this.getAllFailures(sourceFile, enabledRules);
  100. if (fileFailures.length === 0) {
  101. // Usual case: no errors.
  102. return;
  103. }
  104. if (this.options.fix && fileFailures.some(function (f) { return f.hasFix(); })) {
  105. fileFailures = this.applyAllFixes(enabledRules, fileFailures, sourceFile, fileName);
  106. }
  107. // add rule severity to failures
  108. var ruleSeverityMap = new Map(enabledRules.map(function (rule) { return [rule.getOptions().ruleName, rule.getOptions().ruleSeverity]; }));
  109. for (var _i = 0, fileFailures_1 = fileFailures; _i < fileFailures_1.length; _i++) {
  110. var failure = fileFailures_1[_i];
  111. var severity = ruleSeverityMap.get(failure.getRuleName());
  112. if (severity === undefined) {
  113. throw new Error("Severity for rule '" + failure.getRuleName() + "' not found");
  114. }
  115. failure.setRuleSeverity(severity);
  116. }
  117. this.failures = this.failures.concat(fileFailures);
  118. };
  119. Linter.prototype.getResult = function () {
  120. var formatterName = this.options.formatter !== undefined ? this.options.formatter : "prose";
  121. var Formatter = formatterLoader_1.findFormatter(formatterName, this.options.formattersDirectory);
  122. if (Formatter === undefined) {
  123. throw new Error("formatter '" + formatterName + "' not found");
  124. }
  125. var formatter = new Formatter();
  126. var output = formatter.format(this.failures, this.fixes);
  127. var errorCount = this.failures.filter(function (failure) { return failure.getRuleSeverity() === "error"; }).length;
  128. return {
  129. errorCount: errorCount,
  130. failures: this.failures,
  131. fixes: this.fixes,
  132. format: formatterName,
  133. output: output,
  134. warningCount: this.failures.length - errorCount,
  135. };
  136. };
  137. Linter.prototype.getAllFailures = function (sourceFile, enabledRules) {
  138. var _this = this;
  139. var failures = utils_1.flatMap(enabledRules, function (rule) { return _this.applyRule(rule, sourceFile); });
  140. return enableDisableRules_1.removeDisabledFailures(sourceFile, failures);
  141. };
  142. Linter.prototype.applyAllFixes = function (enabledRules, fileFailures, sourceFile, sourceFileName) {
  143. // When fixing, we need to be careful as a fix in one rule may affect other rules.
  144. // So fix each rule separately.
  145. var source = sourceFile.text;
  146. var _loop_1 = function (rule) {
  147. var hasFixes = fileFailures.some(function (f) { return f.hasFix() && f.getRuleName() === rule.getOptions().ruleName; });
  148. if (hasFixes) {
  149. // Get new failures in case the file changed.
  150. var updatedFailures = enableDisableRules_1.removeDisabledFailures(sourceFile, this_1.applyRule(rule, sourceFile));
  151. var fixableFailures = updatedFailures.filter(function (f) { return f.hasFix(); });
  152. this_1.fixes = this_1.fixes.concat(fixableFailures);
  153. source = this_1.applyFixes(sourceFileName, source, fixableFailures);
  154. sourceFile = this_1.getSourceFile(sourceFileName, source);
  155. }
  156. };
  157. var this_1 = this;
  158. for (var _i = 0, enabledRules_1 = enabledRules; _i < enabledRules_1.length; _i++) {
  159. var rule = enabledRules_1[_i];
  160. _loop_1(rule);
  161. }
  162. // If there were fixes, get the *new* list of failures.
  163. return this.getAllFailures(sourceFile, enabledRules);
  164. };
  165. // Only "protected" because a test directly accesses it.
  166. // tslint:disable-next-line member-ordering
  167. Linter.prototype.applyFixes = function (sourceFilePath, source, fixableFailures) {
  168. var _this = this;
  169. var fixesByFile = createMultiMap(fixableFailures, function (f) { return [f.getFileName(), f.getFix()]; });
  170. fixesByFile.forEach(function (fileFixes, filePath) {
  171. var fileNewSource;
  172. if (path.resolve(filePath) === path.resolve(sourceFilePath)) {
  173. source = rule_1.Replacement.applyFixes(source, fileFixes);
  174. fileNewSource = source;
  175. }
  176. else {
  177. var oldSource = fs.readFileSync(filePath, "utf-8");
  178. fileNewSource = rule_1.Replacement.applyFixes(oldSource, fileFixes);
  179. }
  180. fs.writeFileSync(filePath, fileNewSource);
  181. _this.updateProgram(filePath);
  182. });
  183. return source;
  184. };
  185. Linter.prototype.updateProgram = function (sourceFilePath) {
  186. if (this.program !== undefined && this.program.getSourceFile(sourceFilePath) !== undefined) {
  187. var options = this.program.getCompilerOptions();
  188. this.program = ts.createProgram(this.program.getRootFileNames(), options, ts.createCompilerHost(options, true), this.program);
  189. }
  190. };
  191. Linter.prototype.applyRule = function (rule, sourceFile) {
  192. try {
  193. if (this.program !== undefined && rule_1.isTypedRule(rule)) {
  194. return rule.applyWithProgram(sourceFile, this.program);
  195. }
  196. else {
  197. return rule.apply(sourceFile);
  198. }
  199. }
  200. catch (error) {
  201. if (error_1.isError(error) && error.stack !== undefined) {
  202. error_1.showRuleCrashWarning(error.stack, rule.getOptions().ruleName, sourceFile.fileName);
  203. }
  204. else {
  205. error_1.showRuleCrashWarning(String(error), rule.getOptions().ruleName, sourceFile.fileName);
  206. }
  207. return [];
  208. }
  209. };
  210. Linter.prototype.getEnabledRules = function (configuration, isJs) {
  211. if (configuration === void 0) { configuration = configuration_1.DEFAULT_CONFIG; }
  212. var ruleOptionsList = configuration_1.convertRuleOptions(isJs ? configuration.jsRules : configuration.rules);
  213. var rulesDirectories = utils_1.arrayify(this.options.rulesDirectory)
  214. .concat(utils_1.arrayify(configuration.rulesDirectory));
  215. return ruleLoader_1.loadRules(ruleOptionsList, rulesDirectories, isJs);
  216. };
  217. Linter.prototype.getSourceFile = function (fileName, source) {
  218. if (this.program !== undefined) {
  219. var sourceFile = this.program.getSourceFile(fileName);
  220. if (sourceFile === undefined) {
  221. 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);
  222. throw new error_1.FatalError(INVALID_SOURCE_ERROR);
  223. }
  224. return sourceFile;
  225. }
  226. else {
  227. return utils.getSourceFile(fileName, source);
  228. }
  229. };
  230. Linter.VERSION = "5.10.0";
  231. Linter.findConfiguration = configuration_1.findConfiguration;
  232. Linter.findConfigurationPath = configuration_1.findConfigurationPath;
  233. Linter.getRulesDirectories = configuration_1.getRulesDirectories;
  234. Linter.loadConfigurationFromPath = configuration_1.loadConfigurationFromPath;
  235. return Linter;
  236. }());
  237. exports.Linter = Linter;
  238. function createMultiMap(inputs, getPair) {
  239. var map = new Map();
  240. for (var _i = 0, inputs_1 = inputs; _i < inputs_1.length; _i++) {
  241. var input = inputs_1[_i];
  242. var pair = getPair(input);
  243. if (pair !== undefined) {
  244. var k = pair[0], v = pair[1];
  245. var vs = map.get(k);
  246. if (vs !== undefined) {
  247. vs.push(v);
  248. }
  249. else {
  250. map.set(k, [v]);
  251. }
  252. }
  253. }
  254. return map;
  255. }
  256. var templateObject_1;