| /** | 
|  * @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; |