--- /dev/null
+"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
+ .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: true
+ },
+ 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;
+};