Skip to content

Commit 1240968

Browse files
authored
feat: Add require-store-callbacks-use-set-param rule (#284)
* feat: add require-store-callbacks-use-set-param * chore: add changeset * chore: tidy up * fix: update review comments * fix: review comment
1 parent 8802e14 commit 1240968

16 files changed

+284
-1
lines changed

.changeset/fluffy-chicken-happen.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-svelte": minor
3+
---
4+
5+
feat: add `require-store-callbacks-use-set-param` rule

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
271271
| [svelte/no-shorthand-style-property-overrides](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-shorthand-style-property-overrides/) | disallow shorthand style properties that override related longhand properties | :star: |
272272
| [svelte/no-store-async](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-store-async/) | disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features | |
273273
| [svelte/no-unknown-style-directive-property](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unknown-style-directive-property/) | disallow unknown `style:property` | :star: |
274+
| [svelte/require-store-callbacks-use-set-param](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-store-callbacks-use-set-param/) | (no description) | |
274275
| [svelte/valid-compile](https://ota-meshi.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | :star: |
275276

276277
## Security Vulnerability

docs/rules.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
2424
| [svelte/no-shorthand-style-property-overrides](./rules/no-shorthand-style-property-overrides.md) | disallow shorthand style properties that override related longhand properties | :star: |
2525
| [svelte/no-store-async](./rules/no-store-async.md) | disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features | |
2626
| [svelte/no-unknown-style-directive-property](./rules/no-unknown-style-directive-property.md) | disallow unknown `style:property` | :star: |
27+
| [svelte/require-store-callbacks-use-set-param](./rules/require-store-callbacks-use-set-param.md) | (no description) | |
2728
| [svelte/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | :star: |
2829

2930
## Security Vulnerability
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "svelte/require-store-callbacks-use-set-param"
5+
description: "store callbacks must use `set` param"
6+
---
7+
8+
# svelte/require-store-callbacks-use-set-param
9+
10+
> Store callbacks must use `set` param.
11+
12+
## :book: Rule Details
13+
14+
This rule disallows if `readable` / `writable` store's setter function doesn't use `set` parameter.<br>
15+
This rule doesn't check `derived` store. Therefore if you set a updated value asynchronously, please don't forget to use `set` function.
16+
17+
<ESLintCodeBlock>
18+
19+
<!--eslint-skip-->
20+
21+
```svelte
22+
<script>
23+
/* eslint svelte/require-store-callbacks-use-set-param: "error" */
24+
import { readable, writable, derived } from "svelte/store"
25+
26+
/** ✓ GOOD */
27+
readable(null, (set) => {
28+
set(new Date())
29+
const interval = setInterval(() => set(new Date()), 1000)
30+
return () => clearInterval(interval)
31+
})
32+
33+
// `set` is unused but this rule doesn't report.
34+
// For that, please use `no-unused-vars` rule.
35+
// refer: https://eslint.org/docs/latest/rules/no-unused-vars
36+
readable(false, (set) => true)
37+
38+
writable(null, (set) => {
39+
set(1)
40+
return () => { /* no more subscribers */ }
41+
})
42+
43+
writable(false, (set) => true)
44+
45+
derived(a, ($a) => $a * 2)
46+
derived(
47+
a,
48+
($a, set) => {
49+
setTimeout(() => set($a), 1000)
50+
},
51+
"one moment...",
52+
)
53+
54+
/** ✗ BAD */
55+
readable(false, () => true)
56+
readable(false, (foo) => true)
57+
58+
writable(false, () => true)
59+
writable(false, (foo) => true)
60+
</script>
61+
```
62+
63+
</ESLintCodeBlock>
64+
65+
## :wrench: Options
66+
67+
Nothing.
68+
69+
## :books: Further Reading
70+
71+
- [Svelte - Docs > RUN TIME > svelte/store](https://svelte.dev/docs#run-time-svelte-store)
72+
73+
## :mag: Implementation
74+
75+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/require-store-callbacks-use-set-param.ts)
76+
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/require-store-callbacks-use-set-param.ts)

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@
174174
"access": "public"
175175
},
176176
"typeCoverage": {
177-
"atLeast": 98.71,
177+
"atLeast": 98.72,
178178
"cache": true,
179179
"detail": true,
180180
"ignoreAsAssertion": true,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { createRule } from "../utils"
2+
import { extractStoreReferences } from "./reference-helpers/svelte-store"
3+
4+
export default createRule("require-store-callbacks-use-set-param", {
5+
meta: {
6+
docs: {
7+
description: "store callbacks must use `set` param",
8+
category: "Possible Errors",
9+
recommended: false,
10+
},
11+
schema: [],
12+
messages: {
13+
unexpected: "Store callbacks must use `set` param.",
14+
},
15+
type: "suggestion",
16+
},
17+
create(context) {
18+
return {
19+
Program() {
20+
for (const { node } of extractStoreReferences(context, [
21+
"readable",
22+
"writable",
23+
])) {
24+
const [_, fn] = node.arguments
25+
if (
26+
!fn ||
27+
(fn.type !== "ArrowFunctionExpression" &&
28+
fn.type !== "FunctionExpression")
29+
) {
30+
continue
31+
}
32+
const param = fn.params[0]
33+
if (!param || (param.type === "Identifier" && param.name !== "set")) {
34+
context.report({
35+
node: fn,
36+
loc: fn.loc!,
37+
messageId: "unexpected",
38+
})
39+
}
40+
}
41+
},
42+
}
43+
},
44+
})

src/utils/rules.ts

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import preferClassDirective from "../rules/prefer-class-directive"
3333
import preferDestructuredStoreProps from "../rules/prefer-destructured-store-props"
3434
import preferStyleDirective from "../rules/prefer-style-directive"
3535
import requireOptimizedStyleAttribute from "../rules/require-optimized-style-attribute"
36+
import requireStoreCallbacksUseSetParam from "../rules/require-store-callbacks-use-set-param"
3637
import requireStoresInit from "../rules/require-stores-init"
3738
import shorthandAttribute from "../rules/shorthand-attribute"
3839
import shorthandDirective from "../rules/shorthand-directive"
@@ -76,6 +77,7 @@ export const rules = [
7677
preferDestructuredStoreProps,
7778
preferStyleDirective,
7879
requireOptimizedStyleAttribute,
80+
requireStoreCallbacksUseSetParam,
7981
requireStoresInit,
8082
shorthandAttribute,
8183
shorthandDirective,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
- message: Disallow self-closing on HTML elements.
2+
line: 3
3+
column: 3
4+
suggestions: null
5+
- message: Require self-closing on HTML void elements.
6+
line: 4
7+
column: 3
8+
suggestions: null
9+
- message: Disallow self-closing on Svelte custom components.
10+
line: 5
11+
column: 3
12+
suggestions: null
13+
- message: Require self-closing on Svelte special elements.
14+
line: 8
15+
column: 1
16+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
- message: Disallow self-closing on HTML elements.
2+
line: 3
3+
column: 3
4+
suggestions: null
5+
- message: Disallow self-closing on Svelte custom components.
6+
line: 4
7+
column: 3
8+
suggestions: null
9+
- message: Disallow self-closing on HTML void elements.
10+
line: 5
11+
column: 3
12+
suggestions: null
13+
- message: Disallow self-closing on Svelte special elements.
14+
line: 8
15+
column: 1
16+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
- message: Store callbacks must use `set` param.
2+
line: 4
3+
column: 19
4+
suggestions: null
5+
- message: Store callbacks must use `set` param.
6+
line: 5
7+
column: 19
8+
suggestions: null
9+
- message: Store callbacks must use `set` param.
10+
line: 7
11+
column: 19
12+
suggestions: null
13+
- message: Store callbacks must use `set` param.
14+
line: 8
15+
column: 19
16+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { readable, writable } from "svelte/store"
3+
4+
readable(false, () => true)
5+
readable(false, (foo) => true)
6+
7+
writable(false, () => true)
8+
writable(false, (foo) => true)
9+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
- message: Store callbacks must use `set` param.
2+
line: 4
3+
column: 19
4+
suggestions: null
5+
- message: Store callbacks must use `set` param.
6+
line: 5
7+
column: 19
8+
suggestions: null
9+
- message: Store callbacks must use `set` param.
10+
line: 7
11+
column: 19
12+
suggestions: null
13+
- message: Store callbacks must use `set` param.
14+
line: 8
15+
column: 19
16+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { readable, writable } from "svelte/store"
3+
4+
readable(false, function () {})
5+
readable(false, (foo) => function () {})
6+
7+
writable(false, () => function () {})
8+
writable(false, (foo) => function () {})
9+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script>
2+
import { readable, writable, derived } from "svelte/store"
3+
4+
readable(null, (set) => {
5+
set(new Date())
6+
const interval = setInterval(() => set(new Date()), 1000)
7+
return () => clearInterval(interval)
8+
})
9+
10+
readable(false, (set) => true)
11+
writable(null, (set) => {
12+
set(0)
13+
return () => {}
14+
})
15+
16+
derived(a, ($a) => $a * 2)
17+
derived(
18+
a,
19+
($a, set) => {
20+
setTimeout(() => set($a), 1000)
21+
},
22+
"one moment...",
23+
)
24+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script>
2+
import { readable, writable, derived } from "svelte/store"
3+
4+
readable(null, function (set) {
5+
set(new Date())
6+
const interval = setInterval(() => set(new Date()), 1000)
7+
return () => clearInterval(interval)
8+
})
9+
10+
readable(false, function (set) {
11+
/* do nothing */
12+
})
13+
14+
writable(0, function (set) {
15+
return () => {}
16+
})
17+
writable(null, function (set) {
18+
set(0)
19+
return () => {}
20+
})
21+
22+
derived(a, function ($a) {
23+
/* do nothing */
24+
})
25+
derived(
26+
a,
27+
function ($a, set) {
28+
setTimeout(() => set($a), 1000)
29+
},
30+
"one moment...",
31+
)
32+
</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-store-callbacks-use-set-param"
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-store-callbacks-use-set-param",
14+
rule as any,
15+
loadTestCases("require-store-callbacks-use-set-param"),
16+
)

0 commit comments

Comments
 (0)