.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / @eslint / eslintrc / lib / 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 provides 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  * @property {"config" | "ignore" | "implicit-processor"} type The element type.
69  */
70
71 /**
72  * @typedef {Object} ConfigArrayInternalSlots
73  * @property {Map<string, ExtractedConfig>} cache The cache to extract configs.
74  * @property {ReadonlyMap<string, Environment>|null} envMap The map from environment ID to environment definition.
75  * @property {ReadonlyMap<string, Processor>|null} processorMap The map from processor ID to environment definition.
76  * @property {ReadonlyMap<string, Rule>|null} ruleMap The map from rule ID to rule definition.
77  */
78
79 /** @type {WeakMap<ConfigArray, ConfigArrayInternalSlots>} */
80 const internalSlotsMap = new class extends WeakMap {
81     get(key) {
82         let value = super.get(key);
83
84         if (!value) {
85             value = {
86                 cache: new Map(),
87                 envMap: null,
88                 processorMap: null,
89                 ruleMap: null
90             };
91             super.set(key, value);
92         }
93
94         return value;
95     }
96 }();
97
98 /**
99  * Get the indices which are matched to a given file.
100  * @param {ConfigArrayElement[]} elements The elements.
101  * @param {string} filePath The path to a target file.
102  * @returns {number[]} The indices.
103  */
104 function getMatchedIndices(elements, filePath) {
105     const indices = [];
106
107     for (let i = elements.length - 1; i >= 0; --i) {
108         const element = elements[i];
109
110         if (!element.criteria || (filePath && element.criteria.test(filePath))) {
111             indices.push(i);
112         }
113     }
114
115     return indices;
116 }
117
118 /**
119  * Check if a value is a non-null object.
120  * @param {any} x The value to check.
121  * @returns {boolean} `true` if the value is a non-null object.
122  */
123 function isNonNullObject(x) {
124     return typeof x === "object" && x !== null;
125 }
126
127 /**
128  * Merge two objects.
129  *
130  * Assign every property values of `y` to `x` if `x` doesn't have the property.
131  * If `x`'s property value is an object, it does recursive.
132  * @param {Object} target The destination to merge
133  * @param {Object|undefined} source The source to merge.
134  * @returns {void}
135  */
136 function mergeWithoutOverwrite(target, source) {
137     if (!isNonNullObject(source)) {
138         return;
139     }
140
141     for (const key of Object.keys(source)) {
142         if (key === "__proto__") {
143             continue;
144         }
145
146         if (isNonNullObject(target[key])) {
147             mergeWithoutOverwrite(target[key], source[key]);
148         } else if (target[key] === void 0) {
149             if (isNonNullObject(source[key])) {
150                 target[key] = Array.isArray(source[key]) ? [] : {};
151                 mergeWithoutOverwrite(target[key], source[key]);
152             } else if (source[key] !== void 0) {
153                 target[key] = source[key];
154             }
155         }
156     }
157 }
158
159 /**
160  * The error for plugin conflicts.
161  */
162 class PluginConflictError extends Error {
163
164     /**
165      * Initialize this error object.
166      * @param {string} pluginId The plugin ID.
167      * @param {{filePath:string, importerName:string}[]} plugins The resolved plugins.
168      */
169     constructor(pluginId, plugins) {
170         super(`Plugin "${pluginId}" was conflicted between ${plugins.map(p => `"${p.importerName}"`).join(" and ")}.`);
171         this.messageTemplate = "plugin-conflict";
172         this.messageData = { pluginId, plugins };
173     }
174 }
175
176 /**
177  * Merge plugins.
178  * `target`'s definition is prior to `source`'s.
179  * @param {Record<string, DependentPlugin>} target The destination to merge
180  * @param {Record<string, DependentPlugin>|undefined} source The source to merge.
181  * @returns {void}
182  */
183 function mergePlugins(target, source) {
184     if (!isNonNullObject(source)) {
185         return;
186     }
187
188     for (const key of Object.keys(source)) {
189         if (key === "__proto__") {
190             continue;
191         }
192         const targetValue = target[key];
193         const sourceValue = source[key];
194
195         // Adopt the plugin which was found at first.
196         if (targetValue === void 0) {
197             if (sourceValue.error) {
198                 throw sourceValue.error;
199             }
200             target[key] = sourceValue;
201         } else if (sourceValue.filePath !== targetValue.filePath) {
202             throw new PluginConflictError(key, [
203                 {
204                     filePath: targetValue.filePath,
205                     importerName: targetValue.importerName
206                 },
207                 {
208                     filePath: sourceValue.filePath,
209                     importerName: sourceValue.importerName
210                 }
211             ]);
212         }
213     }
214 }
215
216 /**
217  * Merge rule configs.
218  * `target`'s definition is prior to `source`'s.
219  * @param {Record<string, Array>} target The destination to merge
220  * @param {Record<string, RuleConf>|undefined} source The source to merge.
221  * @returns {void}
222  */
223 function mergeRuleConfigs(target, source) {
224     if (!isNonNullObject(source)) {
225         return;
226     }
227
228     for (const key of Object.keys(source)) {
229         if (key === "__proto__") {
230             continue;
231         }
232         const targetDef = target[key];
233         const sourceDef = source[key];
234
235         // Adopt the rule config which was found at first.
236         if (targetDef === void 0) {
237             if (Array.isArray(sourceDef)) {
238                 target[key] = [...sourceDef];
239             } else {
240                 target[key] = [sourceDef];
241             }
242
243         /*
244          * If the first found rule config is severity only and the current rule
245          * config has options, merge the severity and the options.
246          */
247         } else if (
248             targetDef.length === 1 &&
249             Array.isArray(sourceDef) &&
250             sourceDef.length >= 2
251         ) {
252             targetDef.push(...sourceDef.slice(1));
253         }
254     }
255 }
256
257 /**
258  * Create the extracted config.
259  * @param {ConfigArray} instance The config elements.
260  * @param {number[]} indices The indices to use.
261  * @returns {ExtractedConfig} The extracted config.
262  */
263 function createConfig(instance, indices) {
264     const config = new ExtractedConfig();
265     const ignorePatterns = [];
266
267     // Merge elements.
268     for (const index of indices) {
269         const element = instance[index];
270
271         // Adopt the parser which was found at first.
272         if (!config.parser && element.parser) {
273             if (element.parser.error) {
274                 throw element.parser.error;
275             }
276             config.parser = element.parser;
277         }
278
279         // Adopt the processor which was found at first.
280         if (!config.processor && element.processor) {
281             config.processor = element.processor;
282         }
283
284         // Adopt the noInlineConfig which was found at first.
285         if (config.noInlineConfig === void 0 && element.noInlineConfig !== void 0) {
286             config.noInlineConfig = element.noInlineConfig;
287             config.configNameOfNoInlineConfig = element.name;
288         }
289
290         // Adopt the reportUnusedDisableDirectives which was found at first.
291         if (config.reportUnusedDisableDirectives === void 0 && element.reportUnusedDisableDirectives !== void 0) {
292             config.reportUnusedDisableDirectives = element.reportUnusedDisableDirectives;
293         }
294
295         // Collect ignorePatterns
296         if (element.ignorePattern) {
297             ignorePatterns.push(element.ignorePattern);
298         }
299
300         // Merge others.
301         mergeWithoutOverwrite(config.env, element.env);
302         mergeWithoutOverwrite(config.globals, element.globals);
303         mergeWithoutOverwrite(config.parserOptions, element.parserOptions);
304         mergeWithoutOverwrite(config.settings, element.settings);
305         mergePlugins(config.plugins, element.plugins);
306         mergeRuleConfigs(config.rules, element.rules);
307     }
308
309     // Create the predicate function for ignore patterns.
310     if (ignorePatterns.length > 0) {
311         config.ignores = IgnorePattern.createIgnore(ignorePatterns.reverse());
312     }
313
314     return config;
315 }
316
317 /**
318  * Collect definitions.
319  * @template T, U
320  * @param {string} pluginId The plugin ID for prefix.
321  * @param {Record<string,T>} defs The definitions to collect.
322  * @param {Map<string, U>} map The map to output.
323  * @param {function(T): U} [normalize] The normalize function for each value.
324  * @returns {void}
325  */
326 function collect(pluginId, defs, map, normalize) {
327     if (defs) {
328         const prefix = pluginId && `${pluginId}/`;
329
330         for (const [key, value] of Object.entries(defs)) {
331             map.set(
332                 `${prefix}${key}`,
333                 normalize ? normalize(value) : value
334             );
335         }
336     }
337 }
338
339 /**
340  * Normalize a rule definition.
341  * @param {Function|Rule} rule The rule definition to normalize.
342  * @returns {Rule} The normalized rule definition.
343  */
344 function normalizePluginRule(rule) {
345     return typeof rule === "function" ? { create: rule } : rule;
346 }
347
348 /**
349  * Delete the mutation methods from a given map.
350  * @param {Map<any, any>} map The map object to delete.
351  * @returns {void}
352  */
353 function deleteMutationMethods(map) {
354     Object.defineProperties(map, {
355         clear: { configurable: true, value: void 0 },
356         delete: { configurable: true, value: void 0 },
357         set: { configurable: true, value: void 0 }
358     });
359 }
360
361 /**
362  * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
363  * @param {ConfigArrayElement[]} elements The config elements.
364  * @param {ConfigArrayInternalSlots} slots The internal slots.
365  * @returns {void}
366  */
367 function initPluginMemberMaps(elements, slots) {
368     const processed = new Set();
369
370     slots.envMap = new Map();
371     slots.processorMap = new Map();
372     slots.ruleMap = new Map();
373
374     for (const element of elements) {
375         if (!element.plugins) {
376             continue;
377         }
378
379         for (const [pluginId, value] of Object.entries(element.plugins)) {
380             const plugin = value.definition;
381
382             if (!plugin || processed.has(pluginId)) {
383                 continue;
384             }
385             processed.add(pluginId);
386
387             collect(pluginId, plugin.environments, slots.envMap);
388             collect(pluginId, plugin.processors, slots.processorMap);
389             collect(pluginId, plugin.rules, slots.ruleMap, normalizePluginRule);
390         }
391     }
392
393     deleteMutationMethods(slots.envMap);
394     deleteMutationMethods(slots.processorMap);
395     deleteMutationMethods(slots.ruleMap);
396 }
397
398 /**
399  * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
400  * @param {ConfigArray} instance The config elements.
401  * @returns {ConfigArrayInternalSlots} The extracted config.
402  */
403 function ensurePluginMemberMaps(instance) {
404     const slots = internalSlotsMap.get(instance);
405
406     if (!slots.ruleMap) {
407         initPluginMemberMaps(instance, slots);
408     }
409
410     return slots;
411 }
412
413 //------------------------------------------------------------------------------
414 // Public Interface
415 //------------------------------------------------------------------------------
416
417 /**
418  * The Config Array.
419  *
420  * `ConfigArray` instance contains all settings, parsers, and plugins.
421  * You need to call `ConfigArray#extractConfig(filePath)` method in order to
422  * extract, merge and get only the config data which is related to an arbitrary
423  * file.
424  * @extends {Array<ConfigArrayElement>}
425  */
426 class ConfigArray extends Array {
427
428     /**
429      * Get the plugin environments.
430      * The returned map cannot be mutated.
431      * @type {ReadonlyMap<string, Environment>} The plugin environments.
432      */
433     get pluginEnvironments() {
434         return ensurePluginMemberMaps(this).envMap;
435     }
436
437     /**
438      * Get the plugin processors.
439      * The returned map cannot be mutated.
440      * @type {ReadonlyMap<string, Processor>} The plugin processors.
441      */
442     get pluginProcessors() {
443         return ensurePluginMemberMaps(this).processorMap;
444     }
445
446     /**
447      * Get the plugin rules.
448      * The returned map cannot be mutated.
449      * @returns {ReadonlyMap<string, Rule>} The plugin rules.
450      */
451     get pluginRules() {
452         return ensurePluginMemberMaps(this).ruleMap;
453     }
454
455     /**
456      * Check if this config has `root` flag.
457      * @returns {boolean} `true` if this config array is root.
458      */
459     isRoot() {
460         for (let i = this.length - 1; i >= 0; --i) {
461             const root = this[i].root;
462
463             if (typeof root === "boolean") {
464                 return root;
465             }
466         }
467         return false;
468     }
469
470     /**
471      * Extract the config data which is related to a given file.
472      * @param {string} filePath The absolute path to the target file.
473      * @returns {ExtractedConfig} The extracted config data.
474      */
475     extractConfig(filePath) {
476         const { cache } = internalSlotsMap.get(this);
477         const indices = getMatchedIndices(this, filePath);
478         const cacheKey = indices.join(",");
479
480         if (!cache.has(cacheKey)) {
481             cache.set(cacheKey, createConfig(this, indices));
482         }
483
484         return cache.get(cacheKey);
485     }
486
487     /**
488      * Check if a given path is an additional lint target.
489      * @param {string} filePath The absolute path to the target file.
490      * @returns {boolean} `true` if the file is an additional lint target.
491      */
492     isAdditionalTargetPath(filePath) {
493         for (const { criteria, type } of this) {
494             if (
495                 type === "config" &&
496                 criteria &&
497                 !criteria.endsWithWildcard &&
498                 criteria.test(filePath)
499             ) {
500                 return true;
501             }
502         }
503         return false;
504     }
505 }
506
507 const exportObject = {
508     ConfigArray,
509
510     /**
511      * Get the used extracted configs.
512      * CLIEngine will use this method to collect used deprecated rules.
513      * @param {ConfigArray} instance The config array object to get.
514      * @returns {ExtractedConfig[]} The used extracted configs.
515      * @private
516      */
517     getUsedExtractedConfigs(instance) {
518         const { cache } = internalSlotsMap.get(instance);
519
520         return Array.from(cache.values());
521     }
522 };
523
524 module.exports = exportObject;