source: imaps-frontend/node_modules/@eslint/eslintrc/lib/config-array-factory.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

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