diff --git a/.changeset/moody-seas-kick.md b/.changeset/moody-seas-kick.md
new file mode 100644
index 000000000..aebc93044
--- /dev/null
+++ b/.changeset/moody-seas-kick.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-svelte": minor
+---
+
+feat: added the no-unused-class-name rule
diff --git a/.eslintignore b/.eslintignore
index 06758f473..5967ec9be 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -8,6 +8,7 @@
/prettier-playground
/tests/fixtures/rules/indent/invalid/ts
/tests/fixtures/rules/indent/invalid/ts-v5
+/tests/fixtures/rules/no-unused-class-name/valid/unknown-lang01-input.svelte
/tests/fixtures/rules/valid-compile/invalid/ts
/tests/fixtures/rules/valid-compile/valid/babel
/tests/fixtures/rules/valid-compile/valid/ts
diff --git a/README.md b/README.md
index d7ba245c5..d1d8960c9 100644
--- a/README.md
+++ b/README.md
@@ -343,6 +343,7 @@ These rules relate to better ways of doing things to help you avoid problems:
| [svelte/no-immutable-reactive-statements](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-immutable-reactive-statements/) | disallow reactive statements that don't reference reactive values. | |
| [svelte/no-reactive-functions](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-functions/) | it's not necessary to define functions in reactive statements | :bulb: |
| [svelte/no-reactive-literals](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-literals/) | don't assign literal values in reactive statements | :bulb: |
+| [svelte/no-unused-class-name](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-class-name/) | disallow the use of a class in the template without a corresponding style | |
| [svelte/no-unused-svelte-ignore](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/) | disallow unused svelte-ignore comments | :star: |
| [svelte/no-useless-mustaches](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: |
| [svelte/prefer-destructured-store-props](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-destructured-store-props/) | destructure values from object stores for better change tracking & fewer redraws | :bulb: |
diff --git a/docs/rules.md b/docs/rules.md
index 276f34ff8..356fedbf5 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -56,6 +56,7 @@ These rules relate to better ways of doing things to help you avoid problems:
| [svelte/no-immutable-reactive-statements](./rules/no-immutable-reactive-statements.md) | disallow reactive statements that don't reference reactive values. | |
| [svelte/no-reactive-functions](./rules/no-reactive-functions.md) | it's not necessary to define functions in reactive statements | :bulb: |
| [svelte/no-reactive-literals](./rules/no-reactive-literals.md) | don't assign literal values in reactive statements | :bulb: |
+| [svelte/no-unused-class-name](./rules/no-unused-class-name.md) | disallow the use of a class in the template without a corresponding style | |
| [svelte/no-unused-svelte-ignore](./rules/no-unused-svelte-ignore.md) | disallow unused svelte-ignore comments | :star: |
| [svelte/no-useless-mustaches](./rules/no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: |
| [svelte/prefer-destructured-store-props](./rules/prefer-destructured-store-props.md) | destructure values from object stores for better change tracking & fewer redraws | :bulb: |
diff --git a/docs/rules/no-unused-class-name.md b/docs/rules/no-unused-class-name.md
new file mode 100644
index 000000000..9750cb974
--- /dev/null
+++ b/docs/rules/no-unused-class-name.md
@@ -0,0 +1,64 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "svelte/no-unused-class-name"
+description: "disallow the use of a class in the template without a corresponding style"
+since: "v2.16.0"
+---
+
+# svelte/no-unused-class-name
+
+> disallow the use of a class in the template without a corresponding style
+
+## :book: Rule Details
+
+This rule is aimed at reducing unused classes in the HTML template. While `svelte-check` will produce the `css-unused-selector` if your `
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-svelte v2.16.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/no-unused-class-name.ts)
+- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/no-unused-class-name.ts)
diff --git a/package.json b/package.json
index 2e783995e..bc51ce410 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,7 @@
"postcss": "^8.4.5",
"postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.0",
+ "postcss-selector-parser": "^6.0.11",
"svelte-eslint-parser": "^0.29.0"
},
"devDependencies": {
diff --git a/src/rules/no-unused-class-name.ts b/src/rules/no-unused-class-name.ts
new file mode 100644
index 000000000..a781ac49d
--- /dev/null
+++ b/src/rules/no-unused-class-name.ts
@@ -0,0 +1,122 @@
+import { createRule } from "../utils"
+import type {
+ ESLintCompatiblePostCSSNode,
+ SourceLocation,
+ SvelteAttribute,
+ SvelteDirective,
+ SvelteShorthandAttribute,
+ SvelteSpecialDirective,
+ SvelteSpreadAttribute,
+ SvelteStyleDirective,
+} from "svelte-eslint-parser/lib/ast"
+import type { AnyNode } from "postcss"
+import {
+ default as selectorParser,
+ type Node as SelectorNode,
+} from "postcss-selector-parser"
+
+export default createRule("no-unused-class-name", {
+ meta: {
+ docs: {
+ description:
+ "disallow the use of a class in the template without a corresponding style",
+ category: "Best Practices",
+ recommended: false,
+ },
+ schema: [],
+ messages: {},
+ type: "suggestion",
+ },
+ create(context) {
+ const classesUsedInTemplate: Record = {}
+ let classesUsedInStyle: string[] = []
+ let styleASTavailable = true // Starts out true so that the rule triggers in case of no
diff --git a/tests/fixtures/rules/no-unused-class-name/invalid/same-name-id01-errors.yaml b/tests/fixtures/rules/no-unused-class-name/invalid/same-name-id01-errors.yaml
new file mode 100644
index 000000000..b51081d84
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/invalid/same-name-id01-errors.yaml
@@ -0,0 +1,8 @@
+- message: Unused class "div-class".
+ line: 1
+ column: 1
+ suggestions: null
+- message: Unused class "span-class".
+ line: 3
+ column: 1
+ suggestions: null
diff --git a/tests/fixtures/rules/no-unused-class-name/invalid/same-name-id01-input.svelte b/tests/fixtures/rules/no-unused-class-name/invalid/same-name-id01-input.svelte
new file mode 100644
index 000000000..de285fe09
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/invalid/same-name-id01-input.svelte
@@ -0,0 +1,13 @@
+Hello
+
+World!
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/invalid/unused-class-name01-errors.yaml b/tests/fixtures/rules/no-unused-class-name/invalid/unused-class-name01-errors.yaml
new file mode 100644
index 000000000..b51081d84
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/invalid/unused-class-name01-errors.yaml
@@ -0,0 +1,8 @@
+- message: Unused class "div-class".
+ line: 1
+ column: 1
+ suggestions: null
+- message: Unused class "span-class".
+ line: 3
+ column: 1
+ suggestions: null
diff --git a/tests/fixtures/rules/no-unused-class-name/invalid/unused-class-name01-input.svelte b/tests/fixtures/rules/no-unused-class-name/invalid/unused-class-name01-input.svelte
new file mode 100644
index 000000000..a486966cf
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/invalid/unused-class-name01-input.svelte
@@ -0,0 +1,3 @@
+Hello
+
+World!
diff --git a/tests/fixtures/rules/no-unused-class-name/invalid/used-unrelated-class-name01-errors.yaml b/tests/fixtures/rules/no-unused-class-name/invalid/used-unrelated-class-name01-errors.yaml
new file mode 100644
index 000000000..b51081d84
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/invalid/used-unrelated-class-name01-errors.yaml
@@ -0,0 +1,8 @@
+- message: Unused class "div-class".
+ line: 1
+ column: 1
+ suggestions: null
+- message: Unused class "span-class".
+ line: 3
+ column: 1
+ suggestions: null
diff --git a/tests/fixtures/rules/no-unused-class-name/invalid/used-unrelated-class-name01-input.svelte b/tests/fixtures/rules/no-unused-class-name/invalid/used-unrelated-class-name01-input.svelte
new file mode 100644
index 000000000..829cc3ed3
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/invalid/used-unrelated-class-name01-input.svelte
@@ -0,0 +1,9 @@
+Hello
+
+World!
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/adjacent-sibling-combinator01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/adjacent-sibling-combinator01-input.svelte
new file mode 100644
index 000000000..a535d1b47
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/adjacent-sibling-combinator01-input.svelte
@@ -0,0 +1,9 @@
+Hello
+
+World!
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/child-combinator01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/child-combinator01-input.svelte
new file mode 100644
index 000000000..66f147ea9
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/child-combinator01-input.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/descendant-combinator01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/descendant-combinator01-input.svelte
new file mode 100644
index 000000000..28dcb26a8
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/descendant-combinator01-input.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/general-sibling-combinator01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/general-sibling-combinator01-input.svelte
new file mode 100644
index 000000000..9532972bd
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/general-sibling-combinator01-input.svelte
@@ -0,0 +1,9 @@
+Hello
+
+World!
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/multiple-class-names01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/multiple-class-names01-input.svelte
new file mode 100644
index 000000000..f4b324327
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/multiple-class-names01-input.svelte
@@ -0,0 +1,21 @@
+Hello
+
+World!
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/no-class-name01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/no-class-name01-input.svelte
new file mode 100644
index 000000000..f9297796e
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/no-class-name01-input.svelte
@@ -0,0 +1,3 @@
+Hello
+
+World!
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/pseudo01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/pseudo01-input.svelte
new file mode 100644
index 000000000..881e0aec6
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/pseudo01-input.svelte
@@ -0,0 +1,12 @@
+Hello
+
+World!
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/scss-class-name01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/scss-class-name01-input.svelte
new file mode 100644
index 000000000..45bbe8605
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/scss-class-name01-input.svelte
@@ -0,0 +1,18 @@
+
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/selector-list01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/selector-list01-input.svelte
new file mode 100644
index 000000000..e5b42f52a
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/selector-list01-input.svelte
@@ -0,0 +1,10 @@
+Hello
+
+World!
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/unknown-lang01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/unknown-lang01-input.svelte
new file mode 100644
index 000000000..43e1850c4
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/unknown-lang01-input.svelte
@@ -0,0 +1,18 @@
+
+
+
diff --git a/tests/fixtures/rules/no-unused-class-name/valid/used-class-name01-input.svelte b/tests/fixtures/rules/no-unused-class-name/valid/used-class-name01-input.svelte
new file mode 100644
index 000000000..3f4f103e0
--- /dev/null
+++ b/tests/fixtures/rules/no-unused-class-name/valid/used-class-name01-input.svelte
@@ -0,0 +1,13 @@
+Hello
+
+World!
+
+
diff --git a/tests/src/rules/no-unused-class-name.ts b/tests/src/rules/no-unused-class-name.ts
new file mode 100644
index 000000000..0920a419c
--- /dev/null
+++ b/tests/src/rules/no-unused-class-name.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/no-unused-class-name"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: "module",
+ },
+})
+
+tester.run(
+ "no-unused-class-name",
+ rule as any,
+ loadTestCases("no-unused-class-name"),
+)