@@ -6,10 +6,17 @@ import type {
6
6
Node as SelectorNode ,
7
7
Tag as SelectorTag
8
8
} from 'postcss-selector-parser' ;
9
+ import type { SvelteHTMLElement } from 'svelte-eslint-parser/lib/ast' ;
9
10
import { findClassesInAttribute } from '../utils/ast-utils.js' ;
10
11
import { getSourceCode } from '../utils/compat.js' ;
12
+ import { extractExpressionPrefixLiteral } from '../utils/expression-affixes.js' ;
11
13
import { createRule } from '../utils/index.js' ;
12
14
15
+ interface Selections {
16
+ exact : Map < string , AST . SvelteHTMLElement [ ] > ;
17
+ prefixes : Map < string , AST . SvelteHTMLElement [ ] > ;
18
+ }
19
+
13
20
export default createRule ( 'consistent-selector-style' , {
14
21
meta : {
15
22
docs : {
@@ -65,11 +72,14 @@ export default createRule('consistent-selector-style', {
65
72
const whitelistedClasses : string [ ] = [ ] ;
66
73
67
74
const selections : {
68
- class : Map < string , AST . SvelteHTMLElement [ ] > ;
75
+ class : Selections ;
69
76
id : Map < string , AST . SvelteHTMLElement [ ] > ;
70
77
type : Map < string , AST . SvelteHTMLElement [ ] > ;
71
78
} = {
72
- class : new Map ( ) ,
79
+ class : {
80
+ exact : new Map ( ) ,
81
+ prefixes : new Map ( )
82
+ } ,
73
83
id : new Map ( ) ,
74
84
type : new Map ( )
75
85
} ;
@@ -120,7 +130,7 @@ export default createRule('consistent-selector-style', {
120
130
if ( whitelistedClasses . includes ( node . value ) ) {
121
131
return ;
122
132
}
123
- const selection = selections . class . get ( node . value ) ?? [ ] ;
133
+ const selection = matchSelection ( selections . class , node . value ) ;
124
134
for ( const styleValue of style ) {
125
135
if ( styleValue === 'class' ) {
126
136
return ;
@@ -200,19 +210,24 @@ export default createRule('consistent-selector-style', {
200
210
return ;
201
211
}
202
212
addToArrayMap ( selections . type , node . name . name , node ) ;
203
- const classes = node . startTag . attributes . flatMap ( findClassesInAttribute ) ;
204
- for ( const className of classes ) {
205
- addToArrayMap ( selections . class , className , node ) ;
206
- }
207
213
for ( const attribute of node . startTag . attributes ) {
208
214
if ( attribute . type === 'SvelteDirective' && attribute . kind === 'Class' ) {
209
215
whitelistedClasses . push ( attribute . key . name . name ) ;
210
216
}
211
- if ( attribute . type !== 'SvelteAttribute' || attribute . key . name !== 'id' ) {
217
+ for ( const className of findClassesInAttribute ( attribute ) ) {
218
+ addToArrayMap ( selections . class . exact , className , node ) ;
219
+ }
220
+ if ( attribute . type !== 'SvelteAttribute' ) {
212
221
continue ;
213
222
}
214
223
for ( const value of attribute . value ) {
215
- if ( value . type === 'SvelteLiteral' ) {
224
+ if ( attribute . key . name === 'class' && value . type === 'SvelteMustacheTag' ) {
225
+ const prefix = extractExpressionPrefixLiteral ( context , value . expression ) ;
226
+ if ( prefix !== null ) {
227
+ addToArrayMap ( selections . class . prefixes , prefix , node ) ;
228
+ }
229
+ }
230
+ if ( attribute . key . name === 'id' && value . type === 'SvelteLiteral' ) {
216
231
addToArrayMap ( selections . id , value . value , node ) ;
217
232
}
218
233
}
@@ -243,6 +258,19 @@ function addToArrayMap(
243
258
map . set ( key , ( map . get ( key ) ?? [ ] ) . concat ( value ) ) ;
244
259
}
245
260
261
+ /**
262
+ * Finds all nodes in selections that could be matched by key
263
+ */
264
+ function matchSelection ( selections : Selections , key : string ) : SvelteHTMLElement [ ] {
265
+ const selection = selections . exact . get ( key ) ?? [ ] ;
266
+ selections . prefixes . forEach ( ( nodes , prefix ) => {
267
+ if ( key . startsWith ( prefix ) ) {
268
+ selection . push ( ...nodes ) ;
269
+ }
270
+ } ) ;
271
+ return selection ;
272
+ }
273
+
246
274
/**
247
275
* Checks whether a given selection could be obtained using an ID selector
248
276
*/
0 commit comments