@@ -233,11 +233,18 @@ module.exports = {
233
233
staticClasses = new Set ( getClassNames ( String ( staticAttr . value . value ) ) )
234
234
}
235
235
236
+ /** @type {Set<string> } */
236
237
const reported = new Set ( )
238
+
239
+ /** @type {Set<string> } */
237
240
const duplicatesInExpression = new Set ( )
241
+
238
242
/** @type {Map<string, ASTNode> } */
239
243
const seen = new Map ( )
240
244
245
+ /** @type {Map<string, {node: ASTNode, unconditional: boolean}> } */
246
+ const collected = new Map ( )
247
+
241
248
const classNodes = extractClassNodes ( node )
242
249
for ( const { node : reportNode , unconditional } of classNodes ) {
243
250
// report fixable duplicates and collect reported class names
@@ -246,44 +253,53 @@ module.exports = {
246
253
for ( const classes of reportedClasses ) reported . add ( classes )
247
254
}
248
255
249
- // collect duplicates within the expression nodes
250
- if ( unconditional ) {
251
- const classList = getRawValue ( reportNode )
252
- if ( typeof classList === 'string' ) {
253
- const classNames = getClassNames ( classList )
254
- for ( const className of classNames ) {
255
- if ( seen . has ( className ) ) {
256
- duplicatesInExpression . add ( className )
257
- } else {
258
- seen . set ( className , reportNode . parent )
259
- }
256
+ // collect all class names and check for cross nodes duplicates
257
+ const classList = getRawValue ( reportNode )
258
+ if ( typeof classList !== 'string' ) continue
259
+ const classNames = getClassNames ( classList )
260
+
261
+ for ( const className of classNames ) {
262
+ // skip if already reported by reportDuplicateClasses
263
+ if ( reported . has ( className ) ) continue
264
+ const existing = collected . get ( className )
265
+ if ( existing ) {
266
+ // only add duplicate if at least one is unconditional
267
+ if ( existing . unconditional || unconditional ) {
268
+ duplicatesInExpression . add ( className )
269
+ }
270
+ } else {
271
+ collected . set ( className , {
272
+ node : reportNode . parent ,
273
+ unconditional
274
+ } )
275
+ }
276
+ // track unconditional duplicates separately for reporting
277
+ if ( unconditional ) {
278
+ if ( seen . has ( className ) ) {
279
+ duplicatesInExpression . add ( className )
280
+ } else {
281
+ seen . set ( className , reportNode . parent )
260
282
}
261
283
}
262
284
}
263
285
264
- // report duplicates between static and dynamic class attributes
286
+ // report cross attribute duplicates
265
287
if ( staticClasses ) {
266
- const classList = getRawValue ( reportNode )
267
- if ( typeof classList === 'string' ) {
268
- const classNames = getClassNames ( classList )
269
- const intersection = classNames . filter ( ( n ) =>
270
- staticClasses . has ( n )
271
- )
272
- if ( intersection . length > 0 && parent ) {
273
- context . report ( {
274
- node : parent ,
275
- messageId : 'duplicateClassName' ,
276
- data : { name : intersection . join ( ', ' ) }
277
- } )
278
- }
288
+ const intersection = classNames . filter ( ( n ) => staticClasses . has ( n ) )
289
+ if ( intersection . length > 0 && parent ) {
290
+ context . report ( {
291
+ node : parent ,
292
+ messageId : 'duplicateClassName' ,
293
+ data : { name : intersection . join ( ', ' ) }
294
+ } )
279
295
}
280
296
}
281
297
}
282
298
283
- // report duplicates between dynamic class nodes excluding already reported
284
- for ( const r of reported ) duplicatesInExpression . delete ( r )
299
+ // report cross node duplicates
285
300
for ( const className of duplicatesInExpression ) {
286
- const reportNode = seen . get ( className )
301
+ const reportNode =
302
+ seen . get ( className ) || collected . get ( className ) ?. node
287
303
if ( reportNode ) {
288
304
context . report ( {
289
305
node : reportNode ,
0 commit comments