Actualizacion maquina principal
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / cli-engine / config-array / config-array.js
1 /**
2  * @fileoverview `ConfigArray` class.
3  *
4  * `ConfigArray` class expresses the full of a configuration. It has the entry
5  * config file, base config files that were extended, loaded parsers, and loaded
6  * plugins.
7  *
8  * `ConfigArray` class provies three properties and two methods.
9  *
10  * - `pluginEnvironments`
11  * - `pluginProcessors`
12  * - `pluginRules`
13  *      The `Map` objects that contain the members of all plugins that this
14  *      config array contains. Those map objects don't have mutation methods.
15  *      Those keys are the member ID such as `pluginId/memberName`.
16  * - `isRoot()`
17  *      If `true` then this configuration has `root:true` property.
18  * - `extractConfig(filePath)`
19  *      Extract the final configuration for a given file. This means merging
20  *      every config array element which that `criteria` property matched. The
21  *      `filePath` argument must be an absolute path.
22  *
23  * `ConfigArrayFactory` provides the loading logic of config files.
24  *
25  * @author Toru Nagashima <https://github.com/mysticatea>
26  */
27 "use strict";
28
29 //------------------------------------------------------------------------------
30 // Requirements
31 //------------------------------------------------------------------------------
32
33 const { ExtractedConfig } = require("./extracted-config");
34 const { IgnorePattern } = require("./ignore-pattern");
35
36 //------------------------------------------------------------------------------
37 // Helpers
38 //------------------------------------------------------------------------------
39
40 // Define types for VSCode IntelliSense.
41 /** @typedef {import("../../shared/types").Environment} Environment */
42 /** @typedef {import("../../shared/types").GlobalConf} GlobalConf */
43 /** @typedef {import("../../shared/types").RuleConf} RuleConf */
44 /** @typedef {import("../../shared/types").Rule} Rule */
45 /** @typedef {import("../../shared/types").Plugin} Plugin */
46 /** @typedef {import("../../shared/types").Processor} Processor */
47 /** @typedef {import("./config-dependency").DependentParser} DependentParser */
48 /** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */
49 /** @typedef {import("./override-tester")["OverrideTester"]} OverrideTester */
50
51 /**
52  * @typedef {Object} ConfigArrayElement
53  * @property {string} name The name of this config element.
54  * @property {string} filePath The path to the source file of this config element.
55  * @property {InstanceType<OverrideTester>|null} criteria The tester for the `files` and `excludedFiles` of this config element.
56  * @property {Record<string, boolean>|undefined} env The environment settings.
57  * @property {Record<string, GlobalConf>|undefined} globals The global variable settings.
58  * @property {IgnorePattern|undefined} ignorePattern The ignore patterns.
59  * @property {boolean|undefined} noInlineConfig The flag that disables directive comments.
60  * @property {DependentParser|undefined} parser The parser loader.
61  * @property {Object|undefined} parserOptions The parser options.
62  * @property {Record<string, DependentPlugin>|undefined} plugins The plugin loaders.
63  * @property {string|undefined} processor The processor name to refer plugin's processor.
64  * @property {boolean|undefined} reportUnusedDisableDirectives The flag to report unused `eslint-disable` comments.
65  * @property {boolean|undefined} root The flag to express root.
66  * @property {Record<string, RuleConf>|undefined} rules The rule settings
67  * @property {Object|undefined} settings The shared settings.
68  */
69
70 /**
71  * @typedef {Object} ConfigArrayInternalSlots
72  * @property {Map<string, ExtractedConfig>} cache The cache to extract configs.
73  * @property {ReadonlyMap<string, Environment>|null} envMap The map from environment ID to environment definition.
74  * @property {ReadonlyMap<string, Processor>|null} processorMap The map from processor ID to environment definition.
75  * @property {ReadonlyMap<string, Rule>|null} ruleMap The map from rule ID to rule definition.
76  */
77
78 /** @type {WeakMap<ConfigArray, ConfigArrayInternalSlots>} */
79 const internalSlotsMap = new class extends WeakMap {
80     get(key) {
81         let value = super.get(key);
82
83         if (!value) {
84             value = {
85                 cache: new Map(),
86                 envMap: null,
87                 processorMap: null,
88                 ruleMap: null
89             };
90             super.set(key, value);
91         }
92
93         return value;
94     }
95 }();
96
97 /**
98  * Get the indices which are matched to a given file.
99  * @param {ConfigArrayElement[]} elements The elements.
100  * @param {string} filePath The path to a target file.
101  * @returns {number[]} The indices.
102  */
103 function getMatchedIndices(elements, filePath) {
104     const indices = [];
105
106     for (let i = elements.length - 1; i >= 0; --i) {
107         const element = elements[i];
108
109         if (!element.criteria || element.criteria.test(filePath)) {
110             indices.push(i);
111         }
112     }
113
114     return indices;
115 }
116
117 /**
118  * Check if a value is a non-null object.
119  * @param {any} x The value to check.
120  * @returns {boolean} `true` if the value is a non-null object.
121  */
122 function isNonNullObject(x) {
123     return typeof x === "object" && x !== null;
124 }
125
126 /**
127  * Merge two objects.
128  *
129  * Assign every property values of `y` to `x` if `x` doesn't have the property.
130  * If `x`'s property value is an object, it does recursive.
131  * @param {Object} target The destination to merge
132  * @param {Object|undefined} source The source to merge.
133  * @returns {void}
134  */
135 function mergeWithoutOverwrite(target, source) {
136     if (!isNonNullObject(source)) {
137         return;
138     }
139
140     for (const key of Object.keys(source)) {
141         if (key === "__proto__") {
142             continue;
143         }
144
145         if (isNonNullObject(target[key])) {
146             mergeWithoutOverwrite(target[key], source[key]);
147         } else if (target[key] === void 0) {
148             if (isNonNullObject(source[key])) {
149                 target[key] = Array.isArray(source[key]) ? [] : {};
150                 mergeWithoutOverwrite(target[key], source[key]);
151             } else if (source[key] !== void 0) {
152                 target[key] = source[key];
153             }
154         }
155     }
156 }
157
158 /**
159  * Merge plugins.
160  * `target`'s definition is prior to `source`'s.
161  * @param {Record<string, DependentPlugin>} target The destination to merge
162  * @param {Record<string, DependentPlugin>|undefined} source The source to merge.
163  * @returns {void}
164  */
165 function mergePlugins(target, source) {
166     if (!isNonNullObject(source)) {
167         return;
168     }
169
170     for (const key of Object.keys(source)) {
171         if (key === "__proto__") {
172             continue;
173         }
174         const targetValue = target[key];
175         const sourceValue = source[key];
176
177         // Adopt the plugin which was found at first.
178         if (targetValue === void 0) {
179             if (sourceValue.error) {
180                 throw sourceValue.error;
181             }
182             target[key] = sourceValue;
183         }
184     }
185 }
186
187 /**
188  * Merge rule configs.
189  * `target`'s definition is prior to `source`'s.
190  * @param {Record<string, Array>} target The destination to merge
191  * @param {Record<string, RuleConf>|undefined} source The source to merge.
192  * @returns {void}
193  */
194 function mergeRuleConfigs(target, source) {
195     if (!isNonNullObject(source)) {
196         return;
197     }
198
199     for (const key of Object.keys(source)) {
200         if (key === "__proto__") {
201             continue;
202         }
203         const targetDef = target[key];
204         const sourceDef = source[key];
205
206         // Adopt the rule config which was found at first.
207         if (targetDef === void 0) {
208             if (Array.isArray(sourceDef)) {
209                 target[key] = [...sourceDef];
210             } else {
211                 target[key] = [sourceDef];
212             }
213
214         /*
215          * If the first found rule config is severity only and the current rule
216          * config has options, merge the severity and the options.
217          */
218         } else if (
219             targetDef.length === 1 &&
220             Array.isArray(sourceDef) &&
221             sourceDef.length >= 2
222         ) {
223             targetDef.push(...sourceDef.slice(1));
224         }
225     }
226 }
227
228 /**
229  * Create the extracted config.
230  * @param {ConfigArray} instance The config elements.
231  * @param {number[]} indices The indices to use.
232  * @returns {ExtractedConfig} The extracted config.
233  */
234 function createConfig(instance, indices) {
235     const config = new ExtractedConfig();
236     const ignorePatterns = [];
237
238     // Merge elements.
239     for (const index of indices) {
240         const element = instance[index];
241
242         // Adopt the parser which was found at first.
243         if (!config.parser && element.parser) {
244             if (element.parser.error) {
245                 throw element.parser.error;
246             }
247             config.parser = element.parser;
248         }
249
250         // Adopt the processor which was found at first.
251         if (!config.processor && element.processor) {
252             config.processor = element.processor;
253         }
254
255         // Adopt the noInlineConfig which was found at first.
256         if (config.noInlineConfig === void 0 && element.noInlineConfig !== void 0) {
257             config.noInlineConfig = element.noInlineConfig;
258             config.configNameOfNoInlineConfig = element.name;
259         }
260
261         // Adopt the reportUnusedDisableDirectives which was found at first.
262         if (config.reportUnusedDisableDirectives === void 0 && element.reportUnusedDisableDirectives !== void 0) {
263             config.reportUnusedDisableDirectives = element.reportUnusedDisableDirectives;
264         }
265
266         // Collect ignorePatterns
267         if (element.ignorePattern) {
268             ignorePatterns.push(element.ignorePattern);
269         }
270
271         // Merge others.
272         mergeWithoutOverwrite(config.env, element.env);
273         mergeWithoutOverwrite(config.globals, element.globals);
274         mergeWithoutOverwrite(config.parserOptions, element.parserOptions);
275         mergeWithoutOverwrite(config.settings, element.settings);
276         mergePlugins(config.plugins, element.plugins);
277         mergeRuleConfigs(config.rules, element.rules);
278     }
279
280     // Create the predicate function for ignore patterns.
281     if (ignorePatterns.length > 0) {
282         config.ignores = IgnorePattern.createIgnore(ignorePatterns.reverse());
283     }
284
285     return config;
286 }
287
288 /**
289  * Collect definitions.
290  * @template T, U
291  * @param {string} pluginId The plugin ID for prefix.
292  * @param {Record<string,T>} defs The definitions to collect.
293  * @param {Map<string, U>} map The map to output.
294  * @param {function(T): U} [normalize] The normalize function for each value.
295  * @returns {void}
296  */
297 function collect(pluginId, defs, map, normalize) {
298     if (defs) {
299         const prefix = pluginId && `${pluginId}/`;
300
301         for (const [key, value] of Object.entries(defs)) {
302             map.set(
303                 `${prefix}${key}`,
304                 normalize ? normalize(value) : value
305             );
306         }
307     }
308 }
309
310 /**
311  * Normalize a rule definition.
312  * @param {Function|Rule} rule The rule definition to normalize.
313  * @returns {Rule} The normalized rule definition.
314  */
315 function normalizePluginRule(rule) {
316     return typeof rule === "function" ? { create: rule } : rule;
317 }
318
319 /**
320  * Delete the mutation methods from a given map.
321  * @param {Map<any, any>} map The map object to delete.
322  * @returns {void}
323  */
324 function deleteMutationMethods(map) {
325     Object.defineProperties(map, {
326         clear: { configurable: true, value: void 0 },
327         delete: { configurable: true, value: void 0 },
328         set: { configurable: true, value: void 0 }
329     });
330 }
331
332 /**
333  * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
334  * @param {ConfigArrayElement[]} elements The config elements.
335  * @param {ConfigArrayInternalSlots} slots The internal slots.
336  * @returns {void}
337  */
338 function initPluginMemberMaps(elements, slots) {
339     const processed = new Set();
340
341     slots.envMap = new Map();
342     slots.processorMap = new Map();
343     slots.ruleMap = new Map();
344
345     for (const element of elements) {
346         if (!element.plugins) {
347             continue;
348         }
349
350         for (const [pluginId, value] of Object.entries(element.plugins)) {
351             const plugin = value.definition;
352
353             if (!plugin || processed.has(pluginId)) {
354                 continue;
355             }
356             processed.add(pluginId);
357
358             collect(pluginId, plugin.environments, slots.envMap);
359             collect(pluginId, plugin.processors, slots.processorMap);
360             collect(pluginId, plugin.rules, slots.ruleMap, normalizePluginRule);
361         }
362     }
363
364     deleteMutationMethods(slots.envMap);
365     deleteMutationMethods(slots.processorMap);
366     deleteMutationMethods(slots.ruleMap);
367 }
368
369 /**
370  * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
371  * @param {ConfigArray} instance The config elements.
372  * @returns {ConfigArrayInternalSlots} The extracted config.
373  */
374 function ensurePluginMemberMaps(instance) {
375     const slots = internalSlotsMap.get(instance);
376
377     if (!slots.ruleMap) {
378         initPluginMemberMaps(instance, slots);
379     }
380
381     return slots;
382 }
383
384 //------------------------------------------------------------------------------
385 // Public Interface
386 //------------------------------------------------------------------------------
387
388 /**
389  * The Config Array.
390  *
391  * `ConfigArray` instance contains all settings, parsers, and plugins.
392  * You need to call `ConfigArray#extractConfig(filePath)` method in order to
393  * extract, merge and get only the config data which is related to an arbitrary
394  * file.
395  * @extends {Array<ConfigArrayElement>}
396  */
397 class ConfigArray extends Array {
398
399     /**
400      * Get the plugin environments.
401      * The returned map cannot be mutated.
402      * @type {ReadonlyMap<string, Environment>} The plugin environments.
403      */
404     get pluginEnvironments() {
405         return ensurePluginMemberMaps(this).envMap;
406     }
407
408     /**
409      * Get the plugin processors.
410      * The returned map cannot be mutated.
411      * @type {ReadonlyMap<string, Processor>} The plugin processors.
412      */
413     get pluginProcessors() {
414         return ensurePluginMemberMaps(this).processorMap;
415     }
416
417     /**
418      * Get the plugin rules.
419      * The returned map cannot be mutated.
420      * @returns {ReadonlyMap<string, Rule>} The plugin rules.
421      */
422     get pluginRules() {
423         return ensurePluginMemberMaps(this).ruleMap;
424     }
425
426     /**
427      * Check if this config has `root` flag.
428      * @returns {boolean} `true` if this config array is root.
429      */
430     isRoot() {
431         for (let i = this.length - 1; i >= 0; --i) {
432             const root = this[i].root;
433
434             if (typeof root === "boolean") {
435                 return root;
436             }
437         }
438         return false;
439     }
440
441     /**
442      * Extract the config data which is related to a given file.
443      * @param {string} filePath The absolute path to the target file.
444      * @returns {ExtractedConfig} The extracted config data.
445      */
446     extractConfig(filePath) {
447         const { cache } = internalSlotsMap.get(this);
448         const indices = getMatchedIndices(this, filePath);
449         const cacheKey = indices.join(",");
450
451         if (!cache.has(cacheKey)) {
452             cache.set(cacheKey, createConfig(this, indices));
453         }
454
455         return cache.get(cacheKey);
456     }
457 }
458
459 const exportObject = {
460     ConfigArray,
461
462     /**
463      * Get the used extracted configs.
464      * CLIEngine will use this method to collect used deprecated rules.
465      * @param {ConfigArray} instance The config array object to get.
466      * @returns {ExtractedConfig[]} The used extracted configs.
467      * @private
468      */
469     getUsedExtractedConfigs(instance) {
470         const { cache } = internalSlotsMap.get(instance);
471
472         return Array.from(cache.values());
473     }
474 };
475
476 module.exports = exportObject;