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