Skip to content

Commit

Permalink
feat: Added the experimental-require-slot-types rule (#368)
Browse files Browse the repository at this point in the history
Co-authored-by: ota-meshi <otameshiyo23@gmail.com>
  • Loading branch information
marekdedic and ota-meshi committed Feb 10, 2023
1 parent 5c41254 commit fcb5e31
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 0 deletions.
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"),
)

0 comments on commit fcb5e31

Please sign in to comment.