Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

config-ops.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /**
  2. * @fileoverview Config file operations. This file must be usable in the browser,
  3. * so no Node-specific code can be here.
  4. * @author Nicholas C. Zakas
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const minimatch = require("minimatch"),
  11. path = require("path");
  12. const debug = require("debug")("eslint:config-ops");
  13. //------------------------------------------------------------------------------
  14. // Private
  15. //------------------------------------------------------------------------------
  16. const RULE_SEVERITY_STRINGS = ["off", "warn", "error"],
  17. RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce((map, value, index) => {
  18. map[value] = index;
  19. return map;
  20. }, {}),
  21. VALID_SEVERITIES = [0, 1, 2, "off", "warn", "error"];
  22. //------------------------------------------------------------------------------
  23. // Public Interface
  24. //------------------------------------------------------------------------------
  25. module.exports = {
  26. /**
  27. * Creates an empty configuration object suitable for merging as a base.
  28. * @returns {Object} A configuration object.
  29. */
  30. createEmptyConfig() {
  31. return {
  32. globals: {},
  33. env: {},
  34. rules: {},
  35. parserOptions: {}
  36. };
  37. },
  38. /**
  39. * Creates an environment config based on the specified environments.
  40. * @param {Object<string,boolean>} env The environment settings.
  41. * @param {Environments} envContext The environment context.
  42. * @returns {Object} A configuration object with the appropriate rules and globals
  43. * set.
  44. */
  45. createEnvironmentConfig(env, envContext) {
  46. const envConfig = this.createEmptyConfig();
  47. if (env) {
  48. envConfig.env = env;
  49. Object.keys(env).filter(name => env[name]).forEach(name => {
  50. const environment = envContext.get(name);
  51. if (environment) {
  52. debug(`Creating config for environment ${name}`);
  53. if (environment.globals) {
  54. Object.assign(envConfig.globals, environment.globals);
  55. }
  56. if (environment.parserOptions) {
  57. Object.assign(envConfig.parserOptions, environment.parserOptions);
  58. }
  59. }
  60. });
  61. }
  62. return envConfig;
  63. },
  64. /**
  65. * Given a config with environment settings, applies the globals and
  66. * ecmaFeatures to the configuration and returns the result.
  67. * @param {Object} config The configuration information.
  68. * @param {Environments} envContent env context.
  69. * @returns {Object} The updated configuration information.
  70. */
  71. applyEnvironments(config, envContent) {
  72. if (config.env && typeof config.env === "object") {
  73. debug("Apply environment settings to config");
  74. return this.merge(this.createEnvironmentConfig(config.env, envContent), config);
  75. }
  76. return config;
  77. },
  78. /**
  79. * Merges two config objects. This will not only add missing keys, but will also modify values to match.
  80. * @param {Object} target config object
  81. * @param {Object} src config object. Overrides in this config object will take priority over base.
  82. * @param {boolean} [combine] Whether to combine arrays or not
  83. * @param {boolean} [isRule] Whether its a rule
  84. * @returns {Object} merged config object.
  85. */
  86. merge: function deepmerge(target, src, combine, isRule) {
  87. /*
  88. * The MIT License (MIT)
  89. *
  90. * Copyright (c) 2012 Nicholas Fisher
  91. *
  92. * Permission is hereby granted, free of charge, to any person obtaining a copy
  93. * of this software and associated documentation files (the "Software"), to deal
  94. * in the Software without restriction, including without limitation the rights
  95. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  96. * copies of the Software, and to permit persons to whom the Software is
  97. * furnished to do so, subject to the following conditions:
  98. *
  99. * The above copyright notice and this permission notice shall be included in
  100. * all copies or substantial portions of the Software.
  101. *
  102. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  103. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  104. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  105. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  106. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  107. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  108. * THE SOFTWARE.
  109. */
  110. /*
  111. * This code is taken from deepmerge repo
  112. * (https://github.com/KyleAMathews/deepmerge)
  113. * and modified to meet our needs.
  114. */
  115. const array = Array.isArray(src) || Array.isArray(target);
  116. let dst = array && [] || {};
  117. if (array) {
  118. const resolvedTarget = target || [];
  119. // src could be a string, so check for array
  120. if (isRule && Array.isArray(src) && src.length > 1) {
  121. dst = dst.concat(src);
  122. } else {
  123. dst = dst.concat(resolvedTarget);
  124. }
  125. const resolvedSrc = typeof src === "object" ? src : [src];
  126. Object.keys(resolvedSrc).forEach((_, i) => {
  127. const e = resolvedSrc[i];
  128. if (typeof dst[i] === "undefined") {
  129. dst[i] = e;
  130. } else if (typeof e === "object") {
  131. if (isRule) {
  132. dst[i] = e;
  133. } else {
  134. dst[i] = deepmerge(resolvedTarget[i], e, combine, isRule);
  135. }
  136. } else {
  137. if (!combine) {
  138. dst[i] = e;
  139. } else {
  140. if (dst.indexOf(e) === -1) {
  141. dst.push(e);
  142. }
  143. }
  144. }
  145. });
  146. } else {
  147. if (target && typeof target === "object") {
  148. Object.keys(target).forEach(key => {
  149. dst[key] = target[key];
  150. });
  151. }
  152. Object.keys(src).forEach(key => {
  153. if (key === "overrides") {
  154. dst[key] = (target[key] || []).concat(src[key] || []);
  155. } else if (Array.isArray(src[key]) || Array.isArray(target[key])) {
  156. dst[key] = deepmerge(target[key], src[key], key === "plugins" || key === "extends", isRule);
  157. } else if (typeof src[key] !== "object" || !src[key] || key === "exported" || key === "astGlobals") {
  158. dst[key] = src[key];
  159. } else {
  160. dst[key] = deepmerge(target[key] || {}, src[key], combine, key === "rules");
  161. }
  162. });
  163. }
  164. return dst;
  165. },
  166. /**
  167. * Normalizes the severity value of a rule's configuration to a number
  168. * @param {(number|string|[number, ...*]|[string, ...*])} ruleConfig A rule's configuration value, generally
  169. * received from the user. A valid config value is either 0, 1, 2, the string "off" (treated the same as 0),
  170. * the string "warn" (treated the same as 1), the string "error" (treated the same as 2), or an array
  171. * whose first element is one of the above values. Strings are matched case-insensitively.
  172. * @returns {(0|1|2)} The numeric severity value if the config value was valid, otherwise 0.
  173. */
  174. getRuleSeverity(ruleConfig) {
  175. const severityValue = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
  176. if (severityValue === 0 || severityValue === 1 || severityValue === 2) {
  177. return severityValue;
  178. }
  179. if (typeof severityValue === "string") {
  180. return RULE_SEVERITY[severityValue.toLowerCase()] || 0;
  181. }
  182. return 0;
  183. },
  184. /**
  185. * Converts old-style severity settings (0, 1, 2) into new-style
  186. * severity settings (off, warn, error) for all rules. Assumption is that severity
  187. * values have already been validated as correct.
  188. * @param {Object} config The config object to normalize.
  189. * @returns {void}
  190. */
  191. normalizeToStrings(config) {
  192. if (config.rules) {
  193. Object.keys(config.rules).forEach(ruleId => {
  194. const ruleConfig = config.rules[ruleId];
  195. if (typeof ruleConfig === "number") {
  196. config.rules[ruleId] = RULE_SEVERITY_STRINGS[ruleConfig] || RULE_SEVERITY_STRINGS[0];
  197. } else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "number") {
  198. ruleConfig[0] = RULE_SEVERITY_STRINGS[ruleConfig[0]] || RULE_SEVERITY_STRINGS[0];
  199. }
  200. });
  201. }
  202. },
  203. /**
  204. * Determines if the severity for the given rule configuration represents an error.
  205. * @param {int|string|Array} ruleConfig The configuration for an individual rule.
  206. * @returns {boolean} True if the rule represents an error, false if not.
  207. */
  208. isErrorSeverity(ruleConfig) {
  209. return module.exports.getRuleSeverity(ruleConfig) === 2;
  210. },
  211. /**
  212. * Checks whether a given config has valid severity or not.
  213. * @param {number|string|Array} ruleConfig - The configuration for an individual rule.
  214. * @returns {boolean} `true` if the configuration has valid severity.
  215. */
  216. isValidSeverity(ruleConfig) {
  217. let severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
  218. if (typeof severity === "string") {
  219. severity = severity.toLowerCase();
  220. }
  221. return VALID_SEVERITIES.indexOf(severity) !== -1;
  222. },
  223. /**
  224. * Checks whether every rule of a given config has valid severity or not.
  225. * @param {Object} config - The configuration for rules.
  226. * @returns {boolean} `true` if the configuration has valid severity.
  227. */
  228. isEverySeverityValid(config) {
  229. return Object.keys(config).every(ruleId => this.isValidSeverity(config[ruleId]));
  230. },
  231. /**
  232. * Merges all configurations in a given config vector. A vector is an array of objects, each containing a config
  233. * file path and a list of subconfig indices that match the current file path. All config data is assumed to be
  234. * cached.
  235. * @param {Array<Object>} vector list of config files and their subconfig indices that match the current file path
  236. * @param {Object} configCache the config cache
  237. * @returns {Object} config object
  238. */
  239. getConfigFromVector(vector, configCache) {
  240. const cachedConfig = configCache.getMergedVectorConfig(vector);
  241. if (cachedConfig) {
  242. return cachedConfig;
  243. }
  244. debug("Using config from partial cache");
  245. const subvector = Array.from(vector);
  246. let nearestCacheIndex = subvector.length - 1,
  247. partialCachedConfig;
  248. while (nearestCacheIndex >= 0) {
  249. partialCachedConfig = configCache.getMergedVectorConfig(subvector);
  250. if (partialCachedConfig) {
  251. break;
  252. }
  253. subvector.pop();
  254. nearestCacheIndex--;
  255. }
  256. if (!partialCachedConfig) {
  257. partialCachedConfig = {};
  258. }
  259. let finalConfig = partialCachedConfig;
  260. // Start from entry immediately following nearest cached config (first uncached entry)
  261. for (let i = nearestCacheIndex + 1; i < vector.length; i++) {
  262. finalConfig = this.mergeVectorEntry(finalConfig, vector[i], configCache);
  263. configCache.setMergedVectorConfig(vector.slice(0, i + 1), finalConfig);
  264. }
  265. return finalConfig;
  266. },
  267. /**
  268. * Merges the config options from a single vector entry into the supplied config.
  269. * @param {Object} config the base config to merge the vector entry's options into
  270. * @param {Object} vectorEntry a single entry from a vector, consisting of a config file path and an array of
  271. * matching override indices
  272. * @param {Object} configCache the config cache
  273. * @returns {Object} merged config object
  274. */
  275. mergeVectorEntry(config, vectorEntry, configCache) {
  276. const vectorEntryConfig = Object.assign({}, configCache.getConfig(vectorEntry.filePath));
  277. let mergedConfig = Object.assign({}, config),
  278. overrides;
  279. if (vectorEntryConfig.overrides) {
  280. overrides = vectorEntryConfig.overrides.filter(
  281. (override, overrideIndex) => vectorEntry.matchingOverrides.indexOf(overrideIndex) !== -1
  282. );
  283. } else {
  284. overrides = [];
  285. }
  286. mergedConfig = this.merge(mergedConfig, vectorEntryConfig);
  287. delete mergedConfig.overrides;
  288. mergedConfig = overrides.reduce((lastConfig, override) => this.merge(lastConfig, override), mergedConfig);
  289. if (mergedConfig.filePath) {
  290. delete mergedConfig.filePath;
  291. delete mergedConfig.baseDirectory;
  292. } else if (mergedConfig.files) {
  293. delete mergedConfig.files;
  294. }
  295. return mergedConfig;
  296. },
  297. /**
  298. * Checks that the specified file path matches all of the supplied glob patterns.
  299. * @param {string} filePath The file path to test patterns against
  300. * @param {string|string[]} patterns One or more glob patterns, of which at least one should match the file path
  301. * @param {string|string[]} [excludedPatterns] One or more glob patterns, of which none should match the file path
  302. * @returns {boolean} True if all the supplied patterns match the file path, false otherwise
  303. */
  304. pathMatchesGlobs(filePath, patterns, excludedPatterns) {
  305. const patternList = [].concat(patterns);
  306. const excludedPatternList = [].concat(excludedPatterns || []);
  307. patternList.concat(excludedPatternList).forEach(pattern => {
  308. if (path.isAbsolute(pattern) || pattern.includes("..")) {
  309. throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`);
  310. }
  311. });
  312. const opts = { matchBase: true };
  313. return patternList.some(pattern => minimatch(filePath, pattern, opts)) &&
  314. !excludedPatternList.some(excludedPattern => minimatch(filePath, excludedPattern, opts));
  315. }
  316. };