Skip to content

Commit

Permalink
Fix color-* performance (#6868)
Browse files Browse the repository at this point in the history
* Fix `color-*` performance

* cleanup

* more keywords and function names to references

* Update lib/rules/color-function-notation/index.js

Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com>

* fixes

* abc

* Create young-mangos-sin.md

---------

Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com>
  • Loading branch information
romainmenke and ybiquitous committed May 29, 2023
1 parent c81e63f commit 76eb8e7
Show file tree
Hide file tree
Showing 21 changed files with 308 additions and 29 deletions.
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;
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

0 comments on commit 76eb8e7

Please sign in to comment.