1- import parse from 'css-tree/parser'
21import walk from 'css-tree/walker'
32
43/**
@@ -69,9 +68,11 @@ const analyzeSpecificity = (ast) => {
6968 B ++
7069 complexity ++
7170
71+ // Add 1 for [attr=value] (or any variation using *= $= ^= |= )
7272 if ( Boolean ( selector . value ) ) {
7373 complexity ++
7474 }
75+
7576 isA11y = selector . name . name === 'role' || selector . name . name . startsWith ( 'aria-' )
7677 break
7778 }
@@ -84,94 +85,66 @@ const analyzeSpecificity = (ast) => {
8485 break
8586 }
8687 case 'PseudoClassSelector' : {
87- if ( [ 'before' , 'after' , 'first-letter' , 'first-line' ] . includes ( selector . name ) ) {
88- C ++
89- complexity ++
90- return this . skip
91- }
92- // The specificity of an :is(), :not(), or :has() pseudo-class is
93- // replaced by the specificity of the most specific complex
94- // selector in its selector list argument.
95-
96- // CSSTree doesn't parse the arguments of :is, :has and :matches,
97- // so we need to create an AST out of them ourselves
98- if ( [ 'is' , 'has' , 'matches' , '-webkit-any' , '-moz-any' ] . includes ( selector . name ) ) {
99- const childAst = parse ( selector . children . first . value , { context : 'selectorList' } )
100- const selectorList = selectorListSpecificities ( childAst )
101- const [ topA , topB , topC ] = selectorList [ 0 ] . specificity
102- A += topA
103- B += topB
104- C += topC
105-
106- for ( let i = 0 ; i < selectorList . length ; i ++ ) {
107- complexity += selectorList [ i ] . complexity
88+ switch ( selector . name ) {
89+ case 'before' :
90+ case 'after' :
91+ case 'first-letter' :
92+ case 'first-line' : {
93+ C ++
94+ complexity ++
95+ return this . skip
10896 }
109- complexity ++
110- return
111- }
11297
113- // CSSTree *does* parse the arguments of the :not() pseudo-class,
114- // so we have direct access to the AST, instead of having to parse
115- // the arguments ourselves.
116- if ( selector . name === 'not' ) {
117- const selectorList = selectorListSpecificities ( selector )
118- const [ topA , topB , topC ] = selectorList [ 0 ] . specificity
119- A += topA
120- B += topB
121- C += topC
122-
123- for ( let i = 0 ; i < selectorList . length ; i ++ ) {
124- complexity += selectorList [ i ] . complexity
98+ // The specificity of an :is(), :not(), or :has() pseudo-class is
99+ // replaced by the specificity of the most specific complex
100+ // selector in its selector list argument.
101+ case 'where' :
102+ case 'is' :
103+ case 'has' :
104+ case 'matches' :
105+ case '-webkit-any' :
106+ case '-moz-any' :
107+ case 'not' :
108+ case 'nth-child' :
109+ case 'nth-last-child' : {
110+ // The specificity of an :nth-child() or :nth-last-child() selector
111+ // is the specificity of the pseudo class itself (counting as one
112+ // pseudo-class selector) plus the specificity of the most
113+ // specific complex selector in its selector list argument (if any).
114+ if ( [ 'nth-child' , 'nth-last-child' ] . includes ( selector . name ) ) {
115+ // +1 for the pseudo class itself
116+ B ++
117+ }
118+
119+ const selectorList = selectorListSpecificities ( selector )
120+
121+ // Bail out for empty/non-existent :nth-child() params
122+ if ( selectorList . length === 0 ) return
123+
124+ // The specificity of a :where() pseudo-class is replaced by zero,
125+ // but it does count towards complexity.
126+ if ( selector . name !== 'where' ) {
127+ const [ topA , topB , topC ] = selectorList [ 0 ] . specificity
128+ A += topA
129+ B += topB
130+ C += topC
131+ }
132+
133+ for ( let i = 0 ; i < selectorList . length ; i ++ ) {
134+ complexity += selectorList [ i ] . complexity
135+ }
136+
137+ complexity ++
138+ return this . skip
125139 }
126- complexity ++
127- return this . skip
128- }
129-
130- // The specificity of an :nth-child() or :nth-last-child() selector
131- // is the specificity of the pseudo class itself (counting as one
132- // pseudo-class selector) plus the specificity of the most
133- // specific complex selector in its selector list argument (if any).
134- if ( [ 'nth-child' , 'nth-last-child' ] . includes ( selector . name ) ) {
135- // +1 for the pseudo class itself
136- B ++
137-
138- const childSelectors = selectorListSpecificities ( selector )
139-
140- if ( childSelectors . length === 0 ) {
141- return
142- }
143-
144- const [ topA , topB , topC ] = childSelectors [ 0 ] . specificity
145- A += topA
146- B += topB
147- C += topC
148140
149- for ( let i = 0 ; i < childSelectors . length ; i ++ ) {
150- complexity += childSelectors [ i ] . complexity ;
141+ default : {
142+ // Regular pseudo classes have specificity [0,1,0]
143+ complexity ++
144+ B ++
145+ return this . skip
151146 }
152-
153- complexity ++
154- return
155- }
156-
157- // The specificity of a :where() pseudo-class is replaced by zero,
158- // but it does count towards complexity.
159- if ( selector . name === 'where' ) {
160- const childAst = parse ( selector . children . first . value , { context : 'selectorList' } )
161- const childSelectors = selectorListSpecificities ( childAst )
162-
163- for ( let i = 0 ; i < childSelectors . length ; i ++ ) {
164- complexity += childSelectors [ i ] . complexity ;
165- }
166-
167- complexity ++
168- return
169147 }
170-
171- // Regular pseudo classes have specificity [0,1,0]
172- complexity ++
173- B ++
174- break
175148 }
176149 case 'Combinator' : {
177150 complexity ++
0 commit comments