Skip to content

feat: Added the experimental-require-slot-types rule #368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/strong-wombats-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-svelte": minor
---

feat: added the `svelte/experimental-require-slot-types` rule
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ These rules extend the rules provided by ESLint itself, or other plugins to work

| Rule ID | Description | |
|:--------|:------------|:---|
| [svelte/experimental-require-slot-types](https://ota-meshi.github.io/eslint-plugin-svelte/rules/experimental-require-slot-types/) | require slot type declaration using the `$$Slots` interface | |
| [svelte/experimental-require-strict-events](https://ota-meshi.github.io/eslint-plugin-svelte/rules/experimental-require-strict-events/) | require the strictEvents attribute on `<script>` tags | |

## System
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ These rules extend the rules provided by ESLint itself, or other plugins to work

| Rule ID | Description | |
|:--------|:------------|:---|
| [svelte/experimental-require-slot-types](./rules/experimental-require-slot-types.md) | require slot type declaration using the `$$Slots` interface | |
| [svelte/experimental-require-strict-events](./rules/experimental-require-strict-events.md) | require the strictEvents attribute on `<script>` tags | |

## System
Expand Down
113 changes: 113 additions & 0 deletions docs/rules/experimental-require-slot-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
pageClass: "rule-details"
sidebarDepth: 0
title: "svelte/experimental-require-slot-types"
description: "require slot type declaration using the `$$Slots` interface"
---

# svelte/experimental-require-slot-types

> require slot type declaration using the `$$Slots` interface

- :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

This rule enforces the presence of the `$$Slots` interface if any slots are present in the component. This interface declares all of the used slots and their props and enables typechecking both in the component itself as well as all components that include it.
The `$$Slots` interface is experimental and is documented in [svelte RFC #38](https://github.com/dummdidumm/rfcs/blob/ts-typedefs-within-svelte-components/text/ts-typing-props-slots-events.md#typing-slots).

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<!-- ✓ GOOD -->
<script>
/* eslint svelte/experimental-require-slot-types: "error" */
</script>

<b>No slots here!</b>
```

</ESLintCodeBlock>

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<!-- ✓ GOOD -->
<script>
/* eslint svelte/experimental-require-slot-types: "error" */

interface $$Slots {
default: Record<string, never>;
}
</script>

<slot />
```

</ESLintCodeBlock>

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<!-- ✓ GOOD -->
<script lang="ts">
/* eslint svelte/experimental-require-slot-types: "error" */

interface $$Slots {
default: { prop: boolean; };
}
</script>

<slot prop={true} />
```

</ESLintCodeBlock>

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<!-- ✓ GOOD -->
<script lang="ts">
/* eslint svelte/experimental-require-slot-types: "error" */

interface $$Slots {
named: Record<string, never>;
}
</script>

<slot name = "named" />
```

</ESLintCodeBlock>

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<!-- ✗ BAD -->
<script>
/* eslint svelte/experimental-require-slot-types: "error" */
</script>

<slot />
```

</ESLintCodeBlock>

## :wrench: Options

Nothing.

## :mag: Implementation

- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/experimental-require-slot-types.ts)
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/experimental-require-slot-types.ts)
50 changes: 50 additions & 0 deletions src/rules/experimental-require-slot-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createRule } from "../utils"
import { getLangValue } from "../utils/ast-utils"

export default createRule("experimental-require-slot-types", {
meta: {
docs: {
description:
"require slot type declaration using the `$$Slots` interface",
category: "Experimental",
recommended: false,
},
schema: [],
messages: {
missingSlotsInterface: `The component must define the $$Slots interface.`,
},
type: "suggestion",
},
create(context) {
let isTs = false
let hasSlot = false
let hasInterface = false
return {
SvelteScriptElement(node) {
const lang = getLangValue(node)?.toLowerCase()
isTs = lang === "ts" || lang === "typescript"
},
SvelteElement(node) {
if (node.name.type === "SvelteName" && node.name.name === "slot") {
hasSlot = true
}
},
TSInterfaceDeclaration(node) {
if (node.id.name === "$$Slots") {
hasInterface = true
}
},
"Program:exit"() {
if (isTs && hasSlot && !hasInterface) {
context.report({
loc: {
line: 1,
column: 1,
},
messageId: "missingSlotsInterface",
})
}
},
}
},
})
2 changes: 2 additions & 0 deletions src/utils/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import typescriptEslintNoUnnecessaryCondition from "../rules/@typescript-eslint/
import buttonHasType from "../rules/button-has-type"
import commentDirective from "../rules/comment-directive"
import derivedHasSameInputsOutputs from "../rules/derived-has-same-inputs-outputs"
import experimentalRequireSlotTypes from "../rules/experimental-require-slot-types"
import experimentalRequireStrictEvents from "../rules/experimental-require-strict-events"
import firstAttributeLinebreak from "../rules/first-attribute-linebreak"
import htmlClosingBracketSpacing from "../rules/html-closing-bracket-spacing"
Expand Down Expand Up @@ -56,6 +57,7 @@ export const rules = [
buttonHasType,
commentDirective,
derivedHasSameInputsOutputs,
experimentalRequireSlotTypes,
experimentalRequireStrictEvents,
firstAttributeLinebreak,
htmlClosingBracketSpacing,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- message: The component must define the $$Slots interface.
line: 1
column: 2
suggestions: null
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<script lang="ts">
</script>

<slot />
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script lang="ts">
interface $$Slots {
defalt: Record<string, never>
}
</script>

<slot />
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script lang="ts">
interface $$Slots {
named: Record<string, never>
}
</script>

<slot name="named" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<script lang="ts">
</script>

content
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<script>
</script>

<slot />
16 changes: 16 additions & 0 deletions tests/src/rules/experimental-require-slot-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { RuleTester } from "eslint"
import rule from "../../../src/rules/experimental-require-slot-types"
import { loadTestCases } from "../../utils/utils"

const tester = new RuleTester({
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
},
})

tester.run(
"experimental-require-slot-types",
rule as any,
loadTestCases("experimental-require-slot-types"),
)