From cfe9251ea6184c02e957d504b3d8e96a54db1c4e Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 2 Feb 2025 23:12:02 +0900 Subject: [PATCH] feat: add `no-unused-props` rule --- .changeset/twelve-beers-talk.md | 5 + README.md | 1 + .../{vite.config.mts => vite.config.ts} | 0 docs/rules.md | 1 + docs/rules/no-unused-props.md | 208 +++++++++++ .../src/configs/flat/recommended.ts | 1 + .../eslint-plugin-svelte/src/rule-types.ts | 10 + .../src/rules/no-unused-props.ts | 340 ++++++++++++++++++ .../eslint-plugin-svelte/src/utils/rules.ts | 2 + .../invalid/_requirements.json | 3 + .../invalid/builtin-shadow-unused-errors.yaml | 4 + .../builtin-shadow-unused-input.svelte | 7 + .../invalid/builtin-types-unused-errors.yaml | 16 + .../invalid/builtin-types-unused-input.svelte | 12 + .../invalid/class-props-unused-errors.yaml | 4 + .../invalid/class-props-unused-input.svelte | 18 + .../invalid/extends-unused-errors.yaml | 8 + .../invalid/extends-unused-input.svelte | 15 + .../invalid/generic-props-unused-errors.yaml | 4 + .../invalid/generic-props-unused-input.svelte | 8 + .../ignored-pattern-partial-config.json | 7 + .../ignored-pattern-partial-errors.yaml | 8 + .../ignored-pattern-partial-input.svelte | 14 + .../invalid/imported-type-check-config.json | 7 + .../invalid/imported-type-check-errors.yaml | 6 + .../invalid/imported-type-check-input.svelte | 8 + .../invalid/imported-type-unused-errors.yaml | 4 + .../invalid/imported-type-unused-input.svelte | 8 + .../index-signature-no-rest-errors.yaml | 9 + .../index-signature-no-rest-input.svelte | 10 + .../invalid/intersection-unused-errors.yaml | 8 + .../invalid/intersection-unused-input.svelte | 19 + .../multiple-extends-unused-errors.yaml | 8 + .../multiple-extends-unused-input.svelte | 19 + .../invalid/nested-unused-errors.yaml | 4 + .../invalid/nested-unused-input.svelte | 10 + .../invalid/optional-unused-errors.yaml | 8 + .../invalid/optional-unused-input.svelte | 14 + .../parent-interface-unused-errors.yaml | 4 + .../parent-interface-unused-input.svelte | 13 + .../no-unused-props/invalid/shared-types.ts | 3 + .../invalid/simple-unused-errors.yaml | 4 + .../invalid/simple-unused-input.svelte | 8 + .../unused-index-signature-errors.yaml | 5 + .../unused-index-signature-input.svelte | 8 + .../no-unused-props/valid/_requirements.json | 3 + .../no-unused-props/valid/any-input.svelte | 5 + .../valid/assignment-input.svelte | 4 + .../no-unused-props/valid/basic-input.svelte | 8 + .../valid/builtin-types-input.svelte | 18 + .../valid/computed-member-input.svelte | 4 + .../valid/computed-property-input.svelte | 7 + .../valid/conditional-type-input.svelte | 16 + .../valid/extends-input.svelte | 14 + .../valid/function-props-input.svelte | 12 + .../ignored-conditional-type-config.json | 7 + .../ignored-conditional-type-input.svelte | 11 + .../valid/imported-type-config.json | 7 + .../valid/imported-type-default-input.svelte | 8 + .../valid/imported-type-explicit-config.json | 7 + .../valid/imported-type-explicit-input.svelte | 8 + .../valid/imported-type-input.svelte | 8 + .../valid/index-signature-rest-input.svelte | 9 + .../valid/intersection-type-input.svelte | 18 + .../valid/js-basic-input.svelte | 4 + .../valid/js-jsdoc-input.svelte | 10 + .../valid/js-no-types-input.svelte | 4 + .../no-unused-props/valid/member-input.svelte | 8 + .../valid/module-script-input.svelte | 11 + .../multiple-index-signatures-input.svelte | 10 + .../valid/nested-props-input.svelte | 13 + .../valid/new-expression-input.svelte | 4 + .../valid/optional-props-input.svelte | 14 + .../valid/record-type-input.svelte | 10 + .../valid/recursive-type-input.svelte | 9 + .../valid/rename-unused-input.svelte | 10 + .../valid/rest-and-index-input.svelte | 9 + .../valid/rest-with-index-input.svelte | 8 + .../no-unused-props/valid/shared-types.ts | 3 + .../valid/template-usage-input.svelte | 16 + .../valid/typed-props-input.svelte | 8 + .../valid/union-type-input.svelte | 11 + .../valid/used-index-signature-input.svelte | 9 + .../tests/src/rules/no-unused-props.ts | 12 + 84 files changed, 1240 insertions(+) create mode 100644 .changeset/twelve-beers-talk.md rename docs-svelte-kit/{vite.config.mts => vite.config.ts} (100%) create mode 100644 docs/rules/no-unused-props.md create mode 100644 packages/eslint-plugin-svelte/src/rules/no-unused-props.ts create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/_requirements.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-shadow-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-shadow-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-types-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-types-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/class-props-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/class-props-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/extends-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/extends-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/generic-props-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/generic-props-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-config.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-config.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/index-signature-no-rest-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/index-signature-no-rest-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/intersection-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/intersection-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/multiple-extends-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/multiple-extends-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/nested-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/nested-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/optional-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/optional-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/parent-interface-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/parent-interface-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/shared-types.ts create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/simple-unused-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/simple-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/unused-index-signature-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/unused-index-signature-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/_requirements.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/any-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/assignment-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/basic-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/builtin-types-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/computed-member-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/computed-property-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/conditional-type-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/extends-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/function-props-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-conditional-type-config.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-conditional-type-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-config.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-default-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-explicit-config.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-explicit-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/index-signature-rest-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/intersection-type-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-basic-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-jsdoc-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-no-types-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/member-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/module-script-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/multiple-index-signatures-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/nested-props-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/new-expression-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/optional-props-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/record-type-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/recursive-type-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rename-unused-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rest-and-index-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rest-with-index-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/shared-types.ts create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/template-usage-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/typed-props-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/union-type-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/used-index-signature-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/src/rules/no-unused-props.ts diff --git a/.changeset/twelve-beers-talk.md b/.changeset/twelve-beers-talk.md new file mode 100644 index 000000000..ed91087d9 --- /dev/null +++ b/.changeset/twelve-beers-talk.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +feat: add `no-unused-props` rule diff --git a/README.md b/README.md index 01eb6bdfa..0081b9c8e 100644 --- a/README.md +++ b/README.md @@ -362,6 +362,7 @@ These rules relate to better ways of doing things to help you avoid problems: | [svelte/no-reactive-literals](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-literals/) | don't assign literal values in reactive statements | :star::bulb: | | [svelte/no-svelte-internal](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-svelte-internal/) | svelte/internal will be removed in Svelte 6. | :star: | | [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-props](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-props/) | Warns about defined Props properties that are unused | :star: | | [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-children-snippet](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-useless-children-snippet/) | disallow explicit children snippet where it's not needed | :star: | | [svelte/no-useless-mustaches](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :star::wrench: | diff --git a/docs-svelte-kit/vite.config.mts b/docs-svelte-kit/vite.config.ts similarity index 100% rename from docs-svelte-kit/vite.config.mts rename to docs-svelte-kit/vite.config.ts diff --git a/docs/rules.md b/docs/rules.md index 5e509b01e..d15cdae9b 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -59,6 +59,7 @@ These rules relate to better ways of doing things to help you avoid problems: | [svelte/no-reactive-literals](./rules/no-reactive-literals.md) | don't assign literal values in reactive statements | :star::bulb: | | [svelte/no-svelte-internal](./rules/no-svelte-internal.md) | svelte/internal will be removed in Svelte 6. | :star: | | [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-props](./rules/no-unused-props.md) | Warns about defined Props properties that are unused | :star: | | [svelte/no-unused-svelte-ignore](./rules/no-unused-svelte-ignore.md) | disallow unused svelte-ignore comments | :star: | | [svelte/no-useless-children-snippet](./rules/no-useless-children-snippet.md) | disallow explicit children snippet where it's not needed | :star: | | [svelte/no-useless-mustaches](./rules/no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :star::wrench: | diff --git a/docs/rules/no-unused-props.md b/docs/rules/no-unused-props.md new file mode 100644 index 000000000..ad9e59256 --- /dev/null +++ b/docs/rules/no-unused-props.md @@ -0,0 +1,208 @@ +--- +pageClass: 'rule-details' +sidebarDepth: 0 +title: 'svelte/no-unused-props' +description: 'Warns about defined Props properties that are unused' +--- + +# svelte/no-unused-props + +> Warns about defined Props properties that are unused + +- :exclamation: **_This rule has not been released yet._** +- :gear: This rule is included in `"plugin:svelte/recommended"`. + +## :book: Rule Details + +This rule reports properties that are defined in Props but never used in the component code. +It helps to detect dead code and improve component clarity by ensuring that every declared prop is utilized. + +This rule checks various usage patterns of props: + +- Direct property access +- Destructuring assignment +- Method calls +- Computed property access +- Object spread +- Constructor calls (new expressions) +- Assignment to other variables +- Index signatures (e.g. `[key: string]: unknown`) + +Additionally, this rule checks if index signatures are properly used. When an index signature is defined but not captured using the rest operator (`...`), the rule will suggest using it. + +Note: Properties of class types are not checked for usage, as they might be used in other parts of the application. + +:warning: This rule requires `@typescript-eslint/parser` to work. Make sure you have installed `@typescript-eslint/parser` and configured it in your ESLint configuration. Therefore, the rule violations cannot be seen in the examples on this page because this documentation does not use `@typescript-eslint/parser`. + + + +```svelte + + +``` + +```svelte + + +``` + +```svelte + + +``` + +```svelte + + +``` + +```svelte + + +``` + +```svelte + + +``` + +```svelte + + +``` + +```svelte + + +``` + +```svelte + + +``` + +```svelte + + +``` + +## :wrench: Options + +```js +{ + "svelte/no-unused-props": ["error", { + // Whether to check properties from imported types + "checkImportedTypes": false, + // Patterns to ignore when checking for unused props + "ignorePatterns": [] + }] +} +``` + +- `checkImportedTypes` ... Controls whether to check properties from imported types. Default is `false`. +- `ignorePatterns` ... Patterns to ignore when checking for unused props. Default is an empty array. + +Examples: + +```svelte + + +``` + +```svelte + + +``` + +## :gear: Required Configuration + +This rule requires `@typescript-eslint/parser` to work. Please refer to the [User Guide](../user-guide.md) for more information. + +## :mag: Implementation + +- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/src/rules/no-unused-props.ts) +- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/tests/src/rules/no-unused-props.ts) diff --git a/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts b/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts index fc7f0890f..74b0e03ef 100644 --- a/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts +++ b/packages/eslint-plugin-svelte/src/configs/flat/recommended.ts @@ -32,6 +32,7 @@ const config: Linter.Config[] = [ 'svelte/no-store-async': 'error', 'svelte/no-svelte-internal': 'error', 'svelte/no-unknown-style-directive-property': 'error', + 'svelte/no-unused-props': 'error', 'svelte/no-unused-svelte-ignore': 'error', 'svelte/no-useless-children-snippet': 'error', 'svelte/no-useless-mustaches': 'error', diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index 30f15c1c2..4eb475231 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -261,6 +261,11 @@ export interface RuleOptions { * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-class-name/ */ 'svelte/no-unused-class-name'?: Linter.RuleEntry + /** + * Warns about defined Props properties that are unused + * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-props/ + */ + 'svelte/no-unused-props'?: Linter.RuleEntry /** * disallow unused svelte-ignore comments * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/ @@ -512,6 +517,11 @@ type SvelteNoUnknownStyleDirectiveProperty = []|[{ type SvelteNoUnusedClassName = []|[{ allowedClassNames?: string[] }] +// ----- svelte/no-unused-props ----- +type SvelteNoUnusedProps = []|[{ + checkImportedTypes?: boolean + ignorePatterns?: string[] +}] // ----- svelte/no-useless-mustaches ----- type SvelteNoUselessMustaches = []|[{ ignoreIncludesComment?: boolean diff --git a/packages/eslint-plugin-svelte/src/rules/no-unused-props.ts b/packages/eslint-plugin-svelte/src/rules/no-unused-props.ts new file mode 100644 index 000000000..1588f3b32 --- /dev/null +++ b/packages/eslint-plugin-svelte/src/rules/no-unused-props.ts @@ -0,0 +1,340 @@ +import { createRule } from '../utils/index.js'; +import { getTypeScriptTools } from '../utils/ts-utils/index.js'; +import type { TSESTree } from '@typescript-eslint/types'; +import type ts from 'typescript'; +import { findVariable } from '../utils/ast-utils.js'; +import { toRegExp } from '../utils/regexp.js'; +import { getFilename } from '../utils/compat.js'; + +type PropertyPath = string[]; + +export default createRule('no-unused-props', { + meta: { + docs: { + description: 'Warns about defined Props properties that are unused', + category: 'Best Practices', + recommended: true + }, + schema: [ + { + type: 'object', + properties: { + checkImportedTypes: { + type: 'boolean', + default: false + }, + ignorePatterns: { + type: 'array', + items: { + type: 'string' + }, + default: [] + } + }, + additionalProperties: false + } + ], + messages: { + unusedProp: "'{{name}}' is an unused Props property.", + unusedNestedProp: "'{{name}}' in '{{parent}}' is an unused property.", + unusedIndexSignature: + 'Index signature is unused. Consider using rest operator (...) to capture remaining properties.' + }, + type: 'suggestion', + conditions: [ + { + svelteVersions: ['5'], + runes: [true, 'undetermined'] + } + ] + }, + create(context) { + const fileName = getFilename(context); + const tools = getTypeScriptTools(context); + if (!tools) { + return {}; + } + + const typeChecker = tools.service.program.getTypeChecker(); + if (!typeChecker) { + return {}; + } + + const options = context.options[0] ?? {}; + const checkImportedTypes = options.checkImportedTypes ?? false; + const ignorePatterns = (options.ignorePatterns ?? []).map((p: string | RegExp) => { + if (typeof p === 'string') { + return toRegExp(p); + } + return p; + }); + + function shouldIgnore(name: string): boolean { + return ignorePatterns.some((pattern: RegExp) => pattern.test(name)); + } + + function shouldIgnoreType(type: ts.Type): boolean { + const typeStr = typeChecker.typeToString(type); + const symbol = type.getSymbol(); + const symbolName = symbol?.getName(); + return shouldIgnore(typeStr) || (symbolName ? shouldIgnore(symbolName) : false); + } + + function isExternalType(type: ts.Type): boolean { + const symbol = type.getSymbol(); + if (!symbol) return false; + + const declarations = symbol.getDeclarations(); + if (!declarations || declarations.length === 0) return false; + + const sourceFile = declarations[0].getSourceFile(); + return sourceFile.fileName !== fileName; + } + + /** + * Extracts property paths from member expressions. + */ + function getPropertyPath(node: TSESTree.Identifier): PropertyPath { + const paths: PropertyPath = []; + let currentNode: TSESTree.Node = node; + let parentNode: TSESTree.Node | null = currentNode.parent ?? null; + + while (parentNode) { + if (parentNode.type === 'MemberExpression' && parentNode.object === currentNode) { + const property = parentNode.property; + if (property.type === 'Identifier') { + paths.push(property.name); + } else if (property.type === 'Literal' && typeof property.value === 'string') { + paths.push(property.value); + } else { + break; + } + } + currentNode = parentNode; + parentNode = currentNode.parent ?? null; + } + + return paths; + } + + /** + * Finds all property access paths for a given variable. + */ + function getUsedNestedPropertyNames(node: TSESTree.Identifier): PropertyPath[] { + const variable = findVariable(context, node); + if (!variable) return []; + + const paths: PropertyPath[] = []; + for (const reference of variable.references) { + if ('identifier' in reference && reference.identifier.type === 'Identifier') { + const referencePath = getPropertyPath(reference.identifier); + paths.push(referencePath); + } + } + return paths; + } + + /** + * Checks if a property is from TypeScript's built-in type definitions. + * These properties should be ignored as they are not user-defined props. + */ + function isBuiltInProperty(prop: ts.Symbol): boolean { + const declarations = prop.getDeclarations(); + if (!declarations || declarations.length === 0) return false; + + const declaration = declarations[0]; + const sourceFile = declaration.getSourceFile(); + if (!sourceFile) return false; + return sourceFile.fileName.includes('node_modules/typescript/lib/'); + } + + function getUsedPropertiesFromPattern(pattern: TSESTree.ObjectPattern): Set { + const usedProps = new Set(); + for (const prop of pattern.properties) { + if (prop.type === 'Property' && prop.key.type === 'Identifier') { + usedProps.add(prop.key.name); + } else if (prop.type === 'RestElement') { + // If there's a rest element, all properties are potentially used + return new Set(); + } + } + return usedProps; + } + + /** + * Check if the type is a class type (has constructor or prototype) + */ + function isClassType(type: ts.Type): boolean { + if (!type) return false; + + // Check if it's a class instance type + if (type.isClass()) return true; + + // Check for constructor signatures + const constructorType = type.getConstructSignatures(); + if (constructorType.length > 0) return true; + + // Check if it has a prototype property + const symbol = type.getSymbol(); + if (symbol?.members?.has('prototype' as ts.__String)) return true; + + return false; + } + + /** + * Recursively checks for unused properties in a type. + */ + function checkUnusedProperties( + type: ts.Type, + usedPaths: PropertyPath[], + usedProps: Set, + reportNode: TSESTree.Node, + parentPath: string[], + checkedTypes: Set, + reportedProps: Set + ) { + // Skip checking if the type itself is a class + if (isClassType(type)) return; + + const typeStr = typeChecker.typeToString(type); + if (checkedTypes.has(typeStr)) return; + checkedTypes.add(typeStr); + if (shouldIgnoreType(type)) return; + if (!checkImportedTypes && isExternalType(type)) return; + + const properties = typeChecker.getPropertiesOfType(type); + const baseTypes = type.getBaseTypes(); + + if (!properties.length && (!baseTypes || baseTypes.length === 0)) { + return; + } + + if (baseTypes) { + for (const baseType of baseTypes) { + checkUnusedProperties( + baseType, + usedPaths, + usedProps, + reportNode, + parentPath, + checkedTypes, + reportedProps + ); + } + } + + for (const prop of properties) { + if (isBuiltInProperty(prop)) continue; + + const propName = prop.getName(); + const currentPath = [...parentPath, propName]; + const currentPathStr = [...parentPath, propName].join('.'); + + if (reportedProps.has(currentPathStr)) continue; + + const propType = typeChecker.getTypeOfSymbol(prop); + const isUsedInPath = usedPaths.some((path) => { + const usedPath = path.join('.'); + return usedPath === currentPathStr || usedPath.startsWith(`${currentPathStr}.`); + }); + + const isUsedInProps = usedProps.has(propName); + + if (!isUsedInPath && !isUsedInProps) { + reportedProps.add(currentPathStr); + context.report({ + node: reportNode, + messageId: parentPath.length ? 'unusedNestedProp' : 'unusedProp', + data: { + name: propName, + parent: parentPath.join('.') + } + }); + } + + const isUsedNested = usedPaths.some((path) => { + return path.join('.').startsWith(`${currentPathStr}.`); + }); + + if (isUsedNested || isUsedInProps) { + checkUnusedProperties( + propType, + usedPaths, + usedProps, + reportNode, + currentPath, + checkedTypes, + reportedProps + ); + } + } + + // Check for unused index signatures only at the root level + if (parentPath.length === 0) { + const indexType = type.getStringIndexType(); + const numberIndexType = type.getNumberIndexType(); + const hasIndexSignature = Boolean(indexType) || Boolean(numberIndexType); + + if (hasIndexSignature && !hasRestElement(usedProps)) { + context.report({ + node: reportNode, + messageId: 'unusedIndexSignature' + }); + } + } + } + + /** + * Returns true if the destructuring pattern includes a rest element, + * which means all remaining properties are potentially used. + */ + function hasRestElement(usedProps: Set): boolean { + return usedProps.size === 0; + } + + return { + 'VariableDeclaration > VariableDeclarator': (node: TSESTree.VariableDeclarator) => { + // Only check $props declarations + if ( + node.init?.type !== 'CallExpression' || + node.init.callee.type !== 'Identifier' || + node.init.callee.name !== '$props' + ) { + return; + } + + const tsNode = tools.service.esTreeNodeToTSNodeMap.get(node) as ts.VariableDeclaration; + if (!tsNode || !tsNode.type) return; + + const propType = typeChecker.getTypeFromTypeNode(tsNode.type); + let usedPaths: PropertyPath[] = []; + let usedProps = new Set(); + + if (node.id.type === 'ObjectPattern') { + usedProps = getUsedPropertiesFromPattern(node.id); + if (usedProps.size === 0) return; + const identifiers = node.id.properties + .filter((p): p is TSESTree.Property => p.type === 'Property') + .map((p) => p.value) + .filter((v): v is TSESTree.Identifier => v.type === 'Identifier'); + for (const identifier of identifiers) { + const paths = getUsedNestedPropertyNames(identifier); + usedPaths.push(...paths); + } + } else if (node.id.type === 'Identifier') { + usedPaths = getUsedNestedPropertyNames(node.id); + } + + checkUnusedProperties( + propType, + usedPaths, + usedProps, + node.id, + [], + new Set(), + new Set() + ); + } + }; + } +}); diff --git a/packages/eslint-plugin-svelte/src/utils/rules.ts b/packages/eslint-plugin-svelte/src/utils/rules.ts index 2a2ddeeda..609859fe1 100644 --- a/packages/eslint-plugin-svelte/src/utils/rules.ts +++ b/packages/eslint-plugin-svelte/src/utils/rules.ts @@ -51,6 +51,7 @@ import noTargetBlank from '../rules/no-target-blank.js'; import noTrailingSpaces from '../rules/no-trailing-spaces.js'; import noUnknownStyleDirectiveProperty from '../rules/no-unknown-style-directive-property.js'; import noUnusedClassName from '../rules/no-unused-class-name.js'; +import noUnusedProps from '../rules/no-unused-props.js'; import noUnusedSvelteIgnore from '../rules/no-unused-svelte-ignore.js'; import noUselessChildrenSnippet from '../rules/no-useless-children-snippet.js'; import noUselessMustaches from '../rules/no-useless-mustaches.js'; @@ -123,6 +124,7 @@ export const rules = [ noTrailingSpaces, noUnknownStyleDirectiveProperty, noUnusedClassName, + noUnusedProps, noUnusedSvelteIgnore, noUselessChildrenSnippet, noUselessMustaches, diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/_requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/_requirements.json new file mode 100644 index 000000000..0192b1098 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/_requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": ">=5.0.0-0" +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-shadow-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-shadow-unused-errors.yaml new file mode 100644 index 000000000..e1178cce7 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-shadow-unused-errors.yaml @@ -0,0 +1,4 @@ +- message: "'toString' is an unused Props property." + line: 5 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-shadow-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-shadow-unused-input.svelte new file mode 100644 index 000000000..2346d9fd3 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-shadow-unused-input.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-types-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-types-unused-errors.yaml new file mode 100644 index 000000000..75deab84b --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-types-unused-errors.yaml @@ -0,0 +1,16 @@ +- message: "'regexp' is an unused Props property." + line: 10 + column: 8 + suggestions: null +- message: "'promise' is an unused Props property." + line: 10 + column: 8 + suggestions: null +- message: "'map' is an unused Props property." + line: 10 + column: 8 + suggestions: null +- message: "'set' is an unused Props property." + line: 10 + column: 8 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-types-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-types-unused-input.svelte new file mode 100644 index 000000000..4d8f54137 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/builtin-types-unused-input.svelte @@ -0,0 +1,12 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/class-props-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/class-props-unused-errors.yaml new file mode 100644 index 000000000..218b1030f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/class-props-unused-errors.yaml @@ -0,0 +1,4 @@ +- message: "'data' is an unused Props property." + line: 16 + column: 8 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/class-props-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/class-props-unused-input.svelte new file mode 100644 index 000000000..3f06f0ab1 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/class-props-unused-input.svelte @@ -0,0 +1,18 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/extends-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/extends-unused-errors.yaml new file mode 100644 index 000000000..0d968bd01 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/extends-unused-errors.yaml @@ -0,0 +1,8 @@ +- message: "'role' is an unused Props property." + line: 13 + column: 6 + suggestions: null +- message: "'email' is an unused Props property." + line: 13 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/extends-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/extends-unused-input.svelte new file mode 100644 index 000000000..3e0120428 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/extends-unused-input.svelte @@ -0,0 +1,15 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/generic-props-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/generic-props-unused-errors.yaml new file mode 100644 index 000000000..7a1f0a0b4 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/generic-props-unused-errors.yaml @@ -0,0 +1,4 @@ +- message: "'extra' is an unused Props property." + line: 6 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/generic-props-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/generic-props-unused-input.svelte new file mode 100644 index 000000000..f65f0de12 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/generic-props-unused-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-config.json new file mode 100644 index 000000000..c049eb81c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "ignorePatterns": [".*DTO$"] + } + ] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-errors.yaml new file mode 100644 index 000000000..a3a80e9ca --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-errors.yaml @@ -0,0 +1,8 @@ +- message: "'metadata' is an unused Props property." + line: 12 + column: 8 + suggestions: null +- message: "'description' is an unused Props property." + line: 12 + column: 8 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-input.svelte new file mode 100644 index 000000000..3711e44da --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-input.svelte @@ -0,0 +1,14 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-config.json new file mode 100644 index 000000000..2b42ee30f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "checkImportedTypes": true + } + ] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-errors.yaml new file mode 100644 index 000000000..2f341476f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-errors.yaml @@ -0,0 +1,6 @@ +- message: "'name' is an unused Props property." + line: 6 + column: 6 + endLine: 6 + endColumn: 20 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-input.svelte new file mode 100644 index 000000000..69c9a24f0 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-check-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-unused-errors.yaml new file mode 100644 index 000000000..d4dfbf838 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-unused-errors.yaml @@ -0,0 +1,4 @@ +- message: "'role' is an unused Props property." + line: 6 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-unused-input.svelte new file mode 100644 index 000000000..90aa495dc --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/imported-type-unused-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/index-signature-no-rest-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/index-signature-no-rest-errors.yaml new file mode 100644 index 000000000..03b89a8e2 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/index-signature-no-rest-errors.yaml @@ -0,0 +1,9 @@ +- message: "'role' is an unused Props property." + line: 8 + column: 6 + suggestions: null +- message: Index signature is unused. Consider using rest operator (...) to + capture remaining properties. + line: 8 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/index-signature-no-rest-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/index-signature-no-rest-input.svelte new file mode 100644 index 000000000..77a1f17ca --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/index-signature-no-rest-input.svelte @@ -0,0 +1,10 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/intersection-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/intersection-unused-errors.yaml new file mode 100644 index 000000000..128798acb --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/intersection-unused-errors.yaml @@ -0,0 +1,8 @@ +- message: "'version' is an unused Props property." + line: 17 + column: 8 + suggestions: null +- message: "'updatedAt' is an unused Props property." + line: 17 + column: 8 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/intersection-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/intersection-unused-input.svelte new file mode 100644 index 000000000..67c81c9b9 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/intersection-unused-input.svelte @@ -0,0 +1,19 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/multiple-extends-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/multiple-extends-unused-errors.yaml new file mode 100644 index 000000000..3fa480dd7 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/multiple-extends-unused-errors.yaml @@ -0,0 +1,8 @@ +- message: "'email' is an unused Props property." + line: 17 + column: 6 + suggestions: null +- message: "'permissions' is an unused Props property." + line: 17 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/multiple-extends-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/multiple-extends-unused-input.svelte new file mode 100644 index 000000000..49607ad1c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/multiple-extends-unused-input.svelte @@ -0,0 +1,19 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/nested-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/nested-unused-errors.yaml new file mode 100644 index 000000000..4c887b833 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/nested-unused-errors.yaml @@ -0,0 +1,4 @@ +- message: "'location' in 'user' is an unused property." + line: 8 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/nested-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/nested-unused-input.svelte new file mode 100644 index 000000000..8293761a6 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/nested-unused-input.svelte @@ -0,0 +1,10 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/optional-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/optional-unused-errors.yaml new file mode 100644 index 000000000..094710d4b --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/optional-unused-errors.yaml @@ -0,0 +1,8 @@ +- message: "'optionalCallback' is an unused Props property." + line: 9 + column: 8 + suggestions: null +- message: "'optionalData' is an unused Props property." + line: 9 + column: 8 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/optional-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/optional-unused-input.svelte new file mode 100644 index 000000000..26af20105 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/optional-unused-input.svelte @@ -0,0 +1,14 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/parent-interface-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/parent-interface-unused-errors.yaml new file mode 100644 index 000000000..691538c7e --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/parent-interface-unused-errors.yaml @@ -0,0 +1,4 @@ +- message: "'id' is an unused Props property." + line: 11 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/parent-interface-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/parent-interface-unused-input.svelte new file mode 100644 index 000000000..be10ed219 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/parent-interface-unused-input.svelte @@ -0,0 +1,13 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/shared-types.ts b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/shared-types.ts new file mode 100644 index 000000000..a60e61ef1 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/shared-types.ts @@ -0,0 +1,3 @@ +export interface BaseProps { + name: string; +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/simple-unused-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/simple-unused-errors.yaml new file mode 100644 index 000000000..e555f296c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/simple-unused-errors.yaml @@ -0,0 +1,4 @@ +- message: "'age' is an unused Props property." + line: 6 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/simple-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/simple-unused-input.svelte new file mode 100644 index 000000000..17d2e8ae9 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/simple-unused-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/unused-index-signature-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/unused-index-signature-errors.yaml new file mode 100644 index 000000000..b8ee310b0 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/unused-index-signature-errors.yaml @@ -0,0 +1,5 @@ +- message: Index signature is unused. Consider using rest operator (...) to + capture remaining properties. + line: 7 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/unused-index-signature-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/unused-index-signature-input.svelte new file mode 100644 index 000000000..8861e91e8 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/unused-index-signature-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/_requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/_requirements.json new file mode 100644 index 000000000..0192b1098 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/_requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": ">=5.0.0-0" +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/any-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/any-input.svelte new file mode 100644 index 000000000..cc3a654ed --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/any-input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/assignment-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/assignment-input.svelte new file mode 100644 index 000000000..2e3aac0f2 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/assignment-input.svelte @@ -0,0 +1,4 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/basic-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/basic-input.svelte new file mode 100644 index 000000000..1d7029be3 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/basic-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/builtin-types-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/builtin-types-input.svelte new file mode 100644 index 000000000..947bf9eaf --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/builtin-types-input.svelte @@ -0,0 +1,18 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/computed-member-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/computed-member-input.svelte new file mode 100644 index 000000000..72b52383d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/computed-member-input.svelte @@ -0,0 +1,4 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/computed-property-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/computed-property-input.svelte new file mode 100644 index 000000000..1e7ad02d5 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/computed-property-input.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/conditional-type-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/conditional-type-input.svelte new file mode 100644 index 000000000..d24aa0451 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/conditional-type-input.svelte @@ -0,0 +1,16 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/extends-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/extends-input.svelte new file mode 100644 index 000000000..67513d71e --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/extends-input.svelte @@ -0,0 +1,14 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/function-props-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/function-props-input.svelte new file mode 100644 index 000000000..6a877eb1f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/function-props-input.svelte @@ -0,0 +1,12 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-conditional-type-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-conditional-type-config.json new file mode 100644 index 000000000..73df77c94 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-conditional-type-config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "ignorePatterns": ["^Conditional"] + } + ] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-conditional-type-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-conditional-type-input.svelte new file mode 100644 index 000000000..e70238fe6 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-conditional-type-input.svelte @@ -0,0 +1,11 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-config.json new file mode 100644 index 000000000..2b42ee30f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "checkImportedTypes": true + } + ] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-default-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-default-input.svelte new file mode 100644 index 000000000..f6527479d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-default-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-explicit-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-explicit-config.json new file mode 100644 index 000000000..b67e452ad --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-explicit-config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "checkImportedTypes": false + } + ] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-explicit-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-explicit-input.svelte new file mode 100644 index 000000000..f6527479d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-explicit-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-input.svelte new file mode 100644 index 000000000..f6527479d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/imported-type-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/index-signature-rest-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/index-signature-rest-input.svelte new file mode 100644 index 000000000..57952541c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/index-signature-rest-input.svelte @@ -0,0 +1,9 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/intersection-type-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/intersection-type-input.svelte new file mode 100644 index 000000000..2a3efb25c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/intersection-type-input.svelte @@ -0,0 +1,18 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-basic-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-basic-input.svelte new file mode 100644 index 000000000..215fd2a99 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-basic-input.svelte @@ -0,0 +1,4 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-jsdoc-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-jsdoc-input.svelte new file mode 100644 index 000000000..63d35ea7d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-jsdoc-input.svelte @@ -0,0 +1,10 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-no-types-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-no-types-input.svelte new file mode 100644 index 000000000..204dc7729 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/js-no-types-input.svelte @@ -0,0 +1,4 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/member-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/member-input.svelte new file mode 100644 index 000000000..af761c57f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/member-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/module-script-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/module-script-input.svelte new file mode 100644 index 000000000..4bf0d56ea --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/module-script-input.svelte @@ -0,0 +1,11 @@ + + + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/multiple-index-signatures-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/multiple-index-signatures-input.svelte new file mode 100644 index 000000000..6c47f2f67 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/multiple-index-signatures-input.svelte @@ -0,0 +1,10 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/nested-props-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/nested-props-input.svelte new file mode 100644 index 000000000..3e7d799e4 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/nested-props-input.svelte @@ -0,0 +1,13 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/new-expression-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/new-expression-input.svelte new file mode 100644 index 000000000..be3441828 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/new-expression-input.svelte @@ -0,0 +1,4 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/optional-props-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/optional-props-input.svelte new file mode 100644 index 000000000..594560158 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/optional-props-input.svelte @@ -0,0 +1,14 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/record-type-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/record-type-input.svelte new file mode 100644 index 000000000..13a5e08c6 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/record-type-input.svelte @@ -0,0 +1,10 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/recursive-type-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/recursive-type-input.svelte new file mode 100644 index 000000000..f4b66e187 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/recursive-type-input.svelte @@ -0,0 +1,9 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rename-unused-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rename-unused-input.svelte new file mode 100644 index 000000000..d8cff60de --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rename-unused-input.svelte @@ -0,0 +1,10 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rest-and-index-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rest-and-index-input.svelte new file mode 100644 index 000000000..45be43430 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rest-and-index-input.svelte @@ -0,0 +1,9 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rest-with-index-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rest-with-index-input.svelte new file mode 100644 index 000000000..7583fb925 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/rest-with-index-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/shared-types.ts b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/shared-types.ts new file mode 100644 index 000000000..a60e61ef1 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/shared-types.ts @@ -0,0 +1,3 @@ +export interface BaseProps { + name: string; +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/template-usage-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/template-usage-input.svelte new file mode 100644 index 000000000..08e3f5a8f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/template-usage-input.svelte @@ -0,0 +1,16 @@ + + +

{props.title}

+

{props.description}

+
    + {#each props.items as item} +
  • {item}
  • + {/each} +
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/typed-props-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/typed-props-input.svelte new file mode 100644 index 000000000..1957cdc4e --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/typed-props-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/union-type-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/union-type-input.svelte new file mode 100644 index 000000000..ebf4f4b38 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/union-type-input.svelte @@ -0,0 +1,11 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/used-index-signature-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/used-index-signature-input.svelte new file mode 100644 index 000000000..b69a0ccf6 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/used-index-signature-input.svelte @@ -0,0 +1,9 @@ + diff --git a/packages/eslint-plugin-svelte/tests/src/rules/no-unused-props.ts b/packages/eslint-plugin-svelte/tests/src/rules/no-unused-props.ts new file mode 100644 index 000000000..104266b8b --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/src/rules/no-unused-props.ts @@ -0,0 +1,12 @@ +import { RuleTester } from '../../utils/eslint-compat.js'; +import rule from '../../../src/rules/no-unused-props.js'; +import { loadTestCases } from '../../utils/utils.js'; + +const tester = new RuleTester({ + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module' + } +}); + +tester.run('no-unused-props', rule as any, loadTestCases('no-unused-props'));