stylelint
Version:
A mighty CSS linter that helps you avoid errors and enforce conventions.
202 lines (163 loc) • 5.53 kB
JavaScript
// NOTICE: This file is generated by Rollup. To modify it,
// please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
'use strict';
const validateTypes = require('./validateTypes.cjs');
const constants = require('../constants.cjs');
/** @import { DisabledRangeObject, Problem, Range, RuleMessage, StylelintPostcssResult, Utils, WarningOptions } from 'stylelint' */
/**
* Report a problem.
*
* This function accounts for `disabledRanges` attached to the result.
* That is, if the reported problem is within a disabledRange,
* it is ignored. Otherwise, it is attached to the result as a
* postcss warning.
*
* It also accounts for the rule's severity.
*
* You *must* pass *either* a node or a line number.
*
* @type {Utils['report']}
*/
function report(problem) {
const { node, index, endIndex, line, start, end, result, ruleName, word, fix, ...rest } = problem;
const {
disabledRanges,
quiet,
ruleSeverities,
config: { defaultSeverity, ignoreDisables } = {},
customMessages: { [ruleName]: message = rest.message },
customUrls: { [ruleName]: customUrl },
ruleMetadata: { [ruleName]: metadata },
} = result.stylelint;
const { messageArgs = [], severity = ruleSeverities[ruleName] } = rest;
const ruleSeverity =
(validateTypes.isFunction(severity) ? severity(...messageArgs) : severity) ?? defaultSeverity ?? constants.DEFAULT_SEVERITY;
// In quiet mode, mere warnings are ignored
if (quiet && ruleSeverity === constants.SEVERITY_WARNING) return;
if (validateTypes.isFunction(fix) && metadata && !metadata.fixable) {
throw new Error(
`The "${ruleName}" rule requires "meta.fixable" to be truthy if the "fix" callback is being passed`,
);
}
const range = node?.rangeBy({ index, endIndex }) ?? {};
// If a line is not passed, use the node.rangeBy method to get the
// line number that the complaint pertains to
const startLine = line ?? range.start?.line;
if (!startLine) {
throw new Error(
`The "${ruleName}" rule failed to pass either a node or a line number to the \`report()\` function.`,
);
}
if (isFixApplied({ ...problem, line: startLine })) return;
if (isDisabled(ruleName, startLine, disabledRanges)) {
// Collect disabled warnings
// Used to report `needlessDisables` in subsequent processing.
const disabledWarnings = (result.stylelint.disabledWarnings ||= []);
disabledWarnings.push({
rule: ruleName,
line: startLine,
});
if (!ignoreDisables) return;
}
if (!result.stylelint.stylelintError && ruleSeverity === constants.SEVERITY_ERROR) {
result.stylelint.stylelintError = true;
}
if (!result.stylelint.stylelintWarning && ruleSeverity === constants.SEVERITY_WARNING) {
result.stylelint.stylelintWarning = true;
}
/** @type {WarningOptions} */
const warningProperties = {
severity: ruleSeverity,
rule: ruleName,
};
if (node) {
warningProperties.node = node;
}
if (start) {
warningProperties.start = start;
} else if (validateTypes.isNumber(index)) {
warningProperties.index = index;
}
if (end) {
warningProperties.end = end;
} else if (validateTypes.isNumber(endIndex)) {
warningProperties.endIndex = endIndex;
}
if (word) {
warningProperties.word = word;
}
if (customUrl) {
warningProperties.url = customUrl;
}
const warningMessage = buildWarningMessage(message, messageArgs);
result.warn(warningMessage, warningProperties);
}
/**
* @param {RuleMessage} message
* @param {NonNullable<Problem['messageArgs']>} messageArgs
* @returns {string}
*/
function buildWarningMessage(message, messageArgs) {
if (validateTypes.isString(message)) {
return printfLike(message, ...messageArgs);
}
return message(...messageArgs);
}
/**
* @param {string} format
* @param {Array<unknown>} args
* @returns {string}
*/
function printfLike(format, ...args) {
return args.reduce((/** @type {string} */ result, arg) => {
return result.replace(/%[ds]/, String(arg));
}, format);
}
/**
* @param {string} ruleName
* @param {number} startLine
* @param {DisabledRangeObject} disabledRanges
*/
function isDisabled(ruleName, startLine, disabledRanges) {
const ranges = disabledRanges[ruleName] ?? disabledRanges[constants.RULE_NAME_ALL] ?? [];
for (const range of ranges) {
if (
// If the problem is within a disabledRange,
// and that disabledRange's rules include this one
range.start <= startLine &&
(range.end === undefined || range.end >= startLine) &&
(!range.rules || range.rules.includes(ruleName))
) {
return true;
}
}
return false;
}
/** @param {Problem & { line: number }} problem */
function isFixApplied({ fix, line, result: { stylelint }, ruleName }) {
const { disabledRanges, config = {}, fixersData } = stylelint;
if (!validateTypes.isFunction(fix)) {
addFixData({ fixersData, ruleName, fixed: false });
return false;
}
const shouldFix = Boolean(config.fix && !config.rules?.[ruleName][1]?.disableFix);
const mayFix =
shouldFix && (config.ignoreDisables || !isDisabled(ruleName, line, disabledRanges));
addFixData({ fixersData, ruleName, fixed: mayFix });
if (!mayFix) return false;
fix();
return true;
}
/**
* @param {object} o
* @param {StylelintPostcssResult['fixersData']} o.fixersData
* @param {string} o.ruleName
* @param {Range} [o.range]
* @param {boolean} o.fixed
* @todo stylelint/stylelint#7192
*/
function addFixData({ fixersData, ruleName, range, fixed }) {
const ruleFixers = (fixersData[ruleName] ??= []);
ruleFixers.push({ range, fixed });
}
module.exports = report;