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