Skip to content

Commit

Permalink
Fix false negatives for css-in-js object notation in color-hex-length (
Browse files Browse the repository at this point in the history
…#5106)

* Use postcss-value-parser for color-hex-length

`style-search` returns false positives when used with `css-in-js` object
notation. Continuing on the change made in #5101, this updates
`color-hex-length` to switch to using `postcss-value-parser`.

Unit tests have also been added to check the `css-in-js` syntax for this
rule.

* Remove unnecessary return
  • Loading branch information
Dru89 committed Jan 19, 2021
1 parent 753cafe commit dcaac08
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 34 deletions.
192 changes: 192 additions & 0 deletions lib/rules/color-hex-length/__tests__/index.js
Expand Up @@ -186,3 +186,195 @@ testRule(
],
}),
);

testRule({
ruleName,
config: ['short'],
syntax: 'css-in-js',
fix: true,

accept: [
{
code: `
export const s = styled.a({
color: "#aaa",
});
`,
},
{
code: `
export const s = styled.a({
color: "#ababab",
});
`,
},
{
code: `
export const s = styled.a({
stroke: "url(#aaaaaa)",
});
`,
description: 'href with location',
},
{
code: `
export const s = styled.a({
color: "darkred",
});
`,
},
{
code: `
export const s = styled.a({
background: "linear-gradient(#aaa, #ffff, #01234567)",
});
`,
},
{
code: `
export const s = styled("a::before")({
content: '"#aabbcc"',
});
`,
},
{
code: `
export const s = styled.a({
color: "white /* #ffffff */",
});
`,
},
],

reject: [
{
code: `
export const s = styled.a({
color: "#aaaaaa",
});
`,
fixed: `
export const s = styled.a({
color: "#aaa",
});
`,

message: messages.expected('#aaaaaa', '#aaa'),
line: 3,
column: 13,
},
{
code: `
export const s = styled.a({
background: "linear-gradient(#aabbcc, #0000ffcc)",
});
`,
fixed: `
export const s = styled.a({
background: "linear-gradient(#abc, #00fc)",
});
`,

warnings: [
{ message: messages.expected('#aabbcc', '#abc'), line: 3, column: 34 },
{ message: messages.expected('#0000ffcc', '#00fc'), line: 3, column: 43 },
],
},
],
});

testRule({
ruleName,
config: ['long'],
syntax: 'css-in-js',
fix: true,

accept: [
{
code: `
export const s = styled.a({
color: "#aaaaaa",
});
`,
},
{
code: `
export const s = styled.a({
color: "#abababab",
});
`,
},
{
code: `
export const s = styled.a({
stroke: "url(#aaa)",
});
`,
description: 'href with location',
},
{
code: `
export const s = styled.a({
color: "pink",
});
`,
},
{
code: `
export const s = styled.a({
background: "linear-gradient(#aaaaaa, #ffffffff, #01234567)",
});
`,
},
{
code: `
export const s = styled("a::before")({
content: '"#abc"',
});
`,
},
{
code: `
export const s = styled.a({
color: "white /* #fff */",
});
`,
},
],

reject: [
{
code: `
export const s = styled.a({
color: "#aaa",
});
`,
fixed: `
export const s = styled.a({
color: "#aaaaaa",
});
`,

message: messages.expected('#aaa', '#aaaaaa'),
line: 3,
column: 13,
},
{
code: `
export const s = styled.a({
background: "linear-gradient(#abc, #00fc)",
});
`,
fixed: `
export const s = styled.a({
background: "linear-gradient(#aabbcc, #0000ffcc)",
});
`,

warnings: [
{ message: messages.expected('#abc', '#aabbcc'), line: 3, column: 34 },
{ message: messages.expected('#00fc', '#0000ffcc'), line: 3, column: 40 },
],
},
],
});
65 changes: 31 additions & 34 deletions lib/rules/color-hex-length/index.js
Expand Up @@ -2,10 +2,11 @@

'use strict';

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

const declarationValueIndex = require('../../utils/declarationValueIndex');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const styleSearch = require('style-search');
const validateOptions = require('../../utils/validateOptions');

const ruleName = 'color-hex-length';
Expand All @@ -14,6 +15,9 @@ const messages = ruleMessages(ruleName, {
expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
});

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

function rule(expectation, _, context) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
Expand All @@ -26,17 +30,15 @@ function rule(expectation, _, context) {
}

root.walkDecls((decl) => {
const declString = blurFunctionArguments(decl.toString(), 'url');
const fixPositions = [];
const parsedValue = valueParser(getValue(decl));
let needsFix = false;

styleSearch({ source: declString, target: '#' }, (match) => {
const hexMatch = /^#[0-9A-Za-z]+/.exec(declString.substr(match.startIndex));
parsedValue.walk((node) => {
const { value: hexValue } = node;

if (!hexMatch) {
return;
}
if (isIgnoredFunction(node)) return false;

const hexValue = hexMatch[0];
if (!isHexColor(node)) return;

if (expectation === 'long' && hexValue.length !== 4 && hexValue.length !== 5) {
return;
Expand All @@ -50,37 +52,23 @@ function rule(expectation, _, context) {
const expectedHex = variant(hexValue);

if (context.fix) {
fixPositions.unshift({
expectedHex,
currentHex: hexValue,
startIndex: match.startIndex,
});
node.value = expectedHex;
needsFix = true;

return;
}

report({
message: messages.expected(hexValue, expectedHex),
node: decl,
index: match.startIndex,
index: declarationValueIndex(decl) + node.sourceIndex,
result,
ruleName,
});
});

if (fixPositions.length) {
const declProp = decl.prop;
const declBetween = decl.raws.between;

fixPositions.forEach((fixPosition) => {
// 1 — it's a # length
decl.value = replaceHex(
decl.value,
fixPosition.currentHex,
fixPosition.expectedHex,
fixPosition.startIndex - declProp.length - declBetween.length - 1,
);
});
if (needsFix) {
setValue(decl, parsedValue.toString());
}
});
};
Expand Down Expand Up @@ -117,12 +105,21 @@ function longer(hex) {
return hexVariant;
}

function replaceHex(input, searchString, replaceString, startIndex) {
const offset = startIndex + 1;
const stringStart = input.slice(0, offset);
const stringEnd = input.slice(offset + searchString.length);
function isIgnoredFunction({ type, value }) {
return type === 'function' && IGNORED_FUNCTIONS.has(value.toLowerCase());
}

function isHexColor({ type, value }) {
return type === 'word' && HEX.test(value);
}

function getValue(decl) {
return decl.raws.value ? decl.raws.value.raw : decl.value;
}

return stringStart + replaceString + stringEnd;
function setValue(decl, value) {
if (decl.raws.value) decl.raws.value.raw = value;
else decl.value = value;
}

rule.ruleName = ruleName;
Expand Down

0 comments on commit dcaac08

Please sign in to comment.