stylelint
Version:
Modern CSS linter
157 lines (126 loc) • 4.46 kB
JavaScript
import chalk from "chalk"
import path from "path"
import _ from "lodash"
import symbols from "log-symbols"
import stringWidth from "string-width"
import table, { getBorderCharacters } from "table"
import utils from "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
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.gray(message.rule || ""),
]
calculateWidths(row)
return row
})
output += table(
cleanedMessages,
{
border: 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.gray(p1 + ":" + p2)))
.join("\n")
return output
}
export default function (results) {
let output = invalidOptionsFormatter(results)
output += deprecationsFormatter(results)
output = results.reduce((output, result) => {
output += formatter(result.warnings, result.source)
return output
}, output)
// Ensure consistent padding
output = output.trim()
if (output !== "") {
output = "\n" + output + "\n\n"
}
return output
}