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 |
|
---|
41 | import debugOrig from "debug";
|
---|
42 | import fs from "fs";
|
---|
43 | import importFresh from "import-fresh";
|
---|
44 | import { createRequire } from "module";
|
---|
45 | import path from "path";
|
---|
46 | import stripComments from "strip-json-comments";
|
---|
47 |
|
---|
48 | import {
|
---|
49 | ConfigArray,
|
---|
50 | ConfigDependency,
|
---|
51 | IgnorePattern,
|
---|
52 | OverrideTester
|
---|
53 | } from "./config-array/index.js";
|
---|
54 | import ConfigValidator from "./shared/config-validator.js";
|
---|
55 | import * as naming from "./shared/naming.js";
|
---|
56 | import * as ModuleResolver from "./shared/relative-module-resolver.js";
|
---|
57 |
|
---|
58 | const require = createRequire(import.meta.url);
|
---|
59 |
|
---|
60 | const debug = debugOrig("eslintrc:config-array-factory");
|
---|
61 |
|
---|
62 | //------------------------------------------------------------------------------
|
---|
63 | // Helpers
|
---|
64 | //------------------------------------------------------------------------------
|
---|
65 |
|
---|
66 | const 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>} */
|
---|
130 | const internalSlotsMap = new WeakMap();
|
---|
131 |
|
---|
132 | /** @type {WeakMap<object, Plugin>} */
|
---|
133 | const 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 | */
|
---|
140 | function 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 | */
|
---|
153 | function 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 | */
|
---|
164 | function 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 | */
|
---|
188 | function 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 | */
|
---|
212 | function 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 | */
|
---|
234 | function 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 | */
|
---|
252 | function 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 | */
|
---|
278 | function 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 | */
|
---|
300 | function 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 | */
|
---|
317 | function 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 | */
|
---|
345 | function 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 | */
|
---|
376 | function 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 | */
|
---|
410 | function 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 | */
|
---|
439 | class 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 |
|
---|
1151 | export { ConfigArrayFactory, createContext };
|
---|