massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / @eslint / eslintrc / lib / config-array-factory.js
1 /**
2  * @fileoverview The factory of `ConfigArray` objects.
3  *
4  * This class provides methods to create `ConfigArray` instance.
5  *
6  * - `create(configData, options)`
7  *     Create a `ConfigArray` instance from a config data. This is to handle CLI
8  *     options except `--config`.
9  * - `loadFile(filePath, options)`
10  *     Create a `ConfigArray` instance from a config file. This is to handle
11  *     `--config` option. If the file was not found, throws the following error:
12  *      - If the filename was `*.js`, a `MODULE_NOT_FOUND` error.
13  *      - If the filename was `package.json`, an IO error or an
14  *        `ESLINT_CONFIG_FIELD_NOT_FOUND` error.
15  *      - Otherwise, an IO error such as `ENOENT`.
16  * - `loadInDirectory(directoryPath, options)`
17  *     Create a `ConfigArray` instance from a config file which is on a given
18  *     directory. This tries to load `.eslintrc.*` or `package.json`. If not
19  *     found, returns an empty `ConfigArray`.
20  * - `loadESLintIgnore(filePath)`
21  *     Create a `ConfigArray` instance from a config file that is `.eslintignore`
22  *     format. This is to handle `--ignore-path` option.
23  * - `loadDefaultESLintIgnore()`
24  *     Create a `ConfigArray` instance from `.eslintignore` or `package.json` in
25  *     the current working directory.
26  *
27  * `ConfigArrayFactory` class has the responsibility that loads configuration
28  * files, including loading `extends`, `parser`, and `plugins`. The created
29  * `ConfigArray` instance has the loaded `extends`, `parser`, and `plugins`.
30  *
31  * But this class doesn't handle cascading. `CascadingConfigArrayFactory` class
32  * handles cascading and hierarchy.
33  *
34  * @author Toru Nagashima <https://github.com/mysticatea>
35  */
36 "use strict";
37
38 //------------------------------------------------------------------------------
39 // Requirements
40 //------------------------------------------------------------------------------
41
42 const fs = require("fs");
43 const path = require("path");
44 const importFresh = require("import-fresh");
45 const stripComments = require("strip-json-comments");
46 const ConfigValidator = require("./shared/config-validator");
47 const naming = require("./shared/naming");
48 const ModuleResolver = require("./shared/relative-module-resolver");
49 const {
50     ConfigArray,
51     ConfigDependency,
52     IgnorePattern,
53     OverrideTester
54 } = require("./config-array");
55 const debug = require("debug")("eslintrc:config-array-factory");
56
57 //------------------------------------------------------------------------------
58 // Helpers
59 //------------------------------------------------------------------------------
60
61 const configFilenames = [
62     ".eslintrc.js",
63     ".eslintrc.cjs",
64     ".eslintrc.yaml",
65     ".eslintrc.yml",
66     ".eslintrc.json",
67     ".eslintrc",
68     "package.json"
69 ];
70
71 // Define types for VSCode IntelliSense.
72 /** @typedef {import("./shared/types").ConfigData} ConfigData */
73 /** @typedef {import("./shared/types").OverrideConfigData} OverrideConfigData */
74 /** @typedef {import("./shared/types").Parser} Parser */
75 /** @typedef {import("./shared/types").Plugin} Plugin */
76 /** @typedef {import("./shared/types").Rule} Rule */
77 /** @typedef {import("./config-array/config-dependency").DependentParser} DependentParser */
78 /** @typedef {import("./config-array/config-dependency").DependentPlugin} DependentPlugin */
79 /** @typedef {ConfigArray[0]} ConfigArrayElement */
80
81 /**
82  * @typedef {Object} ConfigArrayFactoryOptions
83  * @property {Map<string,Plugin>} [additionalPluginPool] The map for additional plugins.
84  * @property {string} [cwd] The path to the current working directory.
85  * @property {string} [resolvePluginsRelativeTo] A path to the directory that plugins should be resolved from. Defaults to `cwd`.
86  * @property {Map<string,Rule>} builtInRules The rules that are built in to ESLint.
87  * @property {Object} [resolver=ModuleResolver] The module resolver object.
88  * @property {string} eslintAllPath The path to the definitions for eslint:all.
89  * @property {string} eslintRecommendedPath The path to the definitions for eslint:recommended.
90  */
91
92 /**
93  * @typedef {Object} ConfigArrayFactoryInternalSlots
94  * @property {Map<string,Plugin>} additionalPluginPool The map for additional plugins.
95  * @property {string} cwd The path to the current working directory.
96  * @property {string | undefined} resolvePluginsRelativeTo An absolute path the the directory that plugins should be resolved from.
97  * @property {Map<string,Rule>} builtInRules The rules that are built in to ESLint.
98  * @property {Object} [resolver=ModuleResolver] The module resolver object.
99  * @property {string} eslintAllPath The path to the definitions for eslint:all.
100  * @property {string} eslintRecommendedPath The path to the definitions for eslint:recommended.
101  */
102
103 /**
104  * @typedef {Object} ConfigArrayFactoryLoadingContext
105  * @property {string} filePath The path to the current configuration.
106  * @property {string} matchBasePath The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
107  * @property {string} name The name of the current configuration.
108  * @property {string} pluginBasePath The base path to resolve plugins.
109  * @property {"config" | "ignore" | "implicit-processor"} type The type of the current configuration. This is `"config"` in normal. This is `"ignore"` if it came from `.eslintignore`. This is `"implicit-processor"` if it came from legacy file-extension processors.
110  */
111
112 /**
113  * @typedef {Object} ConfigArrayFactoryLoadingContext
114  * @property {string} filePath The path to the current configuration.
115  * @property {string} matchBasePath The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
116  * @property {string} name The name of the current configuration.
117  * @property {"config" | "ignore" | "implicit-processor"} type The type of the current configuration. This is `"config"` in normal. This is `"ignore"` if it came from `.eslintignore`. This is `"implicit-processor"` if it came from legacy file-extension processors.
118  */
119
120 /** @type {WeakMap<ConfigArrayFactory, ConfigArrayFactoryInternalSlots>} */
121 const internalSlotsMap = new WeakMap();
122
123 /**
124  * Check if a given string is a file path.
125  * @param {string} nameOrPath A module name or file path.
126  * @returns {boolean} `true` if the `nameOrPath` is a file path.
127  */
128 function isFilePath(nameOrPath) {
129     return (
130         /^\.{1,2}[/\\]/u.test(nameOrPath) ||
131         path.isAbsolute(nameOrPath)
132     );
133 }
134
135 /**
136  * Convenience wrapper for synchronously reading file contents.
137  * @param {string} filePath The filename to read.
138  * @returns {string} The file contents, with the BOM removed.
139  * @private
140  */
141 function readFile(filePath) {
142     return fs.readFileSync(filePath, "utf8").replace(/^\ufeff/u, "");
143 }
144
145 /**
146  * Loads a YAML configuration from a file.
147  * @param {string} filePath The filename to load.
148  * @returns {ConfigData} The configuration object from the file.
149  * @throws {Error} If the file cannot be read.
150  * @private
151  */
152 function loadYAMLConfigFile(filePath) {
153     debug(`Loading YAML config file: ${filePath}`);
154
155     // lazy load YAML to improve performance when not used
156     const yaml = require("js-yaml");
157
158     try {
159
160         // empty YAML file can be null, so always use
161         return yaml.safeLoad(readFile(filePath)) || {};
162     } catch (e) {
163         debug(`Error reading YAML file: ${filePath}`);
164         e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
165         throw e;
166     }
167 }
168
169 /**
170  * Loads a JSON configuration from a file.
171  * @param {string} filePath The filename to load.
172  * @returns {ConfigData} The configuration object from the file.
173  * @throws {Error} If the file cannot be read.
174  * @private
175  */
176 function loadJSONConfigFile(filePath) {
177     debug(`Loading JSON config file: ${filePath}`);
178
179     try {
180         return JSON.parse(stripComments(readFile(filePath)));
181     } catch (e) {
182         debug(`Error reading JSON file: ${filePath}`);
183         e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
184         e.messageTemplate = "failed-to-read-json";
185         e.messageData = {
186             path: filePath,
187             message: e.message
188         };
189         throw e;
190     }
191 }
192
193 /**
194  * Loads a legacy (.eslintrc) configuration from a file.
195  * @param {string} filePath The filename to load.
196  * @returns {ConfigData} The configuration object from the file.
197  * @throws {Error} If the file cannot be read.
198  * @private
199  */
200 function loadLegacyConfigFile(filePath) {
201     debug(`Loading legacy config file: ${filePath}`);
202
203     // lazy load YAML to improve performance when not used
204     const yaml = require("js-yaml");
205
206     try {
207         return yaml.safeLoad(stripComments(readFile(filePath))) || /* istanbul ignore next */ {};
208     } catch (e) {
209         debug("Error reading YAML file: %s\n%o", filePath, e);
210         e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
211         throw e;
212     }
213 }
214
215 /**
216  * Loads a JavaScript configuration from a file.
217  * @param {string} filePath The filename to load.
218  * @returns {ConfigData} The configuration object from the file.
219  * @throws {Error} If the file cannot be read.
220  * @private
221  */
222 function loadJSConfigFile(filePath) {
223     debug(`Loading JS config file: ${filePath}`);
224     try {
225         return importFresh(filePath);
226     } catch (e) {
227         debug(`Error reading JavaScript file: ${filePath}`);
228         e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
229         throw e;
230     }
231 }
232
233 /**
234  * Loads a configuration from a package.json file.
235  * @param {string} filePath The filename to load.
236  * @returns {ConfigData} The configuration object from the file.
237  * @throws {Error} If the file cannot be read.
238  * @private
239  */
240 function loadPackageJSONConfigFile(filePath) {
241     debug(`Loading package.json config file: ${filePath}`);
242     try {
243         const packageData = loadJSONConfigFile(filePath);
244
245         if (!Object.hasOwnProperty.call(packageData, "eslintConfig")) {
246             throw Object.assign(
247                 new Error("package.json file doesn't have 'eslintConfig' field."),
248                 { code: "ESLINT_CONFIG_FIELD_NOT_FOUND" }
249             );
250         }
251
252         return packageData.eslintConfig;
253     } catch (e) {
254         debug(`Error reading package.json file: ${filePath}`);
255         e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
256         throw e;
257     }
258 }
259
260 /**
261  * Loads a `.eslintignore` from a file.
262  * @param {string} filePath The filename to load.
263  * @returns {string[]} The ignore patterns from the file.
264  * @private
265  */
266 function loadESLintIgnoreFile(filePath) {
267     debug(`Loading .eslintignore file: ${filePath}`);
268
269     try {
270         return readFile(filePath)
271             .split(/\r?\n/gu)
272             .filter(line => line.trim() !== "" && !line.startsWith("#"));
273     } catch (e) {
274         debug(`Error reading .eslintignore file: ${filePath}`);
275         e.message = `Cannot read .eslintignore file: ${filePath}\nError: ${e.message}`;
276         throw e;
277     }
278 }
279
280 /**
281  * Creates an error to notify about a missing config to extend from.
282  * @param {string} configName The name of the missing config.
283  * @param {string} importerName The name of the config that imported the missing config
284  * @param {string} messageTemplate The text template to source error strings from.
285  * @returns {Error} The error object to throw
286  * @private
287  */
288 function configInvalidError(configName, importerName, messageTemplate) {
289     return Object.assign(
290         new Error(`Failed to load config "${configName}" to extend from.`),
291         {
292             messageTemplate,
293             messageData: { configName, importerName }
294         }
295     );
296 }
297
298 /**
299  * Loads a configuration file regardless of the source. Inspects the file path
300  * to determine the correctly way to load the config file.
301  * @param {string} filePath The path to the configuration.
302  * @returns {ConfigData|null} The configuration information.
303  * @private
304  */
305 function loadConfigFile(filePath) {
306     switch (path.extname(filePath)) {
307         case ".js":
308         case ".cjs":
309             return loadJSConfigFile(filePath);
310
311         case ".json":
312             if (path.basename(filePath) === "package.json") {
313                 return loadPackageJSONConfigFile(filePath);
314             }
315             return loadJSONConfigFile(filePath);
316
317         case ".yaml":
318         case ".yml":
319             return loadYAMLConfigFile(filePath);
320
321         default:
322             return loadLegacyConfigFile(filePath);
323     }
324 }
325
326 /**
327  * Write debug log.
328  * @param {string} request The requested module name.
329  * @param {string} relativeTo The file path to resolve the request relative to.
330  * @param {string} filePath The resolved file path.
331  * @returns {void}
332  */
333 function writeDebugLogForLoading(request, relativeTo, filePath) {
334     /* istanbul ignore next */
335     if (debug.enabled) {
336         let nameAndVersion = null;
337
338         try {
339             const packageJsonPath = ModuleResolver.resolve(
340                 `${request}/package.json`,
341                 relativeTo
342             );
343             const { version = "unknown" } = require(packageJsonPath);
344
345             nameAndVersion = `${request}@${version}`;
346         } catch (error) {
347             debug("package.json was not found:", error.message);
348             nameAndVersion = request;
349         }
350
351         debug("Loaded: %s (%s)", nameAndVersion, filePath);
352     }
353 }
354
355 /**
356  * Create a new context with default values.
357  * @param {ConfigArrayFactoryInternalSlots} slots The internal slots.
358  * @param {"config" | "ignore" | "implicit-processor" | undefined} providedType The type of the current configuration. Default is `"config"`.
359  * @param {string | undefined} providedName The name of the current configuration. Default is the relative path from `cwd` to `filePath`.
360  * @param {string | undefined} providedFilePath The path to the current configuration. Default is empty string.
361  * @param {string | undefined} providedMatchBasePath The type of the current configuration. Default is the directory of `filePath` or `cwd`.
362  * @returns {ConfigArrayFactoryLoadingContext} The created context.
363  */
364 function createContext(
365     { cwd, resolvePluginsRelativeTo },
366     providedType,
367     providedName,
368     providedFilePath,
369     providedMatchBasePath
370 ) {
371     const filePath = providedFilePath
372         ? path.resolve(cwd, providedFilePath)
373         : "";
374     const matchBasePath =
375         (providedMatchBasePath && path.resolve(cwd, providedMatchBasePath)) ||
376         (filePath && path.dirname(filePath)) ||
377         cwd;
378     const name =
379         providedName ||
380         (filePath && path.relative(cwd, filePath)) ||
381         "";
382     const pluginBasePath =
383         resolvePluginsRelativeTo ||
384         (filePath && path.dirname(filePath)) ||
385         cwd;
386     const type = providedType || "config";
387
388     return { filePath, matchBasePath, name, pluginBasePath, type };
389 }
390
391 /**
392  * Normalize a given plugin.
393  * - Ensure the object to have four properties: configs, environments, processors, and rules.
394  * - Ensure the object to not have other properties.
395  * @param {Plugin} plugin The plugin to normalize.
396  * @returns {Plugin} The normalized plugin.
397  */
398 function normalizePlugin(plugin) {
399     return {
400         configs: plugin.configs || {},
401         environments: plugin.environments || {},
402         processors: plugin.processors || {},
403         rules: plugin.rules || {}
404     };
405 }
406
407 //------------------------------------------------------------------------------
408 // Public Interface
409 //------------------------------------------------------------------------------
410
411 /**
412  * The factory of `ConfigArray` objects.
413  */
414 class ConfigArrayFactory {
415
416     /**
417      * Initialize this instance.
418      * @param {ConfigArrayFactoryOptions} [options] The map for additional plugins.
419      */
420     constructor({
421         additionalPluginPool = new Map(),
422         cwd = process.cwd(),
423         resolvePluginsRelativeTo,
424         builtInRules,
425         resolver = ModuleResolver,
426         eslintAllPath,
427         eslintRecommendedPath
428     } = {}) {
429         internalSlotsMap.set(this, {
430             additionalPluginPool,
431             cwd,
432             resolvePluginsRelativeTo:
433                 resolvePluginsRelativeTo &&
434                 path.resolve(cwd, resolvePluginsRelativeTo),
435             builtInRules,
436             resolver,
437             eslintAllPath,
438             eslintRecommendedPath
439         });
440     }
441
442     /**
443      * Create `ConfigArray` instance from a config data.
444      * @param {ConfigData|null} configData The config data to create.
445      * @param {Object} [options] The options.
446      * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
447      * @param {string} [options.filePath] The path to this config data.
448      * @param {string} [options.name] The config name.
449      * @returns {ConfigArray} Loaded config.
450      */
451     create(configData, { basePath, filePath, name } = {}) {
452         if (!configData) {
453             return new ConfigArray();
454         }
455
456         const slots = internalSlotsMap.get(this);
457         const ctx = createContext(slots, "config", name, filePath, basePath);
458         const elements = this._normalizeConfigData(configData, ctx);
459
460         return new ConfigArray(...elements);
461     }
462
463     /**
464      * Load a config file.
465      * @param {string} filePath The path to a config file.
466      * @param {Object} [options] The options.
467      * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
468      * @param {string} [options.name] The config name.
469      * @returns {ConfigArray} Loaded config.
470      */
471     loadFile(filePath, { basePath, name } = {}) {
472         const slots = internalSlotsMap.get(this);
473         const ctx = createContext(slots, "config", name, filePath, basePath);
474
475         return new ConfigArray(...this._loadConfigData(ctx));
476     }
477
478     /**
479      * Load the config file on a given directory if exists.
480      * @param {string} directoryPath The path to a directory.
481      * @param {Object} [options] The options.
482      * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
483      * @param {string} [options.name] The config name.
484      * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
485      */
486     loadInDirectory(directoryPath, { basePath, name } = {}) {
487         const slots = internalSlotsMap.get(this);
488
489         for (const filename of configFilenames) {
490             const ctx = createContext(
491                 slots,
492                 "config",
493                 name,
494                 path.join(directoryPath, filename),
495                 basePath
496             );
497
498             if (fs.existsSync(ctx.filePath) && fs.statSync(ctx.filePath).isFile()) {
499                 let configData;
500
501                 try {
502                     configData = loadConfigFile(ctx.filePath);
503                 } catch (error) {
504                     if (!error || error.code !== "ESLINT_CONFIG_FIELD_NOT_FOUND") {
505                         throw error;
506                     }
507                 }
508
509                 if (configData) {
510                     debug(`Config file found: ${ctx.filePath}`);
511                     return new ConfigArray(
512                         ...this._normalizeConfigData(configData, ctx)
513                     );
514                 }
515             }
516         }
517
518         debug(`Config file not found on ${directoryPath}`);
519         return new ConfigArray();
520     }
521
522     /**
523      * Check if a config file on a given directory exists or not.
524      * @param {string} directoryPath The path to a directory.
525      * @returns {string | null} The path to the found config file. If not found then null.
526      */
527     static getPathToConfigFileInDirectory(directoryPath) {
528         for (const filename of configFilenames) {
529             const filePath = path.join(directoryPath, filename);
530
531             if (fs.existsSync(filePath)) {
532                 if (filename === "package.json") {
533                     try {
534                         loadPackageJSONConfigFile(filePath);
535                         return filePath;
536                     } catch { /* ignore */ }
537                 } else {
538                     return filePath;
539                 }
540             }
541         }
542         return null;
543     }
544
545     /**
546      * Load `.eslintignore` file.
547      * @param {string} filePath The path to a `.eslintignore` file to load.
548      * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
549      */
550     loadESLintIgnore(filePath) {
551         const slots = internalSlotsMap.get(this);
552         const ctx = createContext(
553             slots,
554             "ignore",
555             void 0,
556             filePath,
557             slots.cwd
558         );
559         const ignorePatterns = loadESLintIgnoreFile(ctx.filePath);
560
561         return new ConfigArray(
562             ...this._normalizeESLintIgnoreData(ignorePatterns, ctx)
563         );
564     }
565
566     /**
567      * Load `.eslintignore` file in the current working directory.
568      * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
569      */
570     loadDefaultESLintIgnore() {
571         const slots = internalSlotsMap.get(this);
572         const eslintIgnorePath = path.resolve(slots.cwd, ".eslintignore");
573         const packageJsonPath = path.resolve(slots.cwd, "package.json");
574
575         if (fs.existsSync(eslintIgnorePath)) {
576             return this.loadESLintIgnore(eslintIgnorePath);
577         }
578         if (fs.existsSync(packageJsonPath)) {
579             const data = loadJSONConfigFile(packageJsonPath);
580
581             if (Object.hasOwnProperty.call(data, "eslintIgnore")) {
582                 if (!Array.isArray(data.eslintIgnore)) {
583                     throw new Error("Package.json eslintIgnore property requires an array of paths");
584                 }
585                 const ctx = createContext(
586                     slots,
587                     "ignore",
588                     "eslintIgnore in package.json",
589                     packageJsonPath,
590                     slots.cwd
591                 );
592
593                 return new ConfigArray(
594                     ...this._normalizeESLintIgnoreData(data.eslintIgnore, ctx)
595                 );
596             }
597         }
598
599         return new ConfigArray();
600     }
601
602     /**
603      * Load a given config file.
604      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
605      * @returns {IterableIterator<ConfigArrayElement>} Loaded config.
606      * @private
607      */
608     _loadConfigData(ctx) {
609         return this._normalizeConfigData(loadConfigFile(ctx.filePath), ctx);
610     }
611
612     /**
613      * Normalize a given `.eslintignore` data to config array elements.
614      * @param {string[]} ignorePatterns The patterns to ignore files.
615      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
616      * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
617      * @private
618      */
619     *_normalizeESLintIgnoreData(ignorePatterns, ctx) {
620         const elements = this._normalizeObjectConfigData(
621             { ignorePatterns },
622             ctx
623         );
624
625         // Set `ignorePattern.loose` flag for backward compatibility.
626         for (const element of elements) {
627             if (element.ignorePattern) {
628                 element.ignorePattern.loose = true;
629             }
630             yield element;
631         }
632     }
633
634     /**
635      * Normalize a given config to an array.
636      * @param {ConfigData} configData The config data to normalize.
637      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
638      * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
639      * @private
640      */
641     _normalizeConfigData(configData, ctx) {
642         const validator = new ConfigValidator();
643
644         validator.validateConfigSchema(configData, ctx.name || ctx.filePath);
645         return this._normalizeObjectConfigData(configData, ctx);
646     }
647
648     /**
649      * Normalize a given config to an array.
650      * @param {ConfigData|OverrideConfigData} configData The config data to normalize.
651      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
652      * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
653      * @private
654      */
655     *_normalizeObjectConfigData(configData, ctx) {
656         const { files, excludedFiles, ...configBody } = configData;
657         const criteria = OverrideTester.create(
658             files,
659             excludedFiles,
660             ctx.matchBasePath
661         );
662         const elements = this._normalizeObjectConfigDataBody(configBody, ctx);
663
664         // Apply the criteria to every element.
665         for (const element of elements) {
666
667             /*
668              * Merge the criteria.
669              * This is for the `overrides` entries that came from the
670              * configurations of `overrides[].extends`.
671              */
672             element.criteria = OverrideTester.and(criteria, element.criteria);
673
674             /*
675              * Remove `root` property to ignore `root` settings which came from
676              * `extends` in `overrides`.
677              */
678             if (element.criteria) {
679                 element.root = void 0;
680             }
681
682             yield element;
683         }
684     }
685
686     /**
687      * Normalize a given config to an array.
688      * @param {ConfigData} configData The config data to normalize.
689      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
690      * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
691      * @private
692      */
693     *_normalizeObjectConfigDataBody(
694         {
695             env,
696             extends: extend,
697             globals,
698             ignorePatterns,
699             noInlineConfig,
700             parser: parserName,
701             parserOptions,
702             plugins: pluginList,
703             processor,
704             reportUnusedDisableDirectives,
705             root,
706             rules,
707             settings,
708             overrides: overrideList = []
709         },
710         ctx
711     ) {
712         const extendList = Array.isArray(extend) ? extend : [extend];
713         const ignorePattern = ignorePatterns && new IgnorePattern(
714             Array.isArray(ignorePatterns) ? ignorePatterns : [ignorePatterns],
715             ctx.matchBasePath
716         );
717
718         // Flatten `extends`.
719         for (const extendName of extendList.filter(Boolean)) {
720             yield* this._loadExtends(extendName, ctx);
721         }
722
723         // Load parser & plugins.
724         const parser = parserName && this._loadParser(parserName, ctx);
725         const plugins = pluginList && this._loadPlugins(pluginList, ctx);
726
727         // Yield pseudo config data for file extension processors.
728         if (plugins) {
729             yield* this._takeFileExtensionProcessors(plugins, ctx);
730         }
731
732         // Yield the config data except `extends` and `overrides`.
733         yield {
734
735             // Debug information.
736             type: ctx.type,
737             name: ctx.name,
738             filePath: ctx.filePath,
739
740             // Config data.
741             criteria: null,
742             env,
743             globals,
744             ignorePattern,
745             noInlineConfig,
746             parser,
747             parserOptions,
748             plugins,
749             processor,
750             reportUnusedDisableDirectives,
751             root,
752             rules,
753             settings
754         };
755
756         // Flatten `overries`.
757         for (let i = 0; i < overrideList.length; ++i) {
758             yield* this._normalizeObjectConfigData(
759                 overrideList[i],
760                 { ...ctx, name: `${ctx.name}#overrides[${i}]` }
761             );
762         }
763     }
764
765     /**
766      * Load configs of an element in `extends`.
767      * @param {string} extendName The name of a base config.
768      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
769      * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
770      * @private
771      */
772     _loadExtends(extendName, ctx) {
773         debug("Loading {extends:%j} relative to %s", extendName, ctx.filePath);
774         try {
775             if (extendName.startsWith("eslint:")) {
776                 return this._loadExtendedBuiltInConfig(extendName, ctx);
777             }
778             if (extendName.startsWith("plugin:")) {
779                 return this._loadExtendedPluginConfig(extendName, ctx);
780             }
781             return this._loadExtendedShareableConfig(extendName, ctx);
782         } catch (error) {
783             error.message += `\nReferenced from: ${ctx.filePath || ctx.name}`;
784             throw error;
785         }
786     }
787
788     /**
789      * Load configs of an element in `extends`.
790      * @param {string} extendName The name of a base config.
791      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
792      * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
793      * @private
794      */
795     _loadExtendedBuiltInConfig(extendName, ctx) {
796         const { eslintAllPath, eslintRecommendedPath } = internalSlotsMap.get(this);
797
798         if (extendName === "eslint:recommended") {
799             return this._loadConfigData({
800                 ...ctx,
801                 filePath: eslintRecommendedPath,
802                 name: `${ctx.name} Â» ${extendName}`
803             });
804         }
805         if (extendName === "eslint:all") {
806             return this._loadConfigData({
807                 ...ctx,
808                 filePath: eslintAllPath,
809                 name: `${ctx.name} Â» ${extendName}`
810             });
811         }
812
813         throw configInvalidError(extendName, ctx.name, "extend-config-missing");
814     }
815
816     /**
817      * Load configs of an element in `extends`.
818      * @param {string} extendName The name of a base config.
819      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
820      * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
821      * @private
822      */
823     _loadExtendedPluginConfig(extendName, ctx) {
824         const slashIndex = extendName.lastIndexOf("/");
825
826         if (slashIndex === -1) {
827             throw configInvalidError(extendName, ctx.filePath, "plugin-invalid");
828         }
829
830         const pluginName = extendName.slice("plugin:".length, slashIndex);
831         const configName = extendName.slice(slashIndex + 1);
832
833         if (isFilePath(pluginName)) {
834             throw new Error("'extends' cannot use a file path for plugins.");
835         }
836
837         const plugin = this._loadPlugin(pluginName, ctx);
838         const configData =
839             plugin.definition &&
840             plugin.definition.configs[configName];
841
842         if (configData) {
843             return this._normalizeConfigData(configData, {
844                 ...ctx,
845                 filePath: plugin.filePath || ctx.filePath,
846                 name: `${ctx.name} Â» plugin:${plugin.id}/${configName}`
847             });
848         }
849
850         throw plugin.error || configInvalidError(extendName, ctx.filePath, "extend-config-missing");
851     }
852
853     /**
854      * Load configs of an element in `extends`.
855      * @param {string} extendName The name of a base config.
856      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
857      * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
858      * @private
859      */
860     _loadExtendedShareableConfig(extendName, ctx) {
861         const { cwd, resolver } = internalSlotsMap.get(this);
862         const relativeTo = ctx.filePath || path.join(cwd, "__placeholder__.js");
863         let request;
864
865         if (isFilePath(extendName)) {
866             request = extendName;
867         } else if (extendName.startsWith(".")) {
868             request = `./${extendName}`; // For backward compatibility. A ton of tests depended on this behavior.
869         } else {
870             request = naming.normalizePackageName(
871                 extendName,
872                 "eslint-config"
873             );
874         }
875
876         let filePath;
877
878         try {
879             filePath = resolver.resolve(request, relativeTo);
880         } catch (error) {
881             /* istanbul ignore else */
882             if (error && error.code === "MODULE_NOT_FOUND") {
883                 throw configInvalidError(extendName, ctx.filePath, "extend-config-missing");
884             }
885             throw error;
886         }
887
888         writeDebugLogForLoading(request, relativeTo, filePath);
889         return this._loadConfigData({
890             ...ctx,
891             filePath,
892             name: `${ctx.name} Â» ${request}`
893         });
894     }
895
896     /**
897      * Load given plugins.
898      * @param {string[]} names The plugin names to load.
899      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
900      * @returns {Record<string,DependentPlugin>} The loaded parser.
901      * @private
902      */
903     _loadPlugins(names, ctx) {
904         return names.reduce((map, name) => {
905             if (isFilePath(name)) {
906                 throw new Error("Plugins array cannot includes file paths.");
907             }
908             const plugin = this._loadPlugin(name, ctx);
909
910             map[plugin.id] = plugin;
911
912             return map;
913         }, {});
914     }
915
916     /**
917      * Load a given parser.
918      * @param {string} nameOrPath The package name or the path to a parser file.
919      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
920      * @returns {DependentParser} The loaded parser.
921      */
922     _loadParser(nameOrPath, ctx) {
923         debug("Loading parser %j from %s", nameOrPath, ctx.filePath);
924
925         const { cwd } = internalSlotsMap.get(this);
926         const relativeTo = ctx.filePath || path.join(cwd, "__placeholder__.js");
927
928         try {
929             const filePath = ModuleResolver.resolve(nameOrPath, relativeTo);
930
931             writeDebugLogForLoading(nameOrPath, relativeTo, filePath);
932
933             return new ConfigDependency({
934                 definition: require(filePath),
935                 filePath,
936                 id: nameOrPath,
937                 importerName: ctx.name,
938                 importerPath: ctx.filePath
939             });
940         } catch (error) {
941
942             // If the parser name is "espree", load the espree of ESLint.
943             if (nameOrPath === "espree") {
944                 debug("Fallback espree.");
945                 return new ConfigDependency({
946                     definition: require("espree"),
947                     filePath: require.resolve("espree"),
948                     id: nameOrPath,
949                     importerName: ctx.name,
950                     importerPath: ctx.filePath
951                 });
952             }
953
954             debug("Failed to load parser '%s' declared in '%s'.", nameOrPath, ctx.name);
955             error.message = `Failed to load parser '${nameOrPath}' declared in '${ctx.name}': ${error.message}`;
956
957             return new ConfigDependency({
958                 error,
959                 id: nameOrPath,
960                 importerName: ctx.name,
961                 importerPath: ctx.filePath
962             });
963         }
964     }
965
966     /**
967      * Load a given plugin.
968      * @param {string} name The plugin name to load.
969      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
970      * @returns {DependentPlugin} The loaded plugin.
971      * @private
972      */
973     _loadPlugin(name, ctx) {
974         debug("Loading plugin %j from %s", name, ctx.filePath);
975
976         const { additionalPluginPool } = internalSlotsMap.get(this);
977         const request = naming.normalizePackageName(name, "eslint-plugin");
978         const id = naming.getShorthandName(request, "eslint-plugin");
979         const relativeTo = path.join(ctx.pluginBasePath, "__placeholder__.js");
980
981         if (name.match(/\s+/u)) {
982             const error = Object.assign(
983                 new Error(`Whitespace found in plugin name '${name}'`),
984                 {
985                     messageTemplate: "whitespace-found",
986                     messageData: { pluginName: request }
987                 }
988             );
989
990             return new ConfigDependency({
991                 error,
992                 id,
993                 importerName: ctx.name,
994                 importerPath: ctx.filePath
995             });
996         }
997
998         // Check for additional pool.
999         const plugin =
1000             additionalPluginPool.get(request) ||
1001             additionalPluginPool.get(id);
1002
1003         if (plugin) {
1004             return new ConfigDependency({
1005                 definition: normalizePlugin(plugin),
1006                 filePath: "", // It's unknown where the plugin came from.
1007                 id,
1008                 importerName: ctx.name,
1009                 importerPath: ctx.filePath
1010             });
1011         }
1012
1013         let filePath;
1014         let error;
1015
1016         try {
1017             filePath = ModuleResolver.resolve(request, relativeTo);
1018         } catch (resolveError) {
1019             error = resolveError;
1020             /* istanbul ignore else */
1021             if (error && error.code === "MODULE_NOT_FOUND") {
1022                 error.messageTemplate = "plugin-missing";
1023                 error.messageData = {
1024                     pluginName: request,
1025                     resolvePluginsRelativeTo: ctx.pluginBasePath,
1026                     importerName: ctx.name
1027                 };
1028             }
1029         }
1030
1031         if (filePath) {
1032             try {
1033                 writeDebugLogForLoading(request, relativeTo, filePath);
1034
1035                 const startTime = Date.now();
1036                 const pluginDefinition = require(filePath);
1037
1038                 debug(`Plugin ${filePath} loaded in: ${Date.now() - startTime}ms`);
1039
1040                 return new ConfigDependency({
1041                     definition: normalizePlugin(pluginDefinition),
1042                     filePath,
1043                     id,
1044                     importerName: ctx.name,
1045                     importerPath: ctx.filePath
1046                 });
1047             } catch (loadError) {
1048                 error = loadError;
1049             }
1050         }
1051
1052         debug("Failed to load plugin '%s' declared in '%s'.", name, ctx.name);
1053         error.message = `Failed to load plugin '${name}' declared in '${ctx.name}': ${error.message}`;
1054         return new ConfigDependency({
1055             error,
1056             id,
1057             importerName: ctx.name,
1058             importerPath: ctx.filePath
1059         });
1060     }
1061
1062     /**
1063      * Take file expression processors as config array elements.
1064      * @param {Record<string,DependentPlugin>} plugins The plugin definitions.
1065      * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
1066      * @returns {IterableIterator<ConfigArrayElement>} The config array elements of file expression processors.
1067      * @private
1068      */
1069     *_takeFileExtensionProcessors(plugins, ctx) {
1070         for (const pluginId of Object.keys(plugins)) {
1071             const processors =
1072                 plugins[pluginId] &&
1073                 plugins[pluginId].definition &&
1074                 plugins[pluginId].definition.processors;
1075
1076             if (!processors) {
1077                 continue;
1078             }
1079
1080             for (const processorId of Object.keys(processors)) {
1081                 if (processorId.startsWith(".")) {
1082                     yield* this._normalizeObjectConfigData(
1083                         {
1084                             files: [`*${processorId}`],
1085                             processor: `${pluginId}/${processorId}`
1086                         },
1087                         {
1088                             ...ctx,
1089                             type: "implicit-processor",
1090                             name: `${ctx.name}#processors["${pluginId}/${processorId}"]`
1091                         }
1092                     );
1093                 }
1094             }
1095         }
1096     }
1097 }
1098
1099 module.exports = { ConfigArrayFactory, createContext };