flat-compat.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /**
  2. * @fileoverview Compatibility class for flat config.
  3. * @author Nicholas C. Zakas
  4. */
  5. //-----------------------------------------------------------------------------
  6. // Requirements
  7. //-----------------------------------------------------------------------------
  8. import createDebug from "debug";
  9. import path from "node:path";
  10. import environments from "../conf/environments.js";
  11. import { ConfigArrayFactory } from "./config-array-factory.js";
  12. //-----------------------------------------------------------------------------
  13. // Helpers
  14. //-----------------------------------------------------------------------------
  15. /** @typedef {import("../../shared/types").Environment} Environment */
  16. /** @typedef {import("../../shared/types").Processor} Processor */
  17. const debug = createDebug("eslintrc:flat-compat");
  18. const cafactory = Symbol("cafactory");
  19. /**
  20. * Translates an ESLintRC-style config object into a flag-config-style config
  21. * object.
  22. * @param {Object} eslintrcConfig An ESLintRC-style config object.
  23. * @param {Object} options Options to help translate the config.
  24. * @param {string} options.resolveConfigRelativeTo To the directory to resolve
  25. * configs from.
  26. * @param {string} options.resolvePluginsRelativeTo The directory to resolve
  27. * plugins from.
  28. * @param {ReadOnlyMap<string,Environment>} options.pluginEnvironments A map of plugin environment
  29. * names to objects.
  30. * @param {ReadOnlyMap<string,Processor>} options.pluginProcessors A map of plugin processor
  31. * names to objects.
  32. * @returns {Object} A flag-config-style config object.
  33. * @throws {Error} If a plugin or environment cannot be resolved.
  34. */
  35. function translateESLintRC(eslintrcConfig, {
  36. resolveConfigRelativeTo,
  37. resolvePluginsRelativeTo,
  38. pluginEnvironments,
  39. pluginProcessors
  40. }) {
  41. const flatConfig = {};
  42. const configs = [];
  43. const languageOptions = {};
  44. const linterOptions = {};
  45. const keysToCopy = ["settings", "rules", "processor"];
  46. const languageOptionsKeysToCopy = ["globals", "parser", "parserOptions"];
  47. const linterOptionsKeysToCopy = ["noInlineConfig", "reportUnusedDisableDirectives"];
  48. // copy over simple translations
  49. for (const key of keysToCopy) {
  50. if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") {
  51. flatConfig[key] = eslintrcConfig[key];
  52. }
  53. }
  54. // copy over languageOptions
  55. for (const key of languageOptionsKeysToCopy) {
  56. if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") {
  57. // create the languageOptions key in the flat config
  58. flatConfig.languageOptions = languageOptions;
  59. if (key === "parser") {
  60. debug(`Resolving parser '${languageOptions[key]}' relative to ${resolveConfigRelativeTo}`);
  61. if (eslintrcConfig[key].error) {
  62. throw eslintrcConfig[key].error;
  63. }
  64. languageOptions[key] = eslintrcConfig[key].definition;
  65. continue;
  66. }
  67. // clone any object values that are in the eslintrc config
  68. if (eslintrcConfig[key] && typeof eslintrcConfig[key] === "object") {
  69. languageOptions[key] = {
  70. ...eslintrcConfig[key]
  71. };
  72. } else {
  73. languageOptions[key] = eslintrcConfig[key];
  74. }
  75. }
  76. }
  77. // copy over linterOptions
  78. for (const key of linterOptionsKeysToCopy) {
  79. if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") {
  80. flatConfig.linterOptions = linterOptions;
  81. linterOptions[key] = eslintrcConfig[key];
  82. }
  83. }
  84. // move ecmaVersion a level up
  85. if (languageOptions.parserOptions) {
  86. if ("ecmaVersion" in languageOptions.parserOptions) {
  87. languageOptions.ecmaVersion = languageOptions.parserOptions.ecmaVersion;
  88. delete languageOptions.parserOptions.ecmaVersion;
  89. }
  90. if ("sourceType" in languageOptions.parserOptions) {
  91. languageOptions.sourceType = languageOptions.parserOptions.sourceType;
  92. delete languageOptions.parserOptions.sourceType;
  93. }
  94. // check to see if we even need parserOptions anymore and remove it if not
  95. if (Object.keys(languageOptions.parserOptions).length === 0) {
  96. delete languageOptions.parserOptions;
  97. }
  98. }
  99. // overrides
  100. if (eslintrcConfig.criteria) {
  101. flatConfig.files = [absoluteFilePath => eslintrcConfig.criteria.test(absoluteFilePath)];
  102. }
  103. // translate plugins
  104. if (eslintrcConfig.plugins && typeof eslintrcConfig.plugins === "object") {
  105. debug(`Translating plugins: ${eslintrcConfig.plugins}`);
  106. flatConfig.plugins = {};
  107. for (const pluginName of Object.keys(eslintrcConfig.plugins)) {
  108. debug(`Translating plugin: ${pluginName}`);
  109. debug(`Resolving plugin '${pluginName} relative to ${resolvePluginsRelativeTo}`);
  110. const { original: plugin, error } = eslintrcConfig.plugins[pluginName];
  111. if (error) {
  112. throw error;
  113. }
  114. flatConfig.plugins[pluginName] = plugin;
  115. // create a config for any processors
  116. if (plugin.processors) {
  117. for (const processorName of Object.keys(plugin.processors)) {
  118. if (processorName.startsWith(".")) {
  119. debug(`Assigning processor: ${pluginName}/${processorName}`);
  120. configs.unshift({
  121. files: [`**/*${processorName}`],
  122. processor: pluginProcessors.get(`${pluginName}/${processorName}`)
  123. });
  124. }
  125. }
  126. }
  127. }
  128. }
  129. // translate env - must come after plugins
  130. if (eslintrcConfig.env && typeof eslintrcConfig.env === "object") {
  131. for (const envName of Object.keys(eslintrcConfig.env)) {
  132. // only add environments that are true
  133. if (eslintrcConfig.env[envName]) {
  134. debug(`Translating environment: ${envName}`);
  135. if (environments.has(envName)) {
  136. // built-in environments should be defined first
  137. configs.unshift(...translateESLintRC({
  138. criteria: eslintrcConfig.criteria,
  139. ...environments.get(envName)
  140. }, {
  141. resolveConfigRelativeTo,
  142. resolvePluginsRelativeTo
  143. }));
  144. } else if (pluginEnvironments.has(envName)) {
  145. // if the environment comes from a plugin, it should come after the plugin config
  146. configs.push(...translateESLintRC({
  147. criteria: eslintrcConfig.criteria,
  148. ...pluginEnvironments.get(envName)
  149. }, {
  150. resolveConfigRelativeTo,
  151. resolvePluginsRelativeTo
  152. }));
  153. }
  154. }
  155. }
  156. }
  157. // only add if there are actually keys in the config
  158. if (Object.keys(flatConfig).length > 0) {
  159. configs.push(flatConfig);
  160. }
  161. return configs;
  162. }
  163. //-----------------------------------------------------------------------------
  164. // Exports
  165. //-----------------------------------------------------------------------------
  166. /**
  167. * A compatibility class for working with configs.
  168. */
  169. class FlatCompat {
  170. constructor({
  171. baseDirectory = process.cwd(),
  172. resolvePluginsRelativeTo = baseDirectory,
  173. recommendedConfig,
  174. allConfig
  175. } = {}) {
  176. this.baseDirectory = baseDirectory;
  177. this.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
  178. this[cafactory] = new ConfigArrayFactory({
  179. cwd: baseDirectory,
  180. resolvePluginsRelativeTo,
  181. getEslintAllConfig() {
  182. if (!allConfig) {
  183. throw new TypeError("Missing parameter 'allConfig' in FlatCompat constructor.");
  184. }
  185. // remove name property if it exists
  186. const config = { ...allConfig };
  187. delete config.name;
  188. return config;
  189. },
  190. getEslintRecommendedConfig() {
  191. if (!recommendedConfig) {
  192. throw new TypeError("Missing parameter 'recommendedConfig' in FlatCompat constructor.");
  193. }
  194. // remove name property if it exists
  195. const config = { ...recommendedConfig };
  196. delete config.name;
  197. return config;
  198. }
  199. });
  200. }
  201. /**
  202. * Translates an ESLintRC-style config into a flag-config-style config.
  203. * @param {Object} eslintrcConfig The ESLintRC-style config object.
  204. * @returns {Object} A flag-config-style config object.
  205. */
  206. config(eslintrcConfig) {
  207. const eslintrcArray = this[cafactory].create(eslintrcConfig, {
  208. basePath: this.baseDirectory
  209. });
  210. const flatArray = [];
  211. let hasIgnorePatterns = false;
  212. eslintrcArray.forEach(configData => {
  213. if (configData.type === "config") {
  214. hasIgnorePatterns = hasIgnorePatterns || configData.ignorePattern;
  215. flatArray.push(...translateESLintRC(configData, {
  216. resolveConfigRelativeTo: path.join(this.baseDirectory, "__placeholder.js"),
  217. resolvePluginsRelativeTo: path.join(this.resolvePluginsRelativeTo, "__placeholder.js"),
  218. pluginEnvironments: eslintrcArray.pluginEnvironments,
  219. pluginProcessors: eslintrcArray.pluginProcessors
  220. }));
  221. }
  222. });
  223. // combine ignorePatterns to emulate ESLintRC behavior better
  224. if (hasIgnorePatterns) {
  225. flatArray.unshift({
  226. ignores: [filePath => {
  227. // Compute the final config for this file.
  228. // This filters config array elements by `files`/`excludedFiles` then merges the elements.
  229. const finalConfig = eslintrcArray.extractConfig(filePath);
  230. // Test the `ignorePattern` properties of the final config.
  231. return Boolean(finalConfig.ignores) && finalConfig.ignores(filePath);
  232. }]
  233. });
  234. }
  235. return flatArray;
  236. }
  237. /**
  238. * Translates the `env` section of an ESLintRC-style config.
  239. * @param {Object} envConfig The `env` section of an ESLintRC config.
  240. * @returns {Object[]} An array of flag-config objects representing the environments.
  241. */
  242. env(envConfig) {
  243. return this.config({
  244. env: envConfig
  245. });
  246. }
  247. /**
  248. * Translates the `extends` section of an ESLintRC-style config.
  249. * @param {...string} configsToExtend The names of the configs to load.
  250. * @returns {Object[]} An array of flag-config objects representing the config.
  251. */
  252. extends(...configsToExtend) {
  253. return this.config({
  254. extends: configsToExtend
  255. });
  256. }
  257. /**
  258. * Translates the `plugins` section of an ESLintRC-style config.
  259. * @param {...string} plugins The names of the plugins to load.
  260. * @returns {Object[]} An array of flag-config objects representing the plugins.
  261. */
  262. plugins(...plugins) {
  263. return this.config({
  264. plugins
  265. });
  266. }
  267. }
  268. export { FlatCompat };