Skip to content

feat: add svelte/Infinite-reactive-loop rule #332

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 13 commits into from
Feb 6, 2023
5 changes: 5 additions & 0 deletions .changeset/slimy-brooms-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-svelte": minor
---

feat: add `svelte/Infinite-reactive-loop` rule
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ These rules relate to possible syntax or logic errors in Svelte code:

| Rule ID | Description | |
|:--------|:------------|:---|
| [svelte/infinite-reactive-loop](https://ota-meshi.github.io/eslint-plugin-svelte/rules/infinite-reactive-loop/) | Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent. | |
| [svelte/no-dom-manipulating](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dom-manipulating/) | disallow DOM manipulating | |
| [svelte/no-dupe-else-if-blocks](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dupe-else-if-blocks/) | disallow duplicate conditions in `{#if}` / `{:else if}` chains | :star: |
| [svelte/no-dupe-on-directives](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dupe-on-directives/) | disallow duplicate `on:` directives | |
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ These rules relate to possible syntax or logic errors in Svelte code:

| Rule ID | Description | |
|:--------|:------------|:---|
| [svelte/infinite-reactive-loop](./rules/infinite-reactive-loop.md) | Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent. | |
| [svelte/no-dom-manipulating](./rules/no-dom-manipulating.md) | disallow DOM manipulating | |
| [svelte/no-dupe-else-if-blocks](./rules/no-dupe-else-if-blocks.md) | disallow duplicate conditions in `{#if}` / `{:else if}` chains | :star: |
| [svelte/no-dupe-on-directives](./rules/no-dupe-on-directives.md) | disallow duplicate `on:` directives | |
Expand Down
100 changes: 100 additions & 0 deletions docs/rules/infinite-reactive-loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
pageClass: "rule-details"
sidebarDepth: 0
title: "svelte/infinite-reactive-loop"
description: "Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent."
---

# svelte/infinite-reactive-loop

> Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent.

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

Svelte runtime prevents calling the same reactive statement twice in a microtask.<br/>
But between different microtask, it doesn't prevent.<br/>
This rule reports those possible infinite loop.

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<script>
/* eslint svelte/infinite-reactive-loop: "error" */
import { count } from "./store.js"
import { tick } from "svelte"
let a = 0

// ✓ GOOD
$: if (a < 10) {
a += 1
$count += 1
}

$: (async () => {
// You can update a state in the same micro task.
a += 1
$count += 1
await new Promise((resolve) => setTimeout(resolve, 100))
})()

$: (async () => {
await doSomething_ok()
})()

const doSomething_ok = async () => {
await fetchFromServer()
// You can update a state even in different microtask
// if you don't refer the state in reactive statement.
a += 1
}

// ✗ BAD
$: (async () => {
await doSomething()
// Do not update a state in different micro task.
a += 1
$count += 1
})()

$: tick(() => {
a = a + 1
$count += 1
})

$: (async () => {
console.log(a)
// This rule checks caller function recursively.
await doSomething_ng_1()
})()

const doSomething_ng_1 = async () => {
a += 1
await fetchFromServer()
doSomething_ng_2()
}

const doSomething_ng_2 = () => {
a += 1
}
</script>
```

</ESLintCodeBlock>

## :wrench: Options

Nothing.

## :books: Further Reading

- [Svelte - Docs > COMPONENT FORMAT > 3. $: marks a statement as reactive](https://svelte.dev/docs#component-format-script-3-$-marks-a-statement-as-reactive)
- [Svelte - Docs > COMPONENT FORMAT > 4. Prefix stores with $ to access their values](https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values)

## :mag: Implementation

- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/infinite-reactive-loop.ts)
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/infinite-reactive-loop.ts)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@
"access": "public"
},
"typeCoverage": {
"atLeast": 99.05,
"atLeast": 99.06,
"cache": true,
"detail": true,
"ignoreAsAssertion": true,
Expand Down
Loading