Skip to content

Commit 05ce641

Browse files
Bart Venemanbartveneman
authored andcommitted
seperate analysis for complexity and isA11y
1 parent bcf3c3e commit 05ce641

File tree

4 files changed

+161
-135
lines changed

4 files changed

+161
-135
lines changed

benchmark/run.js

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ import byteSize from './format-filesize.js'
22
import { analyze as analyzeCss } from '../dist/analyzer.modern.js'
33
import * as fs from 'fs'
44
const files = [
5-
['bol-com-20190617', 'Bol.com', 135],
5+
['bol-com-20190617', 'Bol.com', 144],
66
['bootstrap-5.0.0', 'Bootstrap 5.0.0', 55],
7-
['cnn-20220403', 'CNN', 445],
8-
['css-tricks-20190319', 'CSS-Tricks', 59],
9-
['facebook-20190319', 'Facebook.com', 79],
10-
['github-20210501', 'GitHub.com', 98],
11-
['gazelle-20210905', 'Gazelle.nl', 345],
12-
['lego-20190617', 'Lego.com', 66],
13-
['smashing-magazine-20190319', 'Smashing Magazine.com', 335],
14-
['trello-20190617', 'Trello.com', 93],
7+
['cnn-20220403', 'CNN', 460],
8+
['css-tricks-20190319', 'CSS-Tricks', 60],
9+
['facebook-20190319', 'Facebook.com', 84],
10+
['github-20210501', 'GitHub.com', 103],
11+
['gazelle-20210905', 'Gazelle.nl', 350],
12+
['lego-20190617', 'Lego.com', 67],
13+
['smashing-magazine-20190319', 'Smashing Magazine.com', 355],
14+
['trello-20190617', 'Trello.com', 95],
1515
]
1616

1717
let maxLen = -1
@@ -38,13 +38,20 @@ files.forEach(([filename, name, expectedDuration]) => {
3838
})
3939

4040
const RUN_COUNT = 25
41+
let memStart = process.memoryUsage().heapUsed
42+
let memMin = Infinity
43+
let memMax = 0
44+
45+
function formatMem(mem) {
46+
return `${Math.ceil(mem / 1024 / 1024)} MB`
47+
}
4148

