Repository: sveltejs/eslint-plugin-svelte
Pull request #961 (2024-12-14 08:57:36)

fix: support each blocks without an item for `require-each-key` rule

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

Latest commit: 179202197238a3fb0a4357cb7877ad78b40ceb1a

**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/each-blocks-without-an-item?filename=.changeset/lemon-bananas-drive.md&value=---%0A%22eslint-plugin-svelte%22%3A%20patch%0A---%0A%0Afix%3A%20support%20each%20blocks%20without%20an%20item%20for%20%60require-each-key%60%20rule%0A)



Patch:
diff --git a/.changeset/blue-panthers-run.md b/.changeset/blue-panthers-run.md
new file mode 100644
index 0000000..afb628d
--- /dev/null
+++ b/.changeset/blue-panthers-run.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': patch
+---
+
+fix: support each blocks without an item
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 7ae8544..672a612 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -8,6 +8,7 @@
   "changesets": [
     "cool-rockets-allow",
     "fast-coins-fly",
+    "hungry-grapes-shave",
     "lazy-eyes-wait",
     "olive-wolves-travel",
     "quiet-toys-burn",
diff --git a/.changeset/sixty-news-look.md b/.changeset/sixty-news-look.md
new file mode 100644
index 0000000..e70b9cd
--- /dev/null
+++ b/.changeset/sixty-news-look.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': minor
+---
+
+feat: support Svelte5 of `valid-prop-names-in-kit-pages` rule
diff --git a/README.md b/README.md
index a959eda..2601875 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,6 @@ You can check on the [Online DEMO](https://eslint-online-playground.netlify.app/
 > For example, <https://github.com/sveltejs/eslint-plugin-svelte/blob/eslint-plugin-svelte%402.46.0/README.md>
 > and <https://github.com/sveltejs/eslint-plugin-svelte/blob/eslint-plugin-svelte%402.46.0/docs>
 
-**_We are working on experimental support for Svelte v5, but may break with new versions of Svelte v5._**
-
 [![NPM license](https://img.shields.io/npm/l/eslint-plugin-svelte.svg)](https://www.npmjs.com/package/eslint-plugin-svelte)
 [![NPM version](https://img.shields.io/npm/v/eslint-plugin-svelte.svg)](https://www.npmjs.com/package/eslint-plugin-svelte)
 [![NPM downloads](https://img.shields.io/badge/dynamic/json.svg?label=downloads&colorB=green&suffix=/day&query=$.downloads&uri=https://api.npmjs.org//downloads/point/last-day/eslint-plugin-svelte&maxAge=3600)](http://www.npmtrends.com/eslint-plugin-svelte)
@@ -46,6 +44,13 @@ We are working on support for Svelte v5, but it is still an experimental feature
 
 <!--DOCS_IGNORE_START-->
 
+## Versioning policy
+
+This plugin follows [Semantic Versioning](https://semver.org/).  
+However, unlike [ESLint’s Semantic Versioning Policy](https://github.com/eslint/eslint#semantic-versioning-policy), this plugin adds new rules to its configs even in minor releases. For example, if you are using the recommended config, a minor update may add new rules, which could cause new lint errors in your project.  
+While [ESLint’s Semantic Versioning Policy](https://github.com/eslint/eslint#semantic-versioning-policy) only adds new rules to configs in major releases, most users (myself included) don’t regularly monitor new rules. This makes it challenging to manually add them to projects whenever they are introduced.  
+By adding new rules to configs in minor releases, this plugin ensures users can adopt them more easily. If any new rules cause issues, you can simply disable them. I believe this approach helps maintain and improve code quality with minimal effort.
+
 ## Migration Guide
 
 To migrate from `eslint-plugin-svelte` v1, or [`@ota-meshi/eslint-plugin-svelte`](https://www.npmjs.com/package/@ota-meshi/eslint-plugin-svelte), please refer to the [migration guide](https://sveltejs.github.io/eslint-plugin-svelte/migration/).
diff --git a/docs/README.md b/docs/README.md
index 38f8187..e7bea45 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -14,8 +14,6 @@ You can check on the [Online DEMO](https://eslint-online-playground.netlify.app/
 > For example, <https://github.com/sveltejs/eslint-plugin-svelte/blob/eslint-plugin-svelte%402.46.0/README.md>
 > and <https://github.com/sveltejs/eslint-plugin-svelte/blob/eslint-plugin-svelte%402.46.0/docs>
 
-**_We are working on experimental support for Svelte v5, but may break with new versions of Svelte v5._**
-
 [![NPM license](https://img.shields.io/npm/l/eslint-plugin-svelte.svg)](https://www.npmjs.com/package/eslint-plugin-svelte)
 [![NPM version](https://img.shields.io/npm/v/eslint-plugin-svelte.svg)](https://www.npmjs.com/package/eslint-plugin-svelte)
 [![NPM downloads](https://img.shields.io/badge/dynamic/json.svg?label=downloads&colorB=green&suffix=/day&query=$.downloads&uri=https://api.npmjs.org//downloads/point/last-day/eslint-plugin-svelte&maxAge=3600)](http://www.npmtrends.com/eslint-plugin-svelte)
diff --git a/package.json b/package.json
index b14d8cd..d0cd91b 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
   "private": true,
   "license": "MIT",
   "funding": "https://github.com/sponsors/ota-meshi",
-  "packageManager": "pnpm@9.15.0",
+  "packageManager": "pnpm@9.15.2",
   "scripts": {
     "prerelease": "cd packages/eslint-plugin-svelte && pnpm clean && pnpm run build && cd ../.. && cp README.md packages/eslint-plugin-svelte",
     "release": "changeset publish",
diff --git a/packages/eslint-plugin-svelte/CHANGELOG.md b/packages/eslint-plugin-svelte/CHANGELOG.md
index 07ae720..e8067a2 100644
--- a/packages/eslint-plugin-svelte/CHANGELOG.md
+++ b/packages/eslint-plugin-svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
 # eslint-plugin-svelte
 
+## 3.0.0-next.5
+
+### Patch Changes
+
+- [#956](https://github.com/sveltejs/eslint-plugin-svelte/pull/956) [`157ee1f`](https://github.com/sveltejs/eslint-plugin-svelte/commit/157ee1fade79aab88f01d125b0b01cf375da4cd1) Thanks [@baseballyama](https://github.com/baseballyama)! - chore: update `svelte-eslint-parser` to v1.0.0-next.4
+
 ## 3.0.0-next.4
 
 ### Major Changes
diff --git a/packages/eslint-plugin-svelte/package.json b/packages/eslint-plugin-svelte/package.json
index 047cc2e..7dccad6 100644
--- a/packages/eslint-plugin-svelte/package.json
+++ b/packages/eslint-plugin-svelte/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-svelte",
-  "version": "3.0.0-next.4",
+  "version": "3.0.0-next.5",
   "description": "ESLint plugin for Svelte using AST",
   "repository": "git+https://github.com/sveltejs/eslint-plugin-svelte.git",
   "homepage": "https://sveltejs.github.io/eslint-plugin-svelte",
@@ -30,7 +30,7 @@
     "cover": "c8 --reporter=lcov pnpm run test",
     "debug": "pnpm run mocha \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
     "lint": "run-p lint:*",
-    "lint-fix": "pnpm run lint:es --fix && pnpm run lint:style --fix",
+    "lint-fix": "pnpm run lint:es --fix",
     "lint:es": "eslint --cache .",
     "mocha": "pnpm run ts ./node_modules/mocha/bin/mocha.js",
     "new": "pnpm run ts ./tools/new-rule.ts",
diff --git a/packages/eslint-plugin-svelte/src/meta.ts b/packages/eslint-plugin-svelte/src/meta.ts
index 81b3a6c..f2a2a6d 100644
--- a/packages/eslint-plugin-svelte/src/meta.ts
+++ b/packages/eslint-plugin-svelte/src/meta.ts
@@ -2,4 +2,4 @@
 // This file has been automatically generated,
 // in order to update its content execute "pnpm run update"
 export const name = 'eslint-plugin-svelte';
-export const version = '3.0.0-next.4';
+export const version = '3.0.0-next.5';
diff --git a/packages/eslint-plugin-svelte/src/rules/require-each-key.ts b/packages/eslint-plugin-svelte/src/rules/require-each-key.ts
index 00a34f5..7303427 100644
--- a/packages/eslint-plugin-svelte/src/rules/require-each-key.ts
+++ b/packages/eslint-plugin-svelte/src/rules/require-each-key.ts
@@ -15,7 +15,9 @@ export default createRule('require-each-key', {
 	create(context) {
 		return {
 			SvelteEachBlock(node: AST.SvelteEachBlock) {
-				if (node.key == null) {
+				// No need a `key` if an each blocks without an item
+				// see: https://svelte.dev/docs/svelte/each#Each-blocks-without-an-item
+				if (node.context != null && node.key == null) {
 					context.report({
 						node,
 						messageId: 'expectedKey'
diff --git a/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts b/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts
index 77b3e98..ff12df7 100644
--- a/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts
+++ b/packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts
@@ -2,9 +2,27 @@ import type { AST } from 'svelte-eslint-parser';
 import type { TSESTree } from '@typescript-eslint/types';
 import { createRule } from '../utils/index.js';
 import { isKitPageComponent } from '../utils/svelte-kit.js';
+import type { RuleContext } from '../types.js';
 
 const EXPECTED_PROP_NAMES = ['data', 'errors', 'form', 'snapshot'];
 
+function checkProp(node: TSESTree.VariableDeclarator, context: RuleContext) {
+	if (node.id.type !== 'ObjectPattern') return;
+	for (const p of node.id.properties) {
+		if (
+			p.type === 'Property' &&
+			p.value.type === 'Identifier' &&
+			!EXPECTED_PROP_NAMES.includes(p.value.name)
+		) {
+			context.report({
+				node: p.value,
+				loc: p.value.loc,
+				messageId: 'unexpected'
+			});
+		}
+	}
+}
+
 export default createRule('valid-prop-names-in-kit-pages', {
 	meta: {
 		docs: {
@@ -39,6 +57,7 @@ export default createRule('valid-prop-names-in-kit-pages', {
 				isScript = false;
 			},
 
+			// Svelte3,4
 			'ExportNamedDeclaration > VariableDeclaration > VariableDeclarator': (
 				node: TSESTree.VariableDeclarator
 			) => {
@@ -57,20 +76,22 @@ export default createRule('valid-prop-names-in-kit-pages', {
 				}
 
 				// export let { xxx, yyy } = zzz
-				if (node.id.type !== 'ObjectPattern') return;
-				for (const p of node.id.properties) {
-					if (
-						p.type === 'Property' &&
-						p.value.type === 'Identifier' &&
-						!EXPECTED_PROP_NAMES.includes(p.value.name)
-					) {
-						context.report({
-							node: p.value,
-							loc: p.value.loc,
-							messageId: 'unexpected'
-						});
-					}
+				checkProp(node, context);
+			},
+
+			// Svelte5
+			// let { foo, bar } = $props();
+			'VariableDeclaration > VariableDeclarator': (node: TSESTree.VariableDeclarator) => {
+				if (!isScript) return;
+				if (
+					node.init?.type !== 'CallExpression' ||
+					node.init.callee?.type !== 'Identifier' ||
+					node.init.callee?.name !== '$props'
+				) {
+					return;
 				}
+
+				checkProp(node, context);
 			}
 		};
 	}


Test patch:
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/require-each-key/valid/svelte5/_requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/require-each-key/valid/svelte5/_requirements.json
new file mode 100644
index 0000000..0192b10
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/require-each-key/valid/svelte5/_requirements.json
@@ -0,0 +1,3 @@
+{
+	"svelte": ">=5.0.0-0"
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/require-each-key/valid/svelte5/each-blocks-without-an-item-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/require-each-key/valid/svelte5/each-blocks-without-an-item-input.svelte
new file mode 100644
index 0000000..e3310ff
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/require-each-key/valid/svelte5/each-blocks-without-an-item-input.svelte
@@ -0,0 +1,7 @@
+<div class="chess-board">
+	{#each { length: 8 }, rank}
+		{#each { length: 8 }}
+			{rank}
+		{/each}
+	{/each}
+</div>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/+svelte5-test001-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/+svelte5-test001-errors.yaml
new file mode 100644
index 0000000..a5dcdf6
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/+svelte5-test001-errors.yaml
@@ -0,0 +1,8 @@
+- message: disallow props other than data or errors in SvelteKit page components.
+  line: 2
+  column: 8
+  suggestions: null
+- message: disallow props other than data or errors in SvelteKit page components.
+  line: 2
+  column: 13
+  suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/+svelte5-test001-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/+svelte5-test001-input.svelte
new file mode 100644
index 0000000..33f7c90
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/+svelte5-test001-input.svelte
@@ -0,0 +1,5 @@
+<script>
+	let { foo, bar } = $props();
+</script>
+
+{foo}, {bar}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/+svelte5-test001-requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/+svelte5-test001-requirements.json
new file mode 100644
index 0000000..0192b10
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/+svelte5-test001-requirements.json
@@ -0,0 +1,3 @@
+{
+	"svelte": ">=5.0.0-0"
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/_config.json
new file mode 100644
index 0000000..7a71ebe
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5/_config.json
@@ -0,0 +1,13 @@
+{
+	"languageOptions": {
+		"parserOptions": {
+			"svelteConfig": {
+				"kit": {
+					"files": {
+						"routes": "tests/fixtures/rules/valid-prop-names-in-kit-pages/invalid/svelte5"
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/not-page/svelte5/test01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/not-page/svelte5/test01-input.svelte
new file mode 100644
index 0000000..b042de2
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/not-page/svelte5/test01-input.svelte
@@ -0,0 +1,5 @@
+<script>
+	let { data, errors, foo, bar } = $props();
+</script>
+
+{data}, {errors}, {foo}, {bar}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/not-page/svelte5/test01-requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/not-page/svelte5/test01-requirements.json
new file mode 100644
index 0000000..0192b10
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/not-page/svelte5/test01-requirements.json
@@ -0,0 +1,3 @@
+{
+	"svelte": ">=5.0.0-0"
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/svelte5/+test001-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/svelte5/+test001-input.svelte
new file mode 100644
index 0000000..6ed2875
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/svelte5/+test001-input.svelte
@@ -0,0 +1,21 @@
+<script>
+	let { data, errors, form } = $props();
+
+	let comment = '';
+
+	export const snapshot = {
+		capture: () => comment,
+		restore: (value) => (comment = value)
+	};
+</script>
+
+{data}, {errors}
+
+{#if form?.success}
+	<p>Successfully logged in! Welcome back, {data.user.name}</p>
+{/if}
+
+<form method="POST">
+	<textarea bind:value={comment} />
+	<button>Post comment</button>
+</form>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/svelte5/+test001-requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/svelte5/+test001-requirements.json
new file mode 100644
index 0000000..0192b10
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/svelte5/+test001-requirements.json
@@ -0,0 +1,3 @@
+{
+	"svelte": ">=5.0.0-0"
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/svelte5/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/svelte5/_config.json
new file mode 100644
index 0000000..cd38d8e
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/svelte5/_config.json
@@ -0,0 +1,13 @@
+{
+	"languageOptions": {
+		"parserOptions": {
+			"svelteConfig": {
+				"kit": {
+					"files": {
+						"routes": "tests/fixtures/rules/valid-prop-names-in-kit-pages/valid/svelte5"
+					}
+				}
+			}
+		}
+	}
+}