123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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. // tslint:disable strict-boolean-expressions (TODO: Fix up options)
  21. var fs = require("fs");
  22. var glob = require("glob");
  23. var minimatch_1 = require("minimatch");
  24. var path = require("path");
  25. var ts = require("typescript");
  26. var configuration_1 = require("./configuration");
  27. var error_1 = require("./error");
  28. var linter_1 = require("./linter");
  29. var utils_1 = require("./utils");
  30. function run(options, logger) {
  31. return tslib_1.__awaiter(this, void 0, void 0, function () {
  32. var error_2;
  33. return tslib_1.__generator(this, function (_a) {
  34. switch (_a.label) {
  35. case 0:
  36. _a.trys.push([0, 2, , 3]);
  37. return [4 /*yield*/, runWorker(options, logger)];
  38. case 1: return [2 /*return*/, _a.sent()];
  39. case 2:
  40. error_2 = _a.sent();
  41. if (error_2 instanceof error_1.FatalError) {
  42. logger.error(error_2.message + "\n");
  43. return [2 /*return*/, 1 /* FatalError */];
  44. }
  45. throw error_2;
  46. case 3: return [2 /*return*/];
  47. }
  48. });
  49. });
  50. }
  51. exports.run = run;
  52. function runWorker(options, logger) {
  53. return tslib_1.__awaiter(this, void 0, void 0, function () {
  54. var test_1, results, _a, output, errorCount;
  55. return tslib_1.__generator(this, function (_b) {
  56. switch (_b.label) {
  57. case 0:
  58. if (options.init) {
  59. if (fs.existsSync(configuration_1.JSON_CONFIG_FILENAME)) {
  60. throw new error_1.FatalError("Cannot generate " + configuration_1.JSON_CONFIG_FILENAME + ": file already exists");
  61. }
  62. fs.writeFileSync(configuration_1.JSON_CONFIG_FILENAME, JSON.stringify(configuration_1.DEFAULT_CONFIG, undefined, " "));
  63. return [2 /*return*/, 0 /* Ok */];
  64. }
  65. if (!options.test) return [3 /*break*/, 2];
  66. return [4 /*yield*/, Promise.resolve().then(function () { return require("./test"); })];
  67. case 1:
  68. test_1 = _b.sent();
  69. results = test_1.runTests((options.files || []).map(trimSingleQuotes), options.rulesDirectory);
  70. return [2 /*return*/, test_1.consoleTestResultsHandler(results, logger) ? 0 /* Ok */ : 1 /* FatalError */];
  71. case 2:
  72. if (options.config && !fs.existsSync(options.config)) {
  73. throw new error_1.FatalError("Invalid option for configuration: " + options.config);
  74. }
  75. return [4 /*yield*/, runLinter(options, logger)];
  76. case 3:
  77. _a = _b.sent(), output = _a.output, errorCount = _a.errorCount;
  78. if (output && output.trim()) {
  79. logger.log(output + "\n");
  80. }
  81. return [2 /*return*/, options.force || errorCount === 0 ? 0 /* Ok */ : 2 /* LintError */];
  82. }
  83. });
  84. });
  85. }
  86. function runLinter(options, logger) {
  87. return tslib_1.__awaiter(this, void 0, void 0, function () {
  88. var _a, files, program, diagnostics, message;
  89. return tslib_1.__generator(this, function (_b) {
  90. _a = resolveFilesAndProgram(options, logger), files = _a.files, program = _a.program;
  91. // if type checking, run the type checker
  92. if (program && options.typeCheck) {
  93. diagnostics = ts.getPreEmitDiagnostics(program);
  94. if (diagnostics.length !== 0) {
  95. message = diagnostics.map(function (d) { return showDiagnostic(d, program, options.outputAbsolutePaths); }).join("\n");
  96. if (options.force) {
  97. logger.error(message + "\n");
  98. }
  99. else {
  100. throw new error_1.FatalError(message);
  101. }
  102. }
  103. }
  104. return [2 /*return*/, doLinting(options, files, program, logger)];
  105. });
  106. });
  107. }
  108. function resolveFilesAndProgram(_a, logger) {
  109. var files = _a.files, project = _a.project, exclude = _a.exclude, outputAbsolutePaths = _a.outputAbsolutePaths;
  110. // remove single quotes which break matching on Windows when glob is passed in single quotes
  111. exclude = exclude.map(trimSingleQuotes);
  112. if (project === undefined) {
  113. return { files: resolveGlobs(files, exclude, outputAbsolutePaths, logger) };
  114. }
  115. var projectPath = findTsconfig(project);
  116. if (projectPath === undefined) {
  117. throw new error_1.FatalError("Invalid option for project: " + project);
  118. }
  119. exclude = exclude.map(function (pattern) { return path.resolve(pattern); });
  120. var program = linter_1.Linter.createProgram(projectPath);
  121. var filesFound;
  122. if (files.length === 0) {
  123. filesFound = filterFiles(linter_1.Linter.getFileNames(program), exclude, false);
  124. }
  125. else {
  126. files = files.map(function (f) { return path.resolve(f); });
  127. filesFound = filterFiles(program.getSourceFiles().map(function (f) { return f.fileName; }), files, true);
  128. filesFound = filterFiles(filesFound, exclude, false);
  129. // find non-glob files that have no matching file in the project and are not excluded by any exclude pattern
  130. for (var _i = 0, _b = filterFiles(files, exclude, false); _i < _b.length; _i++) {
  131. var file = _b[_i];
  132. if (!glob.hasMagic(file) && !filesFound.some(minimatch_1.filter(file))) {
  133. if (fs.existsSync(file)) {
  134. throw new error_1.FatalError("'" + file + "' is not included in project.");
  135. }
  136. logger.error("'" + file + "' does not exist. This will be an error in TSLint 6.\n"); // TODO make this an error in v6.0.0
  137. }
  138. }
  139. }
  140. return { files: filesFound, program: program };
  141. }
  142. function filterFiles(files, patterns, include) {
  143. if (patterns.length === 0) {
  144. return include ? [] : files;
  145. }
  146. var matcher = patterns.map(function (pattern) { return new minimatch_1.Minimatch(pattern, { dot: !include }); }); // `glob` always enables `dot` for ignore patterns
  147. return files.filter(function (file) { return include === matcher.some(function (pattern) { return pattern.match(file); }); });
  148. }
  149. function resolveGlobs(files, ignore, outputAbsolutePaths, logger) {
  150. var results = utils_1.flatMap(files, function (file) { return glob.sync(trimSingleQuotes(file), { ignore: ignore, nodir: true }); });
  151. // warn if `files` contains non-existent files, that are not patters and not excluded by any of the exclude patterns
  152. for (var _i = 0, _a = filterFiles(files, ignore, false); _i < _a.length; _i++) {
  153. var file = _a[_i];
  154. if (!glob.hasMagic(file) && !results.some(minimatch_1.filter(file))) {
  155. logger.error("'" + file + "' does not exist. This will be an error in TSLint 6.\n"); // TODO make this an error in v6.0.0
  156. }
  157. }
  158. var cwd = process.cwd();
  159. return results.map(function (file) { return outputAbsolutePaths ? path.resolve(cwd, file) : path.relative(cwd, file); });
  160. }
  161. function doLinting(options, files, program, logger) {
  162. return tslib_1.__awaiter(this, void 0, void 0, function () {
  163. function isFileExcluded(filepath) {
  164. if (configFile === undefined || configFile.linterOptions == undefined || configFile.linterOptions.exclude == undefined) {
  165. return false;
  166. }
  167. var fullPath = path.resolve(filepath);
  168. return configFile.linterOptions.exclude.some(function (pattern) { return new minimatch_1.Minimatch(pattern).match(fullPath); });
  169. }
  170. var linter, lastFolder, configFile, _i, files_1, file, folder, contents, sourceFile;
  171. return tslib_1.__generator(this, function (_a) {
  172. switch (_a.label) {
  173. case 0:
  174. linter = new linter_1.Linter({
  175. fix: !!options.fix,
  176. formatter: options.format,
  177. formattersDirectory: options.formattersDirectory,
  178. rulesDirectory: options.rulesDirectory,
  179. }, program);
  180. configFile = options.config !== undefined ? configuration_1.findConfiguration(options.config).results : undefined;
  181. _i = 0, files_1 = files;
  182. _a.label = 1;
  183. case 1:
  184. if (!(_i < files_1.length)) return [3 /*break*/, 6];
  185. file = files_1[_i];
  186. if (options.config === undefined) {
  187. folder = path.dirname(file);
  188. if (lastFolder !== folder) {
  189. configFile = configuration_1.findConfiguration(null, folder).results;
  190. lastFolder = folder;
  191. }
  192. }
  193. if (isFileExcluded(file)) {
  194. return [3 /*break*/, 5];
  195. }
  196. contents = void 0;
  197. if (!(program !== undefined)) return [3 /*break*/, 2];
  198. sourceFile = program.getSourceFile(file);
  199. if (sourceFile !== undefined) {
  200. contents = sourceFile.text;
  201. }
  202. return [3 /*break*/, 4];
  203. case 2: return [4 /*yield*/, tryReadFile(file, logger)];
  204. case 3:
  205. contents = _a.sent();
  206. _a.label = 4;
  207. case 4:
  208. if (contents !== undefined) {
  209. linter.lint(file, contents, configFile);
  210. }
  211. _a.label = 5;
  212. case 5:
  213. _i++;
  214. return [3 /*break*/, 1];
  215. case 6: return [2 /*return*/, linter.getResult()];
  216. }
  217. });
  218. });
  219. }
  220. /** Read a file, but return undefined if it is an MPEG '.ts' file. */
  221. function tryReadFile(filename, logger) {
  222. return tslib_1.__awaiter(this, void 0, void 0, function () {
  223. var buffer, fd;
  224. return tslib_1.__generator(this, function (_a) {
  225. if (!fs.existsSync(filename)) {
  226. throw new error_1.FatalError("Unable to open file: " + filename);
  227. }
  228. buffer = new Buffer(256);
  229. fd = fs.openSync(filename, "r");
  230. try {
  231. fs.readSync(fd, buffer, 0, 256, 0);
  232. if (buffer.readInt8(0) === 0x47 && buffer.readInt8(188) === 0x47) {
  233. // MPEG transport streams use the '.ts' file extension. They use 0x47 as the frame
  234. // separator, repeating every 188 bytes. It is unlikely to find that pattern in
  235. // TypeScript source, so tslint ignores files with the specific pattern.
  236. logger.error(filename + ": ignoring MPEG transport stream\n");
  237. return [2 /*return*/, undefined];
  238. }
  239. }
  240. finally {
  241. fs.closeSync(fd);
  242. }
  243. return [2 /*return*/, fs.readFileSync(filename, "utf8")];
  244. });
  245. });
  246. }
  247. function showDiagnostic(_a, program, outputAbsolutePaths) {
  248. var file = _a.file, start = _a.start, category = _a.category, messageText = _a.messageText;
  249. var message = ts.DiagnosticCategory[category];
  250. if (file !== undefined && start !== undefined) {
  251. var _b = file.getLineAndCharacterOfPosition(start), line = _b.line, character = _b.character;
  252. var currentDirectory = program.getCurrentDirectory();
  253. var filePath = outputAbsolutePaths
  254. ? path.resolve(currentDirectory, file.fileName)
  255. : path.relative(currentDirectory, file.fileName);
  256. message += " at " + filePath + ":" + (line + 1) + ":" + (character + 1) + ":";
  257. }
  258. return message + " " + ts.flattenDiagnosticMessageText(messageText, "\n");
  259. }
  260. function trimSingleQuotes(str) {
  261. return str.replace(/^'|'$/g, "");
  262. }
  263. function findTsconfig(project) {
  264. try {
  265. var stats = fs.statSync(project); // throws if file does not exist
  266. if (!stats.isDirectory()) {
  267. return project;
  268. }
  269. var projectFile = path.join(project, "tsconfig.json");
  270. fs.accessSync(projectFile); // throws if file does not exist
  271. return projectFile;
  272. }
  273. catch (e) {
  274. return undefined;
  275. }
  276. }