Skip to content

Commit 8aeccc8

Browse files
authored
Add selector complexity (#140)
* add selector complexity * fix tests
1 parent 87c3585 commit 8aeccc8

File tree

9 files changed

+162
-1
lines changed

9 files changed

+162
-1
lines changed

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"css-media-query-browser-h4cks-analyzer": "^1.0.0",
6060
"css-property-browser-h4cks-analyzer": "^1.1.0",
6161
"css-selector-browser-h4cks-analyzer": "^1.1.0",
62+
"css-selector-complexity": "^0.1.1",
6263
"css-shorthand-expand": "^1.2.0",
6364
"css-unit-sort": "^3.3.0",
6465
"css-value-browser-h4cks-analyzer": "^1.0.1",

readme.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ analyze('foo{}')
122122
// 'selectors.browserhacks.total': 0,
123123
// 'selectors.browserhacks.unique': [],
124124
// 'selectors.browserhacks.totalUnique': 0,
125+
// 'selectors.complexity.average': 1,
126+
// 'selectors.complexity.max.count': 1,
127+
// 'selectors.complexity.max.value': 1,
128+
// 'selectors.complexity.max.selectors': [{value: 'foo', count: 1}],
129+
// 'selectors.complexity.max.count': 1,
130+
// 'selectors.complexity.sum': 1,
131+
// 'selectors.complexity.unique': [{value: 1, count: 1}],
132+
// 'selectors.complexity.totalUnique': 1,
125133
// 'stylesheets.cohesion.average': 0,
126134
// 'stylesheets.cohesion.min.count': 0,
127135
// 'stylesheets.cohesion.min.value': null,
@@ -130,6 +138,8 @@ analyze('foo{}')
130138
// 'stylesheets.filesize.compressed.gzip.compressionRatio': -4,
131139
// 'stylesheets.filesize.compressed.gzip.totalBytes': 25,
132140
// 'stylesheets.filesize.uncompressed.totalBytes': 5,
141+
// 'stylesheets.linesOfCode.sourceLinesOfCode.total': 1,
142+
// 'stylesheets.linesOfCode.total': 1,
133143
// 'stylesheets.simplicity': 1,
134144
// 'stylesheets.size': 5,
135145
// 'stylesheets.browserhacks.total': 0,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const selectorComplexity = require('css-selector-complexity')
2+
const uniquer = require('../../utils/uniquer')
3+
4+
module.exports = selectors => {
5+
const all = selectors.map(selector => {
6+
return {
7+
selector,
8+
complexity: selectorComplexity(selector)
9+
}
10+
})
11+
const allComplexities = all.map(selector => selector.complexity)
12+
const maxComplexity = all.length === 0 ? 0 : Math.max(...allComplexities)
13+
const {
14+
unique: mostComplexSelectors,
15+
totalUnique: mostComplexSelectorsCount
16+
} = uniquer(
17+
all
18+
.filter(({complexity}) => complexity === maxComplexity)
19+
.map(({selector}) => selector)
20+
)
21+
const unique = uniquer(allComplexities)
22+
const totalComplexity = allComplexities.reduce((acc, curr) => acc + curr, 0)
23+
const averageComplexityPerSelector =
24+
all.length === 0 ? 0 : totalComplexity / all.length
25+
26+
return {
27+
max: {
28+
value: maxComplexity,
29+
selectors: mostComplexSelectors,
30+
count: mostComplexSelectorsCount
31+
},
32+
average: averageComplexityPerSelector,
33+
sum: totalComplexity,
34+
...unique
35+
}
36+
}

src/analyzer/selectors/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = selectors => {
77
const accessibility = require('./accessibility.js')(all)
88
const specificity = require('./specificity.js')(all)
99
const identifiers = require('./identifiers.js')(all)
10+
const complexity = require('./complexity.js')(all)
1011
const browserhacks = require('./browserhacks.js')(all)
1112

1213
return {
@@ -18,6 +19,7 @@ module.exports = selectors => {
1819
accessibility,
1920
specificity,
2021
identifiers,
22+
complexity,
2123
browserhacks
2224
}
2325
}

src/parser/atrules.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ module.exports.isKeyframes = rule => {
3535
return (
3636
rule.parent &&
3737
rule.parent.type === 'atrule' &&
38-
rule.parent.name === 'keyframes'
38+
rule.parent.name.includes('keyframes')
3939
)
4040
}

test/analyzer/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ test('it returns the correct analysis object structure', async t => {
102102
'selectors.browserhacks.total': 0,
103103
'selectors.browserhacks.unique': [],
104104
'selectors.browserhacks.totalUnique': 0,
105+
'selectors.complexity.average': 1,
106+
'selectors.complexity.max.value': 1,
107+
'selectors.complexity.max.selectors': [{value: 'foo', count: 1}],
108+
'selectors.complexity.max.count': 1,
109+
'selectors.complexity.sum': 1,
110+
'selectors.complexity.unique': [{value: 1, count: 1}],
111+
'selectors.complexity.totalUnique': 1,
105112
'stylesheets.cohesion.average': 0,
106113
'stylesheets.cohesion.min.count': 0,
107114
'stylesheets.cohesion.min.value': null,
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
const test = require('ava')
2+
const analyze = require('../../../src/analyzer/selectors/complexity')
3+
4+
test('it responds with the correct structure', t => {
5+
const fixture = []
6+
const actual = analyze(fixture)
7+
const expected = {
8+
max: {
9+
value: 0,
10+
count: 0,
11+
selectors: []
12+
},
13+
average: 0,
14+
sum: 0,
15+
unique: [],
16+
totalUnique: 0
17+
}
18+
19+
t.deepEqual(actual, expected)
20+
})
21+
22+
const FIXTURE = [
23+
'a b c',
24+
'a b',
25+
'a b c d',
26+
'a b c d e',
27+
'a b c d e f',
28+
'g h i j k l'
29+
]
30+
31+
test('it finds the selectors with the highest complexity', t => {
32+
const {max: actual} = analyze(FIXTURE)
33+
const expected = {
34+
value: 6,
35+
count: 2,
36+
selectors: [
37+
{value: 'a b c d e f', count: 1},
38+
{value: 'g h i j k l', count: 1}
39+
]
40+
}
41+
42+
t.deepEqual(actual, expected)
43+
})
44+
45+
test('it finds the selectors with the highest complexity and only shows the unique ones', t => {
46+
const {max: actual} = analyze([...FIXTURE, 'a b c d e f'])
47+
const expected = {
48+
value: 6,
49+
count: 2,
50+
selectors: [
51+
{value: 'a b c d e f', count: 2},
52+
{value: 'g h i j k l', count: 1}
53+
]
54+
}
55+
56+
t.deepEqual(actual, expected)
57+
})
58+
59+
test('it counts the sum of all selector complexities', t => {
60+
const {sum: actual} = analyze(FIXTURE)
61+
const expected = 3 + 2 + 4 + 5 + 6 + 6
62+
t.is(actual, expected)
63+
})
64+
65+
test('it finds all unique complexities of all selectors', t => {
66+
const {unique: actual} = analyze(FIXTURE)
67+
const expected = [
68+
{value: 2, count: 1},
69+
{value: 3, count: 1},
70+
{value: 4, count: 1},
71+
{value: 5, count: 1},
72+
{value: 6, count: 2}
73+
]
74+
t.deepEqual(actual, expected)
75+
})
76+
77+
test('it finds the total unique complexities of all selectors', t => {
78+
const {totalUnique: actual} = analyze(FIXTURE)
79+
const expected = 5
80+
t.deepEqual(actual, expected)
81+
})
82+
83+
test('it calculates the average complexity per selector', t => {
84+
const {average: actual} = analyze(FIXTURE)
85+
86+
t.is(actual, 26 / 6)
87+
})

test/parser/selectors.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ test('"selectors" in @keyframes are not passed as actual selectors', async t =>
2020
no-selector-1 { opacity: 1 }
2121
no-selector-2 { opacity: 0 }
2222
}
23+
24+
@-webkit-keyframes {
25+
0% { opacity: 0 }
26+
100% { opacity: 1 }
27+
}
2328
`
2429

2530
const {selectors: actual} = await parser(fixture)

0 commit comments

Comments
 (0)