Skip to content

Commit cb73ab4

Browse files
authored
Adds color duplicates analysis (#55)
1 parent fc02953 commit cb73ab4

File tree

7 files changed

+245
-8
lines changed

7 files changed

+245
-8
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"path": "^0.12.7",
3636
"postcss": "^7.0.1",
3737
"postcss-values-parser": "^1.5.0",
38-
"specificity": "^0.4.0"
38+
"specificity": "^0.4.0",
39+
"tinycolor2": "^1.4.1"
3940
},
4041
"devDependencies": {
4142
"ava": "^0.25.0",

readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ analyze('foo {}')
158158
// colors: {
159159
// total: 0,
160160
// totalUnique: 0,
161-
// unique: []
161+
// unique: [],
162+
// duplicates: []
162163
// },
163164
// fontfamilies: {
164165
// total: 0,

src/analyzer/values/colors.js

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const valueParser = require('postcss-values-parser')
22
const cssColorNames = require('css-color-names')
3+
const tinycolor = require('tinycolor2')
34

45
const uniquer = require('../../utils/uniquer')
56

@@ -47,6 +48,81 @@ function extractColorsFromDeclaration(declaration) {
4748
return declaration
4849
}
4950

51+
const addCount = color => {
52+
return {
53+
...color,
54+
count: color.aliases.reduce((acc, curr) => {
55+
return acc + curr.count
56+
}, 0)
57+
}
58+
}
59+
60+
const addShortestNotation = color => {
61+
return {
62+
...color,
63+
value: [...color.aliases].sort((a, b) => {
64+
return a.value.length - b.value.length
65+
}).shift().value
66+
}
67+
}
68+
69+
const addAliases = (acc, curr) => {
70+
if (!acc[curr.key]) {
71+
acc[curr.key] = {
72+
key: curr.key,
73+
aliases: []
74+
}
75+
}
76+
77+
acc[curr.key] = {
78+
...acc[curr.key],
79+
aliases: [...acc[curr.key].aliases, curr]
80+
}
81+
82+
return acc
83+
}
84+
85+
const filterDuplicateColors = color => {
86+
// Filter out the actual duplicate colors
87+
return color.aliases.length > 1
88+
}
89+
90+
const validateColor = color => {
91+
return tinycolor(color.value).isValid()
92+
}
93+
94+
const normalizeColors = color => {
95+
// Add a normalized value
96+
return {
97+
...color,
98+
key: tinycolor(color.value).toHslString()
99+
}
100+
}
101+
102+
const rmTmpProps = color => {
103+
// Remove temporary props that were needed for analysis
104+
const {key, ...restColor} = color
105+
return {
106+
...restColor,
107+
aliases: color.aliases.map(alias => {
108+
const {key, ...restAlias} = alias
109+
return restAlias
110+
})
111+
}
112+
}
113+
114+
const withAliases = colors => Object
115+
.values(
116+
colors
117+
.filter(validateColor)
118+
.map(normalizeColors)
119+
.reduce(addAliases, {})
120+
)
121+
.filter(filterDuplicateColors)
122+
.map(addCount)
123+
.map(addShortestNotation)
124+
.map(rmTmpProps)
125+
50126
module.exports = declarations => {
51127
const all = declarations
52128
.map(extractColorsFromDeclaration)
@@ -55,9 +131,12 @@ module.exports = declarations => {
55131
.reduce((allColors, declarationColors) => {
56132
return [...allColors, ...declarationColors]
57133
}, [])
134+
const {totalUnique, unique} = uniquer(all)
58135

59136
return {
60137
total: all.length,
61-
...uniquer(all)
138+
unique,
139+
totalUnique,
140+
duplicates: withAliases(unique)
62141
}
63142
}

test/analyzer/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ test('Returns the correct analysis object structure', async t => {
141141
colors: {
142142
total: 0,
143143
totalUnique: 0,
144-
unique: []
144+
unique: [],
145+
duplicates: []
145146
},
146147
fontfamilies: {
147148
total: 0,

test/analyzer/values/input.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
color: hsl(270,60%,70%);
4949
color: hsl(270, 60%, 70%);
5050
color: hsl(270 60% 70%);
51+
/** tinycolor doesn't support these color formats, so they won't show up as aliases/duplicates */
5152
color: hsl(270deg, 60%, 70%);
5253
color: hsl(4.71239rad, 60%, 70%);
5354
color: hsl(.75turn, 60%, 70%);
@@ -57,6 +58,16 @@
5758
color: hsl(270, 60%, 50%, 15%);
5859
color: hsl(270 60% 50% / .15);
5960
color: hsl(270 60% 50% / 15%);
61+
62+
/* Duplicate colors */
63+
color: #000;
64+
color: #000000;
65+
color: black;
66+
color: black; /* duplicate */
67+
color: rgb(0,0,0);
68+
color: rgba(0,0,0,1);
69+
color: hsl(0,0,0);
70+
color: hsla(0,0,0,1);
6071
}
6172
.color-keyword {
6273
outline: 1px solid tomato;

test/analyzer/values/output.json

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"total": 83,
2+
"total": 91,
33
"fontfamilies": {
44
"total": 18,
55
"totalUnique": 12,
@@ -121,12 +121,20 @@
121121
"count": 1
122122
}
123123
],
124-
"share": 0.04819277108433735
124+
"share": 0.04395604395604396
125125
},
126126
"colors": {
127-
"total": 29,
128-
"totalUnique": 28,
127+
"total": 37,
128+
"totalUnique": 35,
129129
"unique": [
130+
{
131+
"value": "#000",
132+
"count": 1
133+
},
134+
{
135+
"value": "#000000",
136+
"count": 1
137+
},
130138
{
131139
"value": "#0000ff00",
132140
"count": 1
@@ -147,10 +155,18 @@
147155
"value": "Aqua",
148156
"count": 1
149157
},
158+
{
159+
"value": "black",
160+
"count": 2
161+
},
150162
{
151163
"value": "hsl(.75turn, 60%, 70%)",
152164
"count": 1
153165
},
166+
{
167+
"value": "hsl(0,0,0)",
168+
"count": 1
169+
},
154170
{
155171
"value": "hsl(100, 10%, 20%)",
156172
"count": 1
@@ -198,6 +214,10 @@
198214
"value": "hsl(4.71239rad, 60%, 70%)",
199215
"count": 1
200216
},
217+
{
218+
"value": "hsla(0,0,0,1)",
219+
"count": 1
220+
},
201221
{
202222
"value": "hsla(100, 20%, 30%, 0.5)",
203223
"count": 1
@@ -206,6 +226,10 @@
206226
"value": "purple",
207227
"count": 2
208228
},
229+
{
230+
"value": "rgb(0,0,0)",
231+
"count": 1
232+
},
209233
{
210234
"value": "rgb(100, 200, 10)",
211235
"count": 1
@@ -214,6 +238,10 @@
214238
"value": "rgb(255, 255, 255)",
215239
"count": 1
216240
},
241+
{
242+
"value": "rgba(0,0,0,1)",
243+
"count": 1
244+
},
217245
{
218246
"value": "rgba(100, 200, 10, .5)",
219247
"count": 1
@@ -238,6 +266,118 @@
238266
"value": "whitesmoke",
239267
"count": 1
240268
}
269+
],
270+
"duplicates": [
271+
{
272+
"count": 8,
273+
"value": "#000",
274+
"aliases": [
275+
{
276+
"value": "#000",
277+
"count": 1
278+
},
279+
{
280+
"value": "#000000",
281+
"count": 1
282+
},
283+
{
284+
"value": "black",
285+
"count": 2
286+
},
287+
{
288+
"value": "hsl(0,0,0)",
289+
"count": 1
290+
},
291+
{
292+
"value": "hsla(0,0,0,1)",
293+
"count": 1
294+
},
295+
{
296+
"value": "rgb(0,0,0)",
297+
"count": 1
298+
},
299+
{
300+
"value": "rgba(0,0,0,1)",
301+
"count": 1
302+
}
303+
]
304+
},
305+
{
306+
"value": "#fff",
307+
"count": 4,
308+
"aliases": [
309+
{
310+
"count": 1,
311+
"value": "#fff"
312+
},
313+
{
314+
"count": 1,
315+
"value": "hsl(360, 100%, 100%)"
316+
},
317+
{
318+
"count": 1,
319+
"value": "rgb(255, 255, 255)"
320+
},
321+
{
322+
"count": 1,
323+
"value": "white"
324+
}
325+
]
326+
},
327+
{
328+
"value": "hsl(270 60% 50% / .15)",
329+
"count": 4,
330+
"aliases": [
331+
{
332+
"count": 1,
333+
"value": "hsl(270 60% 50% / .15)"
334+
},
335+
{
336+
"count": 1,
337+
"value": "hsl(270 60% 50% / 15%)"
338+
},
339+
{
340+
"count": 1,
341+
"value": "hsl(270, 60%, 50%, .15)"
342+
},
343+
{
344+
"count": 1,
345+
"value": "hsl(270, 60%, 50%, 15%)"
346+
}
347+
]
348+
},
349+
{
350+
"value": "hsl(270 60% 70%)",
351+
"count": 3,
352+
"aliases": [
353+
{
354+
"count": 1,
355+
"value": "hsl(270 60% 70%)"
356+
},
357+
{
358+
"count": 1,
359+
"value": "hsl(270, 60%, 70%)"
360+
},
361+
{
362+
"count": 1,
363+
"value": "hsl(270,60%,70%)"
364+
}
365+
]
366+
},
367+
{
368+
"value": "rgba(100, 200, 10, .5)",
369+
"count": 2,
370+
"aliases": [
371+
{
372+
"count": 1,
373+
"value": "rgba(100, 200, 10, .5)"
374+
},
375+
{
376+
"count": 1,
377+
"value": "rgba(100, 200, 10, 0.5)"
378+
}
379+
]
380+
}
241381
]
242382
}
243383
}

yarn.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4474,6 +4474,10 @@ timed-out@^4.0.0:
44744474
version "4.0.1"
44754475
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
44764476

4477+
tinycolor2@^1.4.1:
4478+
version "1.4.1"
4479+
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
4480+
44774481
tmp@^0.0.33:
44784482
version "0.0.33"
44794483
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"

0 commit comments

Comments
 (0)