2 * @fileoverview The factory of `ConfigArray` objects.
4 * This class provides methods to create `ConfigArray` instance.
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.
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`.
31 * But this class doesn't handle cascading. `CascadingConfigArrayFactory` class
32 * handles cascading and hierarchy.
34 * @author Toru Nagashima <https://github.com/mysticatea>
38 //------------------------------------------------------------------------------
40 //------------------------------------------------------------------------------
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 { validateConfigSchema
} = require("../shared/config-validator");
47 const naming
= require("../shared/naming");
48 const ModuleResolver
= require("../shared/relative-module-resolver");
54 } = require("./config-array");
55 const debug
= require("debug")("eslint:config-array-factory");
57 //------------------------------------------------------------------------------
59 //------------------------------------------------------------------------------
61 const eslintRecommendedPath
= path
.resolve(__dirname
, "../../conf/eslint-recommended.js");
62 const eslintAllPath
= path
.resolve(__dirname
, "../../conf/eslint-all.js");
63 const configFilenames
= [
73 // Define types for VSCode IntelliSense.
74 /** @typedef {import("../shared/types").ConfigData} ConfigData */
75 /** @typedef {import("../shared/types").OverrideConfigData} OverrideConfigData */
76 /** @typedef {import("../shared/types").Parser} Parser */
77 /** @typedef {import("../shared/types").Plugin} Plugin */
78 /** @typedef {import("./config-array/config-dependency").DependentParser} DependentParser */
79 /** @typedef {import("./config-array/config-dependency").DependentPlugin} DependentPlugin */
80 /** @typedef {ConfigArray[0]} ConfigArrayElement */
83 * @typedef {Object} ConfigArrayFactoryOptions
84 * @property {Map<string,Plugin>} [additionalPluginPool] The map for additional plugins.
85 * @property {string} [cwd] The path to the current working directory.
86 * @property {string} [resolvePluginsRelativeTo] A path to the directory that plugins should be resolved from. Defaults to `cwd`.
90 * @typedef {Object} ConfigArrayFactoryInternalSlots
91 * @property {Map<string,Plugin>} additionalPluginPool The map for additional plugins.
92 * @property {string} cwd The path to the current working directory.
93 * @property {string | undefined} resolvePluginsRelativeTo An absolute path the the directory that plugins should be resolved from.
97 * @typedef {Object} ConfigArrayFactoryLoadingContext
98 * @property {string} filePath The path to the current configuration.
99 * @property {string} matchBasePath The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
100 * @property {string} name The name of the current configuration.
101 * @property {string} pluginBasePath The base path to resolve plugins.
102 * @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.
106 * @typedef {Object} ConfigArrayFactoryLoadingContext
107 * @property {string} filePath The path to the current configuration.
108 * @property {string} matchBasePath The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
109 * @property {string} name The name of the current configuration.
110 * @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.
113 /** @type {WeakMap<ConfigArrayFactory, ConfigArrayFactoryInternalSlots>} */
114 const internalSlotsMap
= new WeakMap();
117 * Check if a given string is a file path.
118 * @param {string} nameOrPath A module name or file path.
119 * @returns {boolean} `true` if the `nameOrPath` is a file path.
121 function isFilePath(nameOrPath
) {
123 /^\.{1,2}[/\\]/u
.test(nameOrPath
) ||
124 path
.isAbsolute(nameOrPath
)
129 * Convenience wrapper for synchronously reading file contents.
130 * @param {string} filePath The filename to read.
131 * @returns {string} The file contents, with the BOM removed.
134 function readFile(filePath
) {
135 return fs
.readFileSync(filePath
, "utf8").replace(/^\ufeff/u, "");
139 * Loads a YAML configuration from a file.
140 * @param {string} filePath The filename to load.
141 * @returns {ConfigData} The configuration object from the file.
142 * @throws {Error} If the file cannot be read.
145 function loadYAMLConfigFile(filePath
) {
146 debug(`Loading YAML config file: ${filePath}`);
148 // lazy load YAML to improve performance when not used
149 const yaml
= require("js-yaml");
153 // empty YAML file can be null, so always use
154 return yaml
.safeLoad(readFile(filePath
)) || {};
156 debug(`Error reading YAML file: ${filePath}`);
157 e
.message
= `Cannot read config file: ${filePath}\nError: ${e.message}`;
163 * Loads a JSON configuration from a file.
164 * @param {string} filePath The filename to load.
165 * @returns {ConfigData} The configuration object from the file.
166 * @throws {Error} If the file cannot be read.
169 function loadJSONConfigFile(filePath
) {
170 debug(`Loading JSON config file: ${filePath}`);
173 return JSON
.parse(stripComments(readFile(filePath
)));
175 debug(`Error reading JSON file: ${filePath}`);
176 e
.message
= `Cannot read config file: ${filePath}\nError: ${e.message}`;
177 e
.messageTemplate
= "failed-to-read-json";
187 * Loads a legacy (.eslintrc) configuration from a file.
188 * @param {string} filePath The filename to load.
189 * @returns {ConfigData} The configuration object from the file.
190 * @throws {Error} If the file cannot be read.
193 function loadLegacyConfigFile(filePath
) {
194 debug(`Loading legacy config file: ${filePath}`);
196 // lazy load YAML to improve performance when not used
197 const yaml
= require("js-yaml");
200 return yaml
.safeLoad(stripComments(readFile(filePath
))) || /* istanbul ignore next */ {};
202 debug("Error reading YAML file: %s\n%o", filePath
, e
);
203 e
.message
= `Cannot read config file: ${filePath}\nError: ${e.message}`;
209 * Loads a JavaScript configuration from a file.
210 * @param {string} filePath The filename to load.
211 * @returns {ConfigData} The configuration object from the file.
212 * @throws {Error} If the file cannot be read.
215 function loadJSConfigFile(filePath
) {
216 debug(`Loading JS config file: ${filePath}`);
218 return importFresh(filePath
);
220 debug(`Error reading JavaScript file: ${filePath}`);
221 e
.message
= `Cannot read config file: ${filePath}\nError: ${e.message}`;
227 * Loads a configuration from a package.json file.
228 * @param {string} filePath The filename to load.
229 * @returns {ConfigData} The configuration object from the file.
230 * @throws {Error} If the file cannot be read.
233 function loadPackageJSONConfigFile(filePath
) {
234 debug(`Loading package.json config file: ${filePath}`);
236 const packageData
= loadJSONConfigFile(filePath
);
238 if (!Object
.hasOwnProperty
.call(packageData
, "eslintConfig")) {
240 new Error("package.json file doesn't have 'eslintConfig' field."),
241 { code
: "ESLINT_CONFIG_FIELD_NOT_FOUND" }
245 return packageData
.eslintConfig
;
247 debug(`Error reading package.json file: ${filePath}`);
248 e
.message
= `Cannot read config file: ${filePath}\nError: ${e.message}`;
254 * Loads a `.eslintignore` from a file.
255 * @param {string} filePath The filename to load.
256 * @returns {string[]} The ignore patterns from the file.
259 function loadESLintIgnoreFile(filePath
) {
260 debug(`Loading .eslintignore file: ${filePath}`);
263 return readFile(filePath
)
265 .filter(line
=> line
.trim() !== "" && !line
.startsWith("#"));
267 debug(`Error reading .eslintignore file: ${filePath}`);
268 e
.message
= `Cannot read .eslintignore file: ${filePath}\nError: ${e.message}`;
274 * Creates an error to notify about a missing config to extend from.
275 * @param {string} configName The name of the missing config.
276 * @param {string} importerName The name of the config that imported the missing config
277 * @returns {Error} The error object to throw
280 function configMissingError(configName
, importerName
) {
281 return Object
.assign(
282 new Error(`Failed to load config "${configName}" to extend from.`),
284 messageTemplate
: "extend-config-missing",
285 messageData
: { configName
, importerName
}
291 * Loads a configuration file regardless of the source. Inspects the file path
292 * to determine the correctly way to load the config file.
293 * @param {string} filePath The path to the configuration.
294 * @returns {ConfigData|null} The configuration information.
297 function loadConfigFile(filePath
) {
298 switch (path
.extname(filePath
)) {
301 return loadJSConfigFile(filePath
);
304 if (path
.basename(filePath
) === "package.json") {
305 return loadPackageJSONConfigFile(filePath
);
307 return loadJSONConfigFile(filePath
);
311 return loadYAMLConfigFile(filePath
);
314 return loadLegacyConfigFile(filePath
);
320 * @param {string} request The requested module name.
321 * @param {string} relativeTo The file path to resolve the request relative to.
322 * @param {string} filePath The resolved file path.
325 function writeDebugLogForLoading(request
, relativeTo
, filePath
) {
326 /* istanbul ignore next */
328 let nameAndVersion
= null;
331 const packageJsonPath
= ModuleResolver
.resolve(
332 `${request}/package.json`,
335 const { version
= "unknown" } = require(packageJsonPath
);
337 nameAndVersion
= `${request}@${version}`;
339 debug("package.json was not found:", error
.message
);
340 nameAndVersion
= request
;
343 debug("Loaded: %s (%s)", nameAndVersion
, filePath
);
348 * Create a new context with default values.
349 * @param {ConfigArrayFactoryInternalSlots} slots The internal slots.
350 * @param {"config" | "ignore" | "implicit-processor" | undefined} providedType The type of the current configuration. Default is `"config"`.
351 * @param {string | undefined} providedName The name of the current configuration. Default is the relative path from `cwd` to `filePath`.
352 * @param {string | undefined} providedFilePath The path to the current configuration. Default is empty string.
353 * @param {string | undefined} providedMatchBasePath The type of the current configuration. Default is the directory of `filePath` or `cwd`.
354 * @returns {ConfigArrayFactoryLoadingContext} The created context.
356 function createContext(
357 { cwd
, resolvePluginsRelativeTo
},
361 providedMatchBasePath
363 const filePath
= providedFilePath
364 ? path
.resolve(cwd
, providedFilePath
)
366 const matchBasePath
=
367 (providedMatchBasePath
&& path
.resolve(cwd
, providedMatchBasePath
)) ||
368 (filePath
&& path
.dirname(filePath
)) ||
372 (filePath
&& path
.relative(cwd
, filePath
)) ||
374 const pluginBasePath
=
375 resolvePluginsRelativeTo
||
376 (filePath
&& path
.dirname(filePath
)) ||
378 const type
= providedType
|| "config";
380 return { filePath
, matchBasePath
, name
, pluginBasePath
, type
};
384 * Normalize a given plugin.
385 * - Ensure the object to have four properties: configs, environments, processors, and rules.
386 * - Ensure the object to not have other properties.
387 * @param {Plugin} plugin The plugin to normalize.
388 * @returns {Plugin} The normalized plugin.
390 function normalizePlugin(plugin
) {
392 configs
: plugin
.configs
|| {},
393 environments
: plugin
.environments
|| {},
394 processors
: plugin
.processors
|| {},
395 rules
: plugin
.rules
|| {}
399 //------------------------------------------------------------------------------
401 //------------------------------------------------------------------------------
404 * The factory of `ConfigArray` objects.
406 class ConfigArrayFactory
{
409 * Initialize this instance.
410 * @param {ConfigArrayFactoryOptions} [options] The map for additional plugins.
413 additionalPluginPool
= new Map(),
415 resolvePluginsRelativeTo
417 internalSlotsMap
.set(this, {
418 additionalPluginPool
,
420 resolvePluginsRelativeTo
:
421 resolvePluginsRelativeTo
&&
422 path
.resolve(cwd
, resolvePluginsRelativeTo
)
427 * Create `ConfigArray` instance from a config data.
428 * @param {ConfigData|null} configData The config data to create.
429 * @param {Object} [options] The options.
430 * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
431 * @param {string} [options.filePath] The path to this config data.
432 * @param {string} [options.name] The config name.
433 * @returns {ConfigArray} Loaded config.
435 create(configData
, { basePath
, filePath
, name
} = {}) {
437 return new ConfigArray();
440 const slots
= internalSlotsMap
.get(this);
441 const ctx
= createContext(slots
, "config", name
, filePath
, basePath
);
442 const elements
= this._normalizeConfigData(configData
, ctx
);
444 return new ConfigArray(...elements
);
448 * Load a config file.
449 * @param {string} filePath The path to a config file.
450 * @param {Object} [options] The options.
451 * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
452 * @param {string} [options.name] The config name.
453 * @returns {ConfigArray} Loaded config.
455 loadFile(filePath
, { basePath
, name
} = {}) {
456 const slots
= internalSlotsMap
.get(this);
457 const ctx
= createContext(slots
, "config", name
, filePath
, basePath
);
459 return new ConfigArray(...this._loadConfigData(ctx
));
463 * Load the config file on a given directory if exists.
464 * @param {string} directoryPath The path to a directory.
465 * @param {Object} [options] The options.
466 * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
467 * @param {string} [options.name] The config name.
468 * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
470 loadInDirectory(directoryPath
, { basePath
, name
} = {}) {
471 const slots
= internalSlotsMap
.get(this);
473 for (const filename
of configFilenames
) {
474 const ctx
= createContext(
478 path
.join(directoryPath
, filename
),
482 if (fs
.existsSync(ctx
.filePath
)) {
486 configData
= loadConfigFile(ctx
.filePath
);
488 if (!error
|| error
.code
!== "ESLINT_CONFIG_FIELD_NOT_FOUND") {
494 debug(`Config file found: ${ctx.filePath}`);
495 return new ConfigArray(
496 ...this._normalizeConfigData(configData
, ctx
)
502 debug(`Config file not found on ${directoryPath}`);
503 return new ConfigArray();
507 * Check if a config file on a given directory exists or not.
508 * @param {string} directoryPath The path to a directory.
509 * @returns {string | null} The path to the found config file. If not found then null.
511 static getPathToConfigFileInDirectory(directoryPath
) {
512 for (const filename
of configFilenames
) {
513 const filePath
= path
.join(directoryPath
, filename
);
515 if (fs
.existsSync(filePath
)) {
516 if (filename
=== "package.json") {
518 loadPackageJSONConfigFile(filePath
);
520 } catch { /* ignore */ }
530 * Load `.eslintignore` file.
531 * @param {string} filePath The path to a `.eslintignore` file to load.
532 * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
534 loadESLintIgnore(filePath
) {
535 const slots
= internalSlotsMap
.get(this);
536 const ctx
= createContext(
543 const ignorePatterns
= loadESLintIgnoreFile(ctx
.filePath
);
545 return new ConfigArray(
546 ...this._normalizeESLintIgnoreData(ignorePatterns
, ctx
)
551 * Load `.eslintignore` file in the current working directory.
552 * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
554 loadDefaultESLintIgnore() {
555 const slots
= internalSlotsMap
.get(this);
556 const eslintIgnorePath
= path
.resolve(slots
.cwd
, ".eslintignore");
557 const packageJsonPath
= path
.resolve(slots
.cwd
, "package.json");
559 if (fs
.existsSync(eslintIgnorePath
)) {
560 return this.loadESLintIgnore(eslintIgnorePath
);
562 if (fs
.existsSync(packageJsonPath
)) {
563 const data
= loadJSONConfigFile(packageJsonPath
);
565 if (Object
.hasOwnProperty
.call(data
, "eslintIgnore")) {
566 if (!Array
.isArray(data
.eslintIgnore
)) {
567 throw new Error("Package.json eslintIgnore property requires an array of paths");
569 const ctx
= createContext(
572 "eslintIgnore in package.json",
577 return new ConfigArray(
578 ...this._normalizeESLintIgnoreData(data
.eslintIgnore
, ctx
)
583 return new ConfigArray();
587 * Load a given config file.
588 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
589 * @returns {IterableIterator<ConfigArrayElement>} Loaded config.
592 _loadConfigData(ctx
) {
593 return this._normalizeConfigData(loadConfigFile(ctx
.filePath
), ctx
);
597 * Normalize a given `.eslintignore` data to config array elements.
598 * @param {string[]} ignorePatterns The patterns to ignore files.
599 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
600 * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
603 *_normalizeESLintIgnoreData(ignorePatterns
, ctx
) {
604 const elements
= this._normalizeObjectConfigData(
609 // Set `ignorePattern.loose` flag for backward compatibility.
610 for (const element
of elements
) {
611 if (element
.ignorePattern
) {
612 element
.ignorePattern
.loose
= true;
619 * Normalize a given config to an array.
620 * @param {ConfigData} configData The config data to normalize.
621 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
622 * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
625 _normalizeConfigData(configData
, ctx
) {
626 validateConfigSchema(configData
, ctx
.name
|| ctx
.filePath
);
627 return this._normalizeObjectConfigData(configData
, ctx
);
631 * Normalize a given config to an array.
632 * @param {ConfigData|OverrideConfigData} configData The config data to normalize.
633 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
634 * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
637 *_normalizeObjectConfigData(configData
, ctx
) {
638 const { files
, excludedFiles
, ...configBody
} = configData
;
639 const criteria
= OverrideTester
.create(
644 const elements
= this._normalizeObjectConfigDataBody(configBody
, ctx
);
646 // Apply the criteria to every element.
647 for (const element
of elements
) {
650 * Merge the criteria.
651 * This is for the `overrides` entries that came from the
652 * configurations of `overrides[].extends`.
654 element
.criteria
= OverrideTester
.and(criteria
, element
.criteria
);
657 * Remove `root` property to ignore `root` settings which came from
658 * `extends` in `overrides`.
660 if (element
.criteria
) {
661 element
.root
= void 0;
669 * Normalize a given config to an array.
670 * @param {ConfigData} configData The config data to normalize.
671 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
672 * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
675 *_normalizeObjectConfigDataBody(
686 reportUnusedDisableDirectives
,
690 overrides
: overrideList
= []
694 const extendList
= Array
.isArray(extend
) ? extend
: [extend
];
695 const ignorePattern
= ignorePatterns
&& new IgnorePattern(
696 Array
.isArray(ignorePatterns
) ? ignorePatterns
: [ignorePatterns
],
700 // Flatten `extends`.
701 for (const extendName
of extendList
.filter(Boolean
)) {
702 yield* this._loadExtends(extendName
, ctx
);
705 // Load parser & plugins.
706 const parser
= parserName
&& this._loadParser(parserName
, ctx
);
707 const plugins
= pluginList
&& this._loadPlugins(pluginList
, ctx
);
709 // Yield pseudo config data for file extension processors.
711 yield* this._takeFileExtensionProcessors(plugins
, ctx
);
714 // Yield the config data except `extends` and `overrides`.
717 // Debug information.
720 filePath
: ctx
.filePath
,
732 reportUnusedDisableDirectives
,
738 // Flatten `overries`.
739 for (let i
= 0; i
< overrideList
.length
; ++i
) {
740 yield* this._normalizeObjectConfigData(
742 { ...ctx
, name
: `${ctx.name}#overrides[${i}]` }
748 * Load configs of an element in `extends`.
749 * @param {string} extendName The name of a base config.
750 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
751 * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
754 _loadExtends(extendName
, ctx
) {
755 debug("Loading {extends:%j} relative to %s", extendName
, ctx
.filePath
);
757 if (extendName
.startsWith("eslint:")) {
758 return this._loadExtendedBuiltInConfig(extendName
, ctx
);
760 if (extendName
.startsWith("plugin:")) {
761 return this._loadExtendedPluginConfig(extendName
, ctx
);
763 return this._loadExtendedShareableConfig(extendName
, ctx
);
765 error
.message
+= `\nReferenced from: ${ctx.filePath || ctx.name}`;
771 * Load configs of an element in `extends`.
772 * @param {string} extendName The name of a base config.
773 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
774 * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
777 _loadExtendedBuiltInConfig(extendName
, ctx
) {
778 if (extendName
=== "eslint:recommended") {
779 return this._loadConfigData({
781 filePath
: eslintRecommendedPath
,
782 name
: `${ctx.name} » ${extendName}`
785 if (extendName
=== "eslint:all") {
786 return this._loadConfigData({
788 filePath
: eslintAllPath
,
789 name
: `${ctx.name} » ${extendName}`
793 throw configMissingError(extendName
, ctx
.name
);
797 * Load configs of an element in `extends`.
798 * @param {string} extendName The name of a base config.
799 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
800 * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
803 _loadExtendedPluginConfig(extendName
, ctx
) {
804 const slashIndex
= extendName
.lastIndexOf("/");
805 const pluginName
= extendName
.slice("plugin:".length
, slashIndex
);
806 const configName
= extendName
.slice(slashIndex
+ 1);
808 if (isFilePath(pluginName
)) {
809 throw new Error("'extends' cannot use a file path for plugins.");
812 const plugin
= this._loadPlugin(pluginName
, ctx
);
815 plugin
.definition
.configs
[configName
];
818 return this._normalizeConfigData(configData
, {
820 filePath
: plugin
.filePath
|| ctx
.filePath
,
821 name
: `${ctx.name} » plugin:${plugin.id}/${configName}`
825 throw plugin
.error
|| configMissingError(extendName
, ctx
.filePath
);
829 * Load configs of an element in `extends`.
830 * @param {string} extendName The name of a base config.
831 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
832 * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
835 _loadExtendedShareableConfig(extendName
, ctx
) {
836 const { cwd
} = internalSlotsMap
.get(this);
837 const relativeTo
= ctx
.filePath
|| path
.join(cwd
, "__placeholder__.js");
840 if (isFilePath(extendName
)) {
841 request
= extendName
;
842 } else if (extendName
.startsWith(".")) {
843 request
= `./${extendName}`; // For backward compatibility. A ton of tests depended on this behavior.
845 request
= naming
.normalizePackageName(
854 filePath
= ModuleResolver
.resolve(request
, relativeTo
);
856 /* istanbul ignore else */
857 if (error
&& error
.code
=== "MODULE_NOT_FOUND") {
858 throw configMissingError(extendName
, ctx
.filePath
);
863 writeDebugLogForLoading(request
, relativeTo
, filePath
);
864 return this._loadConfigData({
867 name
: `${ctx.name} » ${request}`
872 * Load given plugins.
873 * @param {string[]} names The plugin names to load.
874 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
875 * @returns {Record<string,DependentPlugin>} The loaded parser.
878 _loadPlugins(names
, ctx
) {
879 return names
.reduce((map
, name
) => {
880 if (isFilePath(name
)) {
881 throw new Error("Plugins array cannot includes file paths.");
883 const plugin
= this._loadPlugin(name
, ctx
);
885 map
[plugin
.id
] = plugin
;
892 * Load a given parser.
893 * @param {string} nameOrPath The package name or the path to a parser file.
894 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
895 * @returns {DependentParser} The loaded parser.
897 _loadParser(nameOrPath
, ctx
) {
898 debug("Loading parser %j from %s", nameOrPath
, ctx
.filePath
);
900 const { cwd
} = internalSlotsMap
.get(this);
901 const relativeTo
= ctx
.filePath
|| path
.join(cwd
, "__placeholder__.js");
904 const filePath
= ModuleResolver
.resolve(nameOrPath
, relativeTo
);
906 writeDebugLogForLoading(nameOrPath
, relativeTo
, filePath
);
908 return new ConfigDependency({
909 definition
: require(filePath
),
912 importerName
: ctx
.name
,
913 importerPath
: ctx
.filePath
917 // If the parser name is "espree", load the espree of ESLint.
918 if (nameOrPath
=== "espree") {
919 debug("Fallback espree.");
920 return new ConfigDependency({
921 definition
: require("espree"),
922 filePath
: require
.resolve("espree"),
924 importerName
: ctx
.name
,
925 importerPath
: ctx
.filePath
929 debug("Failed to load parser '%s' declared in '%s'.", nameOrPath
, ctx
.name
);
930 error
.message
= `Failed to load parser '${nameOrPath}' declared in '${ctx.name}': ${error.message}`;
932 return new ConfigDependency({
935 importerName
: ctx
.name
,
936 importerPath
: ctx
.filePath
942 * Load a given plugin.
943 * @param {string} name The plugin name to load.
944 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
945 * @returns {DependentPlugin} The loaded plugin.
948 _loadPlugin(name
, ctx
) {
949 debug("Loading plugin %j from %s", name
, ctx
.filePath
);
951 const { additionalPluginPool
} = internalSlotsMap
.get(this);
952 const request
= naming
.normalizePackageName(name
, "eslint-plugin");
953 const id
= naming
.getShorthandName(request
, "eslint-plugin");
954 const relativeTo
= path
.join(ctx
.pluginBasePath
, "__placeholder__.js");
956 if (name
.match(/\s+/u)) {
957 const error
= Object
.assign(
958 new Error(`Whitespace found in plugin name '${name}'`),
960 messageTemplate
: "whitespace-found",
961 messageData
: { pluginName
: request
}
965 return new ConfigDependency({
968 importerName
: ctx
.name
,
969 importerPath
: ctx
.filePath
973 // Check for additional pool.
975 additionalPluginPool
.get(request
) ||
976 additionalPluginPool
.get(id
);
979 return new ConfigDependency({
980 definition
: normalizePlugin(plugin
),
981 filePath
: "", // It's unknown where the plugin came from.
983 importerName
: ctx
.name
,
984 importerPath
: ctx
.filePath
992 filePath
= ModuleResolver
.resolve(request
, relativeTo
);
993 } catch (resolveError
) {
994 error
= resolveError
;
995 /* istanbul ignore else */
996 if (error
&& error
.code
=== "MODULE_NOT_FOUND") {
997 error
.messageTemplate
= "plugin-missing";
998 error
.messageData
= {
1000 resolvePluginsRelativeTo
: ctx
.pluginBasePath
,
1001 importerName
: ctx
.name
1008 writeDebugLogForLoading(request
, relativeTo
, filePath
);
1010 const startTime
= Date
.now();
1011 const pluginDefinition
= require(filePath
);
1013 debug(`Plugin ${filePath} loaded in: ${Date.now() - startTime}ms`);
1015 return new ConfigDependency({
1016 definition
: normalizePlugin(pluginDefinition
),
1019 importerName
: ctx
.name
,
1020 importerPath
: ctx
.filePath
1022 } catch (loadError
) {
1027 debug("Failed to load plugin '%s' declared in '%s'.", name
, ctx
.name
);
1028 error
.message
= `Failed to load plugin '${name}' declared in '${ctx.name}': ${error.message}`;
1029 return new ConfigDependency({
1032 importerName
: ctx
.name
,
1033 importerPath
: ctx
.filePath
1038 * Take file expression processors as config array elements.
1039 * @param {Record<string,DependentPlugin>} plugins The plugin definitions.
1040 * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
1041 * @returns {IterableIterator<ConfigArrayElement>} The config array elements of file expression processors.
1044 *_takeFileExtensionProcessors(plugins
, ctx
) {
1045 for (const pluginId
of Object
.keys(plugins
)) {
1047 plugins
[pluginId
] &&
1048 plugins
[pluginId
].definition
&&
1049 plugins
[pluginId
].definition
.processors
;
1055 for (const processorId
of Object
.keys(processors
)) {
1056 if (processorId
.startsWith(".")) {
1057 yield* this._normalizeObjectConfigData(
1059 files
: [`*${processorId}`],
1060 processor
: `${pluginId}/${processorId}`
1064 type
: "implicit-processor",
1065 name
: `${ctx.name}#processors["${pluginId}/${processorId}"]`
1074 module
.exports
= { ConfigArrayFactory
, createContext
};