flat-compat.js 11 KB

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