codeframe.js 4.68 KB
Newer Older
YazhouChen's avatar
YazhouChen committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
/**
 * @fileoverview Codeframe reporter
 * @author Vitor Balocco
 */
"use strict";

const chalk = require("chalk");
const codeFrame = require("babel-code-frame");
const path = require("path");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Given a word and a count, append an s if count is not one.
 * @param   {string} word  A word in its singular form.
 * @param   {number} count A number controlling whether word should be pluralized.
 * @returns {string}       The original word with an s on the end if count is not one.
 */
function pluralize(word, count) {
    return (count === 1 ? word : `${word}s`);
}

/**
 * Gets a formatted relative file path from an absolute path and a line/column in the file.
 * @param   {string} filePath The absolute file path to format.
 * @param   {number} line     The line from the file to use for formatting.
 * @param   {number} column   The column from the file to use for formatting.
 * @returns {string}          The formatted file path.
 */
function formatFilePath(filePath, line, column) {
    let relPath = path.relative(process.cwd(), filePath);

    if (line && column) {
        relPath += `:${line}:${column}`;
    }

    return chalk.green(relPath);
}

/**
 * Gets the formatted output for a given message.
 * @param   {Object} message      The object that represents this message.
 * @param   {Object} parentResult The result object that this message belongs to.
 * @returns {string}              The formatted output.
 */
function formatMessage(message, parentResult) {
    const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning");
    const msg = `${chalk.bold(message.message.replace(/([^ ])\.$/, "$1"))}`;
    const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`);
    const filePath = formatFilePath(parentResult.filePath, message.line, message.column);
    const sourceCode = parentResult.output ? parentResult.output : parentResult.source;

    const firstLine = [
        `${type}:`,
        `${msg}`,
        ruleId ? `${ruleId}` : "",
        sourceCode ? `at ${filePath}:` : `at ${filePath}`
    ].filter(String).join(" ");

    const result = [firstLine];

    if (sourceCode) {
        result.push(
            codeFrame(sourceCode, message.line, message.column, { highlightCode: false })
        );
    }

    return result.join("\n");
}

/**
 * Gets the formatted output summary for a given number of errors and warnings.
 * @param   {number} errors   The number of errors.
 * @param   {number} warnings The number of warnings.
 * @param   {number} fixableErrors The number of fixable errors.
 * @param   {number} fixableWarnings The number of fixable warnings.
 * @returns {string}          The formatted output summary.
 */
function formatSummary(errors, warnings, fixableErrors, fixableWarnings) {
    const summaryColor = errors > 0 ? "red" : "yellow";
    const summary = [];
    const fixablesSummary = [];

    if (errors > 0) {
        summary.push(`${errors} ${pluralize("error", errors)}`);
    }

    if (warnings > 0) {
        summary.push(`${warnings} ${pluralize("warning", warnings)}`);
    }

    if (fixableErrors > 0) {
        fixablesSummary.push(`${fixableErrors} ${pluralize("error", fixableErrors)}`);
    }

    if (fixableWarnings > 0) {
        fixablesSummary.push(`${fixableWarnings} ${pluralize("warning", fixableWarnings)}`);
    }

    let output = chalk[summaryColor].bold(`${summary.join(" and ")} found.`);

    if (fixableErrors || fixableWarnings) {
        output += chalk[summaryColor].bold(`\n${fixablesSummary.join(" and ")} potentially fixable with the \`--fix\` option.`);
    }

    return output;
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

module.exports = function(results) {
    let errors = 0;
    let warnings = 0;
    let fixableErrors = 0;
    let fixableWarnings = 0;

    const resultsWithMessages = results.filter(result => result.messages.length > 0);

    let output = resultsWithMessages.reduce((resultsOutput, result) => {
        const messages = result.messages.map(message => `${formatMessage(message, result)}\n\n`);

        errors += result.errorCount;
        warnings += result.warningCount;
        fixableErrors += result.fixableErrorCount;
        fixableWarnings += result.fixableWarningCount;

        return resultsOutput.concat(messages);
    }, []).join("\n");

    output += "\n";
    output += formatSummary(errors, warnings, fixableErrors, fixableWarnings);

    return (errors + warnings) > 0 ? output : "";
};