UNPKG

21.1 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 });
19exports.stringifyConfiguration = exports.isFileExcluded = exports.convertRuleOptions = exports.parseConfigFile = exports.getRulesDirectories = exports.useAsPath = exports.getRelativePath = exports.extendConfigurationFile = exports.readConfigurationFile = exports.loadConfigurationFromPath = exports.findConfigurationPath = exports.findConfiguration = exports.EMPTY_CONFIG = exports.DEFAULT_CONFIG = exports.CONFIG_FILENAMES = exports.CONFIG_FILENAME = exports.JSON_CONFIG_FILENAME = void 0;
20var tslib_1 = require("tslib");
21var fs = require("fs");
22var yaml = require("js-yaml");
23var minimatch_1 = require("minimatch");
24var os = require("os");
25var path = require("path");
26var error_1 = require("./error");
27var ruleLoader_1 = require("./ruleLoader");
28var utils_1 = require("./utils");
29// Note: eslint prefers yaml over json, while tslint prefers json over yaml
30// for backward-compatibility.
31exports.JSON_CONFIG_FILENAME = "tslint.json";
32/** @deprecated use `JSON_CONFIG_FILENAME` or `CONFIG_FILENAMES` instead. */
33exports.CONFIG_FILENAME = exports.JSON_CONFIG_FILENAME;
34exports.CONFIG_FILENAMES = [exports.JSON_CONFIG_FILENAME, "tslint.yaml", "tslint.yml"];
35exports.DEFAULT_CONFIG = {
36 defaultSeverity: "error",
37 extends: ["tslint:recommended"],
38 jsRules: new Map(),
39 rules: new Map(),
40 rulesDirectory: [],
41};
42exports.EMPTY_CONFIG = {
43 defaultSeverity: "error",
44 extends: [],
45 jsRules: new Map(),
46 rules: new Map(),
47 rulesDirectory: [],
48};
49var BUILT_IN_CONFIG = /^tslint:(.*)$/;
50function findConfiguration(configFile, inputFilePath) {
51 var configPath = findConfigurationPath(configFile, inputFilePath);
52 var loadResult = { path: configPath };
53 try {
54 loadResult.results = loadConfigurationFromPath(configPath);
55 return loadResult;
56 }
57 catch (error) {
58 throw new error_1.FatalError("Failed to load " + configPath + ": " + error.message, error);
59 }
60}
61exports.findConfiguration = findConfiguration;
62function findConfigurationPath(suppliedConfigFilePath, inputFilePath) {
63 if (suppliedConfigFilePath != undefined) {
64 if (!fs.existsSync(suppliedConfigFilePath)) {
65 throw new error_1.FatalError("Could not find config file at: " + path.resolve(suppliedConfigFilePath));
66 }
67 else {
68 return path.resolve(suppliedConfigFilePath);
69 }
70 }
71 else {
72 // convert to dir if it's a file or doesn't exist
73 var useDirName = false;
74 try {
75 var stats = fs.statSync(inputFilePath);
76 if (stats.isFile()) {
77 useDirName = true;
78 }
79 }
80 catch (e) {
81 // throws if file doesn't exist
82 useDirName = true;
83 }
84 if (useDirName) {
85 inputFilePath = path.dirname(inputFilePath);
86 }
87 // search for tslint.json from input file location
88 var configFilePath = findup(exports.CONFIG_FILENAMES, path.resolve(inputFilePath));
89 if (configFilePath !== undefined) {
90 return configFilePath;
91 }
92 // search for tslint.json in home directory
93 var homeDir = os.homedir();
94 for (var _i = 0, CONFIG_FILENAMES_1 = exports.CONFIG_FILENAMES; _i < CONFIG_FILENAMES_1.length; _i++) {
95 var configFilename = CONFIG_FILENAMES_1[_i];
96 configFilePath = path.join(homeDir, configFilename);
97 if (fs.existsSync(configFilePath)) {
98 return path.resolve(configFilePath);
99 }
100 }
101 // no path could be found
102 return undefined;
103 }
104}
105exports.findConfigurationPath = findConfigurationPath;
106/**
107 * Find a file by names in a directory or any ancestor directory.
108 * Will try each filename in filenames before recursing to a parent directory.
109 * This is case-insensitive, so it can find 'TsLiNt.JsOn' when searching for 'tslint.json'.
110 */
111function findup(filenames, directory) {
112 while (true) {
113 var res = findFile(directory);
114 if (res !== undefined) {
115 return path.join(directory, res);
116 }
117 var parent = path.dirname(directory);
118 if (parent === directory) {
119 return undefined;
120 }
121 directory = parent;
122 }
123 function findFile(cwd) {
124 var dirFiles = fs.readdirSync(cwd);
125 var _loop_1 = function (filename) {
126 var index = dirFiles.indexOf(filename);
127 if (index > -1) {
128 return { value: filename };
129 }
130 // TODO: remove in v6.0.0
131 // Try reading in the entire directory and looking for a file with different casing.
132 var result = dirFiles.find(function (entry) { return entry.toLowerCase() === filename; });
133 if (result !== undefined) {
134 error_1.showWarningOnce("Using mixed case " + filename + " is deprecated. Found: " + path.join(cwd, result));
135 return { value: result };
136 }
137 };
138 for (var _i = 0, filenames_1 = filenames; _i < filenames_1.length; _i++) {
139 var filename = filenames_1[_i];
140 var state_1 = _loop_1(filename);
141 if (typeof state_1 === "object")
142 return state_1.value;
143 }
144 return undefined;
145 }
146}
147/**
148 * Used Node semantics to load a configuration file given configFilePath.
149 * For example:
150 * '/path/to/config' will be treated as an absolute path
151 * './path/to/config' will be treated as a relative path
152 * 'path/to/config' will attempt to load a to/config file inside a node module named path
153 * @param configFilePath The configuration to load
154 * @param originalFilePath (deprecated) The entry point configuration file
155 * @returns a configuration object for TSLint loaded from the file at configFilePath
156 */
157function loadConfigurationFromPath(configFilePath, _originalFilePath) {
158 if (configFilePath == undefined) {
159 return exports.DEFAULT_CONFIG;
160 }
161 else {
162 var resolvedConfigFilePath = resolveConfigurationPath(configFilePath);
163 var rawConfigFile = readConfigurationFile(resolvedConfigFilePath);
164 return parseConfigFile(rawConfigFile, path.dirname(resolvedConfigFilePath), readConfigurationFile);
165 }
166}
167exports.loadConfigurationFromPath = loadConfigurationFromPath;
168/** Reads the configuration file from disk and parses it as raw JSON, YAML or JS depending on the extension. */
169function readConfigurationFile(filepath) {
170 var resolvedConfigFileExt = path.extname(filepath);
171 if (/\.(json|ya?ml)/.test(resolvedConfigFileExt)) {
172 var fileContent = fs.readFileSync(filepath, "utf8").replace(/^\uFEFF/, "");
173 try {
174 if (resolvedConfigFileExt === ".json") {
175 return JSON.parse(utils_1.stripComments(fileContent));
176 }
177 else {
178 return yaml.safeLoad(fileContent);
179 }
180 }
181 catch (e) {
182 var error = e;
183 // include the configuration file being parsed in the error since it may differ from the directly referenced config
184 throw new Error(error.message + " in " + filepath);
185 }
186 }
187 else {
188 var rawConfigFile = require(filepath);
189 // tslint:disable-next-line no-dynamic-delete
190 delete require.cache[filepath];
191 return rawConfigFile;
192 }
193}
194exports.readConfigurationFile = readConfigurationFile;
195/**
196 * Resolve configuration file path or node_module reference
197 * @param filePath Relative ("./path"), absolute ("/path"), node module ("path"), or built-in ("tslint:path")
198 */
199function resolveConfigurationPath(filePath, relativeTo) {
200 var matches = filePath.match(BUILT_IN_CONFIG);
201 var isBuiltInConfig = matches !== null && matches.length > 0;
202 if (isBuiltInConfig) {
203 var configName = matches[1];
204 try {
205 return require.resolve("./configs/" + configName);
206 }
207 catch (err) {
208 throw new Error(filePath + " is not a built-in config, try \"tslint:recommended\" instead.");
209 }
210 }
211 var basedir = relativeTo !== undefined ? relativeTo : process.cwd();
212 try {
213 var resolvedPackagePath = utils_1.tryResolvePackage(filePath, basedir);
214 if (resolvedPackagePath === undefined) {
215 resolvedPackagePath = require.resolve(filePath);
216 }
217 return resolvedPackagePath;
218 }
219 catch (err) {
220 throw new Error("Invalid \"extends\" configuration value - could not require \"" + filePath + "\". " +
221 "Review the Node lookup algorithm (https://nodejs.org/api/modules.html#modules_all_together) " +
222 "for the approximate method TSLint uses to find the referenced configuration file.");
223 }
224}
225function extendConfigurationFile(targetConfig, nextConfigSource) {
226 function combineProperties(targetProperty, nextProperty) {
227 var combinedProperty = {};
228 add(targetProperty);
229 // next config source overwrites the target config object
230 add(nextProperty);
231 return combinedProperty;
232 function add(property) {
233 if (property !== undefined) {
234 for (var _i = 0, _a = Object.keys(property); _i < _a.length; _i++) {
235 var name = _a[_i];
236 combinedProperty[name] = property[name];
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 */
273// tslint:disable-next-line no-null-undefined-union
274function getRelativePath(directory, relativeTo) {
275 if (directory != undefined) {
276 var basePath = relativeTo !== undefined ? relativeTo : process.cwd();
277 return path.resolve(basePath, directory);
278 }
279 return undefined;
280}
281exports.getRelativePath = getRelativePath;
282// check if directory should be used as path or if it should be resolved like a module
283// matches if directory starts with '/', './', '../', 'node_modules/' or equals '.' or '..'
284function useAsPath(directory) {
285 return /^(?:\.?\.?(?:\/|$)|node_modules\/)/.test(directory);
286}
287exports.useAsPath = useAsPath;
288/**
289 * @param directories A path(s) to a directory of custom rules
290 * @param relativeTo A path that directories provided are relative to.
291 * For example, if the directories come from a tslint.json file, this path
292 * should be the path to the tslint.json file.
293 * @return An array of absolute paths to directories potentially containing rules
294 */
295function getRulesDirectories(directories, relativeTo) {
296 return utils_1.arrayify(directories).map(function (dir) {
297 if (!useAsPath(dir)) {
298 var resolvedPackagePath = utils_1.tryResolvePackage(dir, relativeTo);
299 if (resolvedPackagePath !== undefined) {
300 return path.dirname(resolvedPackagePath);
301 }
302 }
303 var absolutePath = relativeTo === undefined ? path.resolve(dir) : path.resolve(relativeTo, dir);
304 if (absolutePath !== undefined) {
305 if (!fs.existsSync(absolutePath)) {
306 throw new error_1.FatalError("Could not find custom rule directory: " + dir);
307 }
308 }
309 return absolutePath;
310 });
311}
312exports.getRulesDirectories = getRulesDirectories;
313/**
314 * Parses the options of a single rule and upgrades legacy settings such as `true`, `[true, "option"]`
315 *
316 * @param ruleConfigValue The raw option setting of a rule
317 */
318function parseRuleOptions(ruleConfigValue, rawDefaultRuleSeverity) {
319 var ruleArguments;
320 var defaultRuleSeverity = "error";
321 if (rawDefaultRuleSeverity !== undefined) {
322 switch (rawDefaultRuleSeverity.toLowerCase()) {
323 case "warn":
324 case "warning":
325 defaultRuleSeverity = "warning";
326 break;
327 case "off":
328 case "none":
329 defaultRuleSeverity = "off";
330 break;
331 default:
332 defaultRuleSeverity = "error";
333 }
334 }
335 var ruleSeverity = defaultRuleSeverity;
336 if (ruleConfigValue == undefined) {
337 ruleArguments = [];
338 ruleSeverity = "off";
339 }
340 else if (Array.isArray(ruleConfigValue)) {
341 if (ruleConfigValue.length > 0) {
342 // old style: array
343 ruleArguments = ruleConfigValue.slice(1);
344 ruleSeverity = ruleConfigValue[0] === true ? defaultRuleSeverity : "off";
345 }
346 }
347 else if (typeof ruleConfigValue === "boolean") {
348 // old style: boolean
349 ruleArguments = [];
350 ruleSeverity = ruleConfigValue ? defaultRuleSeverity : "off";
351 }
352 else if (typeof ruleConfigValue === "object") {
353 if (ruleConfigValue.severity !== undefined) {
354 switch (ruleConfigValue.severity.toLowerCase()) {
355 case "default":
356 ruleSeverity = defaultRuleSeverity;
357 break;
358 case "error":
359 ruleSeverity = "error";
360 break;
361 case "warn":
362 case "warning":
363 ruleSeverity = "warning";
364 break;
365 case "off":
366 case "none":
367 ruleSeverity = "off";
368 break;
369 default:
370 console.warn("Invalid severity level: " + ruleConfigValue.severity);
371 ruleSeverity = defaultRuleSeverity;
372 }
373 }
374 if (ruleConfigValue.options != undefined) {
375 ruleArguments = utils_1.arrayify(ruleConfigValue.options);
376 }
377 }
378 return {
379 ruleArguments: ruleArguments,
380 ruleSeverity: ruleSeverity,
381 };
382}
383/**
384 * Parses a config file and normalizes legacy config settings.
385 * If `configFileDir` and `readConfig` are provided, this function will load all base configs and reduce them to the final configuration.
386 *
387 * @param configFile The raw object read from the JSON of a config file
388 * @param configFileDir The directory of the config file
389 * @param readConfig Will be used to load all base configurations while parsing. The function is called with the resolved path.
390 */
391function parseConfigFile(configFile, configFileDir, readConfig) {
392 var defaultSeverity = configFile.defaultSeverity;
393 if (readConfig === undefined || configFileDir === undefined) {
394 return parse(configFile, configFileDir);
395 }
396 return loadExtendsRecursive(configFile, configFileDir)
397 .map(function (_a) {
398 var dir = _a.dir, config = _a.config;
399 return parse(config, dir);
400 })
401 .reduce(extendConfigurationFile, exports.EMPTY_CONFIG);
402 /** Read files in order, depth first, and assign `defaultSeverity` (last config in extends wins). */
403 function loadExtendsRecursive(raw, dir) {
404 var configs = [];
405 for (var _i = 0, _a = utils_1.arrayify(raw.extends); _i < _a.length; _i++) {
406 var relativePath = _a[_i];
407 var resolvedPath = resolveConfigurationPath(relativePath, dir);
408 var extendedRaw = readConfig(resolvedPath);
409 configs.push.apply(configs, loadExtendsRecursive(extendedRaw, path.dirname(resolvedPath)));
410 }
411 if (raw.defaultSeverity !== undefined) {
412 defaultSeverity = raw.defaultSeverity;
413 }
414 configs.push({ dir: dir, config: raw });
415 return configs;
416 }
417 function parse(config, dir) {
418 var rulesDirectory = getRulesDirectories(config.rulesDirectory, dir);
419 var rules = parseRules(config.rules);
420 var jsRules = typeof config.jsRules === "boolean"
421 ? filterValidJsRules(rules, config.jsRules, rulesDirectory)
422 : parseRules(config.jsRules);
423 return {
424 extends: utils_1.arrayify(config.extends),
425 jsRules: jsRules,
426 linterOptions: parseLinterOptions(config.linterOptions, dir),
427 rules: rules,
428 rulesDirectory: rulesDirectory,
429 };
430 }
431 function parseRules(config) {
432 var map = new Map();
433 if (config !== undefined) {
434 for (var _i = 0, _a = Object.keys(config); _i < _a.length; _i++) {
435 var ruleName = _a[_i];
436 map.set(ruleName, parseRuleOptions(config[ruleName], defaultSeverity));
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 var Rule = ruleLoader_1.findRule(ruleName, rulesDirectory);
447 if (Rule !== undefined &&
448 (Rule.metadata === undefined || !Rule.metadata.typescriptOnly)) {
449 validJsRules.set(ruleName, ruleOptions);
450 }
451 });
452 }
453 return validJsRules;
454 }
455 function parseLinterOptions(raw, dir) {
456 if (raw === undefined) {
457 return {};
458 }
459 return tslib_1.__assign(tslib_1.__assign({}, (raw.exclude !== undefined
460 ? {
461 exclude: utils_1.arrayify(raw.exclude).map(function (pattern) {
462 return dir === undefined ? path.resolve(pattern) : path.resolve(dir, pattern);
463 }),
464 }
465 : {})), (raw.format !== undefined
466 ? {
467 format: raw.format,
468 }
469 : {}));
470 }
471}
472exports.parseConfigFile = parseConfigFile;
473/**
474 * Fills in default values for `IOption` properties and outputs an array of `IOption`
475 */
476function convertRuleOptions(ruleConfiguration) {
477 var output = [];
478 ruleConfiguration.forEach(function (_a, ruleName) {
479 var ruleArguments = _a.ruleArguments, ruleSeverity = _a.ruleSeverity;
480 var options = {
481 disabledIntervals: [],
482 ruleArguments: ruleArguments != undefined ? ruleArguments : [],
483 ruleName: ruleName,
484 ruleSeverity: ruleSeverity != undefined ? ruleSeverity : "error",
485 };
486 output.push(options);
487 });
488 return output;
489}
490exports.convertRuleOptions = convertRuleOptions;
491function isFileExcluded(filepath, configFile) {
492 if (configFile === undefined ||
493 configFile.linterOptions == undefined ||
494 configFile.linterOptions.exclude == undefined) {
495 return false;
496 }
497 var fullPath = path.resolve(filepath);
498 return configFile.linterOptions.exclude.some(function (pattern) { return new minimatch_1.Minimatch(pattern).match(fullPath); });
499}
500exports.isFileExcluded = isFileExcluded;
501function stringifyConfiguration(configFile) {
502 return JSON.stringify({
503 extends: configFile.extends,
504 jsRules: convertRulesMapToObject(configFile.jsRules),
505 linterOptions: configFile.linterOptions,
506 rules: convertRulesMapToObject(configFile.rules),
507 rulesDirectory: configFile.rulesDirectory,
508 }, undefined, 2);
509}
510exports.stringifyConfiguration = stringifyConfiguration;
511function convertRulesMapToObject(rules) {
512 return Array.from(rules).reduce(function (result, _a) {
513 var _b;
514 var key = _a[0], value = _a[1];
515 return (tslib_1.__assign(tslib_1.__assign({}, result), (_b = {}, _b[key] = value, _b)));
516 }, {});
517}