diff --git a/.changeset/witty-donuts-jog.md b/.changeset/witty-donuts-jog.md new file mode 100644 index 000000000..680a059f4 --- /dev/null +++ b/.changeset/witty-donuts-jog.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": minor +--- + +Added the require-event-dispatcher-types rule diff --git a/README.md b/README.md index 5fb32658c..48cfa6ed0 100644 --- a/README.md +++ b/README.md @@ -336,6 +336,7 @@ These rules relate to better ways of doing things to help you avoid problems: | [svelte/no-unused-svelte-ignore](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/) | disallow unused svelte-ignore comments | :star: | | [svelte/no-useless-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: | | [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: | +| [svelte/require-event-dispatcher-types](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-event-dispatcher-types/) | require type parameters for createEventDispatcher | | | [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 | | | [svelte/require-stores-init](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-stores-init/) | require initial value in store | | diff --git a/docs/rules.md b/docs/rules.md index 59e794afc..b3d5e24b3 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -55,6 +55,7 @@ These rules relate to better ways of doing things to help you avoid problems: | [svelte/no-unused-svelte-ignore](./rules/no-unused-svelte-ignore.md) | disallow unused svelte-ignore comments | :star: | | [svelte/no-useless-mustaches](./rules/no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: | | [svelte/prefer-destructured-store-props](./rules/prefer-destructured-store-props.md) | destructure values from object stores for better change tracking & fewer redraws | :bulb: | +| [svelte/require-event-dispatcher-types](./rules/require-event-dispatcher-types.md) | require type parameters for createEventDispatcher | | | [svelte/require-optimized-style-attribute](./rules/require-optimized-style-attribute.md) | require style attributes that can be optimized | | | [svelte/require-stores-init](./rules/require-stores-init.md) | require initial value in store | | diff --git a/docs/rules/require-event-dispatcher-types.md b/docs/rules/require-event-dispatcher-types.md new file mode 100644 index 000000000..344e1dfa5 --- /dev/null +++ b/docs/rules/require-event-dispatcher-types.md @@ -0,0 +1,50 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/require-event-dispatcher-types" +description: "require type parameters for createEventDispatcher" +--- + +# svelte/require-event-dispatcher-types + +> require type parameters for createEventDispatcher + +## :book: Rule Details + +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). + + + + + +```svelte + +``` + + + +## :wrench: Options + +Nothing. + +## :rocket: Version + +This rule was introduced in eslint-plugin-svelte v2.16.0 + +## :mag: Implementation + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/require-event-dispatcher-types.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/require-event-dispatcher-types.ts) diff --git a/src/rules/reference-helpers/svelte-createEventDispatcher.ts b/src/rules/reference-helpers/svelte-createEventDispatcher.ts new file mode 100644 index 000000000..523f64e10 --- /dev/null +++ b/src/rules/reference-helpers/svelte-createEventDispatcher.ts @@ -0,0 +1,20 @@ +import type { TSESTree } from "@typescript-eslint/types" +import { ReferenceTracker } from "eslint-utils" +import type { RuleContext } from "../../types" + +/** Extract 'svelte createEventDispatcher' references */ +export function* extractCreateEventDispatcherReferences( + context: RuleContext, +): Generator { + const referenceTracker = new ReferenceTracker(context.getScope()) + for (const { node } of referenceTracker.iterateEsmReferences({ + svelte: { + [ReferenceTracker.ESM]: true, + createEventDispatcher: { + [ReferenceTracker.CALL]: true, + }, + }, + })) { + yield node as TSESTree.CallExpression + } +} diff --git a/src/rules/require-event-dispatcher-types.ts b/src/rules/require-event-dispatcher-types.ts new file mode 100644 index 000000000..6c27f7101 --- /dev/null +++ b/src/rules/require-event-dispatcher-types.ts @@ -0,0 +1,39 @@ +import { createRule } from "../utils" +import { getLangValue } from "../utils/ast-utils" +import { extractCreateEventDispatcherReferences } from "./reference-helpers/svelte-createEventDispatcher" + +export default createRule("require-event-dispatcher-types", { + meta: { + docs: { + description: "require type parameters for createEventDispatcher", + category: "Best Practices", + recommended: false, + }, + schema: [], + messages: { + missingTypeParameter: `Type parameters missing for the createEventDispatcher function call.`, + }, + type: "suggestion", + }, + create(context) { + let isTs = false + return { + SvelteScriptElement(node) { + const lang = getLangValue(node)?.toLowerCase() + if (lang === "ts" || lang === "typescript") { + isTs = true + } + }, + "Program:exit"() { + if (!isTs) { + return + } + for (const node of extractCreateEventDispatcherReferences(context)) { + if (node.typeParameters === undefined) { + context.report({ node, messageId: "missingTypeParameter" }) + } + } + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 5a9a8224e..ceba1f76b 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -36,6 +36,7 @@ import noUselessMustaches from "../rules/no-useless-mustaches" import preferClassDirective from "../rules/prefer-class-directive" import preferDestructuredStoreProps from "../rules/prefer-destructured-store-props" import preferStyleDirective from "../rules/prefer-style-directive" +import requireEventDispatcherTypes from "../rules/require-event-dispatcher-types" import requireOptimizedStyleAttribute from "../rules/require-optimized-style-attribute" import requireStoreCallbacksUseSetParam from "../rules/require-store-callbacks-use-set-param" import requireStoreReactiveAccess from "../rules/require-store-reactive-access" @@ -86,6 +87,7 @@ export const rules = [ preferClassDirective, preferDestructuredStoreProps, preferStyleDirective, + requireEventDispatcherTypes, requireOptimizedStyleAttribute, requireStoreCallbacksUseSetParam, requireStoreReactiveAccess, diff --git a/tests/fixtures/rules/require-event-dispatcher-types/invalid/import-alias01-errors.yaml b/tests/fixtures/rules/require-event-dispatcher-types/invalid/import-alias01-errors.yaml new file mode 100644 index 000000000..50e84479d --- /dev/null +++ b/tests/fixtures/rules/require-event-dispatcher-types/invalid/import-alias01-errors.yaml @@ -0,0 +1,4 @@ +- message: Type parameters missing for the createEventDispatcher function call. + line: 4 + column: 20 + suggestions: null diff --git a/tests/fixtures/rules/require-event-dispatcher-types/invalid/import-alias01-input.svelte b/tests/fixtures/rules/require-event-dispatcher-types/invalid/import-alias01-input.svelte new file mode 100644 index 000000000..6c57eeba5 --- /dev/null +++ b/tests/fixtures/rules/require-event-dispatcher-types/invalid/import-alias01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/tests/fixtures/rules/require-event-dispatcher-types/invalid/no-types01-errors.yaml b/tests/fixtures/rules/require-event-dispatcher-types/invalid/no-types01-errors.yaml new file mode 100644 index 000000000..50e84479d --- /dev/null +++ b/tests/fixtures/rules/require-event-dispatcher-types/invalid/no-types01-errors.yaml @@ -0,0 +1,4 @@ +- message: Type parameters missing for the createEventDispatcher function call. + line: 4 + column: 20 + suggestions: null diff --git a/tests/fixtures/rules/require-event-dispatcher-types/invalid/no-types01-input.svelte b/tests/fixtures/rules/require-event-dispatcher-types/invalid/no-types01-input.svelte new file mode 100644 index 000000000..64c45eb98 --- /dev/null +++ b/tests/fixtures/rules/require-event-dispatcher-types/invalid/no-types01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/tests/fixtures/rules/require-event-dispatcher-types/valid/has-types01-input.svelte b/tests/fixtures/rules/require-event-dispatcher-types/valid/has-types01-input.svelte new file mode 100644 index 000000000..167bfde74 --- /dev/null +++ b/tests/fixtures/rules/require-event-dispatcher-types/valid/has-types01-input.svelte @@ -0,0 +1,8 @@ + diff --git a/tests/fixtures/rules/require-event-dispatcher-types/valid/no-typescript01-input.svelte b/tests/fixtures/rules/require-event-dispatcher-types/valid/no-typescript01-input.svelte new file mode 100644 index 000000000..957318ec3 --- /dev/null +++ b/tests/fixtures/rules/require-event-dispatcher-types/valid/no-typescript01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/tests/fixtures/rules/require-event-dispatcher-types/valid/non-svelte-dispatcher01-input.svelte b/tests/fixtures/rules/require-event-dispatcher-types/valid/non-svelte-dispatcher01-input.svelte new file mode 100644 index 000000000..e025c70a4 --- /dev/null +++ b/tests/fixtures/rules/require-event-dispatcher-types/valid/non-svelte-dispatcher01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/tests/src/rules/require-event-dispatcher-types.ts b/tests/src/rules/require-event-dispatcher-types.ts new file mode 100644 index 000000000..a9ea97e41 --- /dev/null +++ b/tests/src/rules/require-event-dispatcher-types.ts @@ -0,0 +1,16 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/require-event-dispatcher-types" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run( + "require-event-dispatcher-types", + rule as any, + loadTestCases("require-event-dispatcher-types"), +)