text: /**
   * @fileoverview `ConfigArray` class.
   *
   * `ConfigArray` class expresses the full of a configuration. It has the entry
   * config file, base config files that were extended, loaded parsers, and loaded
   * plugins.
   *
   * `ConfigArray` class provides three properties and two methods.
   *
   * - `pluginEnvironments`
   * - `pluginProcessors`
   * - `pluginRules`
   *      The `Map` objects that contain the members of all plugins that this
   *      config array contains. Those map objects don't have mutation methods.
   *      Those keys are the member ID such as `pluginId/memberName`.
   * - `isRoot()`
   *      If `true` then this configuration has `root:true` property.
   * - `extractConfig(filePath)`
   *      Extract the final configuration for a given file. This means merging
   *      every config array element which that `criteria` property matched. The
   *      `filePath` argument must be an absolute path.
   *
   * `ConfigArrayFactory` provides the loading logic of config files.
   *
   * @author Toru Nagashima <https://github.com/mysticatea>
   */
  "use strict";
  
  //------------------------------------------------------------------------------
  // Requirements
  //------------------------------------------------------------------------------
  
  const { ExtractedConfig } = require("./extracted-config");
  const { IgnorePattern } = require("./ignore-pattern");
  
  //------------------------------------------------------------------------------
  // Helpers
  //------------------------------------------------------------------------------
  
  // Define types for VSCode IntelliSense.
  /** @typedef {import("../../shared/types").Environment} Environment */
  /** @typedef {import("../../shared/types").GlobalConf} GlobalConf */
  /** @typedef {import("../../shared/types").RuleConf} RuleConf */
  /** @typedef {import("../../shared/types").Rule} Rule */
  /** @typedef {import("../../shared/types").Plugin} Plugin */
  /** @typedef {import("../../shared/types").Processor} Processor */
  /** @typedef {import("./config-dependency").DependentParser} DependentParser */
  /** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */
  /** @typedef {import("./override-tester")["OverrideTester"]} OverrideTester */
  
  /**
   * @typedef {Object} ConfigArrayElement
   * @property {string} name The name of this config element.
   * @property {string} filePath The path to the source file of this config element.
   * @property {InstanceType<OverrideTester>|null} criteria The tester for the `files` and `excludedFiles` of this config element.
   * @property {Record<string, boolean>|undefined} env The environment settings.
   * @property {Record<string, GlobalConf>|undefined} globals The global variable settings.
   * @property {IgnorePattern|undefined} ignorePattern The ignore patterns.
   * @property {boolean|undefined} noInlineConfig The flag that disables directive comments.
   * @property {DependentParser|undefined} parser The parser loader.
   * @property {Object|undefined} parserOptions The parser options.
   * @property {Record<string, DependentPlugin>|undefined} plugins The plugin loaders.
   * @property {string|undefined} processor The processor name to refer plugin's processor.
   * @property {boolean|undefined} reportUnusedDisableDirectives The flag to report unused `eslint-disable` comments.
   * @property {boolean|undefined} root The flag to express root.
   * @property {Record<string, RuleConf>|undefined} rules The rule settings
   * @property {Object|undefined} settings The shared settings.
   * @property {"config" | "ignore" | "implicit-processor"} type The element type.
   */
  
  /**
   * @typedef {Object} ConfigArrayInternalSlots
   * @property {Map<string, ExtractedConfig>} cache The cache to extract configs.
   * @property {ReadonlyMap<string, Environment>|null} envMap The map from environment ID to environment definition.
   * @property {ReadonlyMap<string, Processor>|null} processorMap The map from processor ID to environment definition.
   * @property {ReadonlyMap<string, Rule>|null} ruleMap The map from rule ID to rule definition.
   */
  
  /** @type {WeakMap<ConfigArray, ConfigArrayInternalSlots>} */
  const internalSlotsMap = new class extends WeakMap {
      get(key) {
          let value = super.get(key);
  
          if (!value) {
              value = {
                  cache: new Map(),
                  envMap: null,
                  processorMap: null,
                  ruleMap: null
              };
              super.set(key, value);
          }
  
          return value;
      }
  }();
  
  /**
   * Get the indices which are matched to a given file.
   * @param {ConfigArrayElement[]} elements The elements.
   * @param {string} filePath The path to a target file.
   * @returns {number[]} The indices.
   */
  function getMatchedIndices(elements, filePath) {
      const indices = [];
  
      for (let i = elements.length - 1; i >= 0; --i) {
          const element = elements[i];
  
          if (!element.criteria || (filePath && element.criteria.test(filePath))) {
              indices.push(i);
          }
      }
  
      return indices;
  }
  
  /**
   * Check if a value is a non-null object.
   * @param {any} x The value to check.
   * @returns {boolean} `true` if the value is a non-null object.
   */
  function isNonNullObject(x) {
      return typeof x === "object" && x !== null;
  }
  
  /**
   * Merge two objects.
   *
   * Assign every property values of `y` to `x` if `x` doesn't have the property.
   * If `x`'s property value is an object, it does recursive.
   * @param {Object} target The destination to merge
   * @param {Object|undefined} source The source to merge.
   * @returns {void}
   */
  function mergeWithoutOverwrite(target, source) {
      if (!isNonNullObject(source)) {
          return;
      }
  
      for (const key of Object.keys(source)) {
          if (key === "__proto__") {
              continue;
          }
  
          if (isNonNullObject(target[key])) {
              mergeWithoutOverwrite(target[key], source[key]);
          } else if (target[key] === void 0) {
              if (isNonNullObject(source[key])) {
                  target[key] = Array.isArray(source[key]) ? [] : {};
                  mergeWithoutOverwrite(target[key], source[key]);
              } else if (source[key] !== void 0) {
                  target[key] = source[key];
              }
          }
      }
  }
  
  /**
   * The error for plugin conflicts.
   */
  class PluginConflictError extends Error {
  
      /**
       * Initialize this error object.
       * @param {string} pluginId The plugin ID.
       * @param {{filePath:string, importerName:string}[]} plugins The resolved plugins.
       */
      constructor(pluginId, plugins) {
          super(`Plugin "${pluginId}" was conflicted between ${plugins.map(p => `"${p.importerName}"`).join(" and ")}.`);
          this.messageTemplate = "plugin-conflict";
          this.messageData = { pluginId, plugins };
      }
  }
  
  /**
   * Merge plugins.
   * `target`'s definition is prior to `source`'s.
   * @param {Record<string, DependentPlugin>} target The destination to merge
   * @param {Record<string, DependentPlugin>|undefined} source The source to merge.
   * @returns {void}
   */
  function mergePlugins(target, source) {
      if (!isNonNullObject(source)) {
          return;
      }
  
      for (const key of Object.keys(source)) {
          if (key === "__proto__") {
              continue;
          }
          const targetValue = target[key];
          const sourceValue = source[key];
  
          // Adopt the plugin which was found at first.
          if (targetValue === void 0) {
              if (sourceValue.error) {
                  throw sourceValue.error;
              }
              target[key] = sourceValue;
          } else if (sourceValue.filePath !== targetValue.filePath) {
              throw new PluginConflictError(key, [
                  {
                      filePath: targetValue.filePath,
                      importerName: targetValue.importerName
                  },
                  {
                      filePath: sourceValue.filePath,
                      importerName: sourceValue.importerName
                  }
              ]);
          }
      }
  }
  
  /**
   * Merge rule configs.
   * `target`'s definition is prior to `source`'s.
   * @param {Record<string, Array>} target The destination to merge
   * @param {Record<string, RuleConf>|undefined} source The source to merge.
   * @returns {void}
   */
  function mergeRuleConfigs(target, source) {
      if (!isNonNullObject(source)) {
          return;
      }
  
      for (const key of Object.keys(source)) {
          if (key === "__proto__") {
              continue;
          }
          const targetDef = target[key];
          const sourceDef = source[key];
  
          // Adopt the rule config which was found at first.
          if (targetDef === void 0) {
              if (Array.isArray(sourceDef)) {
                  target[key] = [...sourceDef];
              } else {
                  target[key] = [sourceDef];
              }
  
          /*
           * If the first found rule config is severity only and the current rule
           * config has options, merge the severity and the options.
           */
          } else if (
              targetDef.length === 1 &&
              Array.isArray(sourceDef) &&
              sourceDef.length >= 2
          ) {
              targetDef.push(...sourceDef.slice(1));
          }
      }
  }
  
  /**
   * Create the extracted config.
   * @param {ConfigArray} instance The config elements.
   * @param {number[]} indices The indices to use.
   * @returns {ExtractedConfig} The extracted config.
   */
  function createConfig(instance, indices) {
      const config = new ExtractedConfig();
      const ignorePatterns = [];
  
      // Merge elements.
      for (const index of indices) {
          const element = instance[index];
  
          // Adopt the parser which was found at first.
          if (!config.parser && element.parser) {
              if (element.parser.error) {
                  throw element.parser.error;
              }
              config.parser = element.parser;
          }
  
          // Adopt the processor which was found at first.
          if (!config.processor && element.processor) {
              config.processor = element.processor;
          }
  
          // Adopt the noInlineConfig which was found at first.
          if (config.noInlineConfig === void 0 && element.noInlineConfig !== void 0) {
              config.noInlineConfig = element.noInlineConfig;
              config.configNameOfNoInlineConfig = element.name;
          }
  
          // Adopt the reportUnusedDisableDirectives which was found at first.
          if (config.reportUnusedDisableDirectives === void 0 && element.reportUnusedDisableDirectives !== void 0) {
              config.reportUnusedDisableDirectives = element.reportUnusedDisableDirectives;
          }
  
          // Collect ignorePatterns
          if (element.ignorePattern) {
              ignorePatterns.push(element.ignorePattern);
          }
  
          // Merge others.
          mergeWithoutOverwrite(config.env, element.env);
          mergeWithoutOverwrite(config.globals, element.globals);
          mergeWithoutOverwrite(config.parserOptions, element.parserOptions);
          mergeWithoutOverwrite(config.settings, element.settings);
          mergePlugins(config.plugins, element.plugins);
          mergeRuleConfigs(config.rules, element.rules);
      }
  
      // Create the predicate function for ignore patterns.
      if (ignorePatterns.length > 0) {
          config.ignores = IgnorePattern.createIgnore(ignorePatterns.reverse());
      }
  
      return config;
  }
  
  /**
   * Collect definitions.
   * @template T, U
   * @param {string} pluginId The plugin ID for prefix.
   * @param {Record<string,T>} defs The definitions to collect.
   * @param {Map<string, U>} map The map to output.
   * @param {function(T): U} [normalize] The normalize function for each value.
   * @returns {void}
   */
  function collect(pluginId, defs, map, normalize) {
      if (defs) {
          const prefix = pluginId && `${pluginId}/`;
  
          for (const [key, value] of Object.entries(defs)) {
              map.set(
                  `${prefix}${key}`,
                  normalize ? normalize(value) : value
              );
          }
      }
  }
  
  /**
   * Normalize a rule definition.
   * @param {Function|Rule} rule The rule definition to normalize.
   * @returns {Rule} The normalized rule definition.
   */
  function normalizePluginRule(rule) {
      return typeof rule === "function" ? { create: rule } : rule;
  }
  
  /**
   * Delete the mutation methods from a given map.
   * @param {Map<any, any>} map The map object to delete.
   * @returns {void}
   */
  function deleteMutationMethods(map) {
      Object.defineProperties(map, {
          clear: { configurable: true, value: void 0 },
          delete: { configurable: true, value: void 0 },
          set: { configurable: true, value: void 0 }
      });
  }
  
  /**
   * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
   * @param {ConfigArrayElement[]} elements The config elements.
   * @param {ConfigArrayInternalSlots} slots The internal slots.
   * @returns {void}
   */
  function initPluginMemberMaps(elements, slots) {
      const processed = new Set();
  
      slots.envMap = new Map();
      slots.processorMap = new Map();
      slots.ruleMap = new Map();
  
      for (const element of elements) {
          if (!element.plugins) {
              continue;
          }
  
          for (const [pluginId, value] of Object.entries(element.plugins)) {
              const plugin = value.definition;
  
              if (!plugin || processed.has(pluginId)) {
                  continue;
              }
              processed.add(pluginId);
  
              collect(pluginId, plugin.environments, slots.envMap);
              collect(pluginId, plugin.processors, slots.processorMap);
              collect(pluginId, plugin.rules, slots.ruleMap, normalizePluginRule);
          }
      }
  
      deleteMutationMethods(slots.envMap);
      deleteMutationMethods(slots.processorMap);
      deleteMutationMethods(slots.ruleMap);
  }
  
  /**
   * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
   * @param {ConfigArray} instance The config elements.
   * @returns {ConfigArrayInternalSlots} The extracted config.
   */
  function ensurePluginMemberMaps(instance) {
      const slots = internalSlotsMap.get(instance);
  
      if (!slots.ruleMap) {
          initPluginMemberMaps(instance, slots);
      }
  
      return slots;
  }
  
  //------------------------------------------------------------------------------
  // Public Interface
  //------------------------------------------------------------------------------
  
  /**
   * The Config Array.
   *
   * `ConfigArray` instance contains all settings, parsers, and plugins.
   * You need to call `ConfigArray#extractConfig(filePath)` method in order to
   * extract, merge and get only the config data which is related to an arbitrary
   * file.
   * @extends {Array<ConfigArrayElement>}
   */
  class ConfigArray extends Array {
  
      /**
       * Get the plugin environments.
       * The returned map cannot be mutated.
       * @type {ReadonlyMap<string, Environment>} The plugin environments.
       */
      get pluginEnvironments() {
          return ensurePluginMemberMaps(this).envMap;
      }
  
      /**
       * Get the plugin processors.
       * The returned map cannot be mutated.
       * @type {ReadonlyMap<string, Processor>} The plugin processors.
       */
      get pluginProcessors() {
          return ensurePluginMemberMaps(this).processorMap;
      }
  
      /**
       * Get the plugin rules.
       * The returned map cannot be mutated.
       * @returns {ReadonlyMap<string, Rule>} The plugin rules.
       */
      get pluginRules() {
          return ensurePluginMemberMaps(this).ruleMap;
      }
  
      /**
       * Check if this config has `root` flag.
       * @returns {boolean} `true` if this config array is root.
       */
      isRoot() {
          for (let i = this.length - 1; i >= 0; --i) {
              const root = this[i].root;
  
              if (typeof root === "boolean") {
                  return root;
              }
          }
          return false;
      }
  
      /**
       * Extract the config data which is related to a given file.
       * @param {string} filePath The absolute path to the target file.
       * @returns {ExtractedConfig} The extracted config data.
       */
      extractConfig(filePath) {
          const { cache } = internalSlotsMap.get(this);
          const indices = getMatchedIndices(this, filePath);
          const cacheKey = indices.join(",");
  
          if (!cache.has(cacheKey)) {
              cache.set(cacheKey, createConfig(this, indices));
          }
  
          return cache.get(cacheKey);
      }
  
      /**
       * Check if a given path is an additional lint target.
       * @param {string} filePath The absolute path to the target file.
       * @returns {boolean} `true` if the file is an additional lint target.
       */
      isAdditionalTargetPath(filePath) {
          for (const { criteria, type } of this) {
              if (
                  type === "config" &&
                  criteria &&
                  !criteria.endsWithWildcard &&
                  criteria.test(filePath)
              ) {
                  return true;
              }
          }
          return false;
      }
  }
  
  const exportObject = {
      ConfigArray,
  
      /**
       * Get the used extracted configs.
       * CLIEngine will use this method to collect used deprecated rules.
       * @param {ConfigArray} instance The config array object to get.
       * @returns {ExtractedConfig[]} The used extracted configs.
       * @private
       */
      getUsedExtractedConfigs(instance) {
          const { cache } = internalSlotsMap.get(instance);
  
          return Array.from(cache.values());
      }
  };
  
  module.exports = exportObject;
  