source: imaps-frontend/node_modules/@eslint/eslintrc/lib/config-array/config-array.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: 16.9 KB
Line 
1/**
2 * @fileoverview `ConfigArray` class.
3 *
4 * `ConfigArray` class expresses the full of a configuration. It has the entry
5 * config file, base config files that were extended, loaded parsers, and loaded
6 * plugins.
7 *
8 * `ConfigArray` class provides three properties and two methods.
9 *
10 * - `pluginEnvironments`
11 * - `pluginProcessors`
12 * - `pluginRules`
13 * The `Map` objects that contain the members of all plugins that this
14 * config array contains. Those map objects don't have mutation methods.
15 * Those keys are the member ID such as `pluginId/memberName`.
16 * - `isRoot()`
17 * If `true` then this configuration has `root:true` property.
18 * - `extractConfig(filePath)`
19 * Extract the final configuration for a given file. This means merging
20 * every config array element which that `criteria` property matched. The
21 * `filePath` argument must be an absolute path.
22 *
23 * `ConfigArrayFactory` provides the loading logic of config files.
24 *
25 * @author Toru Nagashima <https://github.com/mysticatea>
26 */
27
28//------------------------------------------------------------------------------
29// Requirements
30//------------------------------------------------------------------------------
31
32import { ExtractedConfig } from "./extracted-config.js";
33import { IgnorePattern } from "./ignore-pattern.js";
34
35//------------------------------------------------------------------------------
36// Helpers
37//------------------------------------------------------------------------------
38
39// Define types for VSCode IntelliSense.
40/** @typedef {import("../../shared/types").Environment} Environment */
41/** @typedef {import("../../shared/types").GlobalConf} GlobalConf */
42/** @typedef {import("../../shared/types").RuleConf} RuleConf */
43/** @typedef {import("../../shared/types").Rule} Rule */
44/** @typedef {import("../../shared/types").Plugin} Plugin */
45/** @typedef {import("../../shared/types").Processor} Processor */
46/** @typedef {import("./config-dependency").DependentParser} DependentParser */
47/** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */
48/** @typedef {import("./override-tester")["OverrideTester"]} OverrideTester */
49
50/**
51 * @typedef {Object} ConfigArrayElement
52 * @property {string} name The name of this config element.
53 * @property {string} filePath The path to the source file of this config element.
54 * @property {InstanceType<OverrideTester>|null} criteria The tester for the `files` and `excludedFiles` of this config element.
55 * @property {Record<string, boolean>|undefined} env The environment settings.
56 * @property {Record<string, GlobalConf>|undefined} globals The global variable settings.
57 * @property {IgnorePattern|undefined} ignorePattern The ignore patterns.
58 * @property {boolean|undefined} noInlineConfig The flag that disables directive comments.
59 * @property {DependentParser|undefined} parser The parser loader.
60 * @property {Object|undefined} parserOptions The parser options.
61 * @property {Record<string, DependentPlugin>|undefined} plugins The plugin loaders.
62 * @property {string|undefined} processor The processor name to refer plugin's processor.
63 * @property {boolean|undefined} reportUnusedDisableDirectives The flag to report unused `eslint-disable` comments.
64 * @property {boolean|undefined} root The flag to express root.
65 * @property {Record<string, RuleConf>|undefined} rules The rule settings
66 * @property {Object|undefined} settings The shared settings.
67 * @property {"config" | "ignore" | "implicit-processor"} type The element type.
68 */
69
70/**
71 * @typedef {Object} ConfigArrayInternalSlots
72 * @property {Map<string, ExtractedConfig>} cache The cache to extract configs.
73 * @property {ReadonlyMap<string, Environment>|null} envMap The map from environment ID to environment definition.
74 * @property {ReadonlyMap<string, Processor>|null} processorMap The map from processor ID to environment definition.
75 * @property {ReadonlyMap<string, Rule>|null} ruleMap The map from rule ID to rule definition.
76 */
77
78/** @type {WeakMap<ConfigArray, ConfigArrayInternalSlots>} */
79const internalSlotsMap = new class extends WeakMap {
80 get(key) {
81 let value = super.get(key);
82
83 if (!value) {
84 value = {
85 cache: new Map(),
86 envMap: null,
87 processorMap: null,
88 ruleMap: null
89 };
90 super.set(key, value);
91 }
92
93 return value;
94 }
95}();
96
97/**
98 * Get the indices which are matched to a given file.
99 * @param {ConfigArrayElement[]} elements The elements.
100 * @param {string} filePath The path to a target file.
101 * @returns {number[]} The indices.
102 */
103function getMatchedIndices(elements, filePath) {
104 const indices = [];
105
106 for (let i = elements.length - 1; i >= 0; --i) {
107 const element = elements[i];
108
109 if (!element.criteria || (filePath && element.criteria.test(filePath))) {
110 indices.push(i);
111 }
112 }
113
114 return indices;
115}
116
117/**
118 * Check if a value is a non-null object.
119 * @param {any} x The value to check.
120 * @returns {boolean} `true` if the value is a non-null object.
121 */
122function isNonNullObject(x) {
123 return typeof x === "object" && x !== null;
124}
125
126/**
127 * Merge two objects.
128 *
129 * Assign every property values of `y` to `x` if `x` doesn't have the property.
130 * If `x`'s property value is an object, it does recursive.
131 * @param {Object} target The destination to merge
132 * @param {Object|undefined} source The source to merge.
133 * @returns {void}
134 */
135function mergeWithoutOverwrite(target, source) {
136 if (!isNonNullObject(source)) {
137 return;
138 }
139
140 for (const key of Object.keys(source)) {
141 if (key === "__proto__") {
142 continue;
143 }
144
145 if (isNonNullObject(target[key])) {
146 mergeWithoutOverwrite(target[key], source[key]);
147 } else if (target[key] === void 0) {
148 if (isNonNullObject(source[key])) {
149 target[key] = Array.isArray(source[key]) ? [] : {};
150 mergeWithoutOverwrite(target[key], source[key]);
151 } else if (source[key] !== void 0) {
152 target[key] = source[key];
153 }
154 }
155 }
156}
157
158/**
159 * The error for plugin conflicts.
160 */
161class PluginConflictError extends Error {
162
163 /**
164 * Initialize this error object.
165 * @param {string} pluginId The plugin ID.
166 * @param {{filePath:string, importerName:string}[]} plugins The resolved plugins.
167 */
168 constructor(pluginId, plugins) {
169 super(`Plugin "${pluginId}" was conflicted between ${plugins.map(p => `"${p.importerName}"`).join(" and ")}.`);
170 this.messageTemplate = "plugin-conflict";
171 this.messageData = { pluginId, plugins };
172 }
173}
174
175/**
176 * Merge plugins.
177 * `target`'s definition is prior to `source`'s.
178 * @param {Record<string, DependentPlugin>} target The destination to merge
179 * @param {Record<string, DependentPlugin>|undefined} source The source to merge.
180 * @returns {void}
181 */
182function mergePlugins(target, source) {
183 if (!isNonNullObject(source)) {
184 return;
185 }
186
187 for (const key of Object.keys(source)) {
188 if (key === "__proto__") {
189 continue;
190 }
191 const targetValue = target[key];
192 const sourceValue = source[key];
193
194 // Adopt the plugin which was found at first.
195 if (targetValue === void 0) {
196 if (sourceValue.error) {
197 throw sourceValue.error;
198 }
199 target[key] = sourceValue;
200 } else if (sourceValue.filePath !== targetValue.filePath) {
201 throw new PluginConflictError(key, [
202 {
203 filePath: targetValue.filePath,
204 importerName: targetValue.importerName
205 },
206 {
207 filePath: sourceValue.filePath,
208 importerName: sourceValue.importerName
209 }
210 ]);
211 }
212 }
213}
214
215/**
216 * Merge rule configs.
217 * `target`'s definition is prior to `source`'s.
218 * @param {Record<string, Array>} target The destination to merge
219 * @param {Record<string, RuleConf>|undefined} source The source to merge.
220 * @returns {void}
221 */
222function mergeRuleConfigs(target, source) {
223 if (!isNonNullObject(source)) {
224 return;
225 }
226
227 for (const key of Object.keys(source)) {
228 if (key === "__proto__") {
229 continue;
230 }
231 const targetDef = target[key];
232 const sourceDef = source[key];
233
234 // Adopt the rule config which was found at first.
235 if (targetDef === void 0) {
236 if (Array.isArray(sourceDef)) {
237 target[key] = [...sourceDef];
238 } else {
239 target[key] = [sourceDef];
240 }
241
242 /*
243 * If the first found rule config is severity only and the current rule
244 * config has options, merge the severity and the options.
245 */
246 } else if (
247 targetDef.length === 1 &&
248 Array.isArray(sourceDef) &&
249 sourceDef.length >= 2
250 ) {
251 targetDef.push(...sourceDef.slice(1));
252 }
253 }
254}
255
256/**
257 * Create the extracted config.
258 * @param {ConfigArray} instance The config elements.
259 * @param {number[]} indices The indices to use.
260 * @returns {ExtractedConfig} The extracted config.
261 */
262function createConfig(instance, indices) {
263 const config = new ExtractedConfig();
264 const ignorePatterns = [];
265
266 // Merge elements.
267 for (const index of indices) {
268 const element = instance[index];
269
270 // Adopt the parser which was found at first.
271 if (!config.parser && element.parser) {
272 if (element.parser.error) {
273 throw element.parser.error;
274 }
275 config.parser = element.parser;
276 }
277
278 // Adopt the processor which was found at first.
279 if (!config.processor && element.processor) {
280 config.processor = element.processor;
281 }
282
283 // Adopt the noInlineConfig which was found at first.
284 if (config.noInlineConfig === void 0 && element.noInlineConfig !== void 0) {
285 config.noInlineConfig = element.noInlineConfig;
286 config.configNameOfNoInlineConfig = element.name;
287 }
288
289 // Adopt the reportUnusedDisableDirectives which was found at first.
290 if (config.reportUnusedDisableDirectives === void 0 && element.reportUnusedDisableDirectives !== void 0) {
291 config.reportUnusedDisableDirectives = element.reportUnusedDisableDirectives;
292 }
293
294 // Collect ignorePatterns
295 if (element.ignorePattern) {
296 ignorePatterns.push(element.ignorePattern);
297 }
298
299 // Merge others.
300 mergeWithoutOverwrite(config.env, element.env);
301 mergeWithoutOverwrite(config.globals, element.globals);
302 mergeWithoutOverwrite(config.parserOptions, element.parserOptions);
303 mergeWithoutOverwrite(config.settings, element.settings);
304 mergePlugins(config.plugins, element.plugins);
305 mergeRuleConfigs(config.rules, element.rules);
306 }
307
308 // Create the predicate function for ignore patterns.
309 if (ignorePatterns.length > 0) {
310 config.ignores = IgnorePattern.createIgnore(ignorePatterns.reverse());
311 }
312
313 return config;
314}
315
316/**
317 * Collect definitions.
318 * @template T, U
319 * @param {string} pluginId The plugin ID for prefix.
320 * @param {Record<string,T>} defs The definitions to collect.
321 * @param {Map<string, U>} map The map to output.
322 * @param {function(T): U} [normalize] The normalize function for each value.
323 * @returns {void}
324 */
325function collect(pluginId, defs, map, normalize) {
326 if (defs) {
327 const prefix = pluginId && `${pluginId}/`;
328
329 for (const [key, value] of Object.entries(defs)) {
330 map.set(
331 `${prefix}${key}`,
332 normalize ? normalize(value) : value
333 );
334 }
335 }
336}
337
338/**
339 * Normalize a rule definition.
340 * @param {Function|Rule} rule The rule definition to normalize.
341 * @returns {Rule} The normalized rule definition.
342 */
343function normalizePluginRule(rule) {
344 return typeof rule === "function" ? { create: rule } : rule;
345}
346
347/**
348 * Delete the mutation methods from a given map.
349 * @param {Map<any, any>} map The map object to delete.
350 * @returns {void}
351 */
352function deleteMutationMethods(map) {
353 Object.defineProperties(map, {
354 clear: { configurable: true, value: void 0 },
355 delete: { configurable: true, value: void 0 },
356 set: { configurable: true, value: void 0 }
357 });
358}
359
360/**
361 * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
362 * @param {ConfigArrayElement[]} elements The config elements.
363 * @param {ConfigArrayInternalSlots} slots The internal slots.
364 * @returns {void}
365 */
366function initPluginMemberMaps(elements, slots) {
367 const processed = new Set();
368
369 slots.envMap = new Map();
370 slots.processorMap = new Map();
371 slots.ruleMap = new Map();
372
373 for (const element of elements) {
374 if (!element.plugins) {
375 continue;
376 }
377
378 for (const [pluginId, value] of Object.entries(element.plugins)) {
379 const plugin = value.definition;
380
381 if (!plugin || processed.has(pluginId)) {
382 continue;
383 }
384 processed.add(pluginId);
385
386 collect(pluginId, plugin.environments, slots.envMap);
387 collect(pluginId, plugin.processors, slots.processorMap);
388 collect(pluginId, plugin.rules, slots.ruleMap, normalizePluginRule);
389 }
390 }
391
392 deleteMutationMethods(slots.envMap);
393 deleteMutationMethods(slots.processorMap);
394 deleteMutationMethods(slots.ruleMap);
395}
396
397/**
398 * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
399 * @param {ConfigArray} instance The config elements.
400 * @returns {ConfigArrayInternalSlots} The extracted config.
401 */
402function ensurePluginMemberMaps(instance) {
403 const slots = internalSlotsMap.get(instance);
404
405 if (!slots.ruleMap) {
406 initPluginMemberMaps(instance, slots);
407 }
408
409 return slots;
410}
411
412//------------------------------------------------------------------------------
413// Public Interface
414//------------------------------------------------------------------------------
415
416/**
417 * The Config Array.
418 *
419 * `ConfigArray` instance contains all settings, parsers, and plugins.
420 * You need to call `ConfigArray#extractConfig(filePath)` method in order to
421 * extract, merge and get only the config data which is related to an arbitrary
422 * file.
423 * @extends {Array<ConfigArrayElement>}
424 */
425class ConfigArray extends Array {
426
427 /**
428 * Get the plugin environments.
429 * The returned map cannot be mutated.
430 * @type {ReadonlyMap<string, Environment>} The plugin environments.
431 */
432 get pluginEnvironments() {
433 return ensurePluginMemberMaps(this).envMap;
434 }
435
436 /**
437 * Get the plugin processors.
438 * The returned map cannot be mutated.
439 * @type {ReadonlyMap<string, Processor>} The plugin processors.
440 */
441 get pluginProcessors() {
442 return ensurePluginMemberMaps(this).processorMap;
443 }
444
445 /**
446 * Get the plugin rules.
447 * The returned map cannot be mutated.
448 * @returns {ReadonlyMap<string, Rule>} The plugin rules.
449 */
450 get pluginRules() {
451 return ensurePluginMemberMaps(this).ruleMap;
452 }
453
454 /**
455 * Check if this config has `root` flag.
456 * @returns {boolean} `true` if this config array is root.
457 */
458 isRoot() {
459 for (let i = this.length - 1; i >= 0; --i) {
460 const root = this[i].root;
461
462 if (typeof root === "boolean") {
463 return root;
464 }
465 }
466 return false;
467 }
468
469 /**
470 * Extract the config data which is related to a given file.
471 * @param {string} filePath The absolute path to the target file.
472 * @returns {ExtractedConfig} The extracted config data.
473 */
474 extractConfig(filePath) {
475 const { cache } = internalSlotsMap.get(this);
476 const indices = getMatchedIndices(this, filePath);
477 const cacheKey = indices.join(",");
478
479 if (!cache.has(cacheKey)) {
480 cache.set(cacheKey, createConfig(this, indices));
481 }
482
483 return cache.get(cacheKey);
484 }
485
486 /**
487 * Check if a given path is an additional lint target.
488 * @param {string} filePath The absolute path to the target file.
489 * @returns {boolean} `true` if the file is an additional lint target.
490 */
491 isAdditionalTargetPath(filePath) {
492 for (const { criteria, type } of this) {
493 if (
494 type === "config" &&
495 criteria &&
496 !criteria.endsWithWildcard &&
497 criteria.test(filePath)
498 ) {
499 return true;
500 }
501 }
502 return false;
503 }
504}
505
506/**
507 * Get the used extracted configs.
508 * CLIEngine will use this method to collect used deprecated rules.
509 * @param {ConfigArray} instance The config array object to get.
510 * @returns {ExtractedConfig[]} The used extracted configs.
511 * @private
512 */
513function getUsedExtractedConfigs(instance) {
514 const { cache } = internalSlotsMap.get(instance);
515
516 return Array.from(cache.values());
517}
518
519
520export {
521 ConfigArray,
522 getUsedExtractedConfigs
523};
Note: See TracBrowser for help on using the repository browser.