stylelint
Version:
A mighty CSS linter that helps you avoid errors and enforce conventions.
192 lines (160 loc) • 5.07 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).
;
const valueParser = require('postcss-value-parser');
const validateTypes = require('../../utils/validateTypes.cjs');
const properties = require('../../reference/properties.cjs');
const colordUtils = require('./colordUtils.cjs');
const nodeFieldIndices = require('../../utils/nodeFieldIndices.cjs');
const hasColorFunction = require('../../utils/hasColorFunction.cjs');
const hasNamedColor = require('../../utils/hasNamedColor.cjs');
const hasValidHex = require('../../utils/hasValidHex.cjs');
const isStandardSyntaxFunction = require('../../utils/isStandardSyntaxFunction.cjs');
const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue.cjs');
const keywords = require('../../reference/keywords.cjs');
const optionsMatches = require('../../utils/optionsMatches.cjs');
const report = require('../../utils/report.cjs');
const ruleMessages = require('../../utils/ruleMessages.cjs');
const validateOptions = require('../../utils/validateOptions.cjs');
const ruleName = 'color-named';
const messages = ruleMessages(ruleName, {
expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
rejected: (keyword) => `Unexpected named color "${keyword}"`,
});
const meta = {
url: 'https://stylelint.io/user-guide/rules/color-named',
};
// Todo tested on case insensitivity
const NODE_TYPES = new Set(['word', 'function']);
const HAS_GRAY_FUNCTION = /\bgray\(/i;
/** @type {import('stylelint').CoreRules[ruleName]} */
const rule = (primary, secondaryOptions) => {
return (root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{
actual: primary,
possible: ['never', 'always-where-possible'],
},
{
actual: secondaryOptions,
possible: {
ignoreProperties: [validateTypes.isString, validateTypes.isRegExp],
ignore: ['inside-function'],
},
optional: true,
},
);
if (!validOptions) {
return;
}
root.walkDecls((decl) => {
if (properties.acceptCustomIdentsProperties.has(decl.prop)) {
return;
}
// Return early if the property is to be ignored
if (optionsMatches(secondaryOptions, 'ignoreProperties', decl.prop)) {
return;
}
const { value: declValue } = decl;
if (primary === 'never' && !hasNamedColor(declValue)) {
return;
}
if (
primary === 'always-where-possible' &&
!hasValidHex(declValue) &&
!hasColorFunction(declValue) &&
!HAS_GRAY_FUNCTION.test(declValue)
) {
return;
}
valueParser(declValue).walk((node) => {
const value = node.value;
const type = node.type;
const sourceIndex = node.sourceIndex;
if (optionsMatches(secondaryOptions, 'ignore', 'inside-function') && type === 'function') {
return false;
}
if (!isStandardSyntaxFunction(node)) {
return false;
}
if (!isStandardSyntaxValue(value)) {
return;
}
// Return early if neither a word nor a function
if (!NODE_TYPES.has(type)) {
return;
}
// Check for named colors for "never" option
if (
primary === 'never' &&
type === 'word' &&
keywords.namedColorsKeywords.has(value.toLowerCase())
) {
complain(
messages.rejected,
[value],
decl,
nodeFieldIndices.declarationValueIndex(decl) + sourceIndex,
value.length,
);
return;
}
// Check "always-where-possible" option ...
if (primary !== 'always-where-possible') {
return;
}
let rawColorString = null;
let colorString = null;
if (type === 'function') {
rawColorString = valueParser.stringify(node);
// First by checking for alternative color function representations ...
// Remove all spaces to match what's in `representations`
colorString = rawColorString.replace(/\s*([,/()])\s*/g, '$1').replace(/\s{2,}/g, ' ');
} else if (type === 'word' && value.startsWith('#')) {
// Then by checking for alternative hex representations
rawColorString = colorString = value;
} else {
return;
}
const color = colordUtils.colord(colorString);
if (!color.isValid()) {
return;
}
const namedColor = color.toName();
if (namedColor && namedColor.toLowerCase() !== 'transparent') {
complain(
messages.expected,
[colorString, namedColor],
decl,
nodeFieldIndices.declarationValueIndex(decl) + sourceIndex,
rawColorString.length,
);
}
});
});
/**
* @param {typeof messages[keyof messages]} message
* @param {string[]} messageArgs
* @param {import('postcss').Node} node
* @param {number} index
* @param {number} length
*/
function complain(message, messageArgs, node, index, length) {
report({
result,
ruleName,
message,
messageArgs,
node,
index,
endIndex: index + length,
});
}
};
};
rule.ruleName = ruleName;
rule.messages = messages;
rule.meta = meta;
module.exports = rule;