stylelint
Version:
A mighty, modern CSS linter.
189 lines (149 loc) • 4.77 kB
JavaScript
;
const _ = require('lodash');
const chalk = require('chalk');
const path = require('path');
const stringWidth = require('string-width');
const symbols = require('log-symbols');
const utils = require('postcss-reporter/lib/util');
let table;
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;
});
if (!table) {
table = require('table');
}
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;
};