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.

flat-config-schema.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /**
  2. * @fileoverview Flat config schema
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //-----------------------------------------------------------------------------
  7. // Type Definitions
  8. //-----------------------------------------------------------------------------
  9. /**
  10. * @typedef ObjectPropertySchema
  11. * @property {Function|string} merge The function or name of the function to call
  12. * to merge multiple objects with this property.
  13. * @property {Function|string} validate The function or name of the function to call
  14. * to validate the value of this property.
  15. */
  16. //-----------------------------------------------------------------------------
  17. // Helpers
  18. //-----------------------------------------------------------------------------
  19. const ruleSeverities = new Map([
  20. [0, 0], ["off", 0],
  21. [1, 1], ["warn", 1],
  22. [2, 2], ["error", 2]
  23. ]);
  24. const globalVariablesValues = new Set([
  25. true, "true", "writable", "writeable",
  26. false, "false", "readonly", "readable", null,
  27. "off"
  28. ]);
  29. /**
  30. * Check if a value is a non-null object.
  31. * @param {any} value The value to check.
  32. * @returns {boolean} `true` if the value is a non-null object.
  33. */
  34. function isNonNullObject(value) {
  35. return typeof value === "object" && value !== null;
  36. }
  37. /**
  38. * Check if a value is undefined.
  39. * @param {any} value The value to check.
  40. * @returns {boolean} `true` if the value is undefined.
  41. */
  42. function isUndefined(value) {
  43. return typeof value === "undefined";
  44. }
  45. /**
  46. * Deeply merges two objects.
  47. * @param {Object} first The base object.
  48. * @param {Object} second The overrides object.
  49. * @returns {Object} An object with properties from both first and second.
  50. */
  51. function deepMerge(first = {}, second = {}) {
  52. /*
  53. * If the second value is an array, just return it. We don't merge
  54. * arrays because order matters and we can't know the correct order.
  55. */
  56. if (Array.isArray(second)) {
  57. return second;
  58. }
  59. /*
  60. * First create a result object where properties from the second object
  61. * overwrite properties from the first. This sets up a baseline to use
  62. * later rather than needing to inspect and change every property
  63. * individually.
  64. */
  65. const result = {
  66. ...first,
  67. ...second
  68. };
  69. for (const key of Object.keys(second)) {
  70. // avoid hairy edge case
  71. if (key === "__proto__") {
  72. continue;
  73. }
  74. const firstValue = first[key];
  75. const secondValue = second[key];
  76. if (isNonNullObject(firstValue)) {
  77. result[key] = deepMerge(firstValue, secondValue);
  78. } else if (isUndefined(firstValue)) {
  79. if (isNonNullObject(secondValue)) {
  80. result[key] = deepMerge(
  81. Array.isArray(secondValue) ? [] : {},
  82. secondValue
  83. );
  84. } else if (!isUndefined(secondValue)) {
  85. result[key] = secondValue;
  86. }
  87. }
  88. }
  89. return result;
  90. }
  91. /**
  92. * Normalizes the rule options config for a given rule by ensuring that
  93. * it is an array and that the first item is 0, 1, or 2.
  94. * @param {Array|string|number} ruleOptions The rule options config.
  95. * @returns {Array} An array of rule options.
  96. */
  97. function normalizeRuleOptions(ruleOptions) {
  98. const finalOptions = Array.isArray(ruleOptions)
  99. ? ruleOptions.slice(0)
  100. : [ruleOptions];
  101. finalOptions[0] = ruleSeverities.get(finalOptions[0]);
  102. return finalOptions;
  103. }
  104. //-----------------------------------------------------------------------------
  105. // Assertions
  106. //-----------------------------------------------------------------------------
  107. /**
  108. * Validates that a value is a valid rule options entry.
  109. * @param {any} value The value to check.
  110. * @returns {void}
  111. * @throws {TypeError} If the value isn't a valid rule options.
  112. */
  113. function assertIsRuleOptions(value) {
  114. if (typeof value !== "string" && typeof value !== "number" && !Array.isArray(value)) {
  115. throw new TypeError("Expected a string, number, or array.");
  116. }
  117. }
  118. /**
  119. * Validates that a value is valid rule severity.
  120. * @param {any} value The value to check.
  121. * @returns {void}
  122. * @throws {TypeError} If the value isn't a valid rule severity.
  123. */
  124. function assertIsRuleSeverity(value) {
  125. const severity = typeof value === "string"
  126. ? ruleSeverities.get(value.toLowerCase())
  127. : ruleSeverities.get(value);
  128. if (typeof severity === "undefined") {
  129. throw new TypeError("Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2.");
  130. }
  131. }
  132. /**
  133. * Validates that a given string is the form pluginName/objectName.
  134. * @param {string} value The string to check.
  135. * @returns {void}
  136. * @throws {TypeError} If the string isn't in the correct format.
  137. */
  138. function assertIsPluginMemberName(value) {
  139. if (!/[@a-z0-9-_$]+(?:\/(?:[a-z0-9-_$]+))+$/iu.test(value)) {
  140. throw new TypeError(`Expected string in the form "pluginName/objectName" but found "${value}".`);
  141. }
  142. }
  143. /**
  144. * Validates that a value is an object.
  145. * @param {any} value The value to check.
  146. * @returns {void}
  147. * @throws {TypeError} If the value isn't an object.
  148. */
  149. function assertIsObject(value) {
  150. if (!isNonNullObject(value)) {
  151. throw new TypeError("Expected an object.");
  152. }
  153. }
  154. /**
  155. * Validates that a value is an object or a string.
  156. * @param {any} value The value to check.
  157. * @returns {void}
  158. * @throws {TypeError} If the value isn't an object or a string.
  159. */
  160. function assertIsObjectOrString(value) {
  161. if ((!value || typeof value !== "object") && typeof value !== "string") {
  162. throw new TypeError("Expected an object or string.");
  163. }
  164. }
  165. //-----------------------------------------------------------------------------
  166. // Low-Level Schemas
  167. //-----------------------------------------------------------------------------
  168. /** @type {ObjectPropertySchema} */
  169. const numberSchema = {
  170. merge: "replace",
  171. validate: "number"
  172. };
  173. /** @type {ObjectPropertySchema} */
  174. const booleanSchema = {
  175. merge: "replace",
  176. validate: "boolean"
  177. };
  178. /** @type {ObjectPropertySchema} */
  179. const deepObjectAssignSchema = {
  180. merge(first = {}, second = {}) {
  181. return deepMerge(first, second);
  182. },
  183. validate: "object"
  184. };
  185. //-----------------------------------------------------------------------------
  186. // High-Level Schemas
  187. //-----------------------------------------------------------------------------
  188. /** @type {ObjectPropertySchema} */
  189. const globalsSchema = {
  190. merge: "assign",
  191. validate(value) {
  192. assertIsObject(value);
  193. for (const key of Object.keys(value)) {
  194. // avoid hairy edge case
  195. if (key === "__proto__") {
  196. continue;
  197. }
  198. if (key !== key.trim()) {
  199. throw new TypeError(`Global "${key}" has leading or trailing whitespace.`);
  200. }
  201. if (!globalVariablesValues.has(value[key])) {
  202. throw new TypeError(`Key "${key}": Expected "readonly", "writable", or "off".`);
  203. }
  204. }
  205. }
  206. };
  207. /** @type {ObjectPropertySchema} */
  208. const parserSchema = {
  209. merge: "replace",
  210. validate(value) {
  211. assertIsObjectOrString(value);
  212. if (typeof value === "object" && typeof value.parse !== "function" && typeof value.parseForESLint !== "function") {
  213. throw new TypeError("Expected object to have a parse() or parseForESLint() method.");
  214. }
  215. if (typeof value === "string") {
  216. assertIsPluginMemberName(value);
  217. }
  218. }
  219. };
  220. /** @type {ObjectPropertySchema} */
  221. const pluginsSchema = {
  222. merge(first = {}, second = {}) {
  223. const keys = new Set([...Object.keys(first), ...Object.keys(second)]);
  224. const result = {};
  225. // manually validate that plugins are not redefined
  226. for (const key of keys) {
  227. // avoid hairy edge case
  228. if (key === "__proto__") {
  229. continue;
  230. }
  231. if (key in first && key in second && first[key] !== second[key]) {
  232. throw new TypeError(`Cannot redefine plugin "${key}".`);
  233. }
  234. result[key] = second[key] || first[key];
  235. }
  236. return result;
  237. },
  238. validate(value) {
  239. // first check the value to be sure it's an object
  240. if (value === null || typeof value !== "object") {
  241. throw new TypeError("Expected an object.");
  242. }
  243. // second check the keys to make sure they are objects
  244. for (const key of Object.keys(value)) {
  245. // avoid hairy edge case
  246. if (key === "__proto__") {
  247. continue;
  248. }
  249. if (value[key] === null || typeof value[key] !== "object") {
  250. throw new TypeError(`Key "${key}": Expected an object.`);
  251. }
  252. }
  253. }
  254. };
  255. /** @type {ObjectPropertySchema} */
  256. const processorSchema = {
  257. merge: "replace",
  258. validate(value) {
  259. if (typeof value === "string") {
  260. assertIsPluginMemberName(value);
  261. } else if (value && typeof value === "object") {
  262. if (typeof value.preprocess !== "function" || typeof value.postprocess !== "function") {
  263. throw new TypeError("Object must have a preprocess() and a postprocess() method.");
  264. }
  265. } else {
  266. throw new TypeError("Expected an object or a string.");
  267. }
  268. }
  269. };
  270. /** @type {ObjectPropertySchema} */
  271. const rulesSchema = {
  272. merge(first = {}, second = {}) {
  273. const result = {
  274. ...first,
  275. ...second
  276. };
  277. for (const ruleId of Object.keys(result)) {
  278. // avoid hairy edge case
  279. if (ruleId === "__proto__") {
  280. /* eslint-disable-next-line no-proto */
  281. delete result.__proto__;
  282. continue;
  283. }
  284. result[ruleId] = normalizeRuleOptions(result[ruleId]);
  285. /*
  286. * If either rule config is missing, then the correct
  287. * config is already present and we just need to normalize
  288. * the severity.
  289. */
  290. if (!(ruleId in first) || !(ruleId in second)) {
  291. continue;
  292. }
  293. const firstRuleOptions = normalizeRuleOptions(first[ruleId]);
  294. const secondRuleOptions = normalizeRuleOptions(second[ruleId]);
  295. /*
  296. * If the second rule config only has a severity (length of 1),
  297. * then use that severity and keep the rest of the options from
  298. * the first rule config.
  299. */
  300. if (secondRuleOptions.length === 1) {
  301. result[ruleId] = [secondRuleOptions[0], ...firstRuleOptions.slice(1)];
  302. continue;
  303. }
  304. /*
  305. * In any other situation, then the second rule config takes
  306. * precedence. That means the value at `result[ruleId]` is
  307. * already correct and no further work is necessary.
  308. */
  309. }
  310. return result;
  311. },
  312. validate(value) {
  313. assertIsObject(value);
  314. let lastRuleId;
  315. // Performance: One try-catch has less overhead than one per loop iteration
  316. try {
  317. /*
  318. * We are not checking the rule schema here because there is no
  319. * guarantee that the rule definition is present at this point. Instead
  320. * we wait and check the rule schema during the finalization step
  321. * of calculating a config.
  322. */
  323. for (const ruleId of Object.keys(value)) {
  324. // avoid hairy edge case
  325. if (ruleId === "__proto__") {
  326. continue;
  327. }
  328. lastRuleId = ruleId;
  329. const ruleOptions = value[ruleId];
  330. assertIsRuleOptions(ruleOptions);
  331. if (Array.isArray(ruleOptions)) {
  332. assertIsRuleSeverity(ruleOptions[0]);
  333. } else {
  334. assertIsRuleSeverity(ruleOptions);
  335. }
  336. }
  337. } catch (error) {
  338. error.message = `Key "${lastRuleId}": ${error.message}`;
  339. throw error;
  340. }
  341. }
  342. };
  343. /** @type {ObjectPropertySchema} */
  344. const sourceTypeSchema = {
  345. merge: "replace",
  346. validate(value) {
  347. if (typeof value !== "string" || !/^(?:script|module|commonjs)$/u.test(value)) {
  348. throw new TypeError("Expected \"script\", \"module\", or \"commonjs\".");
  349. }
  350. }
  351. };
  352. //-----------------------------------------------------------------------------
  353. // Full schema
  354. //-----------------------------------------------------------------------------
  355. exports.flatConfigSchema = {
  356. settings: deepObjectAssignSchema,
  357. linterOptions: {
  358. schema: {
  359. noInlineConfig: booleanSchema,
  360. reportUnusedDisableDirectives: booleanSchema
  361. }
  362. },
  363. languageOptions: {
  364. schema: {
  365. ecmaVersion: numberSchema,
  366. sourceType: sourceTypeSchema,
  367. globals: globalsSchema,
  368. parser: parserSchema,
  369. parserOptions: deepObjectAssignSchema
  370. }
  371. },
  372. processor: processorSchema,
  373. plugins: pluginsSchema,
  374. rules: rulesSchema
  375. };