cli.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /**
  2. * @fileoverview Main CLI object.
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. /*
  7. * The CLI object should *not* call process.exit() directly. It should only return
  8. * exit codes. This allows other programs to use the CLI object and still control
  9. * when the program exits.
  10. */
  11. //------------------------------------------------------------------------------
  12. // Requirements
  13. //------------------------------------------------------------------------------
  14. const fs = require("fs"),
  15. path = require("path"),
  16. mkdirp = require("mkdirp"),
  17. { CLIEngine } = require("./cli-engine"),
  18. options = require("./options"),
  19. log = require("./shared/logging"),
  20. RuntimeInfo = require("./shared/runtime-info");
  21. const debug = require("debug")("eslint:cli");
  22. //------------------------------------------------------------------------------
  23. // Helpers
  24. //------------------------------------------------------------------------------
  25. /**
  26. * Predicate function for whether or not to apply fixes in quiet mode.
  27. * If a message is a warning, do not apply a fix.
  28. * @param {LintResult} lintResult The lint result.
  29. * @returns {boolean} True if the lint message is an error (and thus should be
  30. * autofixed), false otherwise.
  31. */
  32. function quietFixPredicate(lintResult) {
  33. return lintResult.severity === 2;
  34. }
  35. /**
  36. * Translates the CLI options into the options expected by the CLIEngine.
  37. * @param {Object} cliOptions The CLI options to translate.
  38. * @returns {CLIEngineOptions} The options object for the CLIEngine.
  39. * @private
  40. */
  41. function translateOptions(cliOptions) {
  42. return {
  43. envs: cliOptions.env,
  44. extensions: cliOptions.ext,
  45. rules: cliOptions.rule,
  46. plugins: cliOptions.plugin,
  47. globals: cliOptions.global,
  48. ignore: cliOptions.ignore,
  49. ignorePath: cliOptions.ignorePath,
  50. ignorePattern: cliOptions.ignorePattern,
  51. configFile: cliOptions.config,
  52. rulePaths: cliOptions.rulesdir,
  53. useEslintrc: cliOptions.eslintrc,
  54. parser: cliOptions.parser,
  55. parserOptions: cliOptions.parserOptions,
  56. cache: cliOptions.cache,
  57. cacheFile: cliOptions.cacheFile,
  58. cacheLocation: cliOptions.cacheLocation,
  59. fix: (cliOptions.fix || cliOptions.fixDryRun) && (cliOptions.quiet ? quietFixPredicate : true),
  60. fixTypes: cliOptions.fixType,
  61. allowInlineConfig: cliOptions.inlineConfig,
  62. reportUnusedDisableDirectives: cliOptions.reportUnusedDisableDirectives,
  63. resolvePluginsRelativeTo: cliOptions.resolvePluginsRelativeTo,
  64. errorOnUnmatchedPattern: cliOptions.errorOnUnmatchedPattern
  65. };
  66. }
  67. /**
  68. * Outputs the results of the linting.
  69. * @param {CLIEngine} engine The CLIEngine to use.
  70. * @param {LintResult[]} results The results to print.
  71. * @param {string} format The name of the formatter to use or the path to the formatter.
  72. * @param {string} outputFile The path for the output file.
  73. * @returns {boolean} True if the printing succeeds, false if not.
  74. * @private
  75. */
  76. function printResults(engine, results, format, outputFile) {
  77. let formatter;
  78. let rulesMeta;
  79. try {
  80. formatter = engine.getFormatter(format);
  81. } catch (e) {
  82. log.error(e.message);
  83. return false;
  84. }
  85. const output = formatter(results, {
  86. get rulesMeta() {
  87. if (!rulesMeta) {
  88. rulesMeta = {};
  89. for (const [ruleId, rule] of engine.getRules()) {
  90. rulesMeta[ruleId] = rule.meta;
  91. }
  92. }
  93. return rulesMeta;
  94. }
  95. });
  96. if (output) {
  97. if (outputFile) {
  98. const filePath = path.resolve(process.cwd(), outputFile);
  99. if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
  100. log.error("Cannot write to output file path, it is a directory: %s", outputFile);
  101. return false;
  102. }
  103. try {
  104. mkdirp.sync(path.dirname(filePath));
  105. fs.writeFileSync(filePath, output);
  106. } catch (ex) {
  107. log.error("There was a problem writing the output file:\n%s", ex);
  108. return false;
  109. }
  110. } else {
  111. log.info(output);
  112. }
  113. }
  114. return true;
  115. }
  116. //------------------------------------------------------------------------------
  117. // Public Interface
  118. //------------------------------------------------------------------------------
  119. /**
  120. * Encapsulates all CLI behavior for eslint. Makes it easier to test as well as
  121. * for other Node.js programs to effectively run the CLI.
  122. */
  123. const cli = {
  124. /**
  125. * Executes the CLI based on an array of arguments that is passed in.
  126. * @param {string|Array|Object} args The arguments to process.
  127. * @param {string} [text] The text to lint (used for TTY).
  128. * @returns {int} The exit code for the operation.
  129. */
  130. execute(args, text) {
  131. if (Array.isArray(args)) {
  132. debug("CLI args: %o", args.slice(2));
  133. }
  134. let currentOptions;
  135. try {
  136. currentOptions = options.parse(args);
  137. } catch (error) {
  138. log.error(error.message);
  139. return 2;
  140. }
  141. const files = currentOptions._;
  142. const useStdin = typeof text === "string";
  143. if (currentOptions.version) {
  144. log.info(RuntimeInfo.version());
  145. } else if (currentOptions.envInfo) {
  146. try {
  147. log.info(RuntimeInfo.environment());
  148. return 0;
  149. } catch (err) {
  150. log.error(err.message);
  151. return 2;
  152. }
  153. } else if (currentOptions.printConfig) {
  154. if (files.length) {
  155. log.error("The --print-config option must be used with exactly one file name.");
  156. return 2;
  157. }
  158. if (useStdin) {
  159. log.error("The --print-config option is not available for piped-in code.");
  160. return 2;
  161. }
  162. const engine = new CLIEngine(translateOptions(currentOptions));
  163. const fileConfig = engine.getConfigForFile(currentOptions.printConfig);
  164. log.info(JSON.stringify(fileConfig, null, " "));
  165. return 0;
  166. } else if (currentOptions.help || (!files.length && !useStdin)) {
  167. log.info(options.generateHelp());
  168. } else {
  169. debug(`Running on ${useStdin ? "text" : "files"}`);
  170. if (currentOptions.fix && currentOptions.fixDryRun) {
  171. log.error("The --fix option and the --fix-dry-run option cannot be used together.");
  172. return 2;
  173. }
  174. if (useStdin && currentOptions.fix) {
  175. log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead.");
  176. return 2;
  177. }
  178. if (currentOptions.fixType && !currentOptions.fix && !currentOptions.fixDryRun) {
  179. log.error("The --fix-type option requires either --fix or --fix-dry-run.");
  180. return 2;
  181. }
  182. const engine = new CLIEngine(translateOptions(currentOptions));
  183. const report = useStdin ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files);
  184. if (currentOptions.fix) {
  185. debug("Fix mode enabled - applying fixes");
  186. CLIEngine.outputFixes(report);
  187. }
  188. if (currentOptions.quiet) {
  189. debug("Quiet mode enabled - filtering out warnings");
  190. report.results = CLIEngine.getErrorResults(report.results);
  191. }
  192. if (printResults(engine, report.results, currentOptions.format, currentOptions.outputFile)) {
  193. const tooManyWarnings = currentOptions.maxWarnings >= 0 && report.warningCount > currentOptions.maxWarnings;
  194. if (!report.errorCount && tooManyWarnings) {
  195. log.error("ESLint found too many warnings (maximum: %s).", currentOptions.maxWarnings);
  196. }
  197. return (report.errorCount || tooManyWarnings) ? 1 : 0;
  198. }
  199. return 2;
  200. }
  201. return 0;
  202. }
  203. };
  204. module.exports = cli;