Repository: sveltejs/eslint-plugin-svelte
Pull request #1178 (2025-03-31 16:45:19)

fix(no-unused-props): handle alias props name properly
close: https://github.com/sveltejs/eslint-plugin-svelte/issues/1172
close: https://github.com/sveltejs/eslint-plugin-svelte/issues/1182

This PR currently has a false negative in the following case (`test` is unused but not reported):
I’d like to resolve this issue, but wasn’t able to find a good solution in a short time, so I’ve temporarily given up. I’m looking for someone who can help address this issue.

```svelte
<script lang="ts">
	type Props = {
		test: string;
		'aria-label'?: string;
	};

	const { 'aria-label': test }: Props = $props();
</script>

{test}
```

changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 60355cca750827decd5b8c444854002a892957b5

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name                 | Type  |
| -------------------- | ----- |
| eslint-plugin-svelte | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/sveltejs/eslint-plugin-svelte/new/fix/1172?filename=.changeset/honest-colts-relate.md&value=---%0A%22eslint-plugin-svelte%22%3A%20patch%0A---%0A%0Afix(no-unused-props)%3A%20handle%20alias%20props%20name%20properly%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%401178%22%7D#eslint-plugin-svelte%20with%20typescript)

## Install the Instant Preview to Your Local

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

## Published Instant Preview Packages:

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

[View Commit](https://github.com/sveltejs/eslint-plugin-svelte/commit/60355cca750827decd5b8c444854002a892957b5)

Patch:
diff --git a/.changeset/fifty-jeans-thank.md b/.changeset/fifty-jeans-thank.md
new file mode 100644
index 0000000..77ea679
--- /dev/null
+++ b/.changeset/fifty-jeans-thank.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': patch
+---
+
+fix(no-unused-props): handle alias props name properly
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 61b60e9..abcb104 100644
--- a/packages/eslint-plugin-svelte/src/rules/no-unused-props.ts
+++ b/packages/eslint-plugin-svelte/src/rules/no-unused-props.ts
@@ -6,6 +6,7 @@ import { findVariable } from '../utils/ast-utils.js';
 import { toRegExp } from '../utils/regexp.js';
 
 type PropertyPathArray = string[];
+type DeclaredPropertyNames = Set<{ originalName: string; aliasName: string }>;
 
 let isRemovedWarningShown = false;
 
@@ -182,16 +183,27 @@ export default createRule('no-unused-props', {
 			return sourceFile.fileName.includes('node_modules/typescript/lib/');
 		}
 
-		function getUsedPropertyNamesFromPattern(pattern: TSESTree.ObjectPattern): Set<string> {
-			const usedProps = new Set<string>();
+		function getUsedPropertyNamesFromPattern(
+			pattern: TSESTree.ObjectPattern
+		): DeclaredPropertyNames {
+			const usedProps: DeclaredPropertyNames = new Set();
 			for (const prop of pattern.properties) {
-				if (prop.type === 'Property' && prop.key.type === 'Identifier') {
-					usedProps.add(prop.key.name);
+				if (prop.type === 'Property') {
+					if (prop.key.type === 'Identifier') {
+						usedProps.add({ originalName: prop.key.name, aliasName: prop.key.name });
+					} else if (
+						prop.key.type === 'Literal' &&
+						typeof prop.key.value === 'string' &&
+						prop.value.type === 'Identifier'
+					) {
+						usedProps.add({ originalName: prop.key.value, aliasName: prop.value.name });
+					}
 				} else if (prop.type === 'RestElement') {
 					// If there's a rest element, all properties are potentially used
 					return new Set();
 				}
 			}
+
 			return usedProps;
 		}
 
@@ -229,7 +241,7 @@ export default createRule('no-unused-props', {
 		}: {
 			propsType: ts.Type;
 			usedPropertyPaths: string[];
-			declaredPropertyNames: Set<string>;
+			declaredPropertyNames: DeclaredPropertyNames;
 			reportNode: TSESTree.Node;
 			parentPath: string[];
 			checkedPropsTypes: Set<string>;
@@ -287,7 +299,9 @@ export default createRule('no-unused-props', {
 					continue;
 				}
 
-				const isUsedInProps = declaredPropertyNames.has(propName);
+				const isUsedInProps = Array.from(declaredPropertyNames).some((p) => {
+					return p.originalName === propName;
+				});
 
 				if (!isUsedInPath && !isUsedInProps) {
 					reportedPropertyPaths.add(currentPathStr);
@@ -338,8 +352,8 @@ export default createRule('no-unused-props', {
 		 * Returns true if the destructuring pattern includes a rest element,
 		 * which means all remaining properties are potentially used.
 		 */
-		function hasRestElement(usedProps: Set<string>): boolean {
-			return usedProps.size === 0;
+		function hasRestElement(declaredPropertyNames: DeclaredPropertyNames): boolean {
+			return declaredPropertyNames.size === 0;
 		}
 
 		function normalizeUsedPaths(paths: PropertyPathArray[]): PropertyPathArray[] {
@@ -370,7 +384,7 @@ export default createRule('no-unused-props', {
 
 				const propsType = typeChecker.getTypeFromTypeNode(tsNode.type);
 				let usedPropertyPathsArray: PropertyPathArray[] = [];
-				let declaredPropertyNames = new Set<string>();
+				let declaredPropertyNames: DeclaredPropertyNames = new Set();
 
 				if (node.id.type === 'ObjectPattern') {
 					declaredPropertyNames = getUsedPropertyNamesFromPattern(node.id);


Test patch:
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/alias-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/alias-errors.yaml
new file mode 100644
index 0000000..89c1a84
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/alias-errors.yaml
@@ -0,0 +1,4 @@
+- message: "'test' is an unused Props property."
+  line: 7
+  column: 8
+  suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/alias-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/alias-input.svelte
new file mode 100644
index 0000000..59f2319
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/invalid/alias-input.svelte
@@ -0,0 +1,10 @@
+<script lang="ts">
+	type Props = {
+		test: string;
+		'aria-label'?: string;
+	};
+
+	const { 'aria-label': foo }: Props = $props();
+</script>
+
+{foo}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/alias-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/alias-input.svelte
new file mode 100644
index 0000000..857e653
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-unused-props/valid/alias-input.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+	type Props = {
+		test: string;
+		'aria-label'?: string;
+	};
+
+	const { test, 'aria-label': ariaLabel }: Props = $props();
+</script>
+
+<h1>{test}</h1>
+<div aria-label={ariaLabel}>svelte/no-unused-props does not always respect aliases</div>