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 @@ +
+
Hello
+
+ + 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 @@ +
+
Hello
+
+ + 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 @@ +
+
Hello
+ + World! +
+ + 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 @@ +
+
Hello
+ + World! +
+ + 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"), +)