--- /dev/null
+"use strict";
+/**
+ * @license
+ * Copyright 2013 Palantir Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+Object.defineProperty(exports, "__esModule", { value: true });
+var tslib_1 = require("tslib");
+var fs = require("fs");
+var yaml = require("js-yaml");
+var minimatch_1 = require("minimatch");
+var os = require("os");
+var path = require("path");
+var error_1 = require("./error");
+var ruleLoader_1 = require("./ruleLoader");
+var utils_1 = require("./utils");
+// Note: eslint prefers yaml over json, while tslint prefers json over yaml
+// for backward-compatibility.
+exports.JSON_CONFIG_FILENAME = "tslint.json";
+/** @deprecated use `JSON_CONFIG_FILENAME` or `CONFIG_FILENAMES` instead. */
+exports.CONFIG_FILENAME = exports.JSON_CONFIG_FILENAME;
+exports.CONFIG_FILENAMES = [exports.JSON_CONFIG_FILENAME, "tslint.yaml", "tslint.yml"];
+exports.DEFAULT_CONFIG = {
+ defaultSeverity: "error",
+ extends: ["tslint:recommended"],
+ jsRules: new Map(),
+ rules: new Map(),
+ rulesDirectory: [],
+};
+exports.EMPTY_CONFIG = {
+ defaultSeverity: "error",
+ extends: [],
+ jsRules: new Map(),
+ rules: new Map(),
+ rulesDirectory: [],
+};
+var BUILT_IN_CONFIG = /^tslint:(.*)$/;
+function findConfiguration(configFile, inputFilePath) {
+ var configPath = findConfigurationPath(configFile, inputFilePath);
+ var loadResult = { path: configPath };
+ try {
+ loadResult.results = loadConfigurationFromPath(configPath);
+ return loadResult;
+ }
+ catch (error) {
+ throw new error_1.FatalError("Failed to load " + configPath + ": " + error.message, error);
+ }
+}
+exports.findConfiguration = findConfiguration;
+function findConfigurationPath(suppliedConfigFilePath, inputFilePath) {
+ if (suppliedConfigFilePath != undefined) {
+ if (!fs.existsSync(suppliedConfigFilePath)) {
+ throw new error_1.FatalError("Could not find config file at: " + path.resolve(suppliedConfigFilePath));
+ }
+ else {
+ return path.resolve(suppliedConfigFilePath);
+ }
+ }
+ else {
+ // convert to dir if it's a file or doesn't exist
+ var useDirName = false;
+ try {
+ var stats = fs.statSync(inputFilePath);
+ if (stats.isFile()) {
+ useDirName = true;
+ }
+ }
+ catch (e) {
+ // throws if file doesn't exist
+ useDirName = true;
+ }
+ if (useDirName) {
+ inputFilePath = path.dirname(inputFilePath);
+ }
+ // search for tslint.json from input file location
+ var configFilePath = findup(exports.CONFIG_FILENAMES, path.resolve(inputFilePath));
+ if (configFilePath !== undefined) {
+ return configFilePath;
+ }
+ // search for tslint.json in home directory
+ var homeDir = os.homedir();
+ for (var _i = 0, CONFIG_FILENAMES_1 = exports.CONFIG_FILENAMES; _i < CONFIG_FILENAMES_1.length; _i++) {
+ var configFilename = CONFIG_FILENAMES_1[_i];
+ configFilePath = path.join(homeDir, configFilename);
+ if (fs.existsSync(configFilePath)) {
+ return path.resolve(configFilePath);
+ }
+ }
+ // no path could be found
+ return undefined;
+ }
+}
+exports.findConfigurationPath = findConfigurationPath;
+/**
+ * Find a file by names in a directory or any ancestor directory.
+ * Will try each filename in filenames before recursing to a parent directory.
+ * This is case-insensitive, so it can find 'TsLiNt.JsOn' when searching for 'tslint.json'.
+ */
+function findup(filenames, directory) {
+ while (true) {
+ var res = findFile(directory);
+ if (res !== undefined) {
+ return path.join(directory, res);
+ }
+ var parent = path.dirname(directory);
+ if (parent === directory) {
+ return undefined;
+ }
+ directory = parent;
+ }
+ function findFile(cwd) {
+ var dirFiles = fs.readdirSync(cwd);
+ var _loop_1 = function (filename) {
+ var index = dirFiles.indexOf(filename);
+ if (index > -1) {
+ return { value: filename };
+ }
+ // TODO: remove in v6.0.0
+ // Try reading in the entire directory and looking for a file with different casing.
+ var result = dirFiles.find(function (entry) { return entry.toLowerCase() === filename; });
+ if (result !== undefined) {
+ error_1.showWarningOnce("Using mixed case " + filename + " is deprecated. Found: " + path.join(cwd, result));
+ return { value: result };
+ }
+ };
+ for (var _i = 0, filenames_1 = filenames; _i < filenames_1.length; _i++) {
+ var filename = filenames_1[_i];
+ var state_1 = _loop_1(filename);
+ if (typeof state_1 === "object")
+ return state_1.value;
+ }
+ return undefined;
+ }
+}
+/**
+ * Used Node semantics to load a configuration file given configFilePath.
+ * For example:
+ * '/path/to/config' will be treated as an absolute path
+ * './path/to/config' will be treated as a relative path
+ * 'path/to/config' will attempt to load a to/config file inside a node module named path
+ * @param configFilePath The configuration to load
+ * @param originalFilePath (deprecated) The entry point configuration file
+ * @returns a configuration object for TSLint loaded from the file at configFilePath
+ */
+function loadConfigurationFromPath(configFilePath, _originalFilePath) {
+ if (configFilePath == undefined) {
+ return exports.DEFAULT_CONFIG;
+ }
+ else {
+ var resolvedConfigFilePath = resolveConfigurationPath(configFilePath);
+ var rawConfigFile = readConfigurationFile(resolvedConfigFilePath);
+ return parseConfigFile(rawConfigFile, path.dirname(resolvedConfigFilePath), readConfigurationFile);
+ }
+}
+exports.loadConfigurationFromPath = loadConfigurationFromPath;
+/** Reads the configuration file from disk and parses it as raw JSON, YAML or JS depending on the extension. */
+function readConfigurationFile(filepath) {
+ var resolvedConfigFileExt = path.extname(filepath);
+ if (/\.(json|ya?ml)/.test(resolvedConfigFileExt)) {
+ var fileContent = fs.readFileSync(filepath, "utf8").replace(/^\uFEFF/, "");
+ try {
+ if (resolvedConfigFileExt === ".json") {
+ return JSON.parse(utils_1.stripComments(fileContent));
+ }
+ else {
+ return yaml.safeLoad(fileContent);
+ }
+ }
+ catch (e) {
+ var error = e;
+ // include the configuration file being parsed in the error since it may differ from the directly referenced config
+ throw new Error(error.message + " in " + filepath);
+ }
+ }
+ else {
+ var rawConfigFile = require(filepath);
+ // tslint:disable-next-line no-dynamic-delete
+ delete require.cache[filepath];
+ return rawConfigFile;
+ }
+}
+exports.readConfigurationFile = readConfigurationFile;
+/**
+ * Resolve configuration file path or node_module reference
+ * @param filePath Relative ("./path"), absolute ("/path"), node module ("path"), or built-in ("tslint:path")
+ */
+function resolveConfigurationPath(filePath, relativeTo) {
+ var matches = filePath.match(BUILT_IN_CONFIG);
+ var isBuiltInConfig = matches !== null && matches.length > 0;
+ if (isBuiltInConfig) {
+ var configName = matches[1];
+ try {
+ return require.resolve("./configs/" + configName);
+ }
+ catch (err) {
+ throw new Error(filePath + " is not a built-in config, try \"tslint:recommended\" instead.");
+ }
+ }
+ var basedir = relativeTo !== undefined ? relativeTo : process.cwd();
+ try {
+ var resolvedPackagePath = utils_1.tryResolvePackage(filePath, basedir);
+ if (resolvedPackagePath === undefined) {
+ resolvedPackagePath = require.resolve(filePath);
+ }
+ return resolvedPackagePath;
+ }
+ catch (err) {
+ throw new Error("Invalid \"extends\" configuration value - could not require \"" + filePath + "\". " +
+ "Review the Node lookup algorithm (https://nodejs.org/api/modules.html#modules_all_together) " +
+ "for the approximate method TSLint uses to find the referenced configuration file.");
+ }
+}
+function extendConfigurationFile(targetConfig, nextConfigSource) {
+ function combineProperties(targetProperty, nextProperty) {
+ var combinedProperty = {};
+ add(targetProperty);
+ // next config source overwrites the target config object
+ add(nextProperty);
+ return combinedProperty;
+ function add(property) {
+ if (property !== undefined) {
+ for (var _i = 0, _a = Object.keys(property); _i < _a.length; _i++) {
+ var name = _a[_i];
+ combinedProperty[name] = property[name];
+ }
+ }
+ }
+ }
+ function combineMaps(target, next) {
+ var combined = new Map();
+ target.forEach(function (options, ruleName) {
+ combined.set(ruleName, options);
+ });
+ next.forEach(function (options, ruleName) {
+ var combinedRule = combined.get(ruleName);
+ if (combinedRule !== undefined) {
+ combined.set(ruleName, combineProperties(combinedRule, options));
+ }
+ else {
+ combined.set(ruleName, options);
+ }
+ });
+ return combined;
+ }
+ var combinedRulesDirs = targetConfig.rulesDirectory.concat(nextConfigSource.rulesDirectory);
+ var dedupedRulesDirs = Array.from(new Set(combinedRulesDirs));
+ return {
+ extends: [],
+ jsRules: combineMaps(targetConfig.jsRules, nextConfigSource.jsRules),
+ linterOptions: combineProperties(targetConfig.linterOptions, nextConfigSource.linterOptions),
+ rules: combineMaps(targetConfig.rules, nextConfigSource.rules),
+ rulesDirectory: dedupedRulesDirs,
+ };
+}
+exports.extendConfigurationFile = extendConfigurationFile;
+/**
+ * returns the absolute path (contrary to what the name implies)
+ *
+ * @deprecated use `path.resolve` instead
+ */
+// tslint:disable-next-line no-null-undefined-union
+function getRelativePath(directory, relativeTo) {
+ if (directory != undefined) {
+ var basePath = relativeTo !== undefined ? relativeTo : process.cwd();
+ return path.resolve(basePath, directory);
+ }
+ return undefined;
+}
+exports.getRelativePath = getRelativePath;
+// check if directory should be used as path or if it should be resolved like a module
+// matches if directory starts with '/', './', '../', 'node_modules/' or equals '.' or '..'
+function useAsPath(directory) {
+ return /^(?:\.?\.?(?:\/|$)|node_modules\/)/.test(directory);
+}
+exports.useAsPath = useAsPath;
+/**
+ * @param directories A path(s) to a directory of custom rules
+ * @param relativeTo A path that directories provided are relative to.
+ * For example, if the directories come from a tslint.json file, this path
+ * should be the path to the tslint.json file.
+ * @return An array of absolute paths to directories potentially containing rules
+ */
+function getRulesDirectories(directories, relativeTo) {
+ return utils_1.arrayify(directories).map(function (dir) {
+ if (!useAsPath(dir)) {
+ var resolvedPackagePath = utils_1.tryResolvePackage(dir, relativeTo);
+ if (resolvedPackagePath !== undefined) {
+ return path.dirname(resolvedPackagePath);
+ }
+ }
+ var absolutePath = relativeTo === undefined ? path.resolve(dir) : path.resolve(relativeTo, dir);
+ if (absolutePath !== undefined) {
+ if (!fs.existsSync(absolutePath)) {
+ throw new error_1.FatalError("Could not find custom rule directory: " + dir);
+ }
+ }
+ return absolutePath;
+ });
+}
+exports.getRulesDirectories = getRulesDirectories;
+/**
+ * Parses the options of a single rule and upgrades legacy settings such as `true`, `[true, "option"]`
+ *
+ * @param ruleConfigValue The raw option setting of a rule
+ */
+function parseRuleOptions(ruleConfigValue, rawDefaultRuleSeverity) {
+ var ruleArguments;
+ var defaultRuleSeverity = "error";
+ if (rawDefaultRuleSeverity !== undefined) {
+ switch (rawDefaultRuleSeverity.toLowerCase()) {
+ case "warn":
+ case "warning":
+ defaultRuleSeverity = "warning";
+ break;
+ case "off":
+ case "none":
+ defaultRuleSeverity = "off";
+ break;
+ default:
+ defaultRuleSeverity = "error";
+ }
+ }
+ var ruleSeverity = defaultRuleSeverity;
+ if (ruleConfigValue == undefined) {
+ ruleArguments = [];
+ ruleSeverity = "off";
+ }
+ else if (Array.isArray(ruleConfigValue)) {
+ if (ruleConfigValue.length > 0) {
+ // old style: array
+ ruleArguments = ruleConfigValue.slice(1);
+ ruleSeverity = ruleConfigValue[0] === true ? defaultRuleSeverity : "off";
+ }
+ }
+ else if (typeof ruleConfigValue === "boolean") {
+ // old style: boolean
+ ruleArguments = [];
+ ruleSeverity = ruleConfigValue ? defaultRuleSeverity : "off";
+ }
+ else if (typeof ruleConfigValue === "object") {
+ if (ruleConfigValue.severity !== undefined) {
+ switch (ruleConfigValue.severity.toLowerCase()) {
+ case "default":
+ ruleSeverity = defaultRuleSeverity;
+ break;
+ case "error":
+ ruleSeverity = "error";
+ break;
+ case "warn":
+ case "warning":
+ ruleSeverity = "warning";
+ break;
+ case "off":
+ case "none":
+ ruleSeverity = "off";
+ break;
+ default:
+ console.warn("Invalid severity level: " + ruleConfigValue.severity);
+ ruleSeverity = defaultRuleSeverity;
+ }
+ }
+ if (ruleConfigValue.options != undefined) {
+ ruleArguments = utils_1.arrayify(ruleConfigValue.options);
+ }
+ }
+ return {
+ ruleArguments: ruleArguments,
+ ruleSeverity: ruleSeverity,
+ };
+}
+/**
+ * Parses a config file and normalizes legacy config settings.
+ * If `configFileDir` and `readConfig` are provided, this function will load all base configs and reduce them to the final configuration.
+ *
+ * @param configFile The raw object read from the JSON of a config file
+ * @param configFileDir The directory of the config file
+ * @param readConfig Will be used to load all base configurations while parsing. The function is called with the resolved path.
+ */
+function parseConfigFile(configFile, configFileDir, readConfig) {
+ var defaultSeverity = configFile.defaultSeverity;
+ if (readConfig === undefined || configFileDir === undefined) {
+ return parse(configFile, configFileDir);
+ }
+ return loadExtendsRecursive(configFile, configFileDir)
+ .map(function (_a) {
+ var dir = _a.dir, config = _a.config;
+ return parse(config, dir);
+ })
+ .reduce(extendConfigurationFile, exports.EMPTY_CONFIG);
+ /** Read files in order, depth first, and assign `defaultSeverity` (last config in extends wins). */
+ function loadExtendsRecursive(raw, dir) {
+ var configs = [];
+ for (var _i = 0, _a = utils_1.arrayify(raw.extends); _i < _a.length; _i++) {
+ var relativePath = _a[_i];
+ var resolvedPath = resolveConfigurationPath(relativePath, dir);
+ var extendedRaw = readConfig(resolvedPath);
+ configs.push.apply(configs, loadExtendsRecursive(extendedRaw, path.dirname(resolvedPath)));
+ }
+ if (raw.defaultSeverity !== undefined) {
+ defaultSeverity = raw.defaultSeverity;
+ }
+ configs.push({ dir: dir, config: raw });
+ return configs;
+ }
+ function parse(config, dir) {
+ var rulesDirectory = getRulesDirectories(config.rulesDirectory, dir);
+ var rules = parseRules(config.rules);
+ var jsRules = typeof config.jsRules === "boolean"
+ ? filterValidJsRules(rules, config.jsRules, rulesDirectory)
+ : parseRules(config.jsRules);
+ return {
+ extends: utils_1.arrayify(config.extends),
+ jsRules: jsRules,
+ linterOptions: parseLinterOptions(config.linterOptions, dir),
+ rules: rules,
+ rulesDirectory: rulesDirectory,
+ };
+ }
+ function parseRules(config) {
+ var map = new Map();
+ if (config !== undefined) {
+ for (var _i = 0, _a = Object.keys(config); _i < _a.length; _i++) {
+ var ruleName = _a[_i];
+ map.set(ruleName, parseRuleOptions(config[ruleName], defaultSeverity));
+ }
+ }
+ return map;
+ }
+ function filterValidJsRules(rules, copyRulestoJsRules, rulesDirectory) {
+ if (copyRulestoJsRules === void 0) { copyRulestoJsRules = false; }
+ var validJsRules = new Map();
+ if (copyRulestoJsRules) {
+ rules.forEach(function (ruleOptions, ruleName) {
+ var Rule = ruleLoader_1.findRule(ruleName, rulesDirectory);
+ if (Rule !== undefined &&
+ (Rule.metadata === undefined || !Rule.metadata.typescriptOnly)) {
+ validJsRules.set(ruleName, ruleOptions);
+ }
+ });
+ }
+ return validJsRules;
+ }
+ function parseLinterOptions(raw, dir) {
+ if (raw === undefined) {
+ return {};
+ }
+ return tslib_1.__assign({}, (raw.exclude !== undefined
+ ? {
+ exclude: utils_1.arrayify(raw.exclude).map(function (pattern) {
+ return dir === undefined ? path.resolve(pattern) : path.resolve(dir, pattern);
+ }),
+ }
+ : {}), (raw.format !== undefined
+ ? {
+ format: raw.format,
+ }
+ : {}));
+ }
+}
+exports.parseConfigFile = parseConfigFile;
+/**
+ * Fills in default values for `IOption` properties and outputs an array of `IOption`
+ */
+function convertRuleOptions(ruleConfiguration) {
+ var output = [];
+ ruleConfiguration.forEach(function (_a, ruleName) {
+ var ruleArguments = _a.ruleArguments, ruleSeverity = _a.ruleSeverity;
+ var options = {
+ disabledIntervals: [],
+ ruleArguments: ruleArguments != undefined ? ruleArguments : [],
+ ruleName: ruleName,
+ ruleSeverity: ruleSeverity != undefined ? ruleSeverity : "error",
+ };
+ output.push(options);
+ });
+ return output;
+}
+exports.convertRuleOptions = convertRuleOptions;
+function isFileExcluded(filepath, configFile) {
+ if (configFile === undefined ||
+ configFile.linterOptions == undefined ||
+ configFile.linterOptions.exclude == undefined) {
+ return false;
+ }
+ var fullPath = path.resolve(filepath);
+ return configFile.linterOptions.exclude.some(function (pattern) { return new minimatch_1.Minimatch(pattern).match(fullPath); });
+}
+exports.isFileExcluded = isFileExcluded;
+function stringifyConfiguration(configFile) {
+ return JSON.stringify({
+ extends: configFile.extends,
+ jsRules: convertRulesMapToObject(configFile.jsRules),
+ linterOptions: configFile.linterOptions,
+ rules: convertRulesMapToObject(configFile.rules),
+ rulesDirectory: configFile.rulesDirectory,
+ }, undefined, 2);
+}
+exports.stringifyConfiguration = stringifyConfiguration;
+function convertRulesMapToObject(rules) {
+ return Array.from(rules).reduce(function (result, _a) {
+ var _b;
+ var key = _a[0], value = _a[1];
+ return (tslib_1.__assign({}, result, (_b = {}, _b[key] = value, _b)));
+ }, {});
+}