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

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

Update repo after prototype presentation

  • Property mode set to 100644
File size: 18.5 KB
Line 
1/**
2 * @fileoverview `CascadingConfigArrayFactory` class.
3 *
4 * `CascadingConfigArrayFactory` class has a responsibility:
5 *
6 * 1. Handles cascading of config files.
7 *
8 * It provides two methods:
9 *
10 * - `getConfigArrayForFile(filePath)`
11 * Get the corresponded configuration of a given file. This method doesn't
12 * throw even if the given file didn't exist.
13 * - `clearCache()`
14 * Clear the internal cache. You have to call this method when
15 * `additionalPluginPool` was updated if `baseConfig` or `cliConfig` depends
16 * on the additional plugins. (`CLIEngine#addPlugin()` method calls this.)
17 *
18 * @author Toru Nagashima <https://github.com/mysticatea>
19 */
20
21//------------------------------------------------------------------------------
22// Requirements
23//------------------------------------------------------------------------------
24
25import debugOrig from "debug";
26import os from "os";
27import path from "path";
28
29import { ConfigArrayFactory } from "./config-array-factory.js";
30import {
31 ConfigArray,
32 ConfigDependency,
33 IgnorePattern
34} from "./config-array/index.js";
35import ConfigValidator from "./shared/config-validator.js";
36import { emitDeprecationWarning } from "./shared/deprecation-warnings.js";
37
38const debug = debugOrig("eslintrc:cascading-config-array-factory");
39
40//------------------------------------------------------------------------------
41// Helpers
42//------------------------------------------------------------------------------
43
44// Define types for VSCode IntelliSense.
45/** @typedef {import("./shared/types").ConfigData} ConfigData */
46/** @typedef {import("./shared/types").Parser} Parser */
47/** @typedef {import("./shared/types").Plugin} Plugin */
48/** @typedef {import("./shared/types").Rule} Rule */
49/** @typedef {ReturnType<ConfigArrayFactory["create"]>} ConfigArray */
50
51/**
52 * @typedef {Object} CascadingConfigArrayFactoryOptions
53 * @property {Map<string,Plugin>} [additionalPluginPool] The map for additional plugins.
54 * @property {ConfigData} [baseConfig] The config by `baseConfig` option.
55 * @property {ConfigData} [cliConfig] The config by CLI options (`--env`, `--global`, `--ignore-pattern`, `--parser`, `--parser-options`, `--plugin`, and `--rule`). CLI options overwrite the setting in config files.
56 * @property {string} [cwd] The base directory to start lookup.
57 * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
58 * @property {string[]} [rulePaths] The value of `--rulesdir` option.
59 * @property {string} [specificConfigPath] The value of `--config` option.
60 * @property {boolean} [useEslintrc] if `false` then it doesn't load config files.
61 * @property {Function} loadRules The function to use to load rules.
62 * @property {Map<string,Rule>} builtInRules The rules that are built in to ESLint.
63 * @property {Object} [resolver=ModuleResolver] The module resolver object.
64 * @property {string} eslintAllPath The path to the definitions for eslint:all.
65 * @property {Function} getEslintAllConfig Returns the config data for eslint:all.
66 * @property {string} eslintRecommendedPath The path to the definitions for eslint:recommended.
67 * @property {Function} getEslintRecommendedConfig Returns the config data for eslint:recommended.
68 */
69
70/**
71 * @typedef {Object} CascadingConfigArrayFactoryInternalSlots
72 * @property {ConfigArray} baseConfigArray The config array of `baseConfig` option.
73 * @property {ConfigData} baseConfigData The config data of `baseConfig` option. This is used to reset `baseConfigArray`.
74 * @property {ConfigArray} cliConfigArray The config array of CLI options.
75 * @property {ConfigData} cliConfigData The config data of CLI options. This is used to reset `cliConfigArray`.
76 * @property {ConfigArrayFactory} configArrayFactory The factory for config arrays.
77 * @property {Map<string, ConfigArray>} configCache The cache from directory paths to config arrays.
78 * @property {string} cwd The base directory to start lookup.
79 * @property {WeakMap<ConfigArray, ConfigArray>} finalizeCache The cache from config arrays to finalized config arrays.
80 * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
81 * @property {string[]|null} rulePaths The value of `--rulesdir` option. This is used to reset `baseConfigArray`.
82 * @property {string|null} specificConfigPath The value of `--config` option. This is used to reset `cliConfigArray`.
83 * @property {boolean} useEslintrc if `false` then it doesn't load config files.
84 * @property {Function} loadRules The function to use to load rules.
85 * @property {Map<string,Rule>} builtInRules The rules that are built in to ESLint.
86 * @property {Object} [resolver=ModuleResolver] The module resolver object.
87 * @property {string} eslintAllPath The path to the definitions for eslint:all.
88 * @property {Function} getEslintAllConfig Returns the config data for eslint:all.
89 * @property {string} eslintRecommendedPath The path to the definitions for eslint:recommended.
90 * @property {Function} getEslintRecommendedConfig Returns the config data for eslint:recommended.
91 */
92
93/** @type {WeakMap<CascadingConfigArrayFactory, CascadingConfigArrayFactoryInternalSlots>} */
94const internalSlotsMap = new WeakMap();
95
96/**
97 * Create the config array from `baseConfig` and `rulePaths`.
98 * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
99 * @returns {ConfigArray} The config array of the base configs.
100 */
101function createBaseConfigArray({
102 configArrayFactory,
103 baseConfigData,
104 rulePaths,
105 cwd,
106 loadRules
107}) {
108 const baseConfigArray = configArrayFactory.create(
109 baseConfigData,
110 { name: "BaseConfig" }
111 );
112
113 /*
114 * Create the config array element for the default ignore patterns.
115 * This element has `ignorePattern` property that ignores the default
116 * patterns in the current working directory.
117 */
118 baseConfigArray.unshift(configArrayFactory.create(
119 { ignorePatterns: IgnorePattern.DefaultPatterns },
120 { name: "DefaultIgnorePattern" }
121 )[0]);
122
123 /*
124 * Load rules `--rulesdir` option as a pseudo plugin.
125 * Use a pseudo plugin to define rules of `--rulesdir`, so we can validate
126 * the rule's options with only information in the config array.
127 */
128 if (rulePaths && rulePaths.length > 0) {
129 baseConfigArray.push({
130 type: "config",
131 name: "--rulesdir",
132 filePath: "",
133 plugins: {
134 "": new ConfigDependency({
135 definition: {
136 rules: rulePaths.reduce(
137 (map, rulesPath) => Object.assign(
138 map,
139 loadRules(rulesPath, cwd)
140 ),
141 {}
142 )
143 },
144 filePath: "",
145 id: "",
146 importerName: "--rulesdir",
147 importerPath: ""
148 })
149 }
150 });
151 }
152
153 return baseConfigArray;
154}
155
156/**
157 * Create the config array from CLI options.
158 * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
159 * @returns {ConfigArray} The config array of the base configs.
160 */
161function createCLIConfigArray({
162 cliConfigData,
163 configArrayFactory,
164 cwd,
165 ignorePath,
166 specificConfigPath
167}) {
168 const cliConfigArray = configArrayFactory.create(
169 cliConfigData,
170 { name: "CLIOptions" }
171 );
172
173 cliConfigArray.unshift(
174 ...(ignorePath
175 ? configArrayFactory.loadESLintIgnore(ignorePath)
176 : configArrayFactory.loadDefaultESLintIgnore())
177 );
178
179 if (specificConfigPath) {
180 cliConfigArray.unshift(
181 ...configArrayFactory.loadFile(
182 specificConfigPath,
183 { name: "--config", basePath: cwd }
184 )
185 );
186 }
187
188 return cliConfigArray;
189}
190
191/**
192 * The error type when there are files matched by a glob, but all of them have been ignored.
193 */
194class ConfigurationNotFoundError extends Error {
195
196 // eslint-disable-next-line jsdoc/require-description
197 /**
198 * @param {string} directoryPath The directory path.
199 */
200 constructor(directoryPath) {
201 super(`No ESLint configuration found in ${directoryPath}.`);
202 this.messageTemplate = "no-config-found";
203 this.messageData = { directoryPath };
204 }
205}
206
207/**
208 * This class provides the functionality that enumerates every file which is
209 * matched by given glob patterns and that configuration.
210 */
211class CascadingConfigArrayFactory {
212
213 /**
214 * Initialize this enumerator.
215 * @param {CascadingConfigArrayFactoryOptions} options The options.
216 */
217 constructor({
218 additionalPluginPool = new Map(),
219 baseConfig: baseConfigData = null,
220 cliConfig: cliConfigData = null,
221 cwd = process.cwd(),
222 ignorePath,
223 resolvePluginsRelativeTo,
224 rulePaths = [],
225 specificConfigPath = null,
226 useEslintrc = true,
227 builtInRules = new Map(),
228 loadRules,
229 resolver,
230 eslintRecommendedPath,
231 getEslintRecommendedConfig,
232 eslintAllPath,
233 getEslintAllConfig
234 } = {}) {
235 const configArrayFactory = new ConfigArrayFactory({
236 additionalPluginPool,
237 cwd,
238 resolvePluginsRelativeTo,
239 builtInRules,
240 resolver,
241 eslintRecommendedPath,
242 getEslintRecommendedConfig,
243 eslintAllPath,
244 getEslintAllConfig
245 });
246
247 internalSlotsMap.set(this, {
248 baseConfigArray: createBaseConfigArray({
249 baseConfigData,
250 configArrayFactory,
251 cwd,
252 rulePaths,
253 loadRules
254 }),
255 baseConfigData,
256 cliConfigArray: createCLIConfigArray({
257 cliConfigData,
258 configArrayFactory,
259 cwd,
260 ignorePath,
261 specificConfigPath
262 }),
263 cliConfigData,
264 configArrayFactory,
265 configCache: new Map(),
266 cwd,
267 finalizeCache: new WeakMap(),
268 ignorePath,
269 rulePaths,
270 specificConfigPath,
271 useEslintrc,
272 builtInRules,
273 loadRules
274 });
275 }
276
277 /**
278 * The path to the current working directory.
279 * This is used by tests.
280 * @type {string}
281 */
282 get cwd() {
283 const { cwd } = internalSlotsMap.get(this);
284
285 return cwd;
286 }
287
288 /**
289 * Get the config array of a given file.
290 * If `filePath` was not given, it returns the config which contains only
291 * `baseConfigData` and `cliConfigData`.
292 * @param {string} [filePath] The file path to a file.
293 * @param {Object} [options] The options.
294 * @param {boolean} [options.ignoreNotFoundError] If `true` then it doesn't throw `ConfigurationNotFoundError`.
295 * @returns {ConfigArray} The config array of the file.
296 */
297 getConfigArrayForFile(filePath, { ignoreNotFoundError = false } = {}) {
298 const {
299 baseConfigArray,
300 cliConfigArray,
301 cwd
302 } = internalSlotsMap.get(this);
303
304 if (!filePath) {
305 return new ConfigArray(...baseConfigArray, ...cliConfigArray);
306 }
307
308 const directoryPath = path.dirname(path.resolve(cwd, filePath));
309
310 debug(`Load config files for ${directoryPath}.`);
311
312 return this._finalizeConfigArray(
313 this._loadConfigInAncestors(directoryPath),
314 directoryPath,
315 ignoreNotFoundError
316 );
317 }
318
319 /**
320 * Set the config data to override all configs.
321 * Require to call `clearCache()` method after this method is called.
322 * @param {ConfigData} configData The config data to override all configs.
323 * @returns {void}
324 */
325 setOverrideConfig(configData) {
326 const slots = internalSlotsMap.get(this);
327
328 slots.cliConfigData = configData;
329 }
330
331 /**
332 * Clear config cache.
333 * @returns {void}
334 */
335 clearCache() {
336 const slots = internalSlotsMap.get(this);
337
338 slots.baseConfigArray = createBaseConfigArray(slots);
339 slots.cliConfigArray = createCLIConfigArray(slots);
340 slots.configCache.clear();
341 }
342
343 /**
344 * Load and normalize config files from the ancestor directories.
345 * @param {string} directoryPath The path to a leaf directory.
346 * @param {boolean} configsExistInSubdirs `true` if configurations exist in subdirectories.
347 * @returns {ConfigArray} The loaded config.
348 * @private
349 */
350 _loadConfigInAncestors(directoryPath, configsExistInSubdirs = false) {
351 const {
352 baseConfigArray,
353 configArrayFactory,
354 configCache,
355 cwd,
356 useEslintrc
357 } = internalSlotsMap.get(this);
358
359 if (!useEslintrc) {
360 return baseConfigArray;
361 }
362
363 let configArray = configCache.get(directoryPath);
364
365 // Hit cache.
366 if (configArray) {
367 debug(`Cache hit: ${directoryPath}.`);
368 return configArray;
369 }
370 debug(`No cache found: ${directoryPath}.`);
371
372 const homePath = os.homedir();
373
374 // Consider this is root.
375 if (directoryPath === homePath && cwd !== homePath) {
376 debug("Stop traversing because of considered root.");
377 if (configsExistInSubdirs) {
378 const filePath = ConfigArrayFactory.getPathToConfigFileInDirectory(directoryPath);
379
380 if (filePath) {
381 emitDeprecationWarning(
382 filePath,
383 "ESLINT_PERSONAL_CONFIG_SUPPRESS"
384 );
385 }
386 }
387 return this._cacheConfig(directoryPath, baseConfigArray);
388 }
389
390 // Load the config on this directory.
391 try {
392 configArray = configArrayFactory.loadInDirectory(directoryPath);
393 } catch (error) {
394 /* istanbul ignore next */
395 if (error.code === "EACCES") {
396 debug("Stop traversing because of 'EACCES' error.");
397 return this._cacheConfig(directoryPath, baseConfigArray);
398 }
399 throw error;
400 }
401
402 if (configArray.length > 0 && configArray.isRoot()) {
403 debug("Stop traversing because of 'root:true'.");
404 configArray.unshift(...baseConfigArray);
405 return this._cacheConfig(directoryPath, configArray);
406 }
407
408 // Load from the ancestors and merge it.
409 const parentPath = path.dirname(directoryPath);
410 const parentConfigArray = parentPath && parentPath !== directoryPath
411 ? this._loadConfigInAncestors(
412 parentPath,
413 configsExistInSubdirs || configArray.length > 0
414 )
415 : baseConfigArray;
416
417 if (configArray.length > 0) {
418 configArray.unshift(...parentConfigArray);
419 } else {
420 configArray = parentConfigArray;
421 }
422
423 // Cache and return.
424 return this._cacheConfig(directoryPath, configArray);
425 }
426
427 /**
428 * Freeze and cache a given config.
429 * @param {string} directoryPath The path to a directory as a cache key.
430 * @param {ConfigArray} configArray The config array as a cache value.
431 * @returns {ConfigArray} The `configArray` (frozen).
432 */
433 _cacheConfig(directoryPath, configArray) {
434 const { configCache } = internalSlotsMap.get(this);
435
436 Object.freeze(configArray);
437 configCache.set(directoryPath, configArray);
438
439 return configArray;
440 }
441
442 /**
443 * Finalize a given config array.
444 * Concatenate `--config` and other CLI options.
445 * @param {ConfigArray} configArray The parent config array.
446 * @param {string} directoryPath The path to the leaf directory to find config files.
447 * @param {boolean} ignoreNotFoundError If `true` then it doesn't throw `ConfigurationNotFoundError`.
448 * @returns {ConfigArray} The loaded config.
449 * @private
450 */
451 _finalizeConfigArray(configArray, directoryPath, ignoreNotFoundError) {
452 const {
453 cliConfigArray,
454 configArrayFactory,
455 finalizeCache,
456 useEslintrc,
457 builtInRules
458 } = internalSlotsMap.get(this);
459
460 let finalConfigArray = finalizeCache.get(configArray);
461
462 if (!finalConfigArray) {
463 finalConfigArray = configArray;
464
465 // Load the personal config if there are no regular config files.
466 if (
467 useEslintrc &&
468 configArray.every(c => !c.filePath) &&
469 cliConfigArray.every(c => !c.filePath) // `--config` option can be a file.
470 ) {
471 const homePath = os.homedir();
472
473 debug("Loading the config file of the home directory:", homePath);
474
475 const personalConfigArray = configArrayFactory.loadInDirectory(
476 homePath,
477 { name: "PersonalConfig" }
478 );
479
480 if (
481 personalConfigArray.length > 0 &&
482 !directoryPath.startsWith(homePath)
483 ) {
484 const lastElement =
485 personalConfigArray[personalConfigArray.length - 1];
486
487 emitDeprecationWarning(
488 lastElement.filePath,
489 "ESLINT_PERSONAL_CONFIG_LOAD"
490 );
491 }
492
493 finalConfigArray = finalConfigArray.concat(personalConfigArray);
494 }
495
496 // Apply CLI options.
497 if (cliConfigArray.length > 0) {
498 finalConfigArray = finalConfigArray.concat(cliConfigArray);
499 }
500
501 // Validate rule settings and environments.
502 const validator = new ConfigValidator({
503 builtInRules
504 });
505
506 validator.validateConfigArray(finalConfigArray);
507
508 // Cache it.
509 Object.freeze(finalConfigArray);
510 finalizeCache.set(configArray, finalConfigArray);
511
512 debug(
513 "Configuration was determined: %o on %s",
514 finalConfigArray,
515 directoryPath
516 );
517 }
518
519 // At least one element (the default ignore patterns) exists.
520 if (!ignoreNotFoundError && useEslintrc && finalConfigArray.length <= 1) {
521 throw new ConfigurationNotFoundError(directoryPath);
522 }
523
524 return finalConfigArray;
525 }
526}
527
528//------------------------------------------------------------------------------
529// Public Interface
530//------------------------------------------------------------------------------
531
532export { CascadingConfigArrayFactory };
Note: See TracBrowser for help on using the repository browser.