Skip to content

Commit 2f1d89a

Browse files
Added the require-event-dispatcher-types rule (#354)
Co-authored-by: Yosuke Ota <[email protected]>
1 parent 3464f23 commit 2f1d89a

15 files changed

+170
-0
lines changed

.changeset/witty-donuts-jog.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-svelte": minor
3+
---
4+
5+
Added the require-event-dispatcher-types rule

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ These rules relate to better ways of doing things to help you avoid problems:
336336
| [svelte/no-unused-svelte-ignore](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/) | disallow unused svelte-ignore comments | :star: |
337337
| [svelte/no-useless-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: |
338338
| [svelte/prefer-destructured-store-props](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-destructured-store-props/) | destructure values from object stores for better change tracking & fewer redraws | :bulb: |
339+
| [svelte/require-event-dispatcher-types](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-event-dispatcher-types/) | require type parameters for createEventDispatcher | |
339340
| [svelte/require-optimized-style-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/) | require style attributes that can be optimized | |
340341
| [svelte/require-stores-init](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-stores-init/) | require initial value in store | |
341342

docs/rules.md

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ These rules relate to better ways of doing things to help you avoid problems:
5555
| [svelte/no-unused-svelte-ignore](./rules/no-unused-svelte-ignore.md) | disallow unused svelte-ignore comments | :star: |
5656
| [svelte/no-useless-mustaches](./rules/no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: |
5757
| [svelte/prefer-destructured-store-props](./rules/prefer-destructured-store-props.md) | destructure values from object stores for better change tracking & fewer redraws | :bulb: |
58+
| [svelte/require-event-dispatcher-types](./rules/require-event-dispatcher-types.md) | require type parameters for createEventDispatcher | |
5859
| [svelte/require-optimized-style-attribute](./rules/require-optimized-style-attribute.md) | require style attributes that can be optimized | |
5960
| [svelte/require-stores-init](./rules/require-stores-init.md) | require initial value in store | |
6061

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "svelte/require-event-dispatcher-types"
5+
description: "require type parameters for createEventDispatcher"
6+
---
7+
8+
# svelte/require-event-dispatcher-types
9+
10+
> require type parameters for createEventDispatcher
11+
12+
## :book: Rule Details
13+
14+
This rule is aimed to enforce type parameters when calling `createEventDispatcher`. Adding types makes all `dispatch` calls as well as all event listeners typechecked. For more information, see the [svelte docs](https://github.com/sveltejs/language-tools/blob/master/docs/preprocessors/typescript.md#typing-component-events).
15+
16+
<ESLintCodeBlock language="javascript">
17+
18+
<!--eslint-skip-->
19+
20+
```svelte
21+
<script lang="ts">
22+
/* eslint svelte/require-event-dispatcher-types: "error" */
23+
24+
import { createEventDispatcher } from "svelte"
25+
26+
/* ✓ GOOD */
27+
const dispatch1 = createEventDispatcher<{ one: never; two: number }>()
28+
const dispatch2 = createEventDispatcher<Record<string, never>>()
29+
const dispatch3 = createEventDispatcher<any>()
30+
const dispatch4 = createEventDispatcher<unknown>()
31+
32+
/* ✗ BAD */
33+
const dispatch5 = createEventDispatcher()
34+
</script>
35+
```
36+
37+
</ESLintCodeBlock>
38+
39+
## :wrench: Options
40+
41+
Nothing.
42+
43+
## :rocket: Version
44+
45+
This rule was introduced in eslint-plugin-svelte v2.16.0
46+
47+
## :mag: Implementation
48+
49+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/require-event-dispatcher-types.ts)
50+
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/require-event-dispatcher-types.ts)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { TSESTree } from "@typescript-eslint/types"
2+
import { ReferenceTracker } from "eslint-utils"
3+
import type { RuleContext } from "../../types"
4+
5+
/** Extract 'svelte createEventDispatcher' references */
6+
export function* extractCreateEventDispatcherReferences(
7+
context: RuleContext,
8+
): Generator<TSESTree.CallExpression, void> {
9+
const referenceTracker = new ReferenceTracker(context.getScope())
10+
for (const { node } of referenceTracker.iterateEsmReferences({
11+
svelte: {
12+
[ReferenceTracker.ESM]: true,
13+
createEventDispatcher: {
14+
[ReferenceTracker.CALL]: true,
15+
},
16+
},
17+
})) {
18+
yield node as TSESTree.CallExpression
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { createRule } from "../utils"
2+
import { getLangValue } from "../utils/ast-utils"
3+
import { extractCreateEventDispatcherReferences } from "./reference-helpers/svelte-createEventDispatcher"
4+
5+
export default createRule("require-event-dispatcher-types", {
6+
meta: {
7+
docs: {
8+
description: "require type parameters for createEventDispatcher",
9+
category: "Best Practices",
10+
recommended: false,
11+
},
12+
schema: [],
13+
messages: {
14+
missingTypeParameter: `Type parameters missing for the createEventDispatcher function call.`,
15+
},
16+
type: "suggestion",
17+
},
18+
create(context) {
19+
let isTs = false
20+
return {
21+
SvelteScriptElement(node) {
22+
const lang = getLangValue(node)?.toLowerCase()
23+
if (lang === "ts" || lang === "typescript") {
24+
isTs = true
25+
}
26+
},
27+
"Program:exit"() {
28+
if (!isTs) {
29+
return
30+
}
31+
for (const node of extractCreateEventDispatcherReferences(context)) {
32+
if (node.typeParameters === undefined) {
33+
context.report({ node, messageId: "missingTypeParameter" })
34+
}
35+
}
36+
},
37+
}
38+
},
39+
})

src/utils/rules.ts

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import noUselessMustaches from "../rules/no-useless-mustaches"
3636
import preferClassDirective from "../rules/prefer-class-directive"
3737
import preferDestructuredStoreProps from "../rules/prefer-destructured-store-props"
3838
import preferStyleDirective from "../rules/prefer-style-directive"
39+
import requireEventDispatcherTypes from "../rules/require-event-dispatcher-types"
3940
import requireOptimizedStyleAttribute from "../rules/require-optimized-style-attribute"
4041
import requireStoreCallbacksUseSetParam from "../rules/require-store-callbacks-use-set-param"
4142
import requireStoreReactiveAccess from "../rules/require-store-reactive-access"
@@ -86,6 +87,7 @@ export const rules = [
8687
preferClassDirective,
8788
preferDestructuredStoreProps,
8889
preferStyleDirective,
90+
requireEventDispatcherTypes,
8991
requireOptimizedStyleAttribute,
9092
requireStoreCallbacksUseSetParam,
9193
requireStoreReactiveAccess,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: Type parameters missing for the createEventDispatcher function call.
2+
line: 4
3+
column: 20
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
import { createEventDispatcher as ced } from "svelte"
3+
4+
const dispatch = ced()
5+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: Type parameters missing for the createEventDispatcher function call.
2+
line: 4
3+
column: 20
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
import { createEventDispatcher } from "svelte"
3+
4+
const dispatch = createEventDispatcher()
5+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script lang="ts">
2+
import { createEventDispatcher } from "svelte"
3+
4+
const dispatch1 = createEventDispatcher<{ one: never; two: number }>()
5+
const dispatch2 = createEventDispatcher<Record<string, never>>()
6+
const dispatch3 = createEventDispatcher<any>()
7+
const dispatch4 = createEventDispatcher<unknown>()
8+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
import { createEventDispatcher } from "svelte"
3+
4+
const dispatch = createEventDispatcher()
5+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
import { createEventDispatcher } from "./unknown"
3+
4+
const dispatch = createEventDispatcher()
5+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { RuleTester } from "eslint"
2+
import rule from "../../../src/rules/require-event-dispatcher-types"
3+
import { loadTestCases } from "../../utils/utils"
4+
5+
const tester = new RuleTester({
6+
parserOptions: {
7+
ecmaVersion: 2020,
8+
sourceType: "module",
9+
},
10+
})
11+
12+
tester.run(
13+
"require-event-dispatcher-types",
14+
rule as any,
15+
loadTestCases("require-event-dispatcher-types"),
16+
)

0 commit comments

Comments
 (0)