1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | Object.defineProperty(exports, "__esModule", { value: true });
|
19 | var tslib_1 = require("tslib");
|
20 | var fs = require("fs");
|
21 | var yaml = require("js-yaml");
|
22 | var minimatch_1 = require("minimatch");
|
23 | var os = require("os");
|
24 | var path = require("path");
|
25 | var error_1 = require("./error");
|
26 | var ruleLoader_1 = require("./ruleLoader");
|
27 | var utils_1 = require("./utils");
|
28 |
|
29 |
|
30 | exports.JSON_CONFIG_FILENAME = "tslint.json";
|
31 |
|
32 | exports.CONFIG_FILENAME = exports.JSON_CONFIG_FILENAME;
|
33 | exports.CONFIG_FILENAMES = [exports.JSON_CONFIG_FILENAME, "tslint.yaml", "tslint.yml"];
|
34 | exports.DEFAULT_CONFIG = {
|
35 | defaultSeverity: "error",
|
36 | extends: ["tslint:recommended"],
|
37 | jsRules: new Map(),
|
38 | rules: new Map(),
|
39 | rulesDirectory: [],
|
40 | };
|
41 | exports.EMPTY_CONFIG = {
|
42 | defaultSeverity: "error",
|
43 | extends: [],
|
44 | jsRules: new Map(),
|
45 | rules: new Map(),
|
46 | rulesDirectory: [],
|
47 | };
|
48 | var BUILT_IN_CONFIG = /^tslint:(.*)$/;
|
49 | function findConfiguration(configFile, inputFilePath) {
|
50 | var configPath = findConfigurationPath(configFile, inputFilePath);
|
51 | var loadResult = { path: configPath };
|
52 | try {
|
53 | loadResult.results = loadConfigurationFromPath(configPath);
|
54 | return loadResult;
|
55 | }
|
56 | catch (error) {
|
57 | throw new error_1.FatalError("Failed to load " + configPath + ": " + error.message, error);
|
58 | }
|
59 | }
|
60 | exports.findConfiguration = findConfiguration;
|
61 | function findConfigurationPath(suppliedConfigFilePath, inputFilePath) {
|
62 | if (suppliedConfigFilePath != undefined) {
|
63 | if (!fs.existsSync(suppliedConfigFilePath)) {
|
64 | throw new error_1.FatalError("Could not find config file at: " + path.resolve(suppliedConfigFilePath));
|
65 | }
|
66 | else {
|
67 | return path.resolve(suppliedConfigFilePath);
|
68 | }
|
69 | }
|
70 | else {
|
71 |
|
72 | var useDirName = false;
|
73 | try {
|
74 | var stats = fs.statSync(inputFilePath);
|
75 | if (stats.isFile()) {
|
76 | useDirName = true;
|
77 | }
|
78 | }
|
79 | catch (e) {
|
80 |
|
81 | useDirName = true;
|
82 | }
|
83 | if (useDirName) {
|
84 | inputFilePath = path.dirname(inputFilePath);
|
85 | }
|
86 |
|
87 | var configFilePath = findup(exports.CONFIG_FILENAMES, path.resolve(inputFilePath));
|
88 | if (configFilePath !== undefined) {
|
89 | return configFilePath;
|
90 | }
|
91 |
|
92 | var homeDir = os.homedir();
|
93 | for (var _i = 0, CONFIG_FILENAMES_1 = exports.CONFIG_FILENAMES; _i < CONFIG_FILENAMES_1.length; _i++) {
|
94 | var configFilename = CONFIG_FILENAMES_1[_i];
|
95 | configFilePath = path.join(homeDir, configFilename);
|
96 | if (fs.existsSync(configFilePath)) {
|
97 | return path.resolve(configFilePath);
|
98 | }
|
99 | }
|
100 |
|
101 | return undefined;
|
102 | }
|
103 | }
|
104 | exports.findConfigurationPath = findConfigurationPath;
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 | function findup(filenames, directory) {
|
111 | while (true) {
|
112 | var res = findFile(directory);
|
113 | if (res !== undefined) {
|
114 | return path.join(directory, res);
|
115 | }
|
116 | var parent = path.dirname(directory);
|
117 | if (parent === directory) {
|
118 | return undefined;
|
119 | }
|
120 | directory = parent;
|
121 | }
|
122 | function findFile(cwd) {
|
123 | var dirFiles = fs.readdirSync(cwd);
|
124 | var _loop_1 = function (filename) {
|
125 | var index = dirFiles.indexOf(filename);
|
126 | if (index > -1) {
|
127 | return { value: filename };
|
128 | }
|
129 |
|
130 |
|
131 | var result = dirFiles.find(function (entry) { return entry.toLowerCase() === filename; });
|
132 | if (result !== undefined) {
|
133 | error_1.showWarningOnce("Using mixed case " + filename + " is deprecated. Found: " + path.join(cwd, result));
|
134 | return { value: result };
|
135 | }
|
136 | };
|
137 | for (var _i = 0, filenames_1 = filenames; _i < filenames_1.length; _i++) {
|
138 | var filename = filenames_1[_i];
|
139 | var state_1 = _loop_1(filename);
|
140 | if (typeof state_1 === "object")
|
141 | return state_1.value;
|
142 | }
|
143 | return undefined;
|
144 | }
|
145 | }
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 | function loadConfigurationFromPath(configFilePath, _originalFilePath) {
|
157 | if (configFilePath == undefined) {
|
158 | return exports.DEFAULT_CONFIG;
|
159 | }
|
160 | else {
|
161 | var resolvedConfigFilePath = resolveConfigurationPath(configFilePath);
|
162 | var rawConfigFile = readConfigurationFile(resolvedConfigFilePath);
|
163 | return parseConfigFile(rawConfigFile, path.dirname(resolvedConfigFilePath), readConfigurationFile);
|
164 | }
|
165 | }
|
166 | exports.loadConfigurationFromPath = loadConfigurationFromPath;
|
167 |
|
168 | function readConfigurationFile(filepath) {
|
169 | var resolvedConfigFileExt = path.extname(filepath);
|
170 | if (/\.(json|ya?ml)/.test(resolvedConfigFileExt)) {
|
171 | var fileContent = fs.readFileSync(filepath, "utf8").replace(/^\uFEFF/, "");
|
172 | try {
|
173 | if (resolvedConfigFileExt === ".json") {
|
174 | return JSON.parse(utils_1.stripComments(fileContent));
|
175 | }
|
176 | else {
|
177 | return yaml.safeLoad(fileContent);
|
178 | }
|
179 | }
|
180 | catch (e) {
|
181 | var error = e;
|
182 |
|
183 | throw new Error(error.message + " in " + filepath);
|
184 | }
|
185 | }
|
186 | else {
|
187 | var rawConfigFile = require(filepath);
|
188 |
|
189 | delete require.cache[filepath];
|
190 | return rawConfigFile;
|
191 | }
|
192 | }
|
193 | exports.readConfigurationFile = readConfigurationFile;
|
194 |
|
195 |
|
196 |
|
197 |
|
198 | function resolveConfigurationPath(filePath, relativeTo) {
|
199 | var matches = filePath.match(BUILT_IN_CONFIG);
|
200 | var isBuiltInConfig = matches !== null && matches.length > 0;
|
201 | if (isBuiltInConfig) {
|
202 | var configName = matches[1];
|
203 | try {
|
204 | return require.resolve("./configs/" + configName);
|
205 | }
|
206 | catch (err) {
|
207 | throw new Error(filePath + " is not a built-in config, try \"tslint:recommended\" instead.");
|
208 | }
|
209 | }
|
210 | var basedir = relativeTo !== undefined ? relativeTo : process.cwd();
|
211 | try {
|
212 | var resolvedPackagePath = utils_1.tryResolvePackage(filePath, basedir);
|
213 | if (resolvedPackagePath === undefined) {
|
214 | resolvedPackagePath = require.resolve(filePath);
|
215 | }
|
216 | return resolvedPackagePath;
|
217 | }
|
218 | catch (err) {
|
219 | throw new Error("Invalid \"extends\" configuration value - could not require \"" + filePath + "\". " +
|
220 | "Review the Node lookup algorithm (https://nodejs.org/api/modules.html#modules_all_together) " +
|
221 | "for the approximate method TSLint uses to find the referenced configuration file.");
|
222 | }
|
223 | }
|
224 | function extendConfigurationFile(targetConfig, nextConfigSource) {
|
225 | function combineProperties(targetProperty, nextProperty) {
|
226 | var combinedProperty = {};
|
227 | add(targetProperty);
|
228 |
|
229 | add(nextProperty);
|
230 | return combinedProperty;
|
231 | function add(property) {
|
232 | if (property !== undefined) {
|
233 | for (var name in property) {
|
234 | if (utils_1.hasOwnProperty(property, name)) {
|
235 | combinedProperty[name] = property[name];
|
236 | }
|
237 | }
|
238 | }
|
239 | }
|
240 | }
|
241 | function combineMaps(target, next) {
|
242 | var combined = new Map();
|
243 | target.forEach(function (options, ruleName) {
|
244 | combined.set(ruleName, options);
|
245 | });
|
246 | next.forEach(function (options, ruleName) {
|
247 | var combinedRule = combined.get(ruleName);
|
248 | if (combinedRule !== undefined) {
|
249 | combined.set(ruleName, combineProperties(combinedRule, options));
|
250 | }
|
251 | else {
|
252 | combined.set(ruleName, options);
|
253 | }
|
254 | });
|
255 | return combined;
|
256 | }
|
257 | var combinedRulesDirs = targetConfig.rulesDirectory.concat(nextConfigSource.rulesDirectory);
|
258 | var dedupedRulesDirs = Array.from(new Set(combinedRulesDirs));
|
259 | return {
|
260 | extends: [],
|
261 | jsRules: combineMaps(targetConfig.jsRules, nextConfigSource.jsRules),
|
262 | linterOptions: combineProperties(targetConfig.linterOptions, nextConfigSource.linterOptions),
|
263 | rules: combineMaps(targetConfig.rules, nextConfigSource.rules),
|
264 | rulesDirectory: dedupedRulesDirs,
|
265 | };
|
266 | }
|
267 | exports.extendConfigurationFile = extendConfigurationFile;
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 | function getRelativePath(directory, relativeTo) {
|
274 | if (directory != undefined) {
|
275 | var basePath = relativeTo !== undefined ? relativeTo : process.cwd();
|
276 | return path.resolve(basePath, directory);
|
277 | }
|
278 | return undefined;
|
279 | }
|
280 | exports.getRelativePath = getRelativePath;
|
281 |
|
282 |
|
283 | function useAsPath(directory) {
|
284 | return /^(?:\.?\.?(?:\/|$)|node_modules\/)/.test(directory);
|
285 | }
|
286 | exports.useAsPath = useAsPath;
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 | function getRulesDirectories(directories, relativeTo) {
|
295 | return utils_1.arrayify(directories).map(function (dir) {
|
296 | if (!useAsPath(dir)) {
|
297 | var resolvedPackagePath = utils_1.tryResolvePackage(dir, relativeTo);
|
298 | if (resolvedPackagePath !== undefined) {
|
299 | return path.dirname(resolvedPackagePath);
|
300 | }
|
301 | }
|
302 | var absolutePath = relativeTo === undefined ? path.resolve(dir) : path.resolve(relativeTo, dir);
|
303 | if (absolutePath !== undefined) {
|
304 | if (!fs.existsSync(absolutePath)) {
|
305 | throw new error_1.FatalError("Could not find custom rule directory: " + dir);
|
306 | }
|
307 | }
|
308 | return absolutePath;
|
309 | });
|
310 | }
|
311 | exports.getRulesDirectories = getRulesDirectories;
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 | function parseRuleOptions(ruleConfigValue, rawDefaultRuleSeverity) {
|
318 | var ruleArguments;
|
319 | var defaultRuleSeverity = "error";
|
320 | if (rawDefaultRuleSeverity !== undefined) {
|
321 | switch (rawDefaultRuleSeverity.toLowerCase()) {
|
322 | case "warn":
|
323 | case "warning":
|
324 | defaultRuleSeverity = "warning";
|
325 | break;
|
326 | case "off":
|
327 | case "none":
|
328 | defaultRuleSeverity = "off";
|
329 | break;
|
330 | default:
|
331 | defaultRuleSeverity = "error";
|
332 | }
|
333 | }
|
334 | var ruleSeverity = defaultRuleSeverity;
|
335 | if (ruleConfigValue == undefined) {
|
336 | ruleArguments = [];
|
337 | ruleSeverity = "off";
|
338 | }
|
339 | else if (Array.isArray(ruleConfigValue)) {
|
340 | if (ruleConfigValue.length > 0) {
|
341 |
|
342 | ruleArguments = ruleConfigValue.slice(1);
|
343 | ruleSeverity = ruleConfigValue[0] === true ? defaultRuleSeverity : "off";
|
344 | }
|
345 | }
|
346 | else if (typeof ruleConfigValue === "boolean") {
|
347 |
|
348 | ruleArguments = [];
|
349 | ruleSeverity = ruleConfigValue ? defaultRuleSeverity : "off";
|
350 | }
|
351 | else if (typeof ruleConfigValue === "object") {
|
352 | if (ruleConfigValue.severity !== undefined) {
|
353 | switch (ruleConfigValue.severity.toLowerCase()) {
|
354 | case "default":
|
355 | ruleSeverity = defaultRuleSeverity;
|
356 | break;
|
357 | case "error":
|
358 | ruleSeverity = "error";
|
359 | break;
|
360 | case "warn":
|
361 | case "warning":
|
362 | ruleSeverity = "warning";
|
363 | break;
|
364 | case "off":
|
365 | case "none":
|
366 | ruleSeverity = "off";
|
367 | break;
|
368 | default:
|
369 | console.warn("Invalid severity level: " + ruleConfigValue.severity);
|
370 | ruleSeverity = defaultRuleSeverity;
|
371 | }
|
372 | }
|
373 | if (ruleConfigValue.options != undefined) {
|
374 | ruleArguments = utils_1.arrayify(ruleConfigValue.options);
|
375 | }
|
376 | }
|
377 | return {
|
378 | ruleArguments: ruleArguments,
|
379 | ruleSeverity: ruleSeverity,
|
380 | };
|
381 | }
|
382 |
|
383 |
|
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 | function parseConfigFile(configFile, configFileDir, readConfig) {
|
391 | var defaultSeverity = configFile.defaultSeverity;
|
392 | if (readConfig === undefined || configFileDir === undefined) {
|
393 | return parse(configFile, configFileDir);
|
394 | }
|
395 | return loadExtendsRecursive(configFile, configFileDir)
|
396 | .map(function (_a) {
|
397 | var dir = _a.dir, config = _a.config;
|
398 | return parse(config, dir);
|
399 | })
|
400 | .reduce(extendConfigurationFile, exports.EMPTY_CONFIG);
|
401 |
|
402 | function loadExtendsRecursive(raw, dir) {
|
403 | var configs = [];
|
404 | for (var _i = 0, _a = utils_1.arrayify(raw.extends); _i < _a.length; _i++) {
|
405 | var relativePath = _a[_i];
|
406 | var resolvedPath = resolveConfigurationPath(relativePath, dir);
|
407 | var extendedRaw = readConfig(resolvedPath);
|
408 | configs.push.apply(configs, loadExtendsRecursive(extendedRaw, path.dirname(resolvedPath)));
|
409 | }
|
410 | if (raw.defaultSeverity !== undefined) {
|
411 | defaultSeverity = raw.defaultSeverity;
|
412 | }
|
413 | configs.push({ dir: dir, config: raw });
|
414 | return configs;
|
415 | }
|
416 | function parse(config, dir) {
|
417 | var rulesDirectory = getRulesDirectories(config.rulesDirectory, dir);
|
418 | var rules = parseRules(config.rules);
|
419 | var jsRules = typeof config.jsRules === "boolean"
|
420 | ? filterValidJsRules(rules, config.jsRules, rulesDirectory)
|
421 | : parseRules(config.jsRules);
|
422 | return {
|
423 | extends: utils_1.arrayify(config.extends),
|
424 | jsRules: jsRules,
|
425 | linterOptions: parseLinterOptions(config.linterOptions, dir),
|
426 | rules: rules,
|
427 | rulesDirectory: rulesDirectory,
|
428 | };
|
429 | }
|
430 | function parseRules(config) {
|
431 | var map = new Map();
|
432 | if (config !== undefined) {
|
433 | for (var ruleName in config) {
|
434 | if (utils_1.hasOwnProperty(config, ruleName)) {
|
435 | map.set(ruleName, parseRuleOptions(config[ruleName], defaultSeverity));
|
436 | }
|
437 | }
|
438 | }
|
439 | return map;
|
440 | }
|
441 | function filterValidJsRules(rules, copyRulestoJsRules, rulesDirectory) {
|
442 | if (copyRulestoJsRules === void 0) { copyRulestoJsRules = false; }
|
443 | var validJsRules = new Map();
|
444 | if (copyRulestoJsRules) {
|
445 | rules.forEach(function (ruleOptions, ruleName) {
|
446 | if (ruleOptions.ruleSeverity !== "off") {
|
447 | var Rule = ruleLoader_1.findRule(ruleName, rulesDirectory);
|
448 | if (Rule !== undefined &&
|
449 | (Rule.metadata === undefined || !Rule.metadata.typescriptOnly)) {
|
450 | validJsRules.set(ruleName, ruleOptions);
|
451 | }
|
452 | }
|
453 | });
|
454 | }
|
455 | return validJsRules;
|
456 | }
|
457 | function parseLinterOptions(raw, dir) {
|
458 | if (raw === undefined) {
|
459 | return {};
|
460 | }
|
461 | return tslib_1.__assign({}, (raw.exclude !== undefined
|
462 | ? {
|
463 | exclude: utils_1.arrayify(raw.exclude).map(function (pattern) {
|
464 | return dir === undefined
|
465 | ? path.resolve(pattern)
|
466 | : path.resolve(dir, pattern);
|
467 | }),
|
468 | }
|
469 | : {}), (raw.format !== undefined
|
470 | ? {
|
471 | format: raw.format,
|
472 | }
|
473 | : {}));
|
474 | }
|
475 | }
|
476 | exports.parseConfigFile = parseConfigFile;
|
477 |
|
478 |
|
479 |
|
480 | function convertRuleOptions(ruleConfiguration) {
|
481 | var output = [];
|
482 | ruleConfiguration.forEach(function (_a, ruleName) {
|
483 | var ruleArguments = _a.ruleArguments, ruleSeverity = _a.ruleSeverity;
|
484 | var options = {
|
485 | disabledIntervals: [],
|
486 | ruleArguments: ruleArguments != undefined ? ruleArguments : [],
|
487 | ruleName: ruleName,
|
488 | ruleSeverity: ruleSeverity != undefined ? ruleSeverity : "error",
|
489 | };
|
490 | output.push(options);
|
491 | });
|
492 | return output;
|
493 | }
|
494 | exports.convertRuleOptions = convertRuleOptions;
|
495 | function isFileExcluded(filepath, configFile) {
|
496 | if (configFile === undefined ||
|
497 | configFile.linterOptions == undefined ||
|
498 | configFile.linterOptions.exclude == undefined) {
|
499 | return false;
|
500 | }
|
501 | var fullPath = path.resolve(filepath);
|
502 | return configFile.linterOptions.exclude.some(function (pattern) { return new minimatch_1.Minimatch(pattern).match(fullPath); });
|
503 | }
|
504 | exports.isFileExcluded = isFileExcluded;
|