Skip to content

Commit d97f4d9

Browse files
committed
fix: mixed cross node case
1 parent b30dbed commit d97f4d9

File tree

2 files changed

+55
-28
lines changed

2 files changed

+55
-28
lines changed

lib/rules/no-duplicate-class-names.js

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,18 @@ module.exports = {
233233
staticClasses = new Set(getClassNames(String(staticAttr.value.value)))
234234
}
235235

236+
/** @type {Set<string>} */
236237
const reported = new Set()
238+
239+
/** @type {Set<string>} */
237240
const duplicatesInExpression = new Set()
241+
238242
/** @type {Map<string, ASTNode>} */
239243
const seen = new Map()
240244

245+
/** @type {Map<string, {node: ASTNode, unconditional: boolean}>} */
246+
const collected = new Map()
247+
241248
const classNodes = extractClassNodes(node)
242249
for (const { node: reportNode, unconditional } of classNodes) {
243250
// report fixable duplicates and collect reported class names
@@ -246,44 +253,53 @@ module.exports = {
246253
for (const classes of reportedClasses) reported.add(classes)
247254
}
248255

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)
260282
}
261283
}
262284
}
263285

264-
// report duplicates between static and dynamic class attributes
286+
// report cross attribute duplicates
265287
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+
})
279295
}
280296
}
281297
}
282298

283-
// report duplicates between dynamic class nodes excluding already reported
284-
for (const r of reported) duplicatesInExpression.delete(r)
299+
// report cross node duplicates
285300
for (const className of duplicatesInExpression) {
286-
const reportNode = seen.get(className)
301+
const reportNode =
302+
seen.get(className) || collected.get(className)?.node
287303
if (reportNode) {
288304
context.report({
289305
node: reportNode,

tests/lib/rules/no-duplicate-class-names.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,17 @@ tester.run('no-duplicate-class-names', rule, {
345345
type: 'BinaryExpression'
346346
}
347347
]
348+
},
349+
{
350+
filename: 'duplicate-class-cross-node-mixed.vue',
351+
code: `<template><div :class="['foo', { 'foo': true }]"></div></template>`,
352+
output: null,
353+
errors: [
354+
{
355+
message: "Duplicate class name 'foo'.",
356+
type: 'ArrayExpression'
357+
}
358+
]
348359
}
349360
]
350361
})

0 commit comments

Comments
 (0)