blob: 62961094db7e570c8af4fb5490347d7e17415141 [file] [log] [blame]
"use strict";
const _ = require("lodash");
const chalk = require("chalk");
const path = require("path");
const stringWidth = require("string-width");
const symbols = require("log-symbols");
const table = require("table");
const utils = require("postcss-reporter/lib/util");
const MARGIN_WIDTHS = 9;
const levelColors = {
info: "blue",
warning: "yellow",
error: "red"
};
function deprecationsFormatter(results) {
const allDeprecationWarnings = _.flatMap(results, "deprecations");
const uniqueDeprecationWarnings = _.uniqBy(allDeprecationWarnings, "text");
if (!uniqueDeprecationWarnings || !uniqueDeprecationWarnings.length) {
return "";
}
return uniqueDeprecationWarnings.reduce((output, warning) => {
output += chalk.yellow("Deprecation Warning: ");
output += warning.text;
if (warning.reference) {
output += chalk.dim(" See: ");
output += chalk.dim.underline(warning.reference);
}
return output + "\n";
}, "\n");
}
function invalidOptionsFormatter(results) {
const allInvalidOptionWarnings = _.flatMap(results, r =>
r.invalidOptionWarnings.map(w => w.text)
);
const uniqueInvalidOptionWarnings = _.uniq(allInvalidOptionWarnings);
return uniqueInvalidOptionWarnings.reduce((output, warning) => {
output += chalk.red("Invalid Option: ");
output += warning;
return output + "\n";
}, "\n");
}
function logFrom(fromValue) {
if (fromValue.charAt(0) === "<") return fromValue;
return path
.relative(process.cwd(), fromValue)
.split(path.sep)
.join("/");
}
function getMessageWidth(columnWidths) {
if (!process.stdout.isTTY) {
return columnWidths[3];
}
const availableWidth =
process.stdout.columns < 80 ? 80 : process.stdout.columns;
const fullWidth = _.sum(_.values(columnWidths));
// If there is no reason to wrap the text, we won't align the last column to the right
if (availableWidth > fullWidth + MARGIN_WIDTHS) {
return columnWidths[3];
}
return availableWidth - (fullWidth - columnWidths[3] + MARGIN_WIDTHS);
}
function formatter(messages, source) {
if (!messages.length) return "";
const orderedMessages = _.sortBy(
messages,
m => (m.line ? 2 : 1), // positionless first
m => m.line,
m => m.column
);
// Create a list of column widths, needed to calculate
// the size of the message column and if needed wrap it.
const columnWidths = { 0: 1, 1: 1, 2: 1, 3: 1, 4: 1 };
const calculateWidths = function(columns) {
_.forOwn(columns, (value, key) => {
const normalisedValue = value ? value.toString() : value;
columnWidths[key] = Math.max(
columnWidths[key],
stringWidth(normalisedValue)
);
});
return columns;
};
let output = "\n";
if (source) {
output += chalk.underline(logFrom(source)) + "\n";
}
const cleanedMessages = orderedMessages.map(message => {
const location = utils.getLocation(message);
const severity = message.severity;
const row = [
location.line || "",
location.column || "",
symbols[severity]
? chalk[levelColors[severity]](symbols[severity])
: severity,
message.text
// Remove all control characters (newline, tab and etc)
.replace(/[\x01-\x1A]+/g, " ") // eslint-disable-line no-control-regex
.replace(/\.$/, "")
.replace(
new RegExp(_.escapeRegExp("(" + message.rule + ")") + "$"),
""
),
chalk.dim(message.rule || "")
];
calculateWidths(row);
return row;
});
output += table
.table(cleanedMessages, {
border: table.getBorderCharacters("void"),
columns: {
0: { alignment: "right", width: columnWidths[0], paddingRight: 0 },
1: { alignment: "left", width: columnWidths[1] },
2: { alignment: "center", width: columnWidths[2] },
3: {
alignment: "left",
width: getMessageWidth(columnWidths),
wrapWord: getMessageWidth(columnWidths) > 1
},
4: { alignment: "left", width: columnWidths[4], paddingRight: 0 }
},
drawHorizontalLine: () => false
})
.split("\n")
.map(el =>
el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(p1 + ":" + p2))
)
.join("\n");
return output;
}
module.exports = function(results) {
let output = invalidOptionsFormatter(results);
output += deprecationsFormatter(results);
output = results.reduce((output, result) => {
// Treat parseErrors as warnings
if (result.parseErrors) {
result.parseErrors.forEach(error =>
result.warnings.push({
line: error.line,
column: error.column,
rule: error.stylelintType,
severity: "error",
text: `${error.text} (${error.stylelintType})`
})
);
}
output += formatter(result.warnings, result.source);
return output;
}, output);
// Ensure consistent padding
output = output.trim();
if (output !== "") {
output = "\n" + output + "\n\n";
}
return output;
};