stylelint
Version:
A mighty, modern CSS linter.
170 lines (137 loc) • 4.37 kB
JavaScript
// @ts-nocheck
;
const _ = require('lodash');
const declarationValueIndex = require('../../utils/declarationValueIndex');
const isStandardSyntaxFunction = require('../../utils/isStandardSyntaxFunction');
const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
const keywordSets = require('../../reference/keywordSets');
const namedColorDataHex = require('../../reference/namedColorData');
const optionsMatches = require('../../utils/optionsMatches');
const propertySets = require('../../reference/propertySets');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const valueParser = require('postcss-value-parser');
const generateColorFuncs = require('./generateColorFuncs');
const ruleName = 'color-named';
const messages = ruleMessages(ruleName, {
expected: (named, original) => `Expected "${original}" to be "${named}"`,
rejected: (named) => `Unexpected named color "${named}"`,
});
// Todo tested on case insensivity
const NODE_TYPES = ['word', 'function'];
function rule(expectation, options) {
return (root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{
actual: expectation,
possible: ['never', 'always-where-possible'],
},
{
actual: options,
possible: {
ignoreProperties: [_.isString, _.isRegExp],
ignore: ['inside-function'],
},
optional: true,
},
);
if (!validOptions) {
return;
}
const namedColors = Object.keys(namedColorDataHex);
const namedColorData = {};
namedColors.forEach((name) => {
const hex = namedColorDataHex[name];
namedColorData[name] = {
hex,
func: generateColorFuncs(hex[0]),
};
});
root.walkDecls((decl) => {
if (propertySets.acceptCustomIdents.has(decl.prop)) {
return;
}
// Return early if the property is to be ignored
if (optionsMatches(options, 'ignoreProperties', decl.prop)) {
return;
}
valueParser(decl.value).walk((node) => {
const value = node.value;
const type = node.type;
const sourceIndex = node.sourceIndex;
if (optionsMatches(options, '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.includes(type)) {
return;
}
// Check for named colors for "never" option
if (
expectation === 'never' &&
type === 'word' &&
namedColors.includes(value.toLowerCase())
) {
complain(messages.rejected(value), decl, declarationValueIndex(decl) + sourceIndex);
return;
}
// Check "always-where-possible" option ...
if (expectation !== 'always-where-possible') {
return;
}
// First by checking for alternative color function representations ...
if (type === 'function' && keywordSets.colorFunctionNames.has(value.toLowerCase())) {
// Remove all spaces to match what's in `representations`
const normalizedFunctionString = valueParser.stringify(node).replace(/\s+/g, '');
let namedColor;
for (let i = 0, l = namedColors.length; i < l; i++) {
namedColor = namedColors[i];
if (namedColorData[namedColor].func.includes(normalizedFunctionString.toLowerCase())) {
complain(
messages.expected(namedColor, normalizedFunctionString),
decl,
declarationValueIndex(decl) + sourceIndex,
);
return; // Exit as soon as a problem is found
}
}
return;
}
// Then by checking for alternative hex representations
let namedColor;
for (let i = 0, l = namedColors.length; i < l; i++) {
namedColor = namedColors[i];
if (namedColorData[namedColor].hex.includes(value.toLowerCase())) {
complain(
messages.expected(namedColor, value),
decl,
declarationValueIndex(decl) + sourceIndex,
);
return; // Exit as soon as a problem is found
}
}
});
});
function complain(message, node, index) {
report({
result,
ruleName,
message,
node,
index,
});
}
};
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;