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

15 changes: 15 additions & 0 deletions lib/reference/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,22 @@ const camelCaseFunctions = new Set([

const mathFunctions = new Set(['calc', 'clamp', 'max', 'min']);

const colorFunctions = new Set([
'rgb',
'rgba',
'hsl',
'hsla',
'hwb',
'lab',
'lch',
'oklab',
'oklch',
'color',
'color-mix',
ybiquitous marked this conversation as resolved.
Show resolved Hide resolved
]);
Mouvedia marked this conversation as resolved.
Show resolved Hide resolved

module.exports = {
camelCaseFunctions,
mathFunctions,
colorFunctions,
romainmenke marked this conversation as resolved.
Show resolved Hide resolved
};
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,
};
14 changes: 10 additions & 4 deletions lib/rules/color-function-notation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ 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 = /(?:rgba?|hsla?)\(/i;
romainmenke marked this conversation as resolved.
Show resolved Hide resolved
const LEGACY_NOTATION_FUNCTION_NAME = /^(?:rgba?|hsla?)$/i;
const COMMA = /,/;
Fixed Show fixed Hide fixed

/** @type {import('stylelint').Rule} */
const rule = (primary, secondaryOptions, context) => {
Expand All @@ -50,6 +52,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' && !COMMA.test(decl.value)) return;
romainmenke marked this conversation as resolved.
Show resolved Hide resolved

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

Expand All @@ -64,7 +70,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,7 +99,7 @@ const rule = (primary, secondaryOptions, context) => {
});

// Remove trailing 'a' from legacy function name
if (LEGACY_FUNCS.has(node.value.toLowerCase())) {
if (LEGACY_FUNCTION_NAME.test(node.value)) {
node.value = node.value.slice(0, -1);
romainmenke marked this conversation as resolved.
Show resolved Hide resolved
}

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
23 changes: 20 additions & 3 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 = /gray\(/i;

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

if (primary === 'never' && !hasNamedColor(decl.value)) {
romainmenke marked this conversation as resolved.
Show resolved Hide resolved
return;
}

if (
primary === 'always-where-possible' &&
!hasValidHex(decl.value) &&
!hasColorFunction(decl.value) &&
!HAS_GRAY_FUNCTION.test(decl.value)
) {
return;
}

valueParser(decl.value).walk((node) => {
const value = node.value;
const type = node.type;
Expand All @@ -86,9 +105,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