UNPKG

20 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 yaml = require("js-yaml");
22var minimatch_1 = require("minimatch");
23var os = require("os");
24var path = require("path");
25var error_1 = require("./error");
26var ruleLoader_1 = require("./ruleLoader");
27var utils_1 = require("./utils");
28// Note: eslint prefers yaml over json, while tslint prefers json over yaml
29// for backward-compatibility.
30exports.JSON_CONFIG_FILENAME = "tslint.json";
31/** @deprecated use `JSON_CONFIG_FILENAME` or `CONFIG_FILENAMES` instead. */
32exports.CONFIG_FILENAME = exports.JSON_CONFIG_FILENAME;
33exports.CONFIG_FILENAMES = [exports.JSON_CONFIG_FILENAME, "tslint.yaml", "tslint.yml"];
34exports.DEFAULT_CONFIG = {
35 defaultSeverity: "error",
36 extends: ["tslint:recommended"],
37 jsRules: new Map(),
38 rules: new Map(),
39 rulesDirectory: [],
40};
41exports.EMPTY_CONFIG = {
42 defaultSeverity: "error",
43 extends: [],
44 jsRules: new Map(),
45 rules: new Map(),
46 rulesDirectory: [],
47};
48var BUILT_IN_CONFIG = /^tslint:(.*)$/;
49function 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}
60exports.findConfiguration = findConfiguration;
61function 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 // convert to dir if it's a file or doesn't exist
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 // throws if file doesn't exist
81 useDirName = true;
82 }
83 if (useDirName) {
84 inputFilePath = path.dirname(inputFilePath);
85 }
86 // search for tslint.json from input file location
87 var configFilePath = findup(exports.CONFIG_FILENAMES, path.resolve(inputFilePath));
88 if (configFilePath !== undefined) {
89 return configFilePath;
90 }
91 // search for tslint.json in home directory
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 // no path could be found
101 return undefined;
102 }
103}
104exports.findConfigurationPath = findConfigurationPath;
105/**
106 * Find a file by names in a directory or any ancestor directory.
107 * Will try each filename in filenames before recursing to a parent directory.
108 * This is case-insensitive, so it can find 'TsLiNt.JsOn' when searching for 'tslint.json'.
109 */
110function 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 // TODO: remove in v6.0.0
130 // Try reading in the entire directory and looking for a file with different casing.
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 * Used Node semantics to load a configuration file given configFilePath.
148 * For example:
149 * '/path/to/config' will be treated as an absolute path
150 * './path/to/config' will be treated as a relative path
151 * 'path/to/config' will attempt to load a to/config file inside a node module named path
152 * @param configFilePath The configuration to load
153 * @param originalFilePath (deprecated) The entry point configuration file
154 * @returns a configuration object for TSLint loaded from the file at configFilePath
155 */
156function 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}
166exports.loadConfigurationFromPath = loadConfigurationFromPath;
167/** Reads the configuration file from disk and parses it as raw JSON, YAML or JS depending on the extension. */
168function 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 // include the configuration file being parsed in the error since it may differ from the directly referenced config
183 throw new Error(error.message + " in " + filepath);
184 }
185 }
186 else {
187 var rawConfigFile = require(filepath);
188 // tslint:disable-next-line no-dynamic-delete
189 delete require.cache[filepath];
190 return rawConfigFile;
191 }
192}
193exports.readConfigurationFile = readConfigurationFile;
194/**
195 * Resolve configuration file path or node_module reference
196 * @param filePath Relative ("./path"), absolute ("/path"), node module ("path"), or built-in ("tslint:path")
197 */
198function 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}
224function extendConfigurationFile(targetConfig, nextConfigSource) {
225 function combineProperties(targetProperty, nextProperty) {
226 var combinedProperty = {};
227 add(targetProperty);
228 // next config source overwrites the target config object
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}
267exports.extendConfigurationFile = extendConfigurationFile;
268/**
269 * returns the absolute path (contrary to what the name implies)
270 *
271 * @deprecated use `path.resolve` instead
272 */
273function 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}
280exports.getRelativePath = getRelativePath;
281// check if directory should be used as path or if it should be resolved like a module
282// matches if directory starts with '/', './', '../', 'node_modules/' or equals '.' or '..'
283function useAsPath(directory) {
284 return /^(?:\.?\.?(?:\/|$)|node_modules\/)/.test(directory);
285}
286exports.useAsPath = useAsPath;
287/**
288 * @param directories A path(s) to a directory of custom rules
289 * @param relativeTo A path that directories provided are relative to.
290 * For example, if the directories come from a tslint.json file, this path
291 * should be the path to the tslint.json file.
292 * @return An array of absolute paths to directories potentially containing rules
293 */
294function 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}
311exports.getRulesDirectories = getRulesDirectories;
312/**
313 * Parses the options of a single rule and upgrades legacy settings such as `true`, `[true, "option"]`
314 *
315 * @param ruleConfigValue The raw option setting of a rule
316 */
317function 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 // old style: array
342 ruleArguments = ruleConfigValue.slice(1);
343 ruleSeverity = ruleConfigValue[0] === true ? defaultRuleSeverity : "off";
344 }
345 }
346 else if (typeof ruleConfigValue === "boolean") {
347 // old style: boolean
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 * Parses a config file and normalizes legacy config settings.
384 * If `configFileDir` and `readConfig` are provided, this function will load all base configs and reduce them to the final configuration.
385 *
386 * @param configFile The raw object read from the JSON of a config file
387 * @param configFileDir The directory of the config file
388 * @param readConfig Will be used to load all base configurations while parsing. The function is called with the resolved path.
389 */
390function 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 /** Read files in order, depth first, and assign `defaultSeverity` (last config in extends wins). */
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}
476exports.parseConfigFile = parseConfigFile;
477/**
478 * Fills in default values for `IOption` properties and outputs an array of `IOption`
479 */
480function 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}
494exports.convertRuleOptions = convertRuleOptions;
495function 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}
504exports.isFileExcluded = isFileExcluded;