You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
225 lines
5.6 KiB
225 lines
5.6 KiB
'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 MARGIN_WIDTHS = 9; |
|
|
|
const levelColors = { |
|
info: 'blue', |
|
warning: 'yellow', |
|
error: 'red', |
|
success: undefined, |
|
}; |
|
|
|
/** |
|
* @param {import('stylelint').StylelintResult[]} results |
|
* @returns {string} |
|
*/ |
|
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'); |
|
} |
|
|
|
/** |
|
* @param {import('stylelint').StylelintResult[]} results |
|
* @return {string} |
|
*/ |
|
function invalidOptionsFormatter(results) { |
|
const allInvalidOptionWarnings = _.flatMap(results, (r) => |
|
r.invalidOptionWarnings.map((w) => w.text), |
|
); |
|
const uniqueInvalidOptionWarnings = [...new Set(allInvalidOptionWarnings)]; |
|
|
|
return uniqueInvalidOptionWarnings.reduce((output, warning) => { |
|
output += chalk.red('Invalid Option: '); |
|
output += warning; |
|
|
|
return `${output}\n`; |
|
}, '\n'); |
|
} |
|
|
|
/** |
|
* @param {string} fromValue |
|
* @return {string} |
|
*/ |
|
function logFrom(fromValue) { |
|
if (fromValue.startsWith('<')) return fromValue; |
|
|
|
return path.relative(process.cwd(), fromValue).split(path.sep).join('/'); |
|
} |
|
|
|
/** |
|
* @param {{[k: number]: number}} columnWidths |
|
* @return {number} |
|
*/ |
|
function getMessageWidth(columnWidths) { |
|
if (!process.stdout.isTTY) { |
|
return columnWidths[3]; |
|
} |
|
|
|
const availableWidth = process.stdout.columns < 80 ? 80 : process.stdout.columns; |
|
const fullWidth = Object.values(columnWidths).reduce((a, b) => a + b); |
|
|
|
// 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); |
|
} |
|
|
|
/** |
|
* @param {import('stylelint').StylelintWarning[]} messages |
|
* @param {string} source |
|
* @return {string} |
|
*/ |
|
function formatter(messages, source) { |
|
if (!messages.length) return ''; |
|
|
|
const orderedMessages = _.sortBy( |
|
messages, |
|
// eslint-disable-next-line no-confusing-arrow |
|
(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. |
|
* @type {{[k: string]: number}} |
|
*/ |
|
const columnWidths = { 0: 1, 1: 1, 2: 1, 3: 1, 4: 1 }; |
|
|
|
/** |
|
* @param {[string, string, string, string, string]} columns |
|
* @return {[string, string, string, string, string]} |
|
*/ |
|
function calculateWidths(columns) { |
|
for (const [key, value] of Object.entries(columns)) { |
|
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 { line, column } = message; |
|
const severity = /** @type {keyof import('log-symbols')} */ (message.severity); |
|
/** |
|
* @type {[string, string, string, string, string]} |
|
*/ |
|
const row = [ |
|
line ? line.toString() : '', |
|
column ? column.toString() : '', |
|
symbols[severity] |
|
? chalk[/** @type {'blue' | 'red' | 'yellow'} */ (levelColors[severity])](symbols[severity]) |
|
: severity, |
|
message.text |
|
// Remove all control characters (newline, tab and etc) |
|
.replace(/[\u0001-\u001A]+/g, ' ') // eslint-disable-line no-control-regex |
|
.replace(/\.$/, '') |
|
// eslint-disable-next-line prefer-template |
|
.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( |
|
/** |
|
* @param {string} el |
|
* @returns {string} |
|
*/ |
|
(el) => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(`${p1}:${p2}`)), |
|
) |
|
.join('\n'); |
|
|
|
return output; |
|
} |
|
|
|
/** |
|
* @type {import('stylelint').Formatter} |
|
*/ |
|
module.exports = function (results) { |
|
let output = invalidOptionsFormatter(results); |
|
|
|
output += deprecationsFormatter(results); |
|
|
|
output = results.reduce((accum, 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})`, |
|
}), |
|
); |
|
} |
|
|
|
accum += formatter(result.warnings, result.source || ''); |
|
|
|
return accum; |
|
}, output); |
|
|
|
// Ensure consistent padding |
|
output = output.trim(); |
|
|
|
if (output !== '') { |
|
output = `\n${output}\n\n`; |
|
} |
|
|
|
return output; |
|
};
|
|
|