Actualizacion maquina principal
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / cli-engine / 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 { validateConfigArray } = require("../shared/config-validator");
29 const { ConfigArrayFactory } = require("./config-array-factory");
30 const { ConfigArray, ConfigDependency, IgnorePattern } = require("./config-array");
31 const loadRules = require("./load-rules");
32 const debug = require("debug")("eslint: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 {ReturnType<ConfigArrayFactory["create"]>} ConfigArray */
43
44 /**
45  * @typedef {Object} CascadingConfigArrayFactoryOptions
46  * @property {Map<string,Plugin>} [additionalPluginPool] The map for additional plugins.
47  * @property {ConfigData} [baseConfig] The config by `baseConfig` option.
48  * @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.
49  * @property {string} [cwd] The base directory to start lookup.
50  * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
51  * @property {string[]} [rulePaths] The value of `--rulesdir` option.
52  * @property {string} [specificConfigPath] The value of `--config` option.
53  * @property {boolean} [useEslintrc] if `false` then it doesn't load config files.
54  */
55
56 /**
57  * @typedef {Object} CascadingConfigArrayFactoryInternalSlots
58  * @property {ConfigArray} baseConfigArray The config array of `baseConfig` option.
59  * @property {ConfigData} baseConfigData The config data of `baseConfig` option. This is used to reset `baseConfigArray`.
60  * @property {ConfigArray} cliConfigArray The config array of CLI options.
61  * @property {ConfigData} cliConfigData The config data of CLI options. This is used to reset `cliConfigArray`.
62  * @property {ConfigArrayFactory} configArrayFactory The factory for config arrays.
63  * @property {Map<string, ConfigArray>} configCache The cache from directory paths to config arrays.
64  * @property {string} cwd The base directory to start lookup.
65  * @property {WeakMap<ConfigArray, ConfigArray>} finalizeCache The cache from config arrays to finalized config arrays.
66  * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
67  * @property {string[]|null} rulePaths The value of `--rulesdir` option. This is used to reset `baseConfigArray`.
68  * @property {string|null} specificConfigPath The value of `--config` option. This is used to reset `cliConfigArray`.
69  * @property {boolean} useEslintrc if `false` then it doesn't load config files.
70  */
71
72 /** @type {WeakMap<CascadingConfigArrayFactory, CascadingConfigArrayFactoryInternalSlots>} */
73 const internalSlotsMap = new WeakMap();
74
75 /**
76  * Create the config array from `baseConfig` and `rulePaths`.
77  * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
78  * @returns {ConfigArray} The config array of the base configs.
79  */
80 function createBaseConfigArray({
81     configArrayFactory,
82     baseConfigData,
83     rulePaths,
84     cwd
85 }) {
86     const baseConfigArray = configArrayFactory.create(
87         baseConfigData,
88         { name: "BaseConfig" }
89     );
90
91     /*
92      * Create the config array element for the default ignore patterns.
93      * This element has `ignorePattern` property that ignores the default
94      * patterns in the current working directory.
95      */
96     baseConfigArray.unshift(configArrayFactory.create(
97         { ignorePatterns: IgnorePattern.DefaultPatterns },
98         { name: "DefaultIgnorePattern" }
99     )[0]);
100
101     /*
102      * Load rules `--rulesdir` option as a pseudo plugin.
103      * Use a pseudo plugin to define rules of `--rulesdir`, so we can validate
104      * the rule's options with only information in the config array.
105      */
106     if (rulePaths && rulePaths.length > 0) {
107         baseConfigArray.push({
108             name: "--rulesdir",
109             filePath: "",
110             plugins: {
111                 "": new ConfigDependency({
112                     definition: {
113                         rules: rulePaths.reduce(
114                             (map, rulesPath) => Object.assign(
115                                 map,
116                                 loadRules(rulesPath, cwd)
117                             ),
118                             {}
119                         )
120                     },
121                     filePath: "",
122                     id: "",
123                     importerName: "--rulesdir",
124                     importerPath: ""
125                 })
126             }
127         });
128     }
129
130     return baseConfigArray;
131 }
132
133 /**
134  * Create the config array from CLI options.
135  * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
136  * @returns {ConfigArray} The config array of the base configs.
137  */
138 function createCLIConfigArray({
139     cliConfigData,
140     configArrayFactory,
141     ignorePath,
142     specificConfigPath
143 }) {
144     const cliConfigArray = configArrayFactory.create(
145         cliConfigData,
146         { name: "CLIOptions" }
147     );
148
149     cliConfigArray.unshift(
150         ...(ignorePath
151             ? configArrayFactory.loadESLintIgnore(ignorePath)
152             : configArrayFactory.loadDefaultESLintIgnore())
153     );
154
155     if (specificConfigPath) {
156         cliConfigArray.unshift(
157             ...configArrayFactory.loadFile(
158                 specificConfigPath,
159                 { name: "--config" }
160             )
161         );
162     }
163
164     return cliConfigArray;
165 }
166
167 /**
168  * The error type when there are files matched by a glob, but all of them have been ignored.
169  */
170 class ConfigurationNotFoundError extends Error {
171
172     // eslint-disable-next-line jsdoc/require-description
173     /**
174      * @param {string} directoryPath The directory path.
175      */
176     constructor(directoryPath) {
177         super(`No ESLint configuration found in ${directoryPath}.`);
178         this.messageTemplate = "no-config-found";
179         this.messageData = { directoryPath };
180     }
181 }
182
183 /**
184  * This class provides the functionality that enumerates every file which is
185  * matched by given glob patterns and that configuration.
186  */
187 class CascadingConfigArrayFactory {
188
189     /**
190      * Initialize this enumerator.
191      * @param {CascadingConfigArrayFactoryOptions} options The options.
192      */
193     constructor({
194         additionalPluginPool = new Map(),
195         baseConfig: baseConfigData = null,
196         cliConfig: cliConfigData = null,
197         cwd = process.cwd(),
198         ignorePath,
199         resolvePluginsRelativeTo = cwd,
200         rulePaths = [],
201         specificConfigPath = null,
202         useEslintrc = true
203     } = {}) {
204         const configArrayFactory = new ConfigArrayFactory({
205             additionalPluginPool,
206             cwd,
207             resolvePluginsRelativeTo
208         });
209
210         internalSlotsMap.set(this, {
211             baseConfigArray: createBaseConfigArray({
212                 baseConfigData,
213                 configArrayFactory,
214                 cwd,
215                 rulePaths
216             }),
217             baseConfigData,
218             cliConfigArray: createCLIConfigArray({
219                 cliConfigData,
220                 configArrayFactory,
221                 ignorePath,
222                 specificConfigPath
223             }),
224             cliConfigData,
225             configArrayFactory,
226             configCache: new Map(),
227             cwd,
228             finalizeCache: new WeakMap(),
229             ignorePath,
230             rulePaths,
231             specificConfigPath,
232             useEslintrc
233         });
234     }
235
236     /**
237      * The path to the current working directory.
238      * This is used by tests.
239      * @type {string}
240      */
241     get cwd() {
242         const { cwd } = internalSlotsMap.get(this);
243
244         return cwd;
245     }
246
247     /**
248      * Get the config array of a given file.
249      * If `filePath` was not given, it returns the config which contains only
250      * `baseConfigData` and `cliConfigData`.
251      * @param {string} [filePath] The file path to a file.
252      * @param {Object} [options] The options.
253      * @param {boolean} [options.ignoreNotFoundError] If `true` then it doesn't throw `ConfigurationNotFoundError`.
254      * @returns {ConfigArray} The config array of the file.
255      */
256     getConfigArrayForFile(filePath, { ignoreNotFoundError = false } = {}) {
257         const {
258             baseConfigArray,
259             cliConfigArray,
260             cwd
261         } = internalSlotsMap.get(this);
262
263         if (!filePath) {
264             return new ConfigArray(...baseConfigArray, ...cliConfigArray);
265         }
266
267         const directoryPath = path.dirname(path.resolve(cwd, filePath));
268
269         debug(`Load config files for ${directoryPath}.`);
270
271         return this._finalizeConfigArray(
272             this._loadConfigInAncestors(directoryPath),
273             directoryPath,
274             ignoreNotFoundError
275         );
276     }
277
278     /**
279      * Clear config cache.
280      * @returns {void}
281      */
282     clearCache() {
283         const slots = internalSlotsMap.get(this);
284
285         slots.baseConfigArray = createBaseConfigArray(slots);
286         slots.cliConfigArray = createCLIConfigArray(slots);
287         slots.configCache.clear();
288     }
289
290     /**
291      * Load and normalize config files from the ancestor directories.
292      * @param {string} directoryPath The path to a leaf directory.
293      * @returns {ConfigArray} The loaded config.
294      * @private
295      */
296     _loadConfigInAncestors(directoryPath) {
297         const {
298             baseConfigArray,
299             configArrayFactory,
300             configCache,
301             cwd,
302             useEslintrc
303         } = internalSlotsMap.get(this);
304
305         if (!useEslintrc) {
306             return baseConfigArray;
307         }
308
309         let configArray = configCache.get(directoryPath);
310
311         // Hit cache.
312         if (configArray) {
313             debug(`Cache hit: ${directoryPath}.`);
314             return configArray;
315         }
316         debug(`No cache found: ${directoryPath}.`);
317
318         const homePath = os.homedir();
319
320         // Consider this is root.
321         if (directoryPath === homePath && cwd !== homePath) {
322             debug("Stop traversing because of considered root.");
323             return this._cacheConfig(directoryPath, baseConfigArray);
324         }
325
326         // Load the config on this directory.
327         try {
328             configArray = configArrayFactory.loadInDirectory(directoryPath);
329         } catch (error) {
330             /* istanbul ignore next */
331             if (error.code === "EACCES") {
332                 debug("Stop traversing because of 'EACCES' error.");
333                 return this._cacheConfig(directoryPath, baseConfigArray);
334             }
335             throw error;
336         }
337
338         if (configArray.length > 0 && configArray.isRoot()) {
339             debug("Stop traversing because of 'root:true'.");
340             configArray.unshift(...baseConfigArray);
341             return this._cacheConfig(directoryPath, configArray);
342         }
343
344         // Load from the ancestors and merge it.
345         const parentPath = path.dirname(directoryPath);
346         const parentConfigArray = parentPath && parentPath !== directoryPath
347             ? this._loadConfigInAncestors(parentPath)
348             : baseConfigArray;
349
350         if (configArray.length > 0) {
351             configArray.unshift(...parentConfigArray);
352         } else {
353             configArray = parentConfigArray;
354         }
355
356         // Cache and return.
357         return this._cacheConfig(directoryPath, configArray);
358     }
359
360     /**
361      * Freeze and cache a given config.
362      * @param {string} directoryPath The path to a directory as a cache key.
363      * @param {ConfigArray} configArray The config array as a cache value.
364      * @returns {ConfigArray} The `configArray` (frozen).
365      */
366     _cacheConfig(directoryPath, configArray) {
367         const { configCache } = internalSlotsMap.get(this);
368
369         Object.freeze(configArray);
370         configCache.set(directoryPath, configArray);
371
372         return configArray;
373     }
374
375     /**
376      * Finalize a given config array.
377      * Concatenate `--config` and other CLI options.
378      * @param {ConfigArray} configArray The parent config array.
379      * @param {string} directoryPath The path to the leaf directory to find config files.
380      * @param {boolean} ignoreNotFoundError If `true` then it doesn't throw `ConfigurationNotFoundError`.
381      * @returns {ConfigArray} The loaded config.
382      * @private
383      */
384     _finalizeConfigArray(configArray, directoryPath, ignoreNotFoundError) {
385         const {
386             cliConfigArray,
387             configArrayFactory,
388             finalizeCache,
389             useEslintrc
390         } = internalSlotsMap.get(this);
391
392         let finalConfigArray = finalizeCache.get(configArray);
393
394         if (!finalConfigArray) {
395             finalConfigArray = configArray;
396
397             // Load the personal config if there are no regular config files.
398             if (
399                 useEslintrc &&
400                 configArray.every(c => !c.filePath) &&
401                 cliConfigArray.every(c => !c.filePath) // `--config` option can be a file.
402             ) {
403                 debug("Loading the config file of the home directory.");
404
405                 finalConfigArray = configArrayFactory.loadInDirectory(
406                     os.homedir(),
407                     { name: "PersonalConfig", parent: finalConfigArray }
408                 );
409             }
410
411             // Apply CLI options.
412             if (cliConfigArray.length > 0) {
413                 finalConfigArray = finalConfigArray.concat(cliConfigArray);
414             }
415
416             // Validate rule settings and environments.
417             validateConfigArray(finalConfigArray);
418
419             // Cache it.
420             Object.freeze(finalConfigArray);
421             finalizeCache.set(configArray, finalConfigArray);
422
423             debug(
424                 "Configuration was determined: %o on %s",
425                 finalConfigArray,
426                 directoryPath
427             );
428         }
429
430         // At least one element (the default ignore patterns) exists.
431         if (!ignoreNotFoundError && useEslintrc && finalConfigArray.length <= 1) {
432             throw new ConfigurationNotFoundError(directoryPath);
433         }
434
435         return finalConfigArray;
436     }
437 }
438
439 //------------------------------------------------------------------------------
440 // Public Interface
441 //------------------------------------------------------------------------------
442
443 module.exports = { CascadingConfigArrayFactory };