diff options
Diffstat (limited to 'tools/node_modules/eslint/lib/config/config-ops.js')
-rw-r--r-- | tools/node_modules/eslint/lib/config/config-ops.js | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/tools/node_modules/eslint/lib/config/config-ops.js b/tools/node_modules/eslint/lib/config/config-ops.js new file mode 100644 index 0000000000..2ce500a4f4 --- /dev/null +++ b/tools/node_modules/eslint/lib/config/config-ops.js @@ -0,0 +1,383 @@ +/** + * @fileoverview Config file operations. This file must be usable in the browser, + * so no Node-specific code can be here. + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const minimatch = require("minimatch"), + path = require("path"); + +const debug = require("debug")("eslint:config-ops"); + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +const RULE_SEVERITY_STRINGS = ["off", "warn", "error"], + RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce((map, value, index) => { + map[value] = index; + return map; + }, {}), + VALID_SEVERITIES = [0, 1, 2, "off", "warn", "error"]; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + + /** + * Creates an empty configuration object suitable for merging as a base. + * @returns {Object} A configuration object. + */ + createEmptyConfig() { + return { + globals: {}, + env: {}, + rules: {}, + parserOptions: {} + }; + }, + + /** + * Creates an environment config based on the specified environments. + * @param {Object<string,boolean>} env The environment settings. + * @param {Environments} envContext The environment context. + * @returns {Object} A configuration object with the appropriate rules and globals + * set. + */ + createEnvironmentConfig(env, envContext) { + + const envConfig = this.createEmptyConfig(); + + if (env) { + + envConfig.env = env; + + Object.keys(env).filter(name => env[name]).forEach(name => { + const environment = envContext.get(name); + + if (environment) { + debug(`Creating config for environment ${name}`); + if (environment.globals) { + Object.assign(envConfig.globals, environment.globals); + } + + if (environment.parserOptions) { + Object.assign(envConfig.parserOptions, environment.parserOptions); + } + } + }); + } + + return envConfig; + }, + + /** + * Given a config with environment settings, applies the globals and + * ecmaFeatures to the configuration and returns the result. + * @param {Object} config The configuration information. + * @param {Environments} envContent env context. + * @returns {Object} The updated configuration information. + */ + applyEnvironments(config, envContent) { + if (config.env && typeof config.env === "object") { + debug("Apply environment settings to config"); + return this.merge(this.createEnvironmentConfig(config.env, envContent), config); + } + + return config; + }, + + /** + * Merges two config objects. This will not only add missing keys, but will also modify values to match. + * @param {Object} target config object + * @param {Object} src config object. Overrides in this config object will take priority over base. + * @param {boolean} [combine] Whether to combine arrays or not + * @param {boolean} [isRule] Whether its a rule + * @returns {Object} merged config object. + */ + merge: function deepmerge(target, src, combine, isRule) { + + /* + * The MIT License (MIT) + * + * Copyright (c) 2012 Nicholas Fisher + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + /* + * This code is taken from deepmerge repo + * (https://github.com/KyleAMathews/deepmerge) + * and modified to meet our needs. + */ + const array = Array.isArray(src) || Array.isArray(target); + let dst = array && [] || {}; + + combine = !!combine; + isRule = !!isRule; + if (array) { + target = target || []; + + // src could be a string, so check for array + if (isRule && Array.isArray(src) && src.length > 1) { + dst = dst.concat(src); + } else { + dst = dst.concat(target); + } + if (typeof src !== "object" && !Array.isArray(src)) { + src = [src]; + } + Object.keys(src).forEach((e, i) => { + e = src[i]; + if (typeof dst[i] === "undefined") { + dst[i] = e; + } else if (typeof e === "object") { + if (isRule) { + dst[i] = e; + } else { + dst[i] = deepmerge(target[i], e, combine, isRule); + } + } else { + if (!combine) { + dst[i] = e; + } else { + if (dst.indexOf(e) === -1) { + dst.push(e); + } + } + } + }); + } else { + if (target && typeof target === "object") { + Object.keys(target).forEach(key => { + dst[key] = target[key]; + }); + } + Object.keys(src).forEach(key => { + if (key === "overrides") { + dst[key] = (target[key] || []).concat(src[key] || []); + } else if (Array.isArray(src[key]) || Array.isArray(target[key])) { + dst[key] = deepmerge(target[key], src[key], key === "plugins" || key === "extends", isRule); + } else if (typeof src[key] !== "object" || !src[key] || key === "exported" || key === "astGlobals") { + dst[key] = src[key]; + } else { + dst[key] = deepmerge(target[key] || {}, src[key], combine, key === "rules"); + } + }); + } + + return dst; + }, + + /** + * Normalizes the severity value of a rule's configuration to a number + * @param {(number|string|[number, ...*]|[string, ...*])} ruleConfig A rule's configuration value, generally + * received from the user. A valid config value is either 0, 1, 2, the string "off" (treated the same as 0), + * the string "warn" (treated the same as 1), the string "error" (treated the same as 2), or an array + * whose first element is one of the above values. Strings are matched case-insensitively. + * @returns {(0|1|2)} The numeric severity value if the config value was valid, otherwise 0. + */ + getRuleSeverity(ruleConfig) { + const severityValue = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig; + + if (severityValue === 0 || severityValue === 1 || severityValue === 2) { + return severityValue; + } + + if (typeof severityValue === "string") { + return RULE_SEVERITY[severityValue.toLowerCase()] || 0; + } + + return 0; + }, + + /** + * Converts old-style severity settings (0, 1, 2) into new-style + * severity settings (off, warn, error) for all rules. Assumption is that severity + * values have already been validated as correct. + * @param {Object} config The config object to normalize. + * @returns {void} + */ + normalizeToStrings(config) { + + if (config.rules) { + Object.keys(config.rules).forEach(ruleId => { + const ruleConfig = config.rules[ruleId]; + + if (typeof ruleConfig === "number") { + config.rules[ruleId] = RULE_SEVERITY_STRINGS[ruleConfig] || RULE_SEVERITY_STRINGS[0]; + } else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "number") { + ruleConfig[0] = RULE_SEVERITY_STRINGS[ruleConfig[0]] || RULE_SEVERITY_STRINGS[0]; + } + }); + } + }, + + /** + * Determines if the severity for the given rule configuration represents an error. + * @param {int|string|Array} ruleConfig The configuration for an individual rule. + * @returns {boolean} True if the rule represents an error, false if not. + */ + isErrorSeverity(ruleConfig) { + + let severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig; + + if (typeof severity === "string") { + severity = RULE_SEVERITY[severity.toLowerCase()] || 0; + } + + return (typeof severity === "number" && severity === 2); + }, + + /** + * Checks whether a given config has valid severity or not. + * @param {number|string|Array} ruleConfig - The configuration for an individual rule. + * @returns {boolean} `true` if the configuration has valid severity. + */ + isValidSeverity(ruleConfig) { + let severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig; + + if (typeof severity === "string") { + severity = severity.toLowerCase(); + } + return VALID_SEVERITIES.indexOf(severity) !== -1; + }, + + /** + * Checks whether every rule of a given config has valid severity or not. + * @param {Object} config - The configuration for rules. + * @returns {boolean} `true` if the configuration has valid severity. + */ + isEverySeverityValid(config) { + return Object.keys(config).every(ruleId => this.isValidSeverity(config[ruleId])); + }, + + /** + * Merges all configurations in a given config vector. A vector is an array of objects, each containing a config + * file path and a list of subconfig indices that match the current file path. All config data is assumed to be + * cached. + * @param {Array<Object>} vector list of config files and their subconfig indices that match the current file path + * @param {Object} configCache the config cache + * @returns {Object} config object + */ + getConfigFromVector(vector, configCache) { + + const cachedConfig = configCache.getMergedVectorConfig(vector); + + if (cachedConfig) { + return cachedConfig; + } + + debug("Using config from partial cache"); + + const subvector = Array.from(vector); + let nearestCacheIndex = subvector.length - 1, + partialCachedConfig; + + while (nearestCacheIndex >= 0) { + partialCachedConfig = configCache.getMergedVectorConfig(subvector); + if (partialCachedConfig) { + break; + } + subvector.pop(); + nearestCacheIndex--; + } + + if (!partialCachedConfig) { + partialCachedConfig = {}; + } + + let finalConfig = partialCachedConfig; + + // Start from entry immediately following nearest cached config (first uncached entry) + for (let i = nearestCacheIndex + 1; i < vector.length; i++) { + finalConfig = this.mergeVectorEntry(finalConfig, vector[i], configCache); + configCache.setMergedVectorConfig(vector.slice(0, i + 1), finalConfig); + } + + return finalConfig; + }, + + /** + * Merges the config options from a single vector entry into the supplied config. + * @param {Object} config the base config to merge the vector entry's options into + * @param {Object} vectorEntry a single entry from a vector, consisting of a config file path and an array of + * matching override indices + * @param {Object} configCache the config cache + * @returns {Object} merged config object + */ + mergeVectorEntry(config, vectorEntry, configCache) { + const vectorEntryConfig = Object.assign({}, configCache.getConfig(vectorEntry.filePath)); + let mergedConfig = Object.assign({}, config), + overrides; + + if (vectorEntryConfig.overrides) { + overrides = vectorEntryConfig.overrides.filter( + (override, overrideIndex) => vectorEntry.matchingOverrides.indexOf(overrideIndex) !== -1 + ); + } else { + overrides = []; + } + + mergedConfig = this.merge(mergedConfig, vectorEntryConfig); + + delete mergedConfig.overrides; + + mergedConfig = overrides.reduce((lastConfig, override) => this.merge(lastConfig, override), mergedConfig); + + if (mergedConfig.filePath) { + delete mergedConfig.filePath; + delete mergedConfig.baseDirectory; + } else if (mergedConfig.files) { + delete mergedConfig.files; + } + + return mergedConfig; + }, + + /** + * Checks that the specified file path matches all of the supplied glob patterns. + * @param {string} filePath The file path to test patterns against + * @param {string|string[]} patterns One or more glob patterns, of which at least one should match the file path + * @param {string|string[]} [excludedPatterns] One or more glob patterns, of which none should match the file path + * @returns {boolean} True if all the supplied patterns match the file path, false otherwise + */ + pathMatchesGlobs(filePath, patterns, excludedPatterns) { + const patternList = [].concat(patterns); + const excludedPatternList = [].concat(excludedPatterns || []); + + patternList.concat(excludedPatternList).forEach(pattern => { + if (path.isAbsolute(pattern) || pattern.includes("..")) { + throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`); + } + }); + + const opts = { matchBase: true }; + + return patternList.some(pattern => minimatch(filePath, pattern, opts)) && + !excludedPatternList.some(excludedPattern => minimatch(filePath, excludedPattern, opts)); + } +}; |