4249
suite.forEach(([name, fn, expectedDuration, size]) => {
43-
const start = new Date()
50+
const start = performance.now()
4451
for (let i = 0; i < RUN_COUNT; i++) {
4552
fn();
4653
}
47-
const duration = Math.floor((new Date() - start) / RUN_COUNT)
54+
const duration = Math.floor((performance.now() - start) / RUN_COUNT)
4855
const overtime = expectedDuration - duration
4956
const bytesPerSecond = Math.floor(1000 / duration * size)
5057
console.log(
@@ -53,6 +60,14 @@ suite.forEach(([name, fn, expectedDuration, size]) => {
5360
`(${overtime >= 0 ? '-' : '+'}${Math.abs(overtime)}ms ${Math.round(Math.abs(overtime) / duration * 100)}%)`.padStart(10),
5461
`${byteSize(bytesPerSecond)}/s`.padStart(9)
5562
)
63+
const mem = process.memoryUsage().heapUsed
64+
if (mem < memMin) memMin = mem
65+
if (mem > memMax) memMax = mem
5666
})
5767

58-
console.log(`Memory used: ${Math.ceil(process.memoryUsage().heapUsed / 1024 / 1024)}MB`)
68+
console.log({
69+
memStart: formatMem(memStart),
70+
memMin: formatMem(memMin),
71+
memMax: formatMem(memMax),
72+
memRange: formatMem(memMax - memMin),
73+
})

src/index.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import parse from 'css-tree/parser'
22
import walk from 'css-tree/walker'
33
import { calculate } from '@bramus/specificity/core'
44
import { isSupportsBrowserhack, isMediaBrowserhack } from './atrules/atrules.js'
5-
import { analyzeSelector, compareSpecificity } from './selectors/specificity.js'
5+
import { getComplexity, isAccessibility, compareSpecificity } from './selectors/utils.js'
66
import { colorFunctions, colorNames } from './values/colors.js'
77
import { isFontFamilyKeyword, getFamilyFromFont } from './values/font-families.js'
88
import { isFontSizeKeyword, getSizeFromFont } from './values/font-sizes.js'
@@ -217,18 +217,17 @@ const analyze = (css) => {
217217

218218
const [{ value: specificityObj }] = calculate(node)
219219
const specificity = [specificityObj.a, specificityObj.b, specificityObj.c]
220-
const [complexity, isA11y] = analyzeSelector(node)
221220

222221
if (specificity[0] > 0) {
223222
ids.push(selector)
224223
}
225224

226-
if (isA11y === 1) {
225+
if (isAccessibility(node)) {
227226
a11y.push(selector)
228227
}
229228

230229
uniqueSelectors.add(selector)
231-
selectorComplexities.push(complexity)
230+
selectorComplexities.push(getComplexity(node))
232231
uniqueSpecificities.push(specificity)
233232

234233
if (maxSpecificity === undefined) {

src/selectors/specificity.js

Lines changed: 0 additions & 119 deletions
This file was deleted.

src/selectors/utils.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import walk from 'css-tree/walker'
2+
import { startsWith, strEquals } from '../string-utils.js'
3+
import { hasVendorPrefix } from '../vendor-prefix.js'
4+
5+
/**
6+
* Compare specificity A to Specificity B
7+
* @param {[number,number,number]} a - Specificity A
8+
* @param {[number,number,number]} b - Specificity B
9+
* @returns {number} sortIndex - 0 when a==b, 1 when a<b, -1 when a>b
10+
*/
11+
export function compareSpecificity(a, b) {
12+
if (a[0] === b[0]) {
13+
if (a[1] === b[1]) {
14+
return b[2] - a[2]
15+
}
16+
17+
return b[1] - a[1]
18+
}
19+
20+
return b[0] - a[0]
21+
}
22+
23+
/**
24+
*
25+
* @param {import('css-tree').SelectorList} selectorListAst
26+
* @returns {Selector[]} Analyzed selectors in the selectorList
27+
*/
28+
function analyzeList(selectorListAst, cb) {
29+
const childSelectors = []
30+
walk(selectorListAst, {
31+
visit: 'Selector',
32+
enter: function (node) {
33+
childSelectors.push(cb(node))
34+
}
35+
})
36+
37+
return childSelectors
38+
}
39+
40+
function isPseudoFunction(name) {
41+
return (
42+
strEquals(name, 'not')
43+
|| strEquals(name, 'nth-child')
44+
|| strEquals(name, 'nth-last-child')
45+
|| strEquals(name, 'where')
46+
|| strEquals(name, 'is')
47+
|| strEquals(name, 'has')
48+
|| strEquals(name, 'matches')
49+
|| strEquals(name, '-webkit-any')
50+
|| strEquals(name, '-moz-any')
51+
)
52+
}
53+
54+
export function isAccessibility(selector) {
55+
let isA11y = false
56+
57+
walk(selector, function (node) {
58+
if (node.type == 'AttributeSelector') {
59+
if (strEquals('role', node.name.name) || startsWith('aria-', node.name.name)) {
60+
isA11y = true
61+
return this.break
62+
}
63+
} else if (node.type == 'PseudoClassSelector') {
64+
if (isPseudoFunction(node.name)) {
65+
const list = analyzeList(node, isAccessibility)
66+
67+
// Bail out for empty/non-existent :nth-child() params
68+
if (list.length === 0) return
69+
70+
if (list.some(b => b == true)) {
71+
isA11y = true
72+
return this.skip
73+
}
74+
75+
return this.skip
76+
}
77+
}
78+
})
79+
80+
return isA11y;
81+
}
82+
83+
/**
84+
* Get the Complexity for the AST of a Selector Node
85+
* @param {import('css-tree').Selector} ast - AST Node for a Selector
86+
* @return {number} - The numeric complexity of the Selector
87+
*/
88+
export function getComplexity(selector) {
89+
let complexity = 0
90+
91+
walk(selector, function (node) {
92+
if (node.type == 'Selector' || node.type == 'Nth') return
93+
94+
complexity++
95+
96+
if (node.type == 'IdSelector'
97+
|| node.type == 'ClassSelector'
98+
|| node.type == 'PseudoElementSelector'
99+
|| node.type == 'TypeSelector'
100+
|| node.type == 'PseudoClassSelector'
101+
) {
102+
if (hasVendorPrefix(node.name)) {
103+
complexity++
104+
}
105+
}
106+
107+
if (node.type == 'AttributeSelector') {
108+
if (Boolean(node.value)) {
109+
complexity++
110+
}
111+
if (hasVendorPrefix(node.name.name)) {
112+
complexity++
113+
}
114+
return this.skip
115+
}
116+
117+
if (node.type == 'PseudoClassSelector') {
118+
if (isPseudoFunction(node.name)) {
119+
const selectorList = analyzeList(node, getComplexity)
120+
121+
// Bail out for empty/non-existent :nth-child() params
122+
if (selectorList.length === 0) return
123+
124+
selectorList.forEach(c => complexity += c)
125+
return this.skip
126+
}
127+
}
128+
})
129+
130+
return complexity
131+
}

0 commit comments

Comments
 (0)