flat-config-array.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /**
  2. * @fileoverview Flat Config Array
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //-----------------------------------------------------------------------------
  7. // Requirements
  8. //-----------------------------------------------------------------------------
  9. const { ConfigArray, ConfigArraySymbol } = require("@eslint/config-array");
  10. const { flatConfigSchema } = require("./flat-config-schema");
  11. const { defaultConfig } = require("./default-config");
  12. const { Config } = require("./config");
  13. //-----------------------------------------------------------------------------
  14. // Helpers
  15. //-----------------------------------------------------------------------------
  16. /**
  17. * Fields that are considered metadata and not part of the config object.
  18. */
  19. const META_FIELDS = new Set(["name", "basePath"]);
  20. /**
  21. * Wraps a config error with details about where the error occurred.
  22. * @param {Error} error The original error.
  23. * @param {number} originalLength The original length of the config array.
  24. * @param {number} baseLength The length of the base config.
  25. * @returns {TypeError} The new error with details.
  26. */
  27. function wrapConfigErrorWithDetails(error, originalLength, baseLength) {
  28. let location = "user-defined";
  29. let configIndex = error.index;
  30. /*
  31. * A config array is set up in this order:
  32. * 1. Base config
  33. * 2. Original configs
  34. * 3. User-defined configs
  35. * 4. CLI-defined configs
  36. *
  37. * So we need to adjust the index to account for the base config.
  38. *
  39. * - If the index is less than the base length, it's in the base config
  40. * (as specified by `baseConfig` argument to `FlatConfigArray` constructor).
  41. * - If the index is greater than the base length but less than the original
  42. * length + base length, it's in the original config. The original config
  43. * is passed to the `FlatConfigArray` constructor as the first argument.
  44. * - Otherwise, it's in the user-defined config, which is loaded from the
  45. * config file and merged with any command-line options.
  46. */
  47. if (error.index < baseLength) {
  48. location = "base";
  49. } else if (error.index < originalLength + baseLength) {
  50. location = "original";
  51. configIndex = error.index - baseLength;
  52. } else {
  53. configIndex = error.index - originalLength - baseLength;
  54. }
  55. return new TypeError(
  56. `${error.message.slice(0, -1)} at ${location} index ${configIndex}.`,
  57. { cause: error },
  58. );
  59. }
  60. const originalBaseConfig = Symbol("originalBaseConfig");
  61. const originalLength = Symbol("originalLength");
  62. const baseLength = Symbol("baseLength");
  63. //-----------------------------------------------------------------------------
  64. // Exports
  65. //-----------------------------------------------------------------------------
  66. /**
  67. * Represents an array containing configuration information for ESLint.
  68. */
  69. class FlatConfigArray extends ConfigArray {
  70. /**
  71. * Creates a new instance.
  72. * @param {*[]} configs An array of configuration information.
  73. * @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options
  74. * to use for the config array instance.
  75. */
  76. constructor(
  77. configs,
  78. { basePath, shouldIgnore = true, baseConfig = defaultConfig } = {},
  79. ) {
  80. super(configs, {
  81. basePath,
  82. schema: flatConfigSchema,
  83. });
  84. /**
  85. * The original length of the array before any modifications.
  86. * @type {number}
  87. */
  88. this[originalLength] = this.length;
  89. if (baseConfig[Symbol.iterator]) {
  90. this.unshift(...baseConfig);
  91. } else {
  92. this.unshift(baseConfig);
  93. }
  94. /**
  95. * The length of the array after applying the base config.
  96. * @type {number}
  97. */
  98. this[baseLength] = this.length - this[originalLength];
  99. /**
  100. * The base config used to build the config array.
  101. * @type {Array<FlatConfig>}
  102. */
  103. this[originalBaseConfig] = baseConfig;
  104. Object.defineProperty(this, originalBaseConfig, { writable: false });
  105. /**
  106. * Determines if `ignores` fields should be honored.
  107. * If true, then all `ignores` fields are honored.
  108. * if false, then only `ignores` fields in the baseConfig are honored.
  109. * @type {boolean}
  110. */
  111. this.shouldIgnore = shouldIgnore;
  112. Object.defineProperty(this, "shouldIgnore", { writable: false });
  113. }
  114. /**
  115. * Normalizes the array by calling the superclass method and catching/rethrowing
  116. * any ConfigError exceptions with additional details.
  117. * @param {any} [context] The context to use to normalize the array.
  118. * @returns {Promise<FlatConfigArray>} A promise that resolves when the array is normalized.
  119. */
  120. normalize(context) {
  121. return super.normalize(context).catch(error => {
  122. if (error.name === "ConfigError") {
  123. throw wrapConfigErrorWithDetails(
  124. error,
  125. this[originalLength],
  126. this[baseLength],
  127. );
  128. }
  129. throw error;
  130. });
  131. }
  132. /**
  133. * Normalizes the array by calling the superclass method and catching/rethrowing
  134. * any ConfigError exceptions with additional details.
  135. * @param {any} [context] The context to use to normalize the array.
  136. * @returns {FlatConfigArray} The current instance.
  137. * @throws {TypeError} If the config is invalid.
  138. */
  139. normalizeSync(context) {
  140. try {
  141. return super.normalizeSync(context);
  142. } catch (error) {
  143. if (error.name === "ConfigError") {
  144. throw wrapConfigErrorWithDetails(
  145. error,
  146. this[originalLength],
  147. this[baseLength],
  148. );
  149. }
  150. throw error;
  151. }
  152. }
  153. /* eslint-disable class-methods-use-this -- Desired as instance method */
  154. /**
  155. * Replaces a config with another config to allow us to put strings
  156. * in the config array that will be replaced by objects before
  157. * normalization.
  158. * @param {Object} config The config to preprocess.
  159. * @returns {Object} The preprocessed config.
  160. */
  161. [ConfigArraySymbol.preprocessConfig](config) {
  162. /*
  163. * If a config object has `ignores` and no other non-meta fields, then it's an object
  164. * for global ignores. If `shouldIgnore` is false, that object shouldn't apply,
  165. * so we'll remove its `ignores`.
  166. */
  167. if (
  168. !this.shouldIgnore &&
  169. !this[originalBaseConfig].includes(config) &&
  170. config.ignores &&
  171. Object.keys(config).filter(key => !META_FIELDS.has(key)).length ===
  172. 1
  173. ) {
  174. /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
  175. const { ignores, ...otherKeys } = config;
  176. return otherKeys;
  177. }
  178. return config;
  179. }
  180. /**
  181. * Finalizes the config by replacing plugin references with their objects
  182. * and validating rule option schemas.
  183. * @param {Object} config The config to finalize.
  184. * @returns {Object} The finalized config.
  185. * @throws {TypeError} If the config is invalid.
  186. */
  187. [ConfigArraySymbol.finalizeConfig](config) {
  188. return new Config(config);
  189. }
  190. /* eslint-enable class-methods-use-this -- Desired as instance method */
  191. }
  192. exports.FlatConfigArray = FlatConfigArray;