flat-config-array.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /**
  2. * @fileoverview Flat Config Array
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //-----------------------------------------------------------------------------
  7. // Requirements
  8. //-----------------------------------------------------------------------------
  9. const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array");
  10. const { flatConfigSchema } = require("./flat-config-schema");
  11. const { RuleValidator } = require("./rule-validator");
  12. const { defaultConfig } = require("./default-config");
  13. const recommendedConfig = require("../../conf/eslint-recommended");
  14. //-----------------------------------------------------------------------------
  15. // Helpers
  16. //-----------------------------------------------------------------------------
  17. const ruleValidator = new RuleValidator();
  18. /**
  19. * Splits a plugin identifier in the form a/b/c into two parts: a/b and c.
  20. * @param {string} identifier The identifier to parse.
  21. * @returns {{objectName: string, pluginName: string}} The parts of the plugin
  22. * name.
  23. */
  24. function splitPluginIdentifier(identifier) {
  25. const parts = identifier.split("/");
  26. return {
  27. objectName: parts.pop(),
  28. pluginName: parts.join("/")
  29. };
  30. }
  31. const originalBaseConfig = Symbol("originalBaseConfig");
  32. //-----------------------------------------------------------------------------
  33. // Exports
  34. //-----------------------------------------------------------------------------
  35. /**
  36. * Represents an array containing configuration information for ESLint.
  37. */
  38. class FlatConfigArray extends ConfigArray {
  39. /**
  40. * Creates a new instance.
  41. * @param {*[]} configs An array of configuration information.
  42. * @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options
  43. * to use for the config array instance.
  44. */
  45. constructor(configs, {
  46. basePath,
  47. shouldIgnore = true,
  48. baseConfig = defaultConfig
  49. } = {}) {
  50. super(configs, {
  51. basePath,
  52. schema: flatConfigSchema
  53. });
  54. if (baseConfig[Symbol.iterator]) {
  55. this.unshift(...baseConfig);
  56. } else {
  57. this.unshift(baseConfig);
  58. }
  59. /**
  60. * The base config used to build the config array.
  61. * @type {Array<FlatConfig>}
  62. */
  63. this[originalBaseConfig] = baseConfig;
  64. Object.defineProperty(this, originalBaseConfig, { writable: false });
  65. /**
  66. * Determines if `ignores` fields should be honored.
  67. * If true, then all `ignores` fields are honored.
  68. * if false, then only `ignores` fields in the baseConfig are honored.
  69. * @type {boolean}
  70. */
  71. this.shouldIgnore = shouldIgnore;
  72. Object.defineProperty(this, "shouldIgnore", { writable: false });
  73. }
  74. /* eslint-disable class-methods-use-this -- Desired as instance method */
  75. /**
  76. * Replaces a config with another config to allow us to put strings
  77. * in the config array that will be replaced by objects before
  78. * normalization.
  79. * @param {Object} config The config to preprocess.
  80. * @returns {Object} The preprocessed config.
  81. */
  82. [ConfigArraySymbol.preprocessConfig](config) {
  83. if (config === "eslint:recommended") {
  84. return recommendedConfig;
  85. }
  86. if (config === "eslint:all") {
  87. /*
  88. * Load `eslint-all.js` here instead of at the top level to avoid loading all rule modules
  89. * when it isn't necessary. `eslint-all.js` reads `meta` of rule objects to filter out deprecated ones,
  90. * so requiring `eslint-all.js` module loads all rule modules as a consequence.
  91. */
  92. return require("../../conf/eslint-all");
  93. }
  94. /*
  95. * If `shouldIgnore` is false, we remove any ignore patterns specified
  96. * in the config so long as it's not a default config and it doesn't
  97. * have a `files` entry.
  98. */
  99. if (
  100. !this.shouldIgnore &&
  101. !this[originalBaseConfig].includes(config) &&
  102. config.ignores &&
  103. !config.files
  104. ) {
  105. /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
  106. const { ignores, ...otherKeys } = config;
  107. return otherKeys;
  108. }
  109. return config;
  110. }
  111. /**
  112. * Finalizes the config by replacing plugin references with their objects
  113. * and validating rule option schemas.
  114. * @param {Object} config The config to finalize.
  115. * @returns {Object} The finalized config.
  116. * @throws {TypeError} If the config is invalid.
  117. */
  118. [ConfigArraySymbol.finalizeConfig](config) {
  119. const { plugins, languageOptions, processor } = config;
  120. let parserName, processorName;
  121. let invalidParser = false,
  122. invalidProcessor = false;
  123. // Check parser value
  124. if (languageOptions && languageOptions.parser) {
  125. if (typeof languageOptions.parser === "string") {
  126. const { pluginName, objectName: localParserName } = splitPluginIdentifier(languageOptions.parser);
  127. parserName = languageOptions.parser;
  128. if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[localParserName]) {
  129. throw new TypeError(`Key "parser": Could not find "${localParserName}" in plugin "${pluginName}".`);
  130. }
  131. languageOptions.parser = plugins[pluginName].parsers[localParserName];
  132. } else {
  133. invalidParser = true;
  134. }
  135. }
  136. // Check processor value
  137. if (processor) {
  138. if (typeof processor === "string") {
  139. const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor);
  140. processorName = processor;
  141. if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[localProcessorName]) {
  142. throw new TypeError(`Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`);
  143. }
  144. config.processor = plugins[pluginName].processors[localProcessorName];
  145. } else {
  146. invalidProcessor = true;
  147. }
  148. }
  149. ruleValidator.validate(config);
  150. // apply special logic for serialization into JSON
  151. /* eslint-disable object-shorthand -- shorthand would change "this" value */
  152. Object.defineProperty(config, "toJSON", {
  153. value: function() {
  154. if (invalidParser) {
  155. throw new Error("Caching is not supported when parser is an object.");
  156. }
  157. if (invalidProcessor) {
  158. throw new Error("Caching is not supported when processor is an object.");
  159. }
  160. return {
  161. ...this,
  162. plugins: Object.keys(plugins),
  163. languageOptions: {
  164. ...languageOptions,
  165. parser: parserName
  166. },
  167. processor: processorName
  168. };
  169. }
  170. });
  171. /* eslint-enable object-shorthand -- ok to enable now */
  172. return config;
  173. }
  174. /* eslint-enable class-methods-use-this -- Desired as instance method */
  175. }
  176. exports.FlatConfigArray = FlatConfigArray;