Repository: sveltejs/eslint-plugin-svelte
Pull request #1260 (2025-06-27 00:31:57)

fix(no-unused-class-name): detect duplicated class names
close https://github.com/sveltejs/eslint-plugin-svelte/issues/1259

changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: e893046c52c2cdce9c54743c02c3404d20bb09e4

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name                 | Type  |
| -------------------- | ----- |
| eslint-plugin-svelte | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/tbashiyy/eslint-plugin-svelte/new/fix/detect-all-unused-class-name?filename=.changeset/few-chicken-report.md&value=---%0A%22eslint-plugin-svelte%22%3A%20patch%0A---%0A%0Afix(no-unused-class-name)%3A%20fix%20detect%20duplicated%20class%20name%0A)


github-actions[bot]: <!-- posted by pkg.pr.new-comment.mjs -->

## Try the Instant Preview in Online Playground

[ESLint Online Playground](https://eslint-online-playground.netlify.app/?overrideDeps=%7B%22eslint-plugin-svelte%22%3A%22https%3A%2F%2Fpkg.pr.new%2Feslint-plugin-svelte%40e893046%22%7D#eslint-plugin-svelte%20with%20typescript)

## Install the Instant Preview to Your Local

```
npm i https://pkg.pr.new/eslint-plugin-svelte@e893046
```

## Published Instant Preview Packages:

- eslint-plugin-svelte: https://pkg.pr.new/eslint-plugin-svelte@e893046

[View Commit](https://github.com/sveltejs/eslint-plugin-svelte/commit/e893046c52c2cdce9c54743c02c3404d20bb09e4)

Patch:
diff --git a/.changeset/dry-colts-open.md b/.changeset/dry-colts-open.md
new file mode 100644
index 0000000..fd2d972
--- /dev/null
+++ b/.changeset/dry-colts-open.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': patch
+---
+
+fix(no-unused-class-name): detect duplicated class names
diff --git a/packages/eslint-plugin-svelte/src/rules/no-unused-class-name.ts b/packages/eslint-plugin-svelte/src/rules/no-unused-class-name.ts
index 0906905..e7d1dd5 100644
--- a/packages/eslint-plugin-svelte/src/rules/no-unused-class-name.ts
+++ b/packages/eslint-plugin-svelte/src/rules/no-unused-class-name.ts
@@ -36,7 +36,10 @@ export default createRule('no-unused-class-name', {
 			return {};
 		}
 		const allowedClassNames = context.options[0]?.allowedClassNames ?? [];
-		const classesUsedInTemplate: Record<string, AST.SourceLocation> = {};
+		const classesUsedInTemplate: {
+			className: string;
+			loc: AST.SourceLocation;
+		}[] = [];
 
 		return {
 			SvelteElement(node) {
@@ -45,7 +48,7 @@ export default createRule('no-unused-class-name', {
 				}
 				const classes = node.startTag.attributes.flatMap(findClassesInAttribute);
 				for (const className of classes) {
-					classesUsedInTemplate[className] = node.startTag.loc;
+					classesUsedInTemplate.push({ className, loc: node.startTag.loc });
 				}
 			},
 			'Program:exit'() {
@@ -57,7 +60,8 @@ export default createRule('no-unused-class-name', {
 					styleContext.status === 'success'
 						? findClassesInPostCSSNode(styleContext.sourceAst, sourceCode.parserServices)
 						: [];
-				for (const className in classesUsedInTemplate) {
+
+				for (const { className, loc } of classesUsedInTemplate) {
 					if (
 						!allowedClassNames.some((allowedClassName: string) =>
 							toRegExp(allowedClassName).test(className)
@@ -65,7 +69,7 @@ export default createRule('no-unused-class-name', {
 						!classesUsedInStyle.includes(className)
 					) {
 						context.report({
-							loc: classesUsedInTemplate[className],
+							loc,
 							message: `Unused class "${className}".`
 						});
 					}


Test patch:
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-class-name/invalid/duplicated-class-name-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-class-name/invalid/duplicated-class-name-errors.yaml
new file mode 100644
index 0000000..4f5c9cd
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-class-name/invalid/duplicated-class-name-errors.yaml
@@ -0,0 +1,8 @@
+- message: Unused class "div-class".
+  line: 1
+  column: 1
+  suggestions: null
+- message: Unused class "div-class".
+  line: 3
+  column: 1
+  suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-class-name/invalid/duplicated-class-name-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-class-name/invalid/duplicated-class-name-input.svelte
new file mode 100644
index 0000000..57d433d
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-class-name/invalid/duplicated-class-name-input.svelte
@@ -0,0 +1,3 @@
+<div class="div-class">Hello</div>
+
+<span class="div-class">World!</span>