codeframe.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /**
  2. * @fileoverview Codeframe reporter
  3. * @author Vitor Balocco
  4. */
  5. "use strict";
  6. const chalk = require("chalk");
  7. const { codeFrameColumns } = require("@babel/code-frame");
  8. const path = require("path");
  9. //------------------------------------------------------------------------------
  10. // Helpers
  11. //------------------------------------------------------------------------------
  12. /**
  13. * Given a word and a count, append an s if count is not one.
  14. * @param {string} word A word in its singular form.
  15. * @param {number} count A number controlling whether word should be pluralized.
  16. * @returns {string} The original word with an s on the end if count is not one.
  17. */
  18. function pluralize(word, count) {
  19. return (count === 1 ? word : `${word}s`);
  20. }
  21. /**
  22. * Gets a formatted relative file path from an absolute path and a line/column in the file.
  23. * @param {string} filePath The absolute file path to format.
  24. * @param {number} line The line from the file to use for formatting.
  25. * @param {number} column The column from the file to use for formatting.
  26. * @returns {string} The formatted file path.
  27. */
  28. function formatFilePath(filePath, line, column) {
  29. let relPath = path.relative(process.cwd(), filePath);
  30. if (line && column) {
  31. relPath += `:${line}:${column}`;
  32. }
  33. return chalk.green(relPath);
  34. }
  35. /**
  36. * Gets the formatted output for a given message.
  37. * @param {Object} message The object that represents this message.
  38. * @param {Object} parentResult The result object that this message belongs to.
  39. * @returns {string} The formatted output.
  40. */
  41. function formatMessage(message, parentResult) {
  42. const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning");
  43. const msg = `${chalk.bold(message.message.replace(/([^ ])\.$/u, "$1"))}`;
  44. const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`);
  45. const filePath = formatFilePath(parentResult.filePath, message.line, message.column);
  46. const sourceCode = parentResult.output ? parentResult.output : parentResult.source;
  47. const firstLine = [
  48. `${type}:`,
  49. `${msg}`,
  50. ruleId ? `${ruleId}` : "",
  51. sourceCode ? `at ${filePath}:` : `at ${filePath}`
  52. ].filter(String).join(" ");
  53. const result = [firstLine];
  54. if (sourceCode) {
  55. result.push(
  56. codeFrameColumns(sourceCode, { start: { line: message.line, column: message.column } }, { highlightCode: false })
  57. );
  58. }
  59. return result.join("\n");
  60. }
  61. /**
  62. * Gets the formatted output summary for a given number of errors and warnings.
  63. * @param {number} errors The number of errors.
  64. * @param {number} warnings The number of warnings.
  65. * @param {number} fixableErrors The number of fixable errors.
  66. * @param {number} fixableWarnings The number of fixable warnings.
  67. * @returns {string} The formatted output summary.
  68. */
  69. function formatSummary(errors, warnings, fixableErrors, fixableWarnings) {
  70. const summaryColor = errors > 0 ? "red" : "yellow";
  71. const summary = [];
  72. const fixablesSummary = [];
  73. if (errors > 0) {
  74. summary.push(`${errors} ${pluralize("error", errors)}`);
  75. }
  76. if (warnings > 0) {
  77. summary.push(`${warnings} ${pluralize("warning", warnings)}`);
  78. }
  79. if (fixableErrors > 0) {
  80. fixablesSummary.push(`${fixableErrors} ${pluralize("error", fixableErrors)}`);
  81. }
  82. if (fixableWarnings > 0) {
  83. fixablesSummary.push(`${fixableWarnings} ${pluralize("warning", fixableWarnings)}`);
  84. }
  85. let output = chalk[summaryColor].bold(`${summary.join(" and ")} found.`);
  86. if (fixableErrors || fixableWarnings) {
  87. output += chalk[summaryColor].bold(`\n${fixablesSummary.join(" and ")} potentially fixable with the \`--fix\` option.`);
  88. }
  89. return output;
  90. }
  91. //------------------------------------------------------------------------------
  92. // Public Interface
  93. //------------------------------------------------------------------------------
  94. module.exports = function(results) {
  95. let errors = 0;
  96. let warnings = 0;
  97. let fixableErrors = 0;
  98. let fixableWarnings = 0;
  99. const resultsWithMessages = results.filter(result => result.messages.length > 0);
  100. let output = resultsWithMessages.reduce((resultsOutput, result) => {
  101. const messages = result.messages.map(message => `${formatMessage(message, result)}\n\n`);
  102. errors += result.errorCount;
  103. warnings += result.warningCount;
  104. fixableErrors += result.fixableErrorCount;
  105. fixableWarnings += result.fixableWarningCount;
  106. return resultsOutput.concat(messages);
  107. }, []).join("\n");
  108. output += "\n";
  109. output += formatSummary(errors, warnings, fixableErrors, fixableWarnings);
  110. return (errors + warnings) > 0 ? output : "";
  111. };