Repository: sveltejs/eslint-plugin-svelte
Pull request #1132 (2025-03-17 12:05:25)

feat: add `ignorePropertyPatterns` property and rename `ignorePatterns` to `ignoreTypePatterns` in `no-unused-props` rule
After reconsidering, merging #1135 should also resolve the branded types issue. This is because branded types typically originate from external libraries, meaning their properties are externally defined and should thus be fixed by #1135.

Therefore, this becomes a purely additive feature. However, I still think it’s beneficial to allow ignoring patterns based on both type names and properties.

-> I tested this in a project that uses branded types and confirmed that it’s fixed as expected.

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 52f8a672cd07e23eb6c4f538080acd4fc036c41f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/sveltejs/eslint-plugin-svelte/new/feat/enhance-no-unused-props?filename=.changeset/odd-crabs-suffer.md&value=---%0A%22eslint-plugin-svelte%22%3A%20patch%0A---%0A%0Afeat%3A%20add%20%60ignorePropertyPatterns%60%20property%20and%20rename%20%60ignorePatterns%60%20to%20%60ignoreTypePatterns%60%20in%20%60no-unused-props%60%20rule%0A)


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

## Try the Instant Preview in Online Playground

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

## Install the Instant Preview to Your Local

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

## Published Instant Preview Packages:

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

[View Commit](https://github.com/sveltejs/eslint-plugin-svelte/commit/52f8a672cd07e23eb6c4f538080acd4fc036c41f)
baseballyama: I’ll merge this before US working hours start to minimize the impact.

Patch:
diff --git a/.changeset/crazy-peas-laugh.md b/.changeset/crazy-peas-laugh.md
new file mode 100644
index 0000000..c3bbeea
--- /dev/null
+++ b/.changeset/crazy-peas-laugh.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': minor
+---
+
+feat: add `ignorePropertyPatterns` property and rename `ignorePatterns` to `ignoreTypePatterns` in `no-unused-props` rule. The `ignorePatterns` option existed only for a few hours and is removed by this PR. Technically, this is a breaking change, but we’ll handle it as a minor release since very few users are likely affected.
diff --git a/docs/rules/no-unused-props.md b/docs/rules/no-unused-props.md
index c375aa4..fffe386 100644
--- a/docs/rules/no-unused-props.md
+++ b/docs/rules/no-unused-props.md
@@ -159,14 +159,17 @@ Note: Properties of class types are not checked for usage, as they might be used
   "svelte/no-unused-props": ["error", {
     // Whether to check properties from imported types
     "checkImportedTypes": false,
+    // Patterns to ignore when checking property types
+    "ignoreTypePatterns": [],
     // Patterns to ignore when checking for unused props
-    "ignorePatterns": []
+    "ignorePropertyPatterns": [],
   }]
 }
 ```
 
-- `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.
+- `checkImportedTypes` ... Controls whether to check properties from types defined in external files. Default is `false`, meaning the rule only checks types defined within the component file itself. When set to `true`, the rule will also check properties from imported and extended types.
+- `ignoreTypePatterns` ... Regular expression patterns for type names to exclude from checks. Default is `[]` (no exclusions). Most useful when `checkImportedTypes` is `true`, allowing you to exclude specific imported types (like utility types or third-party types) from being checked.
+- `ignorePropertyPatterns` ... Regular expression patterns for property names to exclude from unused checks. Default is `[]` (no exclusions). Most useful when `checkImportedTypes` is `true`, allowing you to ignore specific properties from external types that shouldn't trigger warnings.
 
 Examples:
 
