From 3b40b7e245a091160a1104eabd4194e06d7f4e41 Mon Sep 17 00:00:00 2001 From: Drew Hays Date: Mon, 11 Jan 2021 21:23:17 -0800 Subject: [PATCH 1/2] 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. --- lib/rules/color-hex-length/__tests__/index.js | 192 ++++++++++++++++++ lib/rules/color-hex-length/index.js | 67 +++--- 2 files changed, 225 insertions(+), 34 deletions(-) diff --git a/lib/rules/color-hex-length/__tests__/index.js b/lib/rules/color-hex-length/__tests__/index.js index 83287c85a2..505b0c40d6 100644 --- a/lib/rules/color-hex-length/__tests__/index.js +++ b/lib/rules/color-hex-length/__tests__/index.js @@ -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 }, + ], + }, + ], +}); diff --git a/lib/rules/color-hex-length/index.js b/lib/rules/color-hex-length/index.js index 1ffe6d25bb..a1d85e601f 100644 --- a/lib/rules/color-hex-length/index.js +++ b/lib/rules/color-hex-length/index.js @@ -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'; @@ -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, { @@ -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; @@ -50,11 +52,8 @@ 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; } @@ -62,25 +61,14 @@ function rule(expectation, _, context) { 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()); } }); }; @@ -117,12 +105,23 @@ 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; +} + +function setValue(decl, value) { + if (decl.raws.value) decl.raws.value.raw = value; + else decl.value = value; - return stringStart + replaceString + stringEnd; + return decl; } rule.ruleName = ruleName; From 5a2a78788ca3ea14218799d7a8fa451c99f6dbf3 Mon Sep 17 00:00:00 2001 From: Drew Hays Date: Mon, 18 Jan 2021 11:09:49 -0800 Subject: [PATCH 2/2] Remove unnecessary return --- lib/rules/color-hex-length/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/rules/color-hex-length/index.js b/lib/rules/color-hex-length/index.js index a1d85e601f..0eff2d9189 100644 --- a/lib/rules/color-hex-length/index.js +++ b/lib/rules/color-hex-length/index.js @@ -120,8 +120,6 @@ function getValue(decl) { function setValue(decl, value) { if (decl.raws.value) decl.raws.value.raw = value; else decl.value = value; - - return decl; } rule.ruleName = ruleName;