Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix color-* performance #6868

5 changes: 5 additions & 0 deletions .changeset/young-mangos-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"stylelint": patch
---

Fixed: `color-*` performance
15 changes: 15 additions & 0 deletions lib/reference/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ const camelCaseFunctions = new Set([
'skewY',
]);

const colorFunctions = new Set([
'color',
'color-mix',
'hsl',
'hsla',
'hwb',
'lab',
'lch',
'oklab',
'oklch',
'rgb',
'rgba',
]);

const mathFunctions = new Set([
'abs',
'acos',
Expand All @@ -40,5 +54,6 @@ const mathFunctions = new Set([

module.exports = {
camelCaseFunctions,
colorFunctions,
mathFunctions,
};
153 changes: 153 additions & 0 deletions lib/reference/keywords.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,158 @@ const systemColorsKeywords = new Set([
'visitedtext',
]);

const namedColorsKeywords = new Set([
// https://www.w3.org/TR/css-color-4/#named-colors
'aliceblue',
'antiquewhite',
'aqua',
'aquamarine',
'azure',
'beige',
'bisque',
'black',
'blanchedalmond',
'blue',
'blueviolet',
'brown',
'burlywood',
'cadetblue',
'chartreuse',
'chocolate',
'coral',
'cornflowerblue',
'cornsilk',
'crimson',
'cyan',
'darkblue',
'darkcyan',
'darkgoldenrod',
'darkgray',
'darkgreen',
'darkgrey',
'darkkhaki',
'darkmagenta',
'darkolivegreen',
'darkorange',
'darkorchid',
'darkred',
'darksalmon',
'darkseagreen',
'darkslateblue',
'darkslategray',
'darkslategrey',
'darkturquoise',
'darkviolet',
'deeppink',
'deepskyblue',
'dimgray',
'dimgrey',
'dodgerblue',
'firebrick',
'floralwhite',
'forestgreen',
'fuchsia',
'gainsboro',
'ghostwhite',
'gold',
'goldenrod',
'gray',
'green',
'greenyellow',
'grey',
'honeydew',
'hotpink',
'indianred',
'indigo',
'ivory',
'khaki',
'lavender',
'lavenderblush',
'lawngreen',
'lemonchiffon',
'lightblue',
'lightcoral',
'lightcyan',
'lightgoldenrodyellow',
'lightgray',
'lightgreen',
'lightgrey',
'lightpink',
'lightsalmon',
'lightseagreen',
'lightskyblue',
'lightslategray',
'lightslategrey',
'lightsteelblue',
'lightyellow',
'lime',
'limegreen',
'linen',
'magenta',
'maroon',
'mediumaquamarine',
'mediumblue',
'mediumorchid',
'mediumpurple',
'mediumseagreen',
'mediumslateblue',
'mediumspringgreen',
'mediumturquoise',
'mediumvioletred',
'midnightblue',
'mintcream',
'mistyrose',
'moccasin',
'navajowhite',
'navy',
'oldlace',
'olive',
'olivedrab',
'orange',
'orangered',
'orchid',
'palegoldenrod',
'palegreen',
'paleturquoise',
'palevioletred',
'papayawhip',
'peachpuff',
'peru',
'pink',
'plum',
'powderblue',
'purple',
'rebeccapurple',
'red',
'rosybrown',
'royalblue',
'saddlebrown',
'salmon',
'sandybrown',
'seagreen',
'seashell',
'sienna',
'silver',
'skyblue',
'slateblue',
'slategray',
'slategrey',
'snow',
'springgreen',
'steelblue',
'tan',
'teal',
'thistle',
'tomato',
'turquoise',
'violet',
'wheat',
'white',
'whitesmoke',
'yellow',
'yellowgreen',
]);

module.exports = {
animationNameKeywords,
animationShorthandKeywords,
Expand All @@ -347,6 +499,7 @@ module.exports = {
listStylePositionKeywords,
listStyleShorthandKeywords,
listStyleTypeKeywords,
namedColorsKeywords,
systemColorsKeywords,
systemFontKeywords,
};
15 changes: 10 additions & 5 deletions lib/rules/color-function-notation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ const meta = {
fixable: true,
};

const LEGACY_FUNCS = new Set(['rgba', 'hsla']);
const LEGACY_NOTATION_FUNCS = new Set(['rgb', 'rgba', 'hsl', 'hsla']);
const LEGACY_FUNCTION_NAME = /^(?:rgba|hsla)$/i;
const LEGACY_NOTATION_FUNCTION = /\b(?:rgba?|hsla?)\(/i;
const LEGACY_NOTATION_FUNCTION_NAME = /^(?:rgba?|hsla?)$/i;

/** @type {import('stylelint').Rule} */
const rule = (primary, secondaryOptions, context) => {
Expand All @@ -50,6 +51,10 @@ const rule = (primary, secondaryOptions, context) => {
const ignoreWithVarInside = optionsMatches(secondaryOptions, 'ignore', 'with-var-inside');

root.walkDecls((decl) => {
if (!LEGACY_NOTATION_FUNCTION.test(decl.value)) return;

if (primary === 'modern' && !decl.value.includes(',')) return;

let needsFix = false;
const parsedValue = valueParser(getDeclarationValue(decl));

Expand All @@ -64,7 +69,7 @@ const rule = (primary, secondaryOptions, context) => {
return;
}

if (!LEGACY_NOTATION_FUNCS.has(value.toLowerCase())) return;
if (!LEGACY_NOTATION_FUNCTION_NAME.test(value)) return;

if (primary === 'modern' && !hasCommas(node)) return;

Expand Down Expand Up @@ -93,8 +98,8 @@ const rule = (primary, secondaryOptions, context) => {
});

// Remove trailing 'a' from legacy function name
if (LEGACY_FUNCS.has(node.value.toLowerCase())) {
node.value = node.value.slice(0, -1);
if (LEGACY_FUNCTION_NAME.test(value)) {
node.value = value.slice(0, -1);
}

needsFix = true;
Expand Down
8 changes: 5 additions & 3 deletions lib/rules/color-hex-alpha/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict';

const declarationValueIndex = require('../../utils/declarationValueIndex');
const hasValidHex = require('../../utils/hasValidHex');
const isValidHex = require('../../utils/isValidHex');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
Expand All @@ -17,8 +19,6 @@ const meta = {
url: 'https://stylelint.io/user-guide/rules/color-hex-alpha',
};

const HEX = /^#(?:[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/i;

/** @type {import('stylelint').Rule} */
const rule = (primary) => {
return (root, result) => {
Expand All @@ -30,6 +30,8 @@ const rule = (primary) => {
if (!validOptions) return;

root.walkDecls((decl) => {
if (!hasValidHex(decl.value)) return;

const parsedValue = valueParser(decl.value);

parsedValue.walk((node) => {
Expand Down Expand Up @@ -71,7 +73,7 @@ function isUrlFunction({ type, value }) {
* @param {import('postcss-value-parser').Node} node
*/
function isHexColor({ type, value }) {
return type === 'word' && HEX.test(value);
return type === 'word' && isValidHex(value);
}

/**
Expand Down
5 changes: 4 additions & 1 deletion lib/rules/color-hex-case/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ const meta = {
deprecated: true,
};

const HEX = /^#[0-9A-Za-z]+/;
const HEX = /^#[\da-z]+/i;
const CONTAINS_HEX = /#[\da-z]+/i;
ybiquitous marked this conversation as resolved.
Show resolved Hide resolved
const IGNORED_FUNCTIONS = new Set(['url']);

/** @type {import('stylelint').Rule} */
Expand All @@ -37,6 +38,8 @@ const rule = (primary, _secondaryOptions, context) => {
}

root.walkDecls((decl) => {
if (!CONTAINS_HEX.test(decl.value)) return;

const parsedValue = valueParser(getDeclarationValue(decl));
let needsFix = false;

Expand Down
5 changes: 4 additions & 1 deletion lib/rules/color-hex-length/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ const meta = {
fixable: true,
};

const HEX = /^#[0-9A-Za-z]+/;
const HEX = /^#[\da-z]+$/i;
const CONTAINS_HEX = /#[\da-z]+/i;
const IGNORED_FUNCTIONS = new Set(['url']);

/** @type {import('stylelint').Rule} */
Expand All @@ -36,6 +37,8 @@ const rule = (primary, _secondaryOptions, context) => {
}

root.walkDecls((decl) => {
if (!CONTAINS_HEX.test(decl.value)) return;

const parsedValue = valueParser(getDeclarationValue(decl));
let needsFix = false;

Expand Down
27 changes: 23 additions & 4 deletions lib/rules/color-named/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const validateOptions = require('../../utils/validateOptions');
const valueParser = require('postcss-value-parser');
const { isRegExp, isString } = require('../../utils/validateTypes');
const { colord } = require('./colordUtils');
const hasNamedColor = require('../../utils/hasNamedColor');
const hasValidHex = require('../../utils/hasValidHex');
const hasColorFunction = require('../../utils/hasColorFunction');
const { namedColorsKeywords } = require('../../reference/keywords');

const ruleName = 'color-named';

Expand All @@ -26,6 +30,8 @@ const meta = {
// Todo tested on case insensitivity
const NODE_TYPES = new Set(['word', 'function']);

const HAS_GRAY_FUNCTION = /\bgray\(/i;

/** @type {import('stylelint').Rule} */
const rule = (primary, secondaryOptions) => {
return (root, result) => {
Expand Down Expand Up @@ -60,7 +66,22 @@ const rule = (primary, secondaryOptions) => {
return;
}

valueParser(decl.value).walk((node) => {
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;
Expand All @@ -86,9 +107,7 @@ const rule = (primary, secondaryOptions) => {
if (
primary === 'never' &&
type === 'word' &&
/^[a-z]+$/iu.test(value) &&
value.toLowerCase() !== 'transparent' &&
colord(value).isValid()
namedColorsKeywords.has(value.toLowerCase())
) {
complain(
messages.rejected(value),
Expand Down
5 changes: 4 additions & 1 deletion lib/rules/color-no-hex/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const meta = {
url: 'https://stylelint.io/user-guide/rules/color-no-hex',
};

const HEX = /^#[0-9A-Za-z]+/;
const HEX = /^#[\da-z]+$/i;
const CONTAINS_HEX = /#[\da-z]+/i;
const IGNORED_FUNCTIONS = new Set(['url']);

/** @type {import('stylelint').Rule} */
Expand All @@ -31,6 +32,8 @@ const rule = (primary) => {
}

root.walkDecls((decl) => {
if (!CONTAINS_HEX.test(decl.value)) return;

const parsedValue = valueParser(getDeclarationValue(decl));

parsedValue.walk((node) => {
Expand Down