text: /**
   * @fileoverview `CascadingConfigArrayFactory` class.
   *
   * `CascadingConfigArrayFactory` class has a responsibility:
   *
   * 1. Handles cascading of config files.
   *
   * It provides two methods:
   *
   * - `getConfigArrayForFile(filePath)`
   *     Get the corresponded configuration of a given file. This method doesn't
   *     throw even if the given file didn't exist.
   * - `clearCache()`
   *     Clear the internal cache. You have to call this method when
   *     `additionalPluginPool` was updated if `baseConfig` or `cliConfig` depends
   *     on the additional plugins. (`CLIEngine#addPlugin()` method calls this.)
   *
   * @author Toru Nagashima <https://github.com/mysticatea>
   */
  "use strict";
  
  //------------------------------------------------------------------------------
  // Requirements
  //------------------------------------------------------------------------------
  
  const os = require("os");
  const path = require("path");
  const { validateConfigArray } = require("../shared/config-validator");
  const { emitDeprecationWarning } = require("../shared/deprecation-warnings");
  const { ConfigArrayFactory } = require("./config-array-factory");
  const { ConfigArray, ConfigDependency, IgnorePattern } = require("./config-array");
  const loadRules = require("./load-rules");
  const debug = require("debug")("eslint:cascading-config-array-factory");
  
  //------------------------------------------------------------------------------
  // Helpers
  //------------------------------------------------------------------------------
  
  // Define types for VSCode IntelliSense.
  /** @typedef {import("../shared/types").ConfigData} ConfigData */
  /** @typedef {import("../shared/types").Parser} Parser */
  /** @typedef {import("../shared/types").Plugin} Plugin */
  /** @typedef {ReturnType<ConfigArrayFactory["create"]>} ConfigArray */
  
  /**
   * @typedef {Object} CascadingConfigArrayFactoryOptions
   * @property {Map<string,Plugin>} [additionalPluginPool] The map for additional plugins.
   * @property {ConfigData} [baseConfig] The config by `baseConfig` option.
   * @property {ConfigData} [cliConfig] The config by CLI options (`--env`, `--global`, `--ignore-pattern`, `--parser`, `--parser-options`, `--plugin`, and `--rule`). CLI options overwrite the setting in config files.
   * @property {string} [cwd] The base directory to start lookup.
   * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
   * @property {string[]} [rulePaths] The value of `--rulesdir` option.
   * @property {string} [specificConfigPath] The value of `--config` option.
   * @property {boolean} [useEslintrc] if `false` then it doesn't load config files.
   */
  
  /**
   * @typedef {Object} CascadingConfigArrayFactoryInternalSlots
   * @property {ConfigArray} baseConfigArray The config array of `baseConfig` option.
   * @property {ConfigData} baseConfigData The config data of `baseConfig` option. This is used to reset `baseConfigArray`.
   * @property {ConfigArray} cliConfigArray The config array of CLI options.
   * @property {ConfigData} cliConfigData The config data of CLI options. This is used to reset `cliConfigArray`.
   * @property {ConfigArrayFactory} configArrayFactory The factory for config arrays.
   * @property {Map<string, ConfigArray>} configCache The cache from directory paths to config arrays.
   * @property {string} cwd The base directory to start lookup.
   * @property {WeakMap<ConfigArray, ConfigArray>} finalizeCache The cache from config arrays to finalized config arrays.
   * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
   * @property {string[]|null} rulePaths The value of `--rulesdir` option. This is used to reset `baseConfigArray`.
   * @property {string|null} specificConfigPath The value of `--config` option. This is used to reset `cliConfigArray`.
   * @property {boolean} useEslintrc if `false` then it doesn't load config files.
   */
  
  /** @type {WeakMap<CascadingConfigArrayFactory, CascadingConfigArrayFactoryInternalSlots>} */
  const internalSlotsMap = new WeakMap();
  
  /**
   * Create the config array from `baseConfig` and `rulePaths`.
   * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
   * @returns {ConfigArray} The config array of the base configs.
   */
  function createBaseConfigArray({
      configArrayFactory,
      baseConfigData,
      rulePaths,
      cwd
  }) {
      const baseConfigArray = configArrayFactory.create(
          baseConfigData,
          { name: "BaseConfig" }
      );
  
      /*
       * Create the config array element for the default ignore patterns.
       * This element has `ignorePattern` property that ignores the default
       * patterns in the current working directory.
       */
      baseConfigArray.unshift(configArrayFactory.create(
          { ignorePatterns: IgnorePattern.DefaultPatterns },
          { name: "DefaultIgnorePattern" }
      )[0]);
  
      /*
       * Load rules `--rulesdir` option as a pseudo plugin.
       * Use a pseudo plugin to define rules of `--rulesdir`, so we can validate
       * the rule's options with only information in the config array.
       */
      if (rulePaths && rulePaths.length > 0) {
          baseConfigArray.push({
              type: "config",
              name: "--rulesdir",
              filePath: "",
              plugins: {
                  "": new ConfigDependency({
                      definition: {
                          rules: rulePaths.reduce(
                              (map, rulesPath) => Object.assign(
                                  map,
                                  loadRules(rulesPath, cwd)
                              ),
                              {}
                          )
                      },
                      filePath: "",
                      id: "",
                      importerName: "--rulesdir",
                      importerPath: ""
                  })
              }
          });
      }
  
      return baseConfigArray;
  }
  
  /**
   * Create the config array from CLI options.
   * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
   * @returns {ConfigArray} The config array of the base configs.
   */
  function createCLIConfigArray({
      cliConfigData,
      configArrayFactory,
      cwd,
      ignorePath,
      specificConfigPath
  }) {
      const cliConfigArray = configArrayFactory.create(
          cliConfigData,
          { name: "CLIOptions" }
      );
  
      cliConfigArray.unshift(
          ...(ignorePath
              ? configArrayFactory.loadESLintIgnore(ignorePath)
              : configArrayFactory.loadDefaultESLintIgnore())
      );
  
      if (specificConfigPath) {
          cliConfigArray.unshift(
              ...configArrayFactory.loadFile(
                  specificConfigPath,
                  { name: "--config", basePath: cwd }
              )
          );
      }
  
      return cliConfigArray;
  }
  
  /**
   * The error type when there are files matched by a glob, but all of them have been ignored.
   */
  class ConfigurationNotFoundError extends Error {
  
      // eslint-disable-next-line jsdoc/require-description
      /**
       * @param {string} directoryPath The directory path.
       */
      constructor(directoryPath) {
          super(`No ESLint configuration found in ${directoryPath}.`);
          this.messageTemplate = "no-config-found";
          this.messageData = { directoryPath };
      }
  }
  
  /**
   * This class provides the functionality that enumerates every file which is
   * matched by given glob patterns and that configuration.
   */
  class CascadingConfigArrayFactory {
  
      /**
       * Initialize this enumerator.
       * @param {CascadingConfigArrayFactoryOptions} options The options.
       */
      constructor({
          additionalPluginPool = new Map(),
          baseConfig: baseConfigData = null,
          cliConfig: cliConfigData = null,
          cwd = process.cwd(),
          ignorePath,
          resolvePluginsRelativeTo,
          rulePaths = [],
          specificConfigPath = null,
          useEslintrc = true
      } = {}) {
          const configArrayFactory = new ConfigArrayFactory({
              additionalPluginPool,
              cwd,
              resolvePluginsRelativeTo
          });
  
          internalSlotsMap.set(this, {
              baseConfigArray: createBaseConfigArray({
                  baseConfigData,
                  configArrayFactory,
                  cwd,
                  rulePaths
              }),
              baseConfigData,
              cliConfigArray: createCLIConfigArray({
                  cliConfigData,
                  configArrayFactory,
                  cwd,
                  ignorePath,
                  specificConfigPath
              }),
              cliConfigData,
              configArrayFactory,
              configCache: new Map(),
              cwd,
              finalizeCache: new WeakMap(),
              ignorePath,
              rulePaths,
              specificConfigPath,
              useEslintrc
          });
      }
  
      /**
       * The path to the current working directory.
       * This is used by tests.
       * @type {string}
       */
      get cwd() {
          const { cwd } = internalSlotsMap.get(this);
  
          return cwd;
      }
  
      /**
       * Get the config array of a given file.
       * If `filePath` was not given, it returns the config which contains only
       * `baseConfigData` and `cliConfigData`.
       * @param {string} [filePath] The file path to a file.
       * @param {Object} [options] The options.
       * @param {boolean} [options.ignoreNotFoundError] If `true` then it doesn't throw `ConfigurationNotFoundError`.
       * @returns {ConfigArray} The config array of the file.
       */
      getConfigArrayForFile(filePath, { ignoreNotFoundError = false } = {}) {
          const {
              baseConfigArray,
              cliConfigArray,
              cwd
          } = internalSlotsMap.get(this);
  
          if (!filePath) {
              return new ConfigArray(...baseConfigArray, ...cliConfigArray);
          }
  
          const directoryPath = path.dirname(path.resolve(cwd, filePath));
  
          debug(`Load config files for ${directoryPath}.`);
  
          return this._finalizeConfigArray(
              this._loadConfigInAncestors(directoryPath),
              directoryPath,
              ignoreNotFoundError
          );
      }
  
      /**
       * Set the config data to override all configs.
       * Require to call `clearCache()` method after this method is called.
       * @param {ConfigData} configData The config data to override all configs.
       * @returns {void}
       */
      setOverrideConfig(configData) {
          const slots = internalSlotsMap.get(this);
  
          slots.cliConfigData = configData;
      }
  
      /**
       * Clear config cache.
       * @returns {void}
       */
      clearCache() {
          const slots = internalSlotsMap.get(this);
  
          slots.baseConfigArray = createBaseConfigArray(slots);
          slots.cliConfigArray = createCLIConfigArray(slots);
          slots.configCache.clear();
      }
  
      /**
       * Load and normalize config files from the ancestor directories.
       * @param {string} directoryPath The path to a leaf directory.
       * @param {boolean} configsExistInSubdirs `true` if configurations exist in subdirectories.
       * @returns {ConfigArray} The loaded config.
       * @private
       */
      _loadConfigInAncestors(directoryPath, configsExistInSubdirs = false) {
          const {
              baseConfigArray,
              configArrayFactory,
              configCache,
              cwd,
              useEslintrc
          } = internalSlotsMap.get(this);
  
          if (!useEslintrc) {
              return baseConfigArray;
          }
  
          let configArray = configCache.get(directoryPath);
  
          // Hit cache.
          if (configArray) {
              debug(`Cache hit: ${directoryPath}.`);
              return configArray;
          }
          debug(`No cache found: ${directoryPath}.`);
  
          const homePath = os.homedir();
  
          // Consider this is root.
          if (directoryPath === homePath && cwd !== homePath) {
              debug("Stop traversing because of considered root.");
              if (configsExistInSubdirs) {
                  const filePath = ConfigArrayFactory.getPathToConfigFileInDirectory(directoryPath);
  
                  if (filePath) {
                      emitDeprecationWarning(
                          filePath,
                          "ESLINT_PERSONAL_CONFIG_SUPPRESS"
                      );
                  }
              }
              return this._cacheConfig(directoryPath, baseConfigArray);
          }
  
          // Load the config on this directory.
          try {
              configArray = configArrayFactory.loadInDirectory(directoryPath);
          } catch (error) {
              /* istanbul ignore next */
              if (error.code === "EACCES") {
                  debug("Stop traversing because of 'EACCES' error.");
                  return this._cacheConfig(directoryPath, baseConfigArray);
              }
              throw error;
          }
  
          if (configArray.length > 0 && configArray.isRoot()) {
              debug("Stop traversing because of 'root:true'.");
              configArray.unshift(...baseConfigArray);
              return this._cacheConfig(directoryPath, configArray);
          }
  
          // Load from the ancestors and merge it.
          const parentPath = path.dirname(directoryPath);
          const parentConfigArray = parentPath && parentPath !== directoryPath
              ? this._loadConfigInAncestors(
                  parentPath,
                  configsExistInSubdirs || configArray.length > 0
              )
              : baseConfigArray;
  
          if (configArray.length > 0) {
              configArray.unshift(...parentConfigArray);
          } else {
              configArray = parentConfigArray;
          }
  
          // Cache and return.
          return this._cacheConfig(directoryPath, configArray);
      }
  
      /**
       * Freeze and cache a given config.
       * @param {string} directoryPath The path to a directory as a cache key.
       * @param {ConfigArray} configArray The config array as a cache value.
       * @returns {ConfigArray} The `configArray` (frozen).
       */
      _cacheConfig(directoryPath, configArray) {
          const { configCache } = internalSlotsMap.get(this);
  
          Object.freeze(configArray);
          configCache.set(directoryPath, configArray);
  
          return configArray;
      }
  
      /**
       * Finalize a given config array.
       * Concatenate `--config` and other CLI options.
       * @param {ConfigArray} configArray The parent config array.
       * @param {string} directoryPath The path to the leaf directory to find config files.
       * @param {boolean} ignoreNotFoundError If `true` then it doesn't throw `ConfigurationNotFoundError`.
       * @returns {ConfigArray} The loaded config.
       * @private
       */
      _finalizeConfigArray(configArray, directoryPath, ignoreNotFoundError) {
          const {
              cliConfigArray,
              configArrayFactory,
              finalizeCache,
              useEslintrc
          } = internalSlotsMap.get(this);
  
          let finalConfigArray = finalizeCache.get(configArray);
  
          if (!finalConfigArray) {
              finalConfigArray = configArray;
  
              // Load the personal config if there are no regular config files.
              if (
                  useEslintrc &&
                  configArray.every(c => !c.filePath) &&
                  cliConfigArray.every(c => !c.filePath) // `--config` option can be a file.
              ) {
                  const homePath = os.homedir();
  
                  debug("Loading the config file of the home directory:", homePath);
  
                  const personalConfigArray = configArrayFactory.loadInDirectory(
                      homePath,
                      { name: "PersonalConfig" }
                  );
  
                  if (
                      personalConfigArray.length > 0 &&
                      !directoryPath.startsWith(homePath)
                  ) {
                      const lastElement =
                          personalConfigArray[personalConfigArray.length - 1];
  
                      emitDeprecationWarning(
                          lastElement.filePath,
                          "ESLINT_PERSONAL_CONFIG_LOAD"
                      );
                  }
  
                  finalConfigArray = finalConfigArray.concat(personalConfigArray);
              }
  
              // Apply CLI options.
              if (cliConfigArray.length > 0) {
                  finalConfigArray = finalConfigArray.concat(cliConfigArray);
              }
  
              // Validate rule settings and environments.
              validateConfigArray(finalConfigArray);
  
              // Cache it.
              Object.freeze(finalConfigArray);
              finalizeCache.set(configArray, finalConfigArray);
  
              debug(
                  "Configuration was determined: %o on %s",
                  finalConfigArray,
                  directoryPath
              );
          }
  
          // At least one element (the default ignore patterns) exists.
          if (!ignoreNotFoundError && useEslintrc && finalConfigArray.length <= 1) {
              throw new ConfigurationNotFoundError(directoryPath);
          }
  
          return finalConfigArray;
      }
  }
  
  //------------------------------------------------------------------------------
  // Public Interface
  //------------------------------------------------------------------------------
  
  module.exports = { CascadingConfigArrayFactory };
  