.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / @eslint / eslintrc / lib / cascading-config-array-factory.js
1 /**
2  * @fileoverview `CascadingConfigArrayFactory` class.
3  *
4  * `CascadingConfigArrayFactory` class has a responsibility:
5  *
6  * 1. Handles cascading of config files.
7  *
8  * It provides two methods:
9  *
10  * - `getConfigArrayForFile(filePath)`
11  *     Get the corresponded configuration of a given file. This method doesn't
12  *     throw even if the given file didn't exist.
13  * - `clearCache()`
14  *     Clear the internal cache. You have to call this method when
15  *     `additionalPluginPool` was updated if `baseConfig` or `cliConfig` depends
16  *     on the additional plugins. (`CLIEngine#addPlugin()` method calls this.)
17  *
18  * @author Toru Nagashima <https://github.com/mysticatea>
19  */
20 "use strict";
21
22 //------------------------------------------------------------------------------
23 // Requirements
24 //------------------------------------------------------------------------------
25
26 const os = require("os");
27 const path = require("path");
28 const ConfigValidator = require("./shared/config-validator");
29 const { emitDeprecationWarning } = require("./shared/deprecation-warnings");
30 const { ConfigArrayFactory } = require("./config-array-factory");
31 const { ConfigArray, ConfigDependency, IgnorePattern } = require("./config-array");
32 const debug = require("debug")("eslintrc:cascading-config-array-factory");
33
34 //------------------------------------------------------------------------------
35 // Helpers
36 //------------------------------------------------------------------------------
37
38 // Define types for VSCode IntelliSense.
39 /** @typedef {import("./shared/types").ConfigData} ConfigData */
40 /** @typedef {import("./shared/types").Parser} Parser */
41 /** @typedef {import("./shared/types").Plugin} Plugin */
42 /** @typedef {import("./shared/types").Rule} Rule */
43 /** @typedef {ReturnType<ConfigArrayFactory["create"]>} ConfigArray */
44
45 /**
46  * @typedef {Object} CascadingConfigArrayFactoryOptions
47  * @property {Map<string,Plugin>} [additionalPluginPool] The map for additional plugins.
48  * @property {ConfigData} [baseConfig] The config by `baseConfig` option.
49  * @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.
50  * @property {string} [cwd] The base directory to start lookup.
51  * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
52  * @property {string[]} [rulePaths] The value of `--rulesdir` option.
53  * @property {string} [specificConfigPath] The value of `--config` option.
54  * @property {boolean} [useEslintrc] if `false` then it doesn't load config files.
55  * @property {Function} loadRules The function to use to load rules.
56  * @property {Map<string,Rule>} builtInRules The rules that are built in to ESLint.
57  * @property {Object} [resolver=ModuleResolver] The module resolver object.
58  * @property {string} eslintAllPath The path to the definitions for eslint:all.
59  * @property {string} eslintRecommendedPath The path to the definitions for eslint:recommended.
60  */
61
62 /**
63  * @typedef {Object} CascadingConfigArrayFactoryInternalSlots
64  * @property {ConfigArray} baseConfigArray The config array of `baseConfig` option.
65  * @property {ConfigData} baseConfigData The config data of `baseConfig` option. This is used to reset `baseConfigArray`.
66  * @property {ConfigArray} cliConfigArray The config array of CLI options.
67  * @property {ConfigData} cliConfigData The config data of CLI options. This is used to reset `cliConfigArray`.
68  * @property {ConfigArrayFactory} configArrayFactory The factory for config arrays.
69  * @property {Map<string, ConfigArray>} configCache The cache from directory paths to config arrays.
70  * @property {string} cwd The base directory to start lookup.
71  * @property {WeakMap<ConfigArray, ConfigArray>} finalizeCache The cache from config arrays to finalized config arrays.
72  * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
73  * @property {string[]|null} rulePaths The value of `--rulesdir` option. This is used to reset `baseConfigArray`.
74  * @property {string|null} specificConfigPath The value of `--config` option. This is used to reset `cliConfigArray`.
75  * @property {boolean} useEslintrc if `false` then it doesn't load config files.
76  * @property {Function} loadRules The function to use to load rules.
77  * @property {Map<string,Rule>} builtInRules The rules that are built in to ESLint.
78  * @property {Object} [resolver=ModuleResolver] The module resolver object.
79  * @property {string} eslintAllPath The path to the definitions for eslint:all.
80  * @property {string} eslintRecommendedPath The path to the definitions for eslint:recommended.
81  */
82
83 /** @type {WeakMap<CascadingConfigArrayFactory, CascadingConfigArrayFactoryInternalSlots>} */
84 const internalSlotsMap = new WeakMap();
85
86 /**
87  * Create the config array from `baseConfig` and `rulePaths`.
88  * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
89  * @returns {ConfigArray} The config array of the base configs.
90  */
91 function createBaseConfigArray({
92     configArrayFactory,
93     baseConfigData,
94     rulePaths,
95     cwd,
96     loadRules
97 }) {
98     const baseConfigArray = configArrayFactory.create(
99         baseConfigData,
100         { name: "BaseConfig" }
101     );
102
103     /*
104      * Create the config array element for the default ignore patterns.
105      * This element has `ignorePattern` property that ignores the default
106      * patterns in the current working directory.
107      */
108     baseConfigArray.unshift(configArrayFactory.create(
109         { ignorePatterns: IgnorePattern.DefaultPatterns },
110         { name: "DefaultIgnorePattern" }
111     )[0]);
112
113     /*
114      * Load rules `--rulesdir` option as a pseudo plugin.
115      * Use a pseudo plugin to define rules of `--rulesdir`, so we can validate
116      * the rule's options with only information in the config array.
117      */
118     if (rulePaths && rulePaths.length > 0) {
119         baseConfigArray.push({
120             type: "config",
121             name: "--rulesdir",
122             filePath: "",
123             plugins: {
124                 "": new ConfigDependency({
125                     definition: {
126                         rules: rulePaths.reduce(
127                             (map, rulesPath) => Object.assign(
128                                 map,
129                                 loadRules(rulesPath, cwd)
130                             ),
131                             {}
132                         )
133                     },
134                     filePath: "",
135                     id: "",
136                     importerName: "--rulesdir",
137                     importerPath: ""
138                 })
139             }
140         });
141     }
142
143     return baseConfigArray;
144 }
145
146 /**
147  * Create the config array from CLI options.
148  * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
149  * @returns {ConfigArray} The config array of the base configs.
150  */
151 function createCLIConfigArray({
152     cliConfigData,
153     configArrayFactory,
154     cwd,
155     ignorePath,
156     specificConfigPath
157 }) {
158     const cliConfigArray = configArrayFactory.create(
159         cliConfigData,
160         { name: "CLIOptions" }
161     );
162
163     cliConfigArray.unshift(
164         ...(ignorePath
165             ? configArrayFactory.loadESLintIgnore(ignorePath)
166             : configArrayFactory.loadDefaultESLintIgnore())
167     );
168
169     if (specificConfigPath) {
170         cliConfigArray.unshift(
171             ...configArrayFactory.loadFile(
172                 specificConfigPath,
173                 { name: "--config", basePath: cwd }
174             )
175         );
176     }
177
178     return cliConfigArray;
179 }
180
181 /**
182  * The error type when there are files matched by a glob, but all of them have been ignored.
183  */
184 class ConfigurationNotFoundError extends Error {
185
186     // eslint-disable-next-line jsdoc/require-description
187     /**
188      * @param {string} directoryPath The directory path.
189      */
190     constructor(directoryPath) {
191         super(`No ESLint configuration found in ${directoryPath}.`);
192         this.messageTemplate = "no-config-found";
193         this.messageData = { directoryPath };
194     }
195 }
196
197 /**
198  * This class provides the functionality that enumerates every file which is
199  * matched by given glob patterns and that configuration.
200  */
201 class CascadingConfigArrayFactory {
202
203     /**
204      * Initialize this enumerator.
205      * @param {CascadingConfigArrayFactoryOptions} options The options.
206      */
207     constructor({
208         additionalPluginPool = new Map(),
209         baseConfig: baseConfigData = null,
210         cliConfig: cliConfigData = null,
211         cwd = process.cwd(),
212         ignorePath,
213         resolvePluginsRelativeTo,
214         rulePaths = [],
215         specificConfigPath = null,
216         useEslintrc = true,
217         builtInRules = new Map(),
218         loadRules,
219         resolver,
220         eslintRecommendedPath,
221         eslintAllPath
222     } = {}) {
223         const configArrayFactory = new ConfigArrayFactory({
224             additionalPluginPool,
225             cwd,
226             resolvePluginsRelativeTo,
227             builtInRules,
228             resolver,
229             eslintRecommendedPath,
230             eslintAllPath
231         });
232
233         internalSlotsMap.set(this, {
234             baseConfigArray: createBaseConfigArray({
235                 baseConfigData,
236                 configArrayFactory,
237                 cwd,
238                 rulePaths,
239                 loadRules,
240                 resolver
241             }),
242             baseConfigData,
243             cliConfigArray: createCLIConfigArray({
244                 cliConfigData,
245                 configArrayFactory,
246                 cwd,
247                 ignorePath,
248                 specificConfigPath
249             }),
250             cliConfigData,
251             configArrayFactory,
252             configCache: new Map(),
253             cwd,
254             finalizeCache: new WeakMap(),
255             ignorePath,
256             rulePaths,
257             specificConfigPath,
258             useEslintrc,
259             builtInRules,
260             loadRules
261         });
262     }
263
264     /**
265      * The path to the current working directory.
266      * This is used by tests.
267      * @type {string}
268      */
269     get cwd() {
270         const { cwd } = internalSlotsMap.get(this);
271
272         return cwd;
273     }
274
275     /**
276      * Get the config array of a given file.
277      * If `filePath` was not given, it returns the config which contains only
278      * `baseConfigData` and `cliConfigData`.
279      * @param {string} [filePath] The file path to a file.
280      * @param {Object} [options] The options.
281      * @param {boolean} [options.ignoreNotFoundError] If `true` then it doesn't throw `ConfigurationNotFoundError`.
282      * @returns {ConfigArray} The config array of the file.
283      */
284     getConfigArrayForFile(filePath, { ignoreNotFoundError = false } = {}) {
285         const {
286             baseConfigArray,
287             cliConfigArray,
288             cwd
289         } = internalSlotsMap.get(this);
290
291         if (!filePath) {
292             return new ConfigArray(...baseConfigArray, ...cliConfigArray);
293         }
294
295         const directoryPath = path.dirname(path.resolve(cwd, filePath));
296
297         debug(`Load config files for ${directoryPath}.`);
298
299         return this._finalizeConfigArray(
300             this._loadConfigInAncestors(directoryPath),
301             directoryPath,
302             ignoreNotFoundError
303         );
304     }
305
306     /**
307      * Set the config data to override all configs.
308      * Require to call `clearCache()` method after this method is called.
309      * @param {ConfigData} configData The config data to override all configs.
310      * @returns {void}
311      */
312     setOverrideConfig(configData) {
313         const slots = internalSlotsMap.get(this);
314
315         slots.cliConfigData = configData;
316     }
317
318     /**
319      * Clear config cache.
320      * @returns {void}
321      */
322     clearCache() {
323         const slots = internalSlotsMap.get(this);
324
325         slots.baseConfigArray = createBaseConfigArray(slots);
326         slots.cliConfigArray = createCLIConfigArray(slots);
327         slots.configCache.clear();
328     }
329
330     /**
331      * Load and normalize config files from the ancestor directories.
332      * @param {string} directoryPath The path to a leaf directory.
333      * @param {boolean} configsExistInSubdirs `true` if configurations exist in subdirectories.
334      * @returns {ConfigArray} The loaded config.
335      * @private
336      */
337     _loadConfigInAncestors(directoryPath, configsExistInSubdirs = false) {
338         const {
339             baseConfigArray,
340             configArrayFactory,
341             configCache,
342             cwd,
343             useEslintrc
344         } = internalSlotsMap.get(this);
345
346         if (!useEslintrc) {
347             return baseConfigArray;
348         }
349
350         let configArray = configCache.get(directoryPath);
351
352         // Hit cache.
353         if (configArray) {
354             debug(`Cache hit: ${directoryPath}.`);
355             return configArray;
356         }
357         debug(`No cache found: ${directoryPath}.`);
358
359         const homePath = os.homedir();
360
361         // Consider this is root.
362         if (directoryPath === homePath && cwd !== homePath) {
363             debug("Stop traversing because of considered root.");
364             if (configsExistInSubdirs) {
365                 const filePath = ConfigArrayFactory.getPathToConfigFileInDirectory(directoryPath);
366
367                 if (filePath) {
368                     emitDeprecationWarning(
369                         filePath,
370                         "ESLINT_PERSONAL_CONFIG_SUPPRESS"
371                     );
372                 }
373             }
374             return this._cacheConfig(directoryPath, baseConfigArray);
375         }
376
377         // Load the config on this directory.
378         try {
379             configArray = configArrayFactory.loadInDirectory(directoryPath);
380         } catch (error) {
381             /* istanbul ignore next */
382             if (error.code === "EACCES") {
383                 debug("Stop traversing because of 'EACCES' error.");
384                 return this._cacheConfig(directoryPath, baseConfigArray);
385             }
386             throw error;
387         }
388
389         if (configArray.length > 0 && configArray.isRoot()) {
390             debug("Stop traversing because of 'root:true'.");
391             configArray.unshift(...baseConfigArray);
392             return this._cacheConfig(directoryPath, configArray);
393         }
394
395         // Load from the ancestors and merge it.
396         const parentPath = path.dirname(directoryPath);
397         const parentConfigArray = parentPath && parentPath !== directoryPath
398             ? this._loadConfigInAncestors(
399                 parentPath,
400                 configsExistInSubdirs || configArray.length > 0
401             )
402             : baseConfigArray;
403
404         if (configArray.length > 0) {
405             configArray.unshift(...parentConfigArray);
406         } else {
407             configArray = parentConfigArray;
408         }
409
410         // Cache and return.
411         return this._cacheConfig(directoryPath, configArray);
412     }
413
414     /**
415      * Freeze and cache a given config.
416      * @param {string} directoryPath The path to a directory as a cache key.
417      * @param {ConfigArray} configArray The config array as a cache value.
418      * @returns {ConfigArray} The `configArray` (frozen).
419      */
420     _cacheConfig(directoryPath, configArray) {
421         const { configCache } = internalSlotsMap.get(this);
422
423         Object.freeze(configArray);
424         configCache.set(directoryPath, configArray);
425
426         return configArray;
427     }
428
429     /**
430      * Finalize a given config array.
431      * Concatenate `--config` and other CLI options.
432      * @param {ConfigArray} configArray The parent config array.
433      * @param {string} directoryPath The path to the leaf directory to find config files.
434      * @param {boolean} ignoreNotFoundError If `true` then it doesn't throw `ConfigurationNotFoundError`.
435      * @returns {ConfigArray} The loaded config.
436      * @private
437      */
438     _finalizeConfigArray(configArray, directoryPath, ignoreNotFoundError) {
439         const {
440             cliConfigArray,
441             configArrayFactory,
442             finalizeCache,
443             useEslintrc,
444             builtInRules
445         } = internalSlotsMap.get(this);
446
447         let finalConfigArray = finalizeCache.get(configArray);
448
449         if (!finalConfigArray) {
450             finalConfigArray = configArray;
451
452             // Load the personal config if there are no regular config files.
453             if (
454                 useEslintrc &&
455                 configArray.every(c => !c.filePath) &&
456                 cliConfigArray.every(c => !c.filePath) // `--config` option can be a file.
457             ) {
458                 const homePath = os.homedir();
459
460                 debug("Loading the config file of the home directory:", homePath);
461
462                 const personalConfigArray = configArrayFactory.loadInDirectory(
463                     homePath,
464                     { name: "PersonalConfig" }
465                 );
466
467                 if (
468                     personalConfigArray.length > 0 &&
469                     !directoryPath.startsWith(homePath)
470                 ) {
471                     const lastElement =
472                         personalConfigArray[personalConfigArray.length - 1];
473
474                     emitDeprecationWarning(
475                         lastElement.filePath,
476                         "ESLINT_PERSONAL_CONFIG_LOAD"
477                     );
478                 }
479
480                 finalConfigArray = finalConfigArray.concat(personalConfigArray);
481             }
482
483             // Apply CLI options.
484             if (cliConfigArray.length > 0) {
485                 finalConfigArray = finalConfigArray.concat(cliConfigArray);
486             }
487
488             // Validate rule settings and environments.
489             const validator = new ConfigValidator({
490                 builtInRules
491             });
492
493             validator.validateConfigArray(finalConfigArray);
494
495             // Cache it.
496             Object.freeze(finalConfigArray);
497             finalizeCache.set(configArray, finalConfigArray);
498
499             debug(
500                 "Configuration was determined: %o on %s",
501                 finalConfigArray,
502                 directoryPath
503             );
504         }
505
506         // At least one element (the default ignore patterns) exists.
507         if (!ignoreNotFoundError && useEslintrc && finalConfigArray.length <= 1) {
508             throw new ConfigurationNotFoundError(directoryPath);
509         }
510
511         return finalConfigArray;
512     }
513 }
514
515 //------------------------------------------------------------------------------
516 // Public Interface
517 //------------------------------------------------------------------------------
518
519 module.exports = { CascadingConfigArrayFactory };