Repository: sveltejs/eslint-plugin-svelte (ts)

Issue:
Add rule `no-useless-children-snippet`
### Motivation

Any content inside a component tag becomes the `children` snippet. Thus, If you have a component with a children snippet without parameters, that's unnecessary.

### Description

I'd like to add a rule that bans the `children` snippet if it has no parameters

### Examples

```svelte
<script>
</script>

<!-- ✓ GOOD -->
<Foo>
  {#snippet bar()}
    Hello
  {/snippet}
</Foo>

<Foo>
  {#snippet children(val)}
    Hello {val}
  {/snippet}
</Foo>

<Foo>
  Hello
</Foo>

<!-- ✗ BAD -->
<Foo>
  {#snippet children()}
    Hello
  {/snippet}
</Foo>
```


### Additional comments

_No response_

Pull request description:
feat: added the no-useless-children-snippet rule
Closes #909

Patch:
diff --git a/.changeset/loud-zoos-kiss.md b/.changeset/loud-zoos-kiss.md
new file mode 100644
index 0000000..74689a9
--- /dev/null
+++ b/.changeset/loud-zoos-kiss.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': minor
+---
+
+feat: added the no-useless-children-snippet rule
diff --git a/README.md b/README.md
index 72cad69..35c16c8 100644
--- a/README.md
+++ b/README.md
@@ -366,6 +366,7 @@ These rules relate to better ways of doing things to help you avoid problems:
 | [svelte/no-svelte-internal](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-svelte-internal/) | svelte/internal will be removed in Svelte 6. |  |
 | [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-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 |  |
 | [svelte/no-useless-mustaches](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: |
 | [svelte/prefer-const](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-const/) | Require `const` declarations for variables that are never reassigned after declared | :wrench: |
 | [svelte/prefer-destructured-store-props](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-destructured-store-props/) | destructure values from object stores for better change tracking & fewer redraws | :bulb: |
diff --git a/docs/rules.md b/docs/rules.md
index c5f74fc..047b2ab 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -63,6 +63,7 @@ These rules relate to better ways of doing things to help you avoid problems:
 | [svelte/no-svelte-internal](./rules/no-svelte-internal.md)                               | svelte/internal will be removed in Svelte 6.                                                                                              |          |
 | [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-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                                                                                  |          |
 | [svelte/no-useless-mustaches](./rules/no-useless-mustaches.md)                           | disallow unnecessary mustache interpolations                                                                                              | :wrench: |
 | [svelte/prefer-const](./rules/prefer-const.md)                                           | Require `const` declarations for variables that are never reassigned after declared                                                       | :wrench: |
 | [svelte/prefer-destructured-store-props](./rules/prefer-destructured-store-props.md)     | destructure values from object stores for better change tracking & fewer redraws                                                          | :bulb:   |
diff --git a/docs/rules/no-useless-children-snippet.md b/docs/rules/no-useless-children-snippet.md
new file mode 100644
index 0000000..b5e94e0
--- /dev/null
+++ b/docs/rules/no-useless-children-snippet.md
@@ -0,0 +1,57 @@
+---
+pageClass: 'rule-details'
+sidebarDepth: 0
+title: 'svelte/no-useless-children-snippet'
+description: "disallow explicit children snippet where it's not needed"
+---
+
+# svelte/no-useless-children-snippet
+
+> disallow explicit children snippet where it's not needed
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
+
+## :book: Rule Details
+
+Any content inside component tags that is not a snippet declaration implicitly becomes part of the children snippet. Thus, declaring the children snippet explicitly is only necessary when the snippet has parameters.
+
+<!--eslint-skip-->
+
+```svelte
+<script>
+  /* eslint svelte/no-useless-children-snippet: "error" */
+
+  import { Foo } from './Foo.svelte';
+</script>
+
+<!-- ✓ GOOD -->
+<Foo>
+  {#snippet bar()}
+    Hello
+  {/snippet}
+</Foo>
+
+<Foo>
+  {#snippet children(val)}
+    Hello {val}
+  {/snippet}
+</Foo>
+
+<Foo>Hello</Foo>
+
+<!-- ✗ BAD -->
+<Foo>
+  {#snippet children()}
+    Hello
+  {/snippet}
+</Foo>
+```
+
+## :wrench: Options
+
+Nothing.
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/src/rules/no-useless-children-snippet.ts)
+- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/tests/src/rules/no-useless-children-snippet.ts)
diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts
index 0492d4c..b07451a 100644
--- a/packages/eslint-plugin-svelte/src/rule-types.ts
+++ b/packages/eslint-plugin-svelte/src/rule-types.ts
@@ -260,6 +260,11 @@ export interface RuleOptions {
    * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/
    */
   'svelte/no-unused-svelte-ignore'?: Linter.RuleEntry<[]>
+  /**
+   * disallow explicit children snippet where it's not needed
+   * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-useless-children-snippet/
+   */
+  'svelte/no-useless-children-snippet'?: Linter.RuleEntry<[]>
   /**
    * disallow unnecessary mustache interpolations
    * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/
diff --git a/packages/eslint-plugin-svelte/src/rules/no-useless-children-snippet.ts b/packages/eslint-plugin-svelte/src/rules/no-useless-children-snippet.ts
new file mode 100644
index 0000000..a9e730b
--- /dev/null
+++ b/packages/eslint-plugin-svelte/src/rules/no-useless-children-snippet.ts
@@ -0,0 +1,29 @@
+import { createRule } from '../utils/index.js';
+
+export default createRule('no-useless-children-snippet', {
+	meta: {
+		docs: {
+			description: "disallow explicit children snippet where it's not needed",
+			category: 'Best Practices',
+			recommended: false
+		},
+		schema: [],
+		messages: {
+			uselessSnippet: 'Found an unnecessary children snippet.'
+		},
+		type: 'suggestion'
+	},
+	create(context) {
+		return {
+			SvelteSnippetBlock(node) {
+				if (
+					node.parent.type === 'SvelteElement' &&
+					node.id.name === 'children' &&
+					node.params.length === 0
+				) {
+					context.report({ node, messageId: 'uselessSnippet' });
+				}
+			}
+		};
+	}
+});
diff --git a/packages/eslint-plugin-svelte/src/utils/rules.ts b/packages/eslint-plugin-svelte/src/utils/rules.ts
index aab6375..cb0cbe8 100644
--- a/packages/eslint-plugin-svelte/src/utils/rules.ts
+++ b/packages/eslint-plugin-svelte/src/utils/rules.ts
@@ -51,6 +51,7 @@ 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 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';
 import preferClassDirective from '../rules/prefer-class-directive.js';
 import preferConst from '../rules/prefer-const.js';
@@ -121,6 +122,7 @@ export const rules = [
 	noUnknownStyleDirectiveProperty,
 	noUnusedClassName,
 	noUnusedSvelteIgnore,
+	noUselessChildrenSnippet,
 	noUselessMustaches,
 	preferClassDirective,
 	preferConst,


Test patch:
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/invalid/_requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/invalid/_requirements.json
new file mode 100644
index 0000000..4986613
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/invalid/_requirements.json
@@ -0,0 +1,3 @@
+{
+	"svelte": ">=5.0.0"
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/invalid/children-snippet01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/invalid/children-snippet01-errors.yaml
new file mode 100644
index 0000000..1459ef6
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/invalid/children-snippet01-errors.yaml
@@ -0,0 +1,4 @@
+- message: Found an unnecessary children snippet.
+  line: 2
+  column: 3
+  suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/invalid/children-snippet01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/invalid/children-snippet01-input.svelte
new file mode 100644
index 0000000..e1e35ee
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/invalid/children-snippet01-input.svelte
@@ -0,0 +1,5 @@
+<Foo>
+  {#snippet children()}
+    Hello
+  {/snippet}
+</Foo>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/_requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/_requirements.json
new file mode 100644
index 0000000..4986613
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/_requirements.json
@@ -0,0 +1,3 @@
+{
+	"svelte": ">=5.0.0"
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/implicit-snippet01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/implicit-snippet01-input.svelte
new file mode 100644
index 0000000..a1285d0
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/implicit-snippet01-input.svelte
@@ -0,0 +1,3 @@
+<Foo>
+  Hello
+</Foo>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/named-snippet01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/named-snippet01-input.svelte
new file mode 100644
index 0000000..16adcc4
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/named-snippet01-input.svelte
@@ -0,0 +1,5 @@
+<Foo>
+  {#snippet bar()}
+    Hello
+  {/snippet}
+</Foo>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/snippet-with-params01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/snippet-with-params01-input.svelte
new file mode 100644
index 0000000..b421867
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/snippet-with-params01-input.svelte
@@ -0,0 +1,5 @@
+<Foo>
+  {#snippet children(val)}
+    Hello {val}
+  {/snippet}
+</Foo>
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/standalone-snippet01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/standalone-snippet01-input.svelte
new file mode 100644
index 0000000..29bba27
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-useless-children-snippet/valid/standalone-snippet01-input.svelte
@@ -0,0 +1,5 @@
+{#snippet children()}
+  Hello
+{/snippet}
+
+{@render children()}
diff --git a/packages/eslint-plugin-svelte/tests/src/rules/no-useless-children-snippet.ts b/packages/eslint-plugin-svelte/tests/src/rules/no-useless-children-snippet.ts
new file mode 100644
index 0000000..90fae26
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/src/rules/no-useless-children-snippet.ts
@@ -0,0 +1,12 @@
+import { RuleTester } from '../../utils/eslint-compat';
+import rule from '../../../src/rules/no-useless-children-snippet';
+import { loadTestCases } from '../../utils/utils';
+
+const tester = new RuleTester({
+	languageOptions: {
+		ecmaVersion: 2020,
+		sourceType: 'module'
+	}
+});
+
+tester.run('no-useless-children-snippet', rule as any, loadTestCases('no-useless-children-snippet'));