Skip to content
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

feat: Added the experimental-require-strict-events rule #365

Merged
merged 12 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 11 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/tasty-houses-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-svelte": minor
---

Added the experimental-require-strict-events rule
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,14 @@ These rules extend the rules provided by ESLint itself, or other plugins to work
| [svelte/no-inner-declarations](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-inner-declarations/) | disallow variable or `function` declarations in nested blocks | :star: |
| [svelte/no-trailing-spaces](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-trailing-spaces/) | disallow trailing whitespace at the end of lines | :wrench: |

## Experimental

:warning: These rules are considered experimental and may change or be removed in the future:

| Rule ID | Description | |
|:--------|:------------|:---|
| [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

These rules relate to this plugin works:
Expand Down
8 changes: 8 additions & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ These rules extend the rules provided by ESLint itself, or other plugins to work
| [svelte/no-inner-declarations](./rules/no-inner-declarations.md) | disallow variable or `function` declarations in nested blocks | :star: |
| [svelte/no-trailing-spaces](./rules/no-trailing-spaces.md) | disallow trailing whitespace at the end of lines | :wrench: |

## Experimental

:warning: These rules are considered experimental and may change or be removed in the future:

| Rule ID | Description | |
|:--------|:------------|:---|
| [svelte/experimental-require-strict-events](./rules/experimental-require-strict-events.md) | require the strictEvents attribute on <script> tags | |

## System

These rules relate to this plugin works:
Expand Down
47 changes: 47 additions & 0 deletions docs/rules/experimental-require-strict-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
pageClass: "rule-details"
sidebarDepth: 0
title: "svelte/experimental-require-strict-events"
description: "require the strictEvents attribute on <script> tags"
---

# svelte/experimental-require-strict-events

> require the strictEvents attribute on <script> tags
- :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 `strictEvents` attribute on the main `<script>` tag of all components. This attributes enforces typechecking of events dispatched by the component, e.g. making it a typescript error to listen to any non-existent events. Alternatively, the event types may be defined manually by declaring the `$$Events` interface. The `strictEvents` attribute and the `$$Events` interface are experimental and are documented in [svelte RFC #38](https://github.com/dummdidumm/rfcs/blob/ts-typedefs-within-svelte-components/text/ts-typing-props-slots-events.md#typing-events).

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<!-- eslint svelte/experimental-require-strict-events: "error" -->
<!-- ✓ GOOD -->
<script lang="ts" strictEvents>
</script>
<script lang="ts">
interface $$Events {}
</script>
<!-- ✗ BAD -->
<script lang="ts">
</script>
```

</ESLintCodeBlock>

## :wrench: Options

Nothing.

## :mag: Implementation

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

export default createRule("experimental-require-strict-events", {
meta: {
docs: {
description: "require the strictEvents attribute on <script> tags",
category: "Experimental",
recommended: false,
},
schema: [],
messages: {
missingStrictEvents: `The component must have the strictEvents attribute on its <script> tag or it must define the $$Events interface.`,
},
type: "suggestion",
},
create(context) {
let isTs = false
let hasAttribute = false
let hasInterface = false
return {
SvelteScriptElement(node) {
const lang = getLangValue(node)?.toLowerCase()
isTs = lang === "ts" || lang === "typescript"
hasAttribute = findAttribute(node, "strictEvents") !== null
},
TSInterfaceDeclaration(node) {
if (node.id.name === "$$Events") {
hasInterface = true
}
},
"Program:exit"() {
if (isTs && !hasAttribute && !hasInterface) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think the strictEvents attribute should be given even if createEventDispatcher or on:click are not used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I do - the strictEvents attributes makes all "callers" of the component (i.e. components that contain it) have their on:something handlers checked as well - so even if a component has no events, the strictEvents attribute brings a benefit in that the callers will error if they try to add a handler to a non-existent event.

context.report({
loc: {
marekdedic marked this conversation as resolved.
Show resolved Hide resolved
line: 1,
column: 1,
},
messageId: "missingStrictEvents",
})
}
},
}
},
})
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export type RuleCategory =
| "Best Practices"
| "Stylistic Issues"
| "Extension Rules"
| "Experimental"
| "System"

export interface RuleMetaData {
Expand Down
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 experimentalRequireStrictEvents from "../rules/experimental-require-strict-events"
import firstAttributeLinebreak from "../rules/first-attribute-linebreak"
import htmlClosingBracketSpacing from "../rules/html-closing-bracket-spacing"
import htmlQuotes from "../rules/html-quotes"
Expand Down Expand Up @@ -55,6 +56,7 @@ export const rules = [
buttonHasType,
commentDirective,
derivedHasSameInputsOutputs,
experimentalRequireStrictEvents,
firstAttributeLinebreak,
htmlClosingBracketSpacing,
htmlQuotes,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- message:
The component must have the strictEvents attribute on its <script> tag
or it must define the $$Events interface.
line: 1
column: 2
suggestions: null
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<script lang="ts">
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<script lang="ts">
interface $$Events {}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<script lang="ts" strictEvents>
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<script>
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script lang="ts" context="module">
</script>

<script lang="ts" strictEvents>
</script>
16 changes: 16 additions & 0 deletions tests/src/rules/experimental-require-strict-events.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-strict-events"
import { loadTestCases } from "../../utils/utils"

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

tester.run(
"experimental-require-strict-events",
rule as any,
loadTestCases("experimental-require-strict-events"),
)
3 changes: 3 additions & 0 deletions tools/render-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const categories = [
"Best Practices",
"Stylistic Issues",
"Extension Rules",
"Experimental",
"System",
] as const

Expand All @@ -21,6 +22,8 @@ const descriptions: Record<(typeof categories)[number], string> = {
"These rules relate to style guidelines, and are therefore quite subjective:",
"Extension Rules":
"These rules extend the rules provided by ESLint itself, or other plugins to work well in Svelte:",
Experimental:
":warning: These rules are considered experimental and may change or be removed in the future:",
System: "These rules relate to this plugin works:",
}

Expand Down