mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-05 19:41:12 +08:00
R=jacktfranklin@chromium.org Bug: none Change-Id: I961be18530d3ccbc4efed0c2cda9bd603b14e27b Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3372931 Reviewed-by: Jack Franklin <jacktfranklin@chromium.org> Commit-Queue: Tim Van der Lippe <tvanderlippe@chromium.org>
272 lines
6.1 KiB
JavaScript
272 lines
6.1 KiB
JavaScript
'use strict';
|
||
|
||
const path = require('path');
|
||
const stringWidth = require('string-width');
|
||
const table = require('table');
|
||
const { yellow, dim, underline, blue, red, green } = require('picocolors');
|
||
|
||
const MARGIN_WIDTHS = 9;
|
||
|
||
/**
|
||
* @param {string} s
|
||
* @returns {string}
|
||
*/
|
||
function nope(s) {
|
||
return s;
|
||
}
|
||
|
||
const levelColors = {
|
||
info: blue,
|
||
warning: yellow,
|
||
error: red,
|
||
success: nope,
|
||
};
|
||
|
||
const symbols = {
|
||
info: blue('ℹ'),
|
||
warning: yellow('⚠'),
|
||
error: red('✖'),
|
||
success: green('✔'),
|
||
};
|
||
|
||
/**
|
||
* @param {import('stylelint').LintResult[]} results
|
||
* @returns {string}
|
||
*/
|
||
function deprecationsFormatter(results) {
|
||
const allDeprecationWarnings = results.flatMap((result) => result.deprecations);
|
||
|
||
if (allDeprecationWarnings.length === 0) {
|
||
return '';
|
||
}
|
||
|
||
const seenText = new Set();
|
||
|
||
return allDeprecationWarnings.reduce((output, warning) => {
|
||
if (seenText.has(warning.text)) return output;
|
||
|
||
seenText.add(warning.text);
|
||
|
||
output += yellow('Deprecation Warning: ');
|
||
output += warning.text;
|
||
|
||
if (warning.reference) {
|
||
output += dim(' See: ');
|
||
output += dim(underline(warning.reference));
|
||
}
|
||
|
||
return `${output}\n`;
|
||
}, '\n');
|
||
}
|
||
|
||
/**
|
||
* @param {import('stylelint').LintResult[]} results
|
||
* @return {string}
|
||
*/
|
||
function invalidOptionsFormatter(results) {
|
||
const allInvalidOptionWarnings = results.flatMap((result) =>
|
||
result.invalidOptionWarnings.map((warning) => warning.text),
|
||
);
|
||
const uniqueInvalidOptionWarnings = [...new Set(allInvalidOptionWarnings)];
|
||
|
||
return uniqueInvalidOptionWarnings.reduce((output, warning) => {
|
||
output += red('Invalid Option: ');
|
||
output += warning;
|
||
|
||
return `${output}\n`;
|
||
}, '\n');
|
||
}
|
||
|
||
/**
|
||
* @param {string} fromValue
|
||
* @param {string} cwd
|
||
* @return {string}
|
||
*/
|
||
function logFrom(fromValue, cwd) {
|
||
if (fromValue.startsWith('<')) return fromValue;
|
||
|
||
return path.relative(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').Warning[]} messages
|
||
* @param {string} source
|
||
* @param {string} cwd
|
||
* @return {string}
|
||
*/
|
||
function formatter(messages, source, cwd) {
|
||
if (!messages.length) return '';
|
||
|
||
const orderedMessages = [...messages].sort((a, b) => {
|
||
// positionless first
|
||
if (!a.line && b.line) return -1;
|
||
|
||
// positionless first
|
||
if (a.line && !b.line) return 1;
|
||
|
||
if (a.line < b.line) return -1;
|
||
|
||
if (a.line > b.line) return 1;
|
||
|
||
if (a.column < b.column) return -1;
|
||
|
||
if (a.column > b.column) return 1;
|
||
|
||
return 0;
|
||
});
|
||
|
||
/**
|
||
* 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 += `${underline(logFrom(source, cwd))}\n`;
|
||
}
|
||
|
||
/**
|
||
* @param {import('stylelint').Warning} message
|
||
* @return {string}
|
||
*/
|
||
function formatMessageText(message) {
|
||
let result = message.text;
|
||
|
||
result = result
|
||
// Remove all control characters (newline, tab and etc)
|
||
.replace(/[\u0001-\u001A]+/g, ' ') // eslint-disable-line no-control-regex
|
||
.replace(/\.$/, '');
|
||
|
||
const ruleString = ` (${message.rule})`;
|
||
|
||
if (result.endsWith(ruleString)) {
|
||
result = result.slice(0, result.lastIndexOf(ruleString));
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
const cleanedMessages = orderedMessages.map((message) => {
|
||
const { line, column, severity } = message;
|
||
/**
|
||
* @type {[string, string, string, string, string]}
|
||
*/
|
||
const row = [
|
||
line ? line.toString() : '',
|
||
column ? column.toString() : '',
|
||
symbols[severity] ? levelColors[severity](symbols[severity]) : severity,
|
||
formatMessageText(message),
|
||
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) => dim(`${p1}:${p2}`)),
|
||
)
|
||
.join('\n');
|
||
|
||
return output;
|
||
}
|
||
|
||
/**
|
||
* @type {import('stylelint').Formatter}
|
||
*/
|
||
module.exports = function (results, returnValue) {
|
||
let output = invalidOptionsFormatter(results);
|
||
|
||
output += deprecationsFormatter(results);
|
||
|
||
output = results.reduce((accum, result) => {
|
||
// Treat parseErrors as warnings
|
||
if (result.parseErrors) {
|
||
for (const error of result.parseErrors)
|
||
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 || '',
|
||
(returnValue && returnValue.cwd) || process.cwd(),
|
||
);
|
||
|
||
return accum;
|
||
}, output);
|
||
|
||
// Ensure consistent padding
|
||
output = output.trim();
|
||
|
||
if (output !== '') {
|
||
output = `\n${output}\n\n`;
|
||
}
|
||
|
||
return output;
|
||
};
|