Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cli.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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. { promisify } = require("util"),
  17. { ESLint } = require("./eslint"),
  18. CLIOptions = require("./options"),
  19. log = require("./shared/logging"),
  20. RuntimeInfo = require("./shared/runtime-info");
  21. const debug = require("debug")("eslint:cli");
  22. //------------------------------------------------------------------------------
  23. // Types
  24. //------------------------------------------------------------------------------
  25. /** @typedef {import("./eslint/eslint").ESLintOptions} ESLintOptions */
  26. /** @typedef {import("./eslint/eslint").LintMessage} LintMessage */
  27. /** @typedef {import("./eslint/eslint").LintResult} LintResult */
  28. /** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
  29. //------------------------------------------------------------------------------
  30. // Helpers
  31. //------------------------------------------------------------------------------
  32. const mkdir = promisify(fs.mkdir);
  33. const stat = promisify(fs.stat);
  34. const writeFile = promisify(fs.writeFile);
  35. /**
  36. * Predicate function for whether or not to apply fixes in quiet mode.
  37. * If a message is a warning, do not apply a fix.
  38. * @param {LintMessage} message The lint result.
  39. * @returns {boolean} True if the lint message is an error (and thus should be
  40. * autofixed), false otherwise.
  41. */
  42. function quietFixPredicate(message) {
  43. return message.severity === 2;
  44. }
  45. /**
  46. * Translates the CLI options into the options expected by the CLIEngine.
  47. * @param {ParsedCLIOptions} cliOptions The CLI options to translate.
  48. * @returns {ESLintOptions} The options object for the CLIEngine.
  49. * @private
  50. */
  51. function translateOptions({
  52. cache,
  53. cacheFile,
  54. cacheLocation,
  55. cacheStrategy,
  56. config,
  57. env,
  58. errorOnUnmatchedPattern,
  59. eslintrc,
  60. ext,
  61. fix,
  62. fixDryRun,
  63. fixType,
  64. global,
  65. ignore,
  66. ignorePath,
  67. ignorePattern,
  68. inlineConfig,
  69. parser,
  70. parserOptions,
  71. plugin,
  72. quiet,
  73. reportUnusedDisableDirectives,
  74. resolvePluginsRelativeTo,
  75. rule,
  76. rulesdir
  77. }) {
  78. return {
  79. allowInlineConfig: inlineConfig,
  80. cache,
  81. cacheLocation: cacheLocation || cacheFile,
  82. cacheStrategy,
  83. errorOnUnmatchedPattern,
  84. extensions: ext,
  85. fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
  86. fixTypes: fixType,
  87. ignore,
  88. ignorePath,
  89. overrideConfig: {
  90. env: env && env.reduce((obj, name) => {
  91. obj[name] = true;
  92. return obj;
  93. }, {}),
  94. globals: global && global.reduce((obj, name) => {
  95. if (name.endsWith(":true")) {
  96. obj[name.slice(0, -5)] = "writable";
  97. } else {
  98. obj[name] = "readonly";
  99. }
  100. return obj;
  101. }, {}),
  102. ignorePatterns: ignorePattern,
  103. parser,
  104. parserOptions,
  105. plugins: plugin,
  106. rules: rule
  107. },
  108. overrideConfigFile: config,
  109. reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0,
  110. resolvePluginsRelativeTo,
  111. rulePaths: rulesdir,
  112. useEslintrc: eslintrc
  113. };
  114. }
  115. /**
  116. * Count error messages.
  117. * @param {LintResult[]} results The lint results.
  118. * @returns {{errorCount:number;warningCount:number}} The number of error messages.
  119. */
  120. function countErrors(results) {
  121. let errorCount = 0;
  122. let fatalErrorCount = 0;
  123. let warningCount = 0;
  124. for (const result of results) {
  125. errorCount += result.errorCount;
  126. fatalErrorCount += result.fatalErrorCount;
  127. warningCount += result.warningCount;
  128. }
  129. return { errorCount, fatalErrorCount, warningCount };
  130. }
  131. /**
  132. * Check if a given file path is a directory or not.
  133. * @param {string} filePath The path to a file to check.
  134. * @returns {Promise<boolean>} `true` if the given path is a directory.
  135. */
  136. async function isDirectory(filePath) {
  137. try {
  138. return (await stat(filePath)).isDirectory();
  139. } catch (error) {
  140. if (error.code === "ENOENT" || error.code === "ENOTDIR") {
  141. return false;
  142. }
  143. throw error;
  144. }
  145. }
  146. /**
  147. * Outputs the results of the linting.
  148. * @param {ESLint} engine The ESLint instance to use.
  149. * @param {LintResult[]} results The results to print.
  150. * @param {string} format The name of the formatter to use or the path to the formatter.
  151. * @param {string} outputFile The path for the output file.
  152. * @returns {Promise<boolean>} True if the printing succeeds, false if not.
  153. * @private
  154. */
  155. async function printResults(engine, results, format, outputFile) {
  156. let formatter;
  157. try {
  158. formatter = await engine.loadFormatter(format);
  159. } catch (e) {
  160. log.error(e.message);
  161. return false;
  162. }
  163. const output = formatter.format(results);
  164. if (output) {
  165. if (outputFile) {
  166. const filePath = path.resolve(process.cwd(), outputFile);
  167. if (await isDirectory(filePath)) {
  168. log.error("Cannot write to output file path, it is a directory: %s", outputFile);
  169. return false;
  170. }
  171. try {
  172. await mkdir(path.dirname(filePath), { recursive: true });
  173. await writeFile(filePath, output);
  174. } catch (ex) {
  175. log.error("There was a problem writing the output file:\n%s", ex);
  176. return false;
  177. }
  178. } else {
  179. log.info(output);
  180. }
  181. }
  182. return true;
  183. }
  184. //------------------------------------------------------------------------------
  185. // Public Interface
  186. //------------------------------------------------------------------------------
  187. /**
  188. * Encapsulates all CLI behavior for eslint. Makes it easier to test as well as
  189. * for other Node.js programs to effectively run the CLI.
  190. */
  191. const cli = {
  192. /**
  193. * Executes the CLI based on an array of arguments that is passed in.
  194. * @param {string|Array|Object} args The arguments to process.
  195. * @param {string} [text] The text to lint (used for TTY).
  196. * @returns {Promise<number>} The exit code for the operation.
  197. */
  198. async execute(args, text) {
  199. if (Array.isArray(args)) {
  200. debug("CLI args: %o", args.slice(2));
  201. }
  202. /** @type {ParsedCLIOptions} */
  203. let options;
  204. try {
  205. options = CLIOptions.parse(args);
  206. } catch (error) {
  207. log.error(error.message);
  208. return 2;
  209. }
  210. const files = options._;
  211. const useStdin = typeof text === "string";
  212. if (options.help) {
  213. log.info(CLIOptions.generateHelp());
  214. return 0;
  215. }
  216. if (options.version) {
  217. log.info(RuntimeInfo.version());
  218. return 0;
  219. }
  220. if (options.envInfo) {
  221. try {
  222. log.info(RuntimeInfo.environment());
  223. return 0;
  224. } catch (err) {
  225. log.error(err.message);
  226. return 2;
  227. }
  228. }
  229. if (options.printConfig) {
  230. if (files.length) {
  231. log.error("The --print-config option must be used with exactly one file name.");
  232. return 2;
  233. }
  234. if (useStdin) {
  235. log.error("The --print-config option is not available for piped-in code.");
  236. return 2;
  237. }
  238. const engine = new ESLint(translateOptions(options));
  239. const fileConfig =
  240. await engine.calculateConfigForFile(options.printConfig);
  241. log.info(JSON.stringify(fileConfig, null, " "));
  242. return 0;
  243. }
  244. debug(`Running on ${useStdin ? "text" : "files"}`);
  245. if (options.fix && options.fixDryRun) {
  246. log.error("The --fix option and the --fix-dry-run option cannot be used together.");
  247. return 2;
  248. }
  249. if (useStdin && options.fix) {
  250. log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead.");
  251. return 2;
  252. }
  253. if (options.fixType && !options.fix && !options.fixDryRun) {
  254. log.error("The --fix-type option requires either --fix or --fix-dry-run.");
  255. return 2;
  256. }
  257. const engine = new ESLint(translateOptions(options));
  258. let results;
  259. if (useStdin) {
  260. results = await engine.lintText(text, {
  261. filePath: options.stdinFilename,
  262. warnIgnored: true
  263. });
  264. } else {
  265. results = await engine.lintFiles(files);
  266. }
  267. if (options.fix) {
  268. debug("Fix mode enabled - applying fixes");
  269. await ESLint.outputFixes(results);
  270. }
  271. let resultsToPrint = results;
  272. if (options.quiet) {
  273. debug("Quiet mode enabled - filtering out warnings");
  274. resultsToPrint = ESLint.getErrorResults(resultsToPrint);
  275. }
  276. if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) {
  277. // Errors and warnings from the original unfiltered results should determine the exit code
  278. const { errorCount, fatalErrorCount, warningCount } = countErrors(results);
  279. const tooManyWarnings =
  280. options.maxWarnings >= 0 && warningCount > options.maxWarnings;
  281. const shouldExitForFatalErrors =
  282. options.exitOnFatalError && fatalErrorCount > 0;
  283. if (!errorCount && tooManyWarnings) {
  284. log.error(
  285. "ESLint found too many warnings (maximum: %s).",
  286. options.maxWarnings
  287. );
  288. }
  289. if (shouldExitForFatalErrors) {
  290. return 2;
  291. }
  292. return (errorCount || tooManyWarnings) ? 1 : 0;
  293. }
  294. return 2;
  295. }
  296. };
  297. module.exports = cli;