| /** | 
|  * @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 ConfigValidator = require("./shared/config-validator"); | 
| const { emitDeprecationWarning } = require("./shared/deprecation-warnings"); | 
| const { ConfigArrayFactory } = require("./config-array-factory"); | 
| const { ConfigArray, ConfigDependency, IgnorePattern } = require("./config-array"); | 
| const debug = require("debug")("eslintrc: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 {import("./shared/types").Rule} Rule */ | 
| /** @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. | 
|  * @property {Function} loadRules The function to use to load rules. | 
|  * @property {Map<string,Rule>} builtInRules The rules that are built in to ESLint. | 
|  * @property {Object} [resolver=ModuleResolver] The module resolver object. | 
|  * @property {string} eslintAllPath The path to the definitions for eslint:all. | 
|  * @property {string} eslintRecommendedPath The path to the definitions for eslint:recommended. | 
|  */ | 
|   | 
| /** | 
|  * @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. | 
|  * @property {Function} loadRules The function to use to load rules. | 
|  * @property {Map<string,Rule>} builtInRules The rules that are built in to ESLint. | 
|  * @property {Object} [resolver=ModuleResolver] The module resolver object. | 
|  * @property {string} eslintAllPath The path to the definitions for eslint:all. | 
|  * @property {string} eslintRecommendedPath The path to the definitions for eslint:recommended. | 
|  */ | 
|   | 
| /** @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, | 
|     loadRules | 
| }) { | 
|     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, | 
|         builtInRules = new Map(), | 
|         loadRules, | 
|         resolver, | 
|         eslintRecommendedPath, | 
|         eslintAllPath | 
|     } = {}) { | 
|         const configArrayFactory = new ConfigArrayFactory({ | 
|             additionalPluginPool, | 
|             cwd, | 
|             resolvePluginsRelativeTo, | 
|             builtInRules, | 
|             resolver, | 
|             eslintRecommendedPath, | 
|             eslintAllPath | 
|         }); | 
|   | 
|         internalSlotsMap.set(this, { | 
|             baseConfigArray: createBaseConfigArray({ | 
|                 baseConfigData, | 
|                 configArrayFactory, | 
|                 cwd, | 
|                 rulePaths, | 
|                 loadRules, | 
|                 resolver | 
|             }), | 
|             baseConfigData, | 
|             cliConfigArray: createCLIConfigArray({ | 
|                 cliConfigData, | 
|                 configArrayFactory, | 
|                 cwd, | 
|                 ignorePath, | 
|                 specificConfigPath | 
|             }), | 
|             cliConfigData, | 
|             configArrayFactory, | 
|             configCache: new Map(), | 
|             cwd, | 
|             finalizeCache: new WeakMap(), | 
|             ignorePath, | 
|             rulePaths, | 
|             specificConfigPath, | 
|             useEslintrc, | 
|             builtInRules, | 
|             loadRules | 
|         }); | 
|     } | 
|   | 
|     /** | 
|      * 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, | 
|             builtInRules | 
|         } = 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. | 
|             const validator = new ConfigValidator({ | 
|                 builtInRules | 
|             }); | 
|   | 
|             validator.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 }; |