UNPKG

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