Skip to content

Commit

Permalink
Fix false positives for hex colours in color-function-notation (#5650)
Browse files Browse the repository at this point in the history
  • Loading branch information
lachieh committed Oct 28, 2021
1 parent 6491946 commit b8a8d99
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 1 deletion.
3 changes: 3 additions & 0 deletions lib/rules/color-function-notation/__tests__/index.js
Expand Up @@ -378,6 +378,9 @@ testRule({
{
code: 'a { color: rgb($a $b $c / $d) }',
},
{
code: 'a { color: rgba(#fff, 0.85) }',
},
],

reject: [
Expand Down
6 changes: 5 additions & 1 deletion lib/rules/color-function-notation/index.js
Expand Up @@ -4,9 +4,11 @@ const valueParser = require('postcss-value-parser');

const declarationValueIndex = require('../../utils/declarationValueIndex');
const getDeclarationValue = require('../../utils/getDeclarationValue');
const isStandardSyntaxColorFunction = require('../../utils/isStandardSyntaxColorFunction');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const setDeclarationValue = require('../../utils/setDeclarationValue');
const { isValueFunction } = require('../../utils/typeGuards');
const validateOptions = require('../../utils/validateOptions');

const ruleName = 'color-function-notation';
Expand All @@ -33,7 +35,9 @@ const rule = (primary, _secondaryOptions, context) => {
const parsedValue = valueParser(getDeclarationValue(decl));

parsedValue.walk((node) => {
if (node.type !== 'function') return;
if (!isValueFunction(node)) return;

if (!isStandardSyntaxColorFunction(node)) return;

const { value, sourceIndex, nodes } = node;

Expand Down
69 changes: 69 additions & 0 deletions lib/utils/__tests__/isStandardSyntaxColorFunction.test.js
@@ -0,0 +1,69 @@
'use strict';

const isStandardSyntaxColorFunction = require('../isStandardSyntaxColorFunction');
const postcss = require('postcss');
const valueParser = require('postcss-value-parser');

describe('isStandardSyntaxColorFunction', () => {
it('legacy syntax', () => {
expect(isStandardSyntaxColorFunction(func('a { color: rgb(0, 0, 0) }'))).toBe(true);
});

it('legacy syntax alpha', () => {
expect(isStandardSyntaxColorFunction(func('a { color: rgba(0, 0, 0, 0) }'))).toBe(true);
});

it('modern syntax', () => {
expect(isStandardSyntaxColorFunction(func('a { color: rgba(0 0 0) }'))).toBe(true);
});

it('modern syntax alpha', () => {
expect(isStandardSyntaxColorFunction(func('a { color: rgba(0 0 0 / 50%) }'))).toBe(true);
});

it('space', () => {
expect(isStandardSyntaxColorFunction(func('a { color: rgba( 0 0 0 / 50% ) }'))).toBe(true);
});

it('comment', () => {
expect(isStandardSyntaxColorFunction(func('a { color: rgba(/* #aaa */ 0 0 0 / 50%) }'))).toBe(
true,
);
});

it('custom property', () => {
expect(isStandardSyntaxColorFunction(func('a { color: rgba(var(--red) 0 0 / 50%) }'))).toBe(
true,
);
});

it('function', () => {
expect(
isStandardSyntaxColorFunction(func('a { color: rgba(calc(var(--red) + 50) 0 0 / 50%) }')),
).toBe(true);
});

it('scss color function conversion', () => {
expect(isStandardSyntaxColorFunction(func('a { color: rbga(#aaa, 0.5) }'))).toBe(false);
});

it('scss color function conversion with comment', () => {
expect(isStandardSyntaxColorFunction(func('a { color: rbga(/* comment */ #aaa, 0.5) }'))).toBe(
false,
);
});
});

function func(css) {
const functions = [];

postcss.parse(css).walkDecls((decl) => {
valueParser(decl.value).walk((valueNode) => {
if (valueNode.type === 'function') {
functions.push(valueNode);
}
});
});

return functions[0];
}
20 changes: 20 additions & 0 deletions lib/utils/isStandardSyntaxColorFunction.js
@@ -0,0 +1,20 @@
'use strict';

const isStandardSyntaxFunction = require('./isStandardSyntaxFunction');

/**
* Check whether a function is standard syntax color function
*
* @param {import('postcss-value-parser').FunctionNode} node
* @returns {boolean}
*/
module.exports = function isStandardSyntaxColorFunction(node) {
if (!isStandardSyntaxFunction(node)) return false;

// scss rgba() function can accept a hex as the first param
for (const fnNode of node.nodes) {
if (fnNode.type === 'word' && fnNode.value.startsWith('#')) return false;
}

return true;
};
8 changes: 8 additions & 0 deletions lib/utils/typeGuards.js
Expand Up @@ -43,6 +43,14 @@ module.exports.isDeclaration = function isDeclaration(node) {
return node.type === 'decl';
};

/**
* @param {import('postcss-value-parser').Node} node
* @returns {node is import('postcss-value-parser').FunctionNode}
*/
module.exports.isValueFunction = function isValueFunction(node) {
return node.type === 'function';
};

/**
* @param {Node} node
* @returns {node is (Node & {source: NodeSource})}
Expand Down

0 comments on commit b8a8d99

Please sign in to comment.