租房小程序前端代码
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.

380 lines
12 KiB

6 months ago
  1. /**
  2. * @fileoverview Flat Config Array
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //-----------------------------------------------------------------------------
  7. // Requirements
  8. //-----------------------------------------------------------------------------
  9. const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array");
  10. const { flatConfigSchema } = require("./flat-config-schema");
  11. const { RuleValidator } = require("./rule-validator");
  12. const { defaultConfig } = require("./default-config");
  13. const jsPlugin = require("@eslint/js");
  14. //-----------------------------------------------------------------------------
  15. // Helpers
  16. //-----------------------------------------------------------------------------
  17. /**
  18. * Fields that are considered metadata and not part of the config object.
  19. */
  20. const META_FIELDS = new Set(["name"]);
  21. const ruleValidator = new RuleValidator();
  22. /**
  23. * Splits a plugin identifier in the form a/b/c into two parts: a/b and c.
  24. * @param {string} identifier The identifier to parse.
  25. * @returns {{objectName: string, pluginName: string}} The parts of the plugin
  26. * name.
  27. */
  28. function splitPluginIdentifier(identifier) {
  29. const parts = identifier.split("/");
  30. return {
  31. objectName: parts.pop(),
  32. pluginName: parts.join("/")
  33. };
  34. }
  35. /**
  36. * Returns the name of an object in the config by reading its `meta` key.
  37. * @param {Object} object The object to check.
  38. * @returns {string?} The name of the object if found or `null` if there
  39. * is no name.
  40. */
  41. function getObjectId(object) {
  42. // first check old-style name
  43. let name = object.name;
  44. if (!name) {
  45. if (!object.meta) {
  46. return null;
  47. }
  48. name = object.meta.name;
  49. if (!name) {
  50. return null;
  51. }
  52. }
  53. // now check for old-style version
  54. let version = object.version;
  55. if (!version) {
  56. version = object.meta && object.meta.version;
  57. }
  58. // if there's a version then append that
  59. if (version) {
  60. return `${name}@${version}`;
  61. }
  62. return name;
  63. }
  64. /**
  65. * Wraps a config error with details about where the error occurred.
  66. * @param {Error} error The original error.
  67. * @param {number} originalLength The original length of the config array.
  68. * @param {number} baseLength The length of the base config.
  69. * @returns {TypeError} The new error with details.
  70. */
  71. function wrapConfigErrorWithDetails(error, originalLength, baseLength) {
  72. let location = "user-defined";
  73. let configIndex = error.index;
  74. /*
  75. * A config array is set up in this order:
  76. * 1. Base config
  77. * 2. Original configs
  78. * 3. User-defined configs
  79. * 4. CLI-defined configs
  80. *
  81. * So we need to adjust the index to account for the base config.
  82. *
  83. * - If the index is less than the base length, it's in the base config
  84. * (as specified by `baseConfig` argument to `FlatConfigArray` constructor).
  85. * - If the index is greater than the base length but less than the original
  86. * length + base length, it's in the original config. The original config
  87. * is passed to the `FlatConfigArray` constructor as the first argument.
  88. * - Otherwise, it's in the user-defined config, which is loaded from the
  89. * config file and merged with any command-line options.
  90. */
  91. if (error.index < baseLength) {
  92. location = "base";
  93. } else if (error.index < originalLength + baseLength) {
  94. location = "original";
  95. configIndex = error.index - baseLength;
  96. } else {
  97. configIndex = error.index - originalLength - baseLength;
  98. }
  99. return new TypeError(
  100. `${error.message.slice(0, -1)} at ${location} index ${configIndex}.`,
  101. { cause: error }
  102. );
  103. }
  104. const originalBaseConfig = Symbol("originalBaseConfig");
  105. const originalLength = Symbol("originalLength");
  106. const baseLength = Symbol("baseLength");
  107. //-----------------------------------------------------------------------------
  108. // Exports
  109. //-----------------------------------------------------------------------------
  110. /**
  111. * Represents an array containing configuration information for ESLint.
  112. */
  113. class FlatConfigArray extends ConfigArray {
  114. /**
  115. * Creates a new instance.
  116. * @param {*[]} configs An array of configuration information.
  117. * @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options
  118. * to use for the config array instance.
  119. */
  120. constructor(configs, {
  121. basePath,
  122. shouldIgnore = true,
  123. baseConfig = defaultConfig
  124. } = {}) {
  125. super(configs, {
  126. basePath,
  127. schema: flatConfigSchema
  128. });
  129. /**
  130. * The original length of the array before any modifications.
  131. * @type {number}
  132. */
  133. this[originalLength] = this.length;
  134. if (baseConfig[Symbol.iterator]) {
  135. this.unshift(...baseConfig);
  136. } else {
  137. this.unshift(baseConfig);
  138. }
  139. /**
  140. * The length of the array after applying the base config.
  141. * @type {number}
  142. */
  143. this[baseLength] = this.length - this[originalLength];
  144. /**
  145. * The base config used to build the config array.
  146. * @type {Array<FlatConfig>}
  147. */
  148. this[originalBaseConfig] = baseConfig;
  149. Object.defineProperty(this, originalBaseConfig, { writable: false });
  150. /**
  151. * Determines if `ignores` fields should be honored.
  152. * If true, then all `ignores` fields are honored.
  153. * if false, then only `ignores` fields in the baseConfig are honored.
  154. * @type {boolean}
  155. */
  156. this.shouldIgnore = shouldIgnore;
  157. Object.defineProperty(this, "shouldIgnore", { writable: false });
  158. }
  159. /**
  160. * Normalizes the array by calling the superclass method and catching/rethrowing
  161. * any ConfigError exceptions with additional details.
  162. * @param {any} [context] The context to use to normalize the array.
  163. * @returns {Promise<FlatConfigArray>} A promise that resolves when the array is normalized.
  164. */
  165. normalize(context) {
  166. return super.normalize(context)
  167. .catch(error => {
  168. if (error.name === "ConfigError") {
  169. throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
  170. }
  171. throw error;
  172. });
  173. }
  174. /**
  175. * Normalizes the array by calling the superclass method and catching/rethrowing
  176. * any ConfigError exceptions with additional details.
  177. * @param {any} [context] The context to use to normalize the array.
  178. * @returns {FlatConfigArray} The current instance.
  179. * @throws {TypeError} If the config is invalid.
  180. */
  181. normalizeSync(context) {
  182. try {
  183. return super.normalizeSync(context);
  184. } catch (error) {
  185. if (error.name === "ConfigError") {
  186. throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
  187. }
  188. throw error;
  189. }
  190. }
  191. /* eslint-disable class-methods-use-this -- Desired as instance method */
  192. /**
  193. * Replaces a config with another config to allow us to put strings
  194. * in the config array that will be replaced by objects before
  195. * normalization.
  196. * @param {Object} config The config to preprocess.
  197. * @returns {Object} The preprocessed config.
  198. */
  199. [ConfigArraySymbol.preprocessConfig](config) {
  200. if (config === "eslint:recommended") {
  201. // if we are in a Node.js environment warn the user
  202. if (typeof process !== "undefined" && process.emitWarning) {
  203. process.emitWarning("The 'eslint:recommended' string configuration is deprecated and will be replaced by the @eslint/js package's 'recommended' config.");
  204. }
  205. return jsPlugin.configs.recommended;
  206. }
  207. if (config === "eslint:all") {
  208. // if we are in a Node.js environment warn the user
  209. if (typeof process !== "undefined" && process.emitWarning) {
  210. process.emitWarning("The 'eslint:all' string configuration is deprecated and will be replaced by the @eslint/js package's 'all' config.");
  211. }
  212. return jsPlugin.configs.all;
  213. }
  214. /*
  215. * If a config object has `ignores` and no other non-meta fields, then it's an object
  216. * for global ignores. If `shouldIgnore` is false, that object shouldn't apply,
  217. * so we'll remove its `ignores`.
  218. */
  219. if (
  220. !this.shouldIgnore &&
  221. !this[originalBaseConfig].includes(config) &&
  222. config.ignores &&
  223. Object.keys(config).filter(key => !META_FIELDS.has(key)).length === 1
  224. ) {
  225. /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
  226. const { ignores, ...otherKeys } = config;
  227. return otherKeys;
  228. }
  229. return config;
  230. }
  231. /**
  232. * Finalizes the config by replacing plugin references with their objects
  233. * and validating rule option schemas.
  234. * @param {Object} config The config to finalize.
  235. * @returns {Object} The finalized config.
  236. * @throws {TypeError} If the config is invalid.
  237. */
  238. [ConfigArraySymbol.finalizeConfig](config) {
  239. const { plugins, languageOptions, processor } = config;
  240. let parserName, processorName;
  241. let invalidParser = false,
  242. invalidProcessor = false;
  243. // Check parser value
  244. if (languageOptions && languageOptions.parser) {
  245. const { parser } = languageOptions;
  246. if (typeof parser === "object") {
  247. parserName = getObjectId(parser);
  248. if (!parserName) {
  249. invalidParser = true;
  250. }
  251. } else {
  252. invalidParser = true;
  253. }
  254. }
  255. // Check processor value
  256. if (processor) {
  257. if (typeof processor === "string") {
  258. const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor);
  259. processorName = processor;
  260. if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[localProcessorName]) {
  261. throw new TypeError(`Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`);
  262. }
  263. config.processor = plugins[pluginName].processors[localProcessorName];
  264. } else if (typeof processor === "object") {
  265. processorName = getObjectId(processor);
  266. if (!processorName) {
  267. invalidProcessor = true;
  268. }
  269. } else {
  270. invalidProcessor = true;
  271. }
  272. }
  273. ruleValidator.validate(config);
  274. // apply special logic for serialization into JSON
  275. /* eslint-disable object-shorthand -- shorthand would change "this" value */
  276. Object.defineProperty(config, "toJSON", {
  277. value: function() {
  278. if (invalidParser) {
  279. throw new Error("Could not serialize parser object (missing 'meta' object).");
  280. }
  281. if (invalidProcessor) {
  282. throw new Error("Could not serialize processor object (missing 'meta' object).");
  283. }
  284. return {
  285. ...this,
  286. plugins: Object.entries(plugins).map(([namespace, plugin]) => {
  287. const pluginId = getObjectId(plugin);
  288. if (!pluginId) {
  289. return namespace;
  290. }
  291. return `${namespace}:${pluginId}`;
  292. }),
  293. languageOptions: {
  294. ...languageOptions,
  295. parser: parserName
  296. },
  297. processor: processorName
  298. };
  299. }
  300. });
  301. /* eslint-enable object-shorthand -- ok to enable now */
  302. return config;
  303. }
  304. /* eslint-enable class-methods-use-this -- Desired as instance method */
  305. }
  306. exports.FlatConfigArray = FlatConfigArray;