UNPKG

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