translate-cli-options.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /**
  2. * @fileoverview Translates CLI options into ESLint constructor options.
  3. * @author Nicholas C. Zakas
  4. * @author Francesco Trotta
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const { normalizeSeverityToString } = require("./severity");
  11. const { getShorthandName, normalizePackageName } = require("./naming");
  12. const { ModuleImporter } = require("@humanwhocodes/module-importer");
  13. //------------------------------------------------------------------------------
  14. // Types
  15. //------------------------------------------------------------------------------
  16. /** @typedef {import("../types").ESLint.Options} ESLintOptions */
  17. /** @typedef {import("../types").ESLint.LegacyOptions} LegacyESLintOptions */
  18. /** @typedef {import("../types").Linter.LintMessage} LintMessage */
  19. /** @typedef {import("../options").ParsedCLIOptions} ParsedCLIOptions */
  20. /** @typedef {import("../types").ESLint.Plugin} Plugin */
  21. //------------------------------------------------------------------------------
  22. // Helpers
  23. //------------------------------------------------------------------------------
  24. /**
  25. * Loads plugins with the specified names.
  26. * @param {{ "import": (name: string) => Promise<any> }} importer An object with an `import` method called once for each plugin.
  27. * @param {string[]} pluginNames The names of the plugins to be loaded, with or without the "eslint-plugin-" prefix.
  28. * @returns {Promise<Record<string, Plugin>>} A mapping of plugin short names to implementations.
  29. */
  30. async function loadPlugins(importer, pluginNames) {
  31. const plugins = {};
  32. await Promise.all(
  33. pluginNames.map(async pluginName => {
  34. const longName = normalizePackageName(pluginName, "eslint-plugin");
  35. const module = await importer.import(longName);
  36. if (!("default" in module)) {
  37. throw new Error(
  38. `"${longName}" cannot be used with the \`--plugin\` option because its default module does not provide a \`default\` export`,
  39. );
  40. }
  41. const shortName = getShorthandName(pluginName, "eslint-plugin");
  42. plugins[shortName] = module.default;
  43. }),
  44. );
  45. return plugins;
  46. }
  47. /**
  48. * Predicate function for whether or not to apply fixes in quiet mode.
  49. * If a message is a warning, do not apply a fix.
  50. * @param {LintMessage} message The lint result.
  51. * @returns {boolean} True if the lint message is an error (and thus should be
  52. * autofixed), false otherwise.
  53. */
  54. function quietFixPredicate(message) {
  55. return message.severity === 2;
  56. }
  57. /**
  58. * Predicate function for whether or not to run a rule in quiet mode.
  59. * If a rule is set to warning, do not run it.
  60. * @param {{ ruleId: string; severity: number; }} rule The rule id and severity.
  61. * @returns {boolean} True if the lint rule should run, false otherwise.
  62. */
  63. function quietRuleFilter(rule) {
  64. return rule.severity === 2;
  65. }
  66. //------------------------------------------------------------------------------
  67. // Public Interface
  68. //------------------------------------------------------------------------------
  69. /**
  70. * Translates the CLI options into the options expected by the ESLint constructor.
  71. * @param {ParsedCLIOptions} cliOptions The CLI options to translate.
  72. * @param {"flat"|"eslintrc"} [configType="eslintrc"] The format of the config to generate.
  73. * @returns {Promise<ESLintOptions | LegacyESLintOptions>} The options object for the ESLint constructor.
  74. */
  75. async function translateOptions(
  76. {
  77. cache,
  78. cacheFile,
  79. cacheLocation,
  80. cacheStrategy,
  81. concurrency,
  82. config,
  83. configLookup,
  84. env,
  85. errorOnUnmatchedPattern,
  86. eslintrc,
  87. ext,
  88. fix,
  89. fixDryRun,
  90. fixType,
  91. flag,
  92. global,
  93. ignore,
  94. ignorePath,
  95. ignorePattern,
  96. inlineConfig,
  97. parser,
  98. parserOptions,
  99. plugin,
  100. quiet,
  101. reportUnusedDisableDirectives,
  102. reportUnusedDisableDirectivesSeverity,
  103. reportUnusedInlineConfigs,
  104. resolvePluginsRelativeTo,
  105. rule,
  106. rulesdir,
  107. stats,
  108. warnIgnored,
  109. passOnNoPatterns,
  110. maxWarnings,
  111. },
  112. configType,
  113. ) {
  114. let overrideConfig, overrideConfigFile;
  115. const importer = new ModuleImporter();
  116. if (configType === "flat") {
  117. overrideConfigFile =
  118. typeof config === "string" ? config : !configLookup;
  119. if (overrideConfigFile === false) {
  120. overrideConfigFile = void 0;
  121. }
  122. const languageOptions = {};
  123. if (global) {
  124. languageOptions.globals = global.reduce((obj, name) => {
  125. if (name.endsWith(":true")) {
  126. obj[name.slice(0, -5)] = "writable";
  127. } else {
  128. obj[name] = "readonly";
  129. }
  130. return obj;
  131. }, {});
  132. }
  133. if (parserOptions) {
  134. languageOptions.parserOptions = parserOptions;
  135. }
  136. if (parser) {
  137. languageOptions.parser = await importer.import(parser);
  138. }
  139. overrideConfig = [
  140. {
  141. ...(Object.keys(languageOptions).length > 0
  142. ? { languageOptions }
  143. : {}),
  144. rules: rule ? rule : {},
  145. },
  146. ];
  147. if (
  148. reportUnusedDisableDirectives ||
  149. reportUnusedDisableDirectivesSeverity !== void 0
  150. ) {
  151. overrideConfig[0].linterOptions = {
  152. reportUnusedDisableDirectives: reportUnusedDisableDirectives
  153. ? "error"
  154. : normalizeSeverityToString(
  155. reportUnusedDisableDirectivesSeverity,
  156. ),
  157. };
  158. }
  159. if (reportUnusedInlineConfigs !== void 0) {
  160. overrideConfig[0].linterOptions = {
  161. ...overrideConfig[0].linterOptions,
  162. reportUnusedInlineConfigs: normalizeSeverityToString(
  163. reportUnusedInlineConfigs,
  164. ),
  165. };
  166. }
  167. if (plugin) {
  168. overrideConfig[0].plugins = await loadPlugins(importer, plugin);
  169. }
  170. if (ext) {
  171. overrideConfig.push({
  172. files: ext.map(
  173. extension =>
  174. `**/*${extension.startsWith(".") ? "" : "."}${extension}`,
  175. ),
  176. });
  177. }
  178. } else {
  179. overrideConfigFile = config;
  180. overrideConfig = {
  181. env:
  182. env &&
  183. env.reduce((obj, name) => {
  184. obj[name] = true;
  185. return obj;
  186. }, {}),
  187. globals:
  188. global &&
  189. global.reduce((obj, name) => {
  190. if (name.endsWith(":true")) {
  191. obj[name.slice(0, -5)] = "writable";
  192. } else {
  193. obj[name] = "readonly";
  194. }
  195. return obj;
  196. }, {}),
  197. ignorePatterns: ignorePattern,
  198. parser,
  199. parserOptions,
  200. plugins: plugin,
  201. rules: rule,
  202. };
  203. }
  204. const options = {
  205. allowInlineConfig: inlineConfig,
  206. cache,
  207. cacheLocation: cacheLocation || cacheFile,
  208. cacheStrategy,
  209. errorOnUnmatchedPattern,
  210. fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
  211. fixTypes: fixType,
  212. ignore,
  213. overrideConfig,
  214. overrideConfigFile,
  215. passOnNoPatterns,
  216. };
  217. if (configType === "flat") {
  218. options.concurrency = concurrency;
  219. options.flags = flag;
  220. options.ignorePatterns = ignorePattern;
  221. options.stats = stats;
  222. options.warnIgnored = warnIgnored;
  223. /*
  224. * For performance reasons rules not marked as 'error' are filtered out in quiet mode. As maxWarnings
  225. * requires rules set to 'warn' to be run, we only filter out 'warn' rules if maxWarnings is not specified.
  226. */
  227. options.ruleFilter =
  228. quiet && maxWarnings === -1 ? quietRuleFilter : () => true;
  229. } else {
  230. options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
  231. options.rulePaths = rulesdir;
  232. options.useEslintrc = eslintrc;
  233. options.extensions = ext;
  234. options.ignorePath = ignorePath;
  235. if (
  236. reportUnusedDisableDirectives ||
  237. reportUnusedDisableDirectivesSeverity !== void 0
  238. ) {
  239. options.reportUnusedDisableDirectives =
  240. reportUnusedDisableDirectives
  241. ? "error"
  242. : normalizeSeverityToString(
  243. reportUnusedDisableDirectivesSeverity,
  244. );
  245. }
  246. }
  247. return options;
  248. }
  249. module.exports = translateOptions;