@@ -187,8 +190,26 @@ Examples:
 ```svelte
 <!-- ✓ Good Examples -->
 <script lang="ts">
-  /* eslint svelte/no-unused-props: ["error", { "ignorePatterns": ["^_"] }] */
-  // Ignore properties starting with underscore
+  /* eslint svelte/no-unused-props: ["error", { "ignoreTypePatterns": ["/^Internal/"] }] */
+  // Ignore properties from types matching the pattern
+  interface InternalConfig {
+    secretKey: string;
+    debugMode: boolean;
+  }
+  interface Props {
+    config: InternalConfig;
+    value: number;
+  }
+  let { config, value }: Props = $props();
+  console.log(value, config.secretKey);
+</script>
+```
+
+```svelte
+<!-- ✓ Good Examples -->
+<script lang="ts">
+  /* eslint svelte/no-unused-props: ["error", { "ignorePropertyPatterns": ["/^_/"] }] */
+  // Ignore properties with names matching the pattern
   interface Props {
     _internal: string;
     value: number;
diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts
index 86e6d65..19e30a3 100644
--- a/packages/eslint-plugin-svelte/src/rule-types.ts
+++ b/packages/eslint-plugin-svelte/src/rule-types.ts
@@ -535,7 +535,8 @@ type SvelteNoUnusedClassName = []|[{
 // ----- svelte/no-unused-props -----
 type SvelteNoUnusedProps = []|[{
   checkImportedTypes?: boolean
-  ignorePatterns?: string[]
+  ignoreTypePatterns?: string[]
+  ignorePropertyPatterns?: string[]
 }]
 // ----- svelte/no-useless-mustaches -----
 type SvelteNoUselessMustaches = []|[{
diff --git a/packages/eslint-plugin-svelte/src/rules/no-unused-props.ts b/packages/eslint-plugin-svelte/src/rules/no-unused-props.ts
index d425026..84faa36 100644
--- a/packages/eslint-plugin-svelte/src/rules/no-unused-props.ts
+++ b/packages/eslint-plugin-svelte/src/rules/no-unused-props.ts
@@ -8,6 +8,8 @@ import { getFilename } from '../utils/compat.js';
 
 type PropertyPath = string[];
 
+let isRemovedWarningShown = false;
+
 export default createRule('no-unused-props', {
 	meta: {
 		docs: {
@@ -23,7 +25,14 @@ export default createRule('no-unused-props', {
 						type: 'boolean',
 						default: false
 					},
-					ignorePatterns: {
+					ignoreTypePatterns: {
+						type: 'array',
+						items: {
+							type: 'string'
+						},
+						default: []
+					},
+					ignorePropertyPatterns: {
 						type: 'array',
 						items: {
 							type: 'string'
@@ -61,23 +70,48 @@ export default createRule('no-unused-props', {
 		}
 
 		const options = context.options[0] ?? {};
+
+		// TODO: Remove in v4
+		// MEMO: `ignorePatterns` was a property that only existed from v3.2.0 to v3.2.2.
+		// From v3.3.0, it was replaced with `ignorePropertyPatterns` and `ignoreTypePatterns`.
+		if (options.ignorePatterns != null && !isRemovedWarningShown) {
+			console.warn(
+				'eslint-plugin-svelte: The `ignorePatterns` option in the `no-unused-props` rule has been removed. Please use `ignorePropertyPatterns` or/and `ignoreTypePatterns` instead.'
+			);
+			isRemovedWarningShown = true;
+		}
+
 		const checkImportedTypes = options.checkImportedTypes ?? false;
-		const ignorePatterns = (options.ignorePatterns ?? []).map((p: string | RegExp) => {
+
+		const ignoreTypePatterns = (options.ignoreTypePatterns ?? []).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));
+		const ignorePropertyPatterns = (options.ignorePropertyPatterns ?? []).map(
+			(p: string | RegExp) => {
+				if (typeof p === 'string') {
+					return toRegExp(p);
+				}
+				return p;
+			}
+		);
+
+		function shouldIgnoreProperty(name: string): boolean {
+			return ignorePropertyPatterns.some((pattern: RegExp) => pattern.test(name));
 		}
 
 		function shouldIgnoreType(type: ts.Type): boolean {
+			function isMatched(name: string): boolean {
+				return ignoreTypePatterns.some((pattern: RegExp) => pattern.test(name));
+			}
+
 			const typeStr = typeChecker.typeToString(type);
 			const symbol = type.getSymbol();
 			const symbolName = symbol?.getName();
-			return shouldIgnore(typeStr) || (symbolName ? shouldIgnore(symbolName) : false);
+			return isMatched(typeStr) || (symbolName ? isMatched(symbolName) : false);
 		}
 
 		function isInternalProperty(symbol: ts.Symbol): boolean {
@@ -223,6 +257,8 @@ export default createRule('no-unused-props', {
 				if (!checkImportedTypes && !isInternalProperty(prop)) continue;
 
 				const propName = prop.getName();
+				if (shouldIgnoreProperty(propName)) continue;
+
 				const currentPath = [...parentPath, propName];
 				const currentPathStr = [...parentPath, propName].join('.');
 


Test patch:
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/custom-config-combination-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/custom-config-combination-config.json
new file mode 100644
index 0000000..57ebeb2
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/custom-config-combination-config.json
@@ -0,0 +1,9 @@
+{
+	"options": [
+		{
+			"checkImportedTypes": true,
+			"ignoreTypePatterns": ["BaseProps"],
+			"ignorePropertyPatterns": ["/^(_|baz)/"]
+		}
+	]
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/custom-config-combination-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/custom-config-combination-errors.yaml
new file mode 100644
index 0000000..52e7467
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/custom-config-combination-errors.yaml
@@ -0,0 +1,12 @@
+- message: "'base' is an unused Props property."
+  line: 10
+  column: 6
+  suggestions: null
+- message: "'bar' in 'my_foo' is an unused property."
+  line: 10
+  column: 6
+  suggestions: null
+- message: "'qux' is an unused Props property."
+  line: 10
+  column: 6
+  suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/custom-config-combination-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/custom-config-combination-input.svelte
new file mode 100644
index 0000000..c0558ee
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/custom-config-combination-input.svelte
@@ -0,0 +1,12 @@
+<script lang="ts">
+	import type { BaseProps, FooDTO } from './shared-types';
+	interface Props {
+		base: BaseProps;
+		my_foo: FooDTO;
+		_my_bar: string;
+		baz: string;
+		qux: string;
+	}
+	let { my_foo }: Props = $props();
+	console.log(my_foo.foo);
+</script>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignore-property-patterns-custom-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignore-property-patterns-custom-config.json
new file mode 100644
index 0000000..65233c1
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignore-property-patterns-custom-config.json
@@ -0,0 +1,7 @@
+{
+	"options": [
+		{
+			"ignorePropertyPatterns": ["^foo$"]
+		}
+	]
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignore-property-patterns-custom-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignore-property-patterns-custom-errors.yaml
new file mode 100644
index 0000000..32c6591
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignore-property-patterns-custom-errors.yaml
@@ -0,0 +1,8 @@
+- message: "'foo' is an unused Props property."
+  line: 8
+  column: 8
+  suggestions: null
+- message: "'_foo' is an unused Props property."
+  line: 8
+  column: 8
+  suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignore-property-patterns-custom-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignore-property-patterns-custom-input.svelte
new file mode 100644
index 0000000..7bee930
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignore-property-patterns-custom-input.svelte
@@ -0,0 +1,9 @@
+<script lang="ts">
+	interface Props {
+		foo: string;
+		_foo: string;
+		bar: string;
+	}
+
+	const { bar }: Props = $props();
+</script>
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
deleted file mode 100644
index c049eb8..0000000
--- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-config.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-	"options": [
-		{
-			"ignorePatterns": [".*DTO$"]
-		}
-	]
-}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-type-patterns-custom-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-type-patterns-custom-config.json
new file mode 100644
index 0000000..ee109aa
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-type-patterns-custom-config.json
@@ -0,0 +1,7 @@
+{
+	"options": [
+		{
+			"ignoreTypePatterns": [".*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-type-patterns-custom-errors.yaml
similarity index 100%
rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-errors.yaml
rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-type-patterns-custom-errors.yaml
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-type-patterns-custom-input.svelte
similarity index 100%
rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-pattern-partial-input.svelte
rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/ignored-type-patterns-custom-input.svelte
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/custom-config-combination-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/custom-config-combination-config.json
new file mode 100644
index 0000000..57ebeb2
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/custom-config-combination-config.json
@@ -0,0 +1,9 @@
+{
+	"options": [
+		{
+			"checkImportedTypes": true,
+			"ignoreTypePatterns": ["BaseProps"],
+			"ignorePropertyPatterns": ["/^(_|baz)/"]
+		}
+	]
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/custom-config-combination-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/custom-config-combination-input.svelte
new file mode 100644
index 0000000..c34bc85
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/custom-config-combination-input.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+	import type { BaseProps, FooDTO } from './shared-types';
+	interface Props {
+		base: BaseProps;
+		my_foo: FooDTO;
+		_my_bar: string;
+		baz: string;
+	}
+	let { base, my_foo }: Props = $props();
+	console.log(base.age, my_foo.foo, my_foo.bar);
+</script>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignore-property-patterns-custom-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignore-property-patterns-custom-config.json
new file mode 100644
index 0000000..c22dc16
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignore-property-patterns-custom-config.json
@@ -0,0 +1,7 @@
+{
+	"options": [
+		{
+			"ignorePropertyPatterns": ["/^[#$@_~]/"]
+		}
+	]
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignore-property-patterns-custom-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignore-property-patterns-custom-input.svelte
new file mode 100644
index 0000000..1dc0ca1
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignore-property-patterns-custom-input.svelte
@@ -0,0 +1,13 @@
+<script lang="ts">
+	interface Props {
+		_internal: string;
+		$store: boolean;
+		'@decorator': string;
+		'#private': number;
+		'~tilde': boolean;
+		normalUsed: string;
+	}
+
+	const { normalUsed }: Props = $props();
+	console.log(normalUsed);
+</script>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignore-property-patterns-default-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignore-property-patterns-default-input.svelte
new file mode 100644
index 0000000..5694cb2
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignore-property-patterns-default-input.svelte
@@ -0,0 +1,8 @@
+<script lang="ts">
+	interface Props {
+		foo: string;
+		bar: string;
+	}
+
+	const { foo, bar }: Props = $props();
+</script>
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
deleted file mode 100644
index 73df77c..0000000
--- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-conditional-type-config.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-	"options": [
-		{
-			"ignorePatterns": ["^Conditional"]
-		}
-	]
-}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-type-patterns-custom-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-type-patterns-custom-config.json
new file mode 100644
index 0000000..3a5c1de
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-type-patterns-custom-config.json
@@ -0,0 +1,7 @@
+{
+	"options": [
+		{
+			"ignoreTypePatterns": ["/^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-type-patterns-custom-input.svelte
similarity index 100%
rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-conditional-type-input.svelte
rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-type-patterns-custom-input.svelte
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-type-patterns-custom2-config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-type-patterns-custom2-config.json
new file mode 100644
index 0000000..347ef2b
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-type-patterns-custom2-config.json
@@ -0,0 +1,7 @@
+{
+	"options": [
+		{
+			"ignoreTypePatterns": ["/^Internal/"]
+		}
+	]
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-type-patterns-custom2-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-type-patterns-custom2-input.svelte
new file mode 100644
index 0000000..0f465ea
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/ignored-type-patterns-custom2-input.svelte
@@ -0,0 +1,12 @@
+<script lang="ts">
+	interface InternalConfig {
+		secretKey: string;
+		debugMode: boolean;
+	}
+	interface Props {
+		config: InternalConfig;
+		value: number;
+	}
+	let { config, value }: Props = $props();
+	console.log(value, config.secretKey);
+</script>
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
index e33a3a2..76d6b33 100644
--- 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
@@ -1,5 +1,6 @@
 export interface BaseProps {
 	name: string;
+	age: number;
 }
 
 export interface FooDTO {