Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

config-array.js 17KB

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