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..0eff2d9189 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,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;