flat-compat.js 11 KB

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