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'));