Repository: ota-meshi/eslint-plugin-svelte
Pull request #284 (2022-10-30 06:39:21)

feat: Add `require-store-callbacks-use-set-param` rule
resolve: https://github.com/ota-meshi/eslint-plugin-svelte/issues/267

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: fe6aa71e34c29d638133ada9c4c560bb2e3a7362

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/baseballyama/eslint-plugin-svelte/new/feat/require-store-callbacks-use-set-param?filename=.changeset/yellow-insects-fly.md&value=---%0A%22eslint-plugin-svelte%22%3A%20patch%0A---%0A%0Afeat%3A%20Add%20%60require-store-callbacks-use-set-param%60%20rule%0A)


codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/ota-meshi/eslint-plugin-svelte/pr/284/builds/310085) or the icon next to each commit SHA.

Patch:
diff --git a/.changeset/fluffy-chicken-happen.md b/.changeset/fluffy-chicken-happen.md
new file mode 100644
index 0000000..7120e26
--- /dev/null
+++ b/.changeset/fluffy-chicken-happen.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-svelte": minor
+---
+
+feat: add `require-store-callbacks-use-set-param` rule
diff --git a/.changeset/gentle-fireants-shake.md b/.changeset/gentle-fireants-shake.md
new file mode 100644
index 0000000..82c7ef1
--- /dev/null
+++ b/.changeset/gentle-fireants-shake.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-svelte": patch
+---
+
+fix(deps): update dependency known-css-properties to ^0.26.0
diff --git a/README.md b/README.md
index 4f19f5e..bc6fc8f 100644
--- a/README.md
+++ b/README.md
@@ -271,6 +271,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
 | [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: |
 | [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 |  |
 | [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: |
+| [svelte/require-store-callbacks-use-set-param](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-store-callbacks-use-set-param/) | (no description) |  |
 | [svelte/valid-compile](https://ota-meshi.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | :star: |
 
 ## Security Vulnerability
diff --git a/docs/rules.md b/docs/rules.md
index 9601f45..946972a 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -24,6 +24,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
 | [svelte/no-shorthand-style-property-overrides](./rules/no-shorthand-style-property-overrides.md) | disallow shorthand style properties that override related longhand properties | :star: |
 | [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 |  |
 | [svelte/no-unknown-style-directive-property](./rules/no-unknown-style-directive-property.md) | disallow unknown `style:property` | :star: |
+| [svelte/require-store-callbacks-use-set-param](./rules/require-store-callbacks-use-set-param.md) | (no description) |  |
 | [svelte/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | :star: |
 
 ## Security Vulnerability
diff --git a/docs/rules/require-store-callbacks-use-set-param.md b/docs/rules/require-store-callbacks-use-set-param.md
new file mode 100644
index 0000000..e7300da
--- /dev/null
+++ b/docs/rules/require-store-callbacks-use-set-param.md
@@ -0,0 +1,76 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "svelte/require-store-callbacks-use-set-param"
+description: "store callbacks must use `set` param"
+---
+
+# svelte/require-store-callbacks-use-set-param
+
+> Store callbacks must use `set` param.
+
+## :book: Rule Details
+
+This rule disallows if `readable` / `writable` store's setter function doesn't use `set` parameter.<br>
+This rule doesn't check `derived` store. Therefore if you set a updated value asynchronously, please don't forget to use `set` function.
+
+<ESLintCodeBlock>
+
+<!--eslint-skip-->
+
+```svelte
+<script>
+  /* eslint svelte/require-store-callbacks-use-set-param: "error" */
+  import { readable, writable, derived } from "svelte/store"
+
+  /** ✓ GOOD  */
+  readable(null, (set) => {
+    set(new Date())
+    const interval = setInterval(() => set(new Date()), 1000)
+    return () => clearInterval(interval)
+  })
+
+  // `set` is unused but this rule doesn't report.
+  // For that, please use `no-unused-vars` rule.
+  // refer: https://eslint.org/docs/latest/rules/no-unused-vars
+  readable(false, (set) => true)
+
+  writable(null, (set) => {
+    set(1)
+    return () => { /* no more subscribers */ }
+  })
+
+  writable(false, (set) => true)
+
+  derived(a, ($a) => $a * 2)
+  derived(
+    a,
+    ($a, set) => {
+      setTimeout(() => set($a), 1000)
+    },
+    "one moment...",
+  )
+
+  /** ✗ BAD  */
+  readable(false, () => true)
+  readable(false, (foo) => true)
+
+  writable(false, () => true)
+  writable(false, (foo) => true)
+</script>
+```
+
+</ESLintCodeBlock>
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Svelte - Docs > RUN TIME > svelte/store](https://svelte.dev/docs#run-time-svelte-store)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/require-store-callbacks-use-set-param.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/require-store-callbacks-use-set-param.ts)
diff --git a/package.json b/package.json
index 09bf152..2d5a742 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
     "debug": "^4.3.1",
     "eslint-utils": "^3.0.0",
     "esutils": "^2.0.3",
-    "known-css-properties": "^0.25.0",
+    "known-css-properties": "^0.26.0",
     "postcss": "^8.4.5",
     "postcss-load-config": "^3.1.4",
     "postcss-safe-parser": "^6.0.0",
@@ -134,7 +134,7 @@
     "eslint-plugin-node-dependencies": "^0.10.0",
     "eslint-plugin-prettier": "^4.0.0",
     "eslint-plugin-regexp": "^1.0.0",
-    "eslint-plugin-svelte": "^2.0.0",
+    "eslint-plugin-svelte": "^2.11.0",
     "eslint-plugin-yml": "^1.0.0",
     "eslint-scope": "^7.1.1",
     "estree-walker": "^3.0.0",
@@ -174,7 +174,7 @@
     "access": "public"
   },
   "typeCoverage": {
-    "atLeast": 98.71,
+    "atLeast": 98.72,
     "cache": true,
     "detail": true,
     "ignoreAsAssertion": true,
diff --git a/src/rules/require-store-callbacks-use-set-param.ts b/src/rules/require-store-callbacks-use-set-param.ts
new file mode 100644
index 0000000..005b186
--- /dev/null
+++ b/src/rules/require-store-callbacks-use-set-param.ts
@@ -0,0 +1,44 @@
+import { createRule } from "../utils"
+import { extractStoreReferences } from "./reference-helpers/svelte-store"
+
+export default createRule("require-store-callbacks-use-set-param", {
+  meta: {
+    docs: {
+      description: "store callbacks must use `set` param",
+      category: "Possible Errors",
+      recommended: false,
+    },
+    schema: [],
+    messages: {
+      unexpected: "Store callbacks must use `set` param.",
+    },
+    type: "suggestion",
+  },
+  create(context) {
+    return {
+      Program() {
+        for (const { node } of extractStoreReferences(context, [
+          "readable",
+          "writable",
+        ])) {
+          const [_, fn] = node.arguments
+          if (
+            !fn ||
+            (fn.type !== "ArrowFunctionExpression" &&
+              fn.type !== "FunctionExpression")
+          ) {
+            continue
+          }
+          const param = fn.params[0]
+          if (!param || (param.type === "Identifier" && param.name !== "set")) {
+            context.report({
+              node: fn,
+              loc: fn.loc!,
+              messageId: "unexpected",
+            })
+          }
+        }
+      },
+    }
+  },
+})
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index d4157c2..f4bc55b 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -33,6 +33,7 @@ import preferClassDirective from "../rules/prefer-class-directive"
 import preferDestructuredStoreProps from "../rules/prefer-destructured-store-props"
 import preferStyleDirective from "../rules/prefer-style-directive"
 import requireOptimizedStyleAttribute from "../rules/require-optimized-style-attribute"
+import requireStoreCallbacksUseSetParam from "../rules/require-store-callbacks-use-set-param"
 import requireStoresInit from "../rules/require-stores-init"
 import shorthandAttribute from "../rules/shorthand-attribute"
 import shorthandDirective from "../rules/shorthand-directive"
@@ -76,6 +77,7 @@ export const rules = [
   preferDestructuredStoreProps,
   preferStyleDirective,
   requireOptimizedStyleAttribute,
+  requireStoreCallbacksUseSetParam,
   requireStoresInit,
   shorthandAttribute,
   shorthandDirective,


Test patch:
diff --git a/tests/fixtures/rules/html-self-closing/invalid/presets/html/preset-html-errors.yaml b/tests/fixtures/rules/html-self-closing/invalid/presets/html/preset-html-errors.yaml
new file mode 100644
index 0000000..b22c065
--- /dev/null
+++ b/tests/fixtures/rules/html-self-closing/invalid/presets/html/preset-html-errors.yaml
@@ -0,0 +1,16 @@
+- message: Disallow self-closing on HTML elements.
+  line: 3
+  column: 3
+  suggestions: null
+- message: Require self-closing on HTML void elements.
+  line: 4
+  column: 3
+  suggestions: null
+- message: Disallow self-closing on Svelte custom components.
+  line: 5
+  column: 3
+  suggestions: null
+- message: Require self-closing on Svelte special elements.
+  line: 8
+  column: 1
+  suggestions: null
diff --git a/tests/fixtures/rules/html-self-closing/invalid/presets/none/preset-none-errors.yaml b/tests/fixtures/rules/html-self-closing/invalid/presets/none/preset-none-errors.yaml
new file mode 100644
index 0000000..cde3eff
--- /dev/null
+++ b/tests/fixtures/rules/html-self-closing/invalid/presets/none/preset-none-errors.yaml
@@ -0,0 +1,16 @@
+- message: Disallow self-closing on HTML elements.
+  line: 3
+  column: 3
+  suggestions: null
+- message: Disallow self-closing on Svelte custom components.
+  line: 4
+  column: 3
+  suggestions: null
+- message: Disallow self-closing on HTML void elements.
+  line: 5
+  column: 3
+  suggestions: null
+- message: Disallow self-closing on Svelte special elements.
+  line: 8
+  column: 1
+  suggestions: null
diff --git a/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test01-errors.yaml b/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test01-errors.yaml
new file mode 100644
index 0000000..89483b9
--- /dev/null
+++ b/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test01-errors.yaml
@@ -0,0 +1,16 @@
+- message: Store callbacks must use `set` param.
+  line: 4
+  column: 19
+  suggestions: null
+- message: Store callbacks must use `set` param.
+  line: 5
+  column: 19
+  suggestions: null
+- message: Store callbacks must use `set` param.
+  line: 7
+  column: 19
+  suggestions: null
+- message: Store callbacks must use `set` param.
+  line: 8
+  column: 19
+  suggestions: null
diff --git a/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test01-input.svelte b/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test01-input.svelte
new file mode 100644
index 0000000..ddc584c
--- /dev/null
+++ b/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test01-input.svelte
@@ -0,0 +1,9 @@
+<script>
+  import { readable, writable } from "svelte/store"
+
+  readable(false, () => true)
+  readable(false, (foo) => true)
+
+  writable(false, () => true)
+  writable(false, (foo) => true)
+</script>
diff --git a/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test02-errors.yaml b/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test02-errors.yaml
new file mode 100644
index 0000000..89483b9
--- /dev/null
+++ b/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test02-errors.yaml
@@ -0,0 +1,16 @@
+- message: Store callbacks must use `set` param.
+  line: 4
+  column: 19
+  suggestions: null
+- message: Store callbacks must use `set` param.
+  line: 5
+  column: 19
+  suggestions: null
+- message: Store callbacks must use `set` param.
+  line: 7
+  column: 19
+  suggestions: null
+- message: Store callbacks must use `set` param.
+  line: 8
+  column: 19
+  suggestions: null
diff --git a/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test02-input.svelte b/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test02-input.svelte
new file mode 100644
index 0000000..2f789ab
--- /dev/null
+++ b/tests/fixtures/rules/require-store-callbacks-use-set-param/invalid/test02-input.svelte
@@ -0,0 +1,9 @@
+<script>
+  import { readable, writable } from "svelte/store"
+
+  readable(false, function () {})
+  readable(false, (foo) => function () {})
+
+  writable(false, () => function () {})
+  writable(false, (foo) => function () {})
+</script>
diff --git a/tests/fixtures/rules/require-store-callbacks-use-set-param/valid/test01-input.svelte b/tests/fixtures/rules/require-store-callbacks-use-set-param/valid/test01-input.svelte
new file mode 100644
index 0000000..bd88ad4
--- /dev/null
+++ b/tests/fixtures/rules/require-store-callbacks-use-set-param/valid/test01-input.svelte
@@ -0,0 +1,24 @@
+<script>
+  import { readable, writable, derived } from "svelte/store"
+
+  readable(null, (set) => {
+    set(new Date())
+    const interval = setInterval(() => set(new Date()), 1000)
+    return () => clearInterval(interval)
+  })
+
+  readable(false, (set) => true)
+  writable(null, (set) => {
+    set(0)
+    return () => {}
+  })
+
+  derived(a, ($a) => $a * 2)
+  derived(
+    a,
+    ($a, set) => {
+      setTimeout(() => set($a), 1000)
+    },
+    "one moment...",
+  )
+</script>
diff --git a/tests/fixtures/rules/require-store-callbacks-use-set-param/valid/test02-input.svelte b/tests/fixtures/rules/require-store-callbacks-use-set-param/valid/test02-input.svelte
new file mode 100644
index 0000000..160e18a
--- /dev/null
+++ b/tests/fixtures/rules/require-store-callbacks-use-set-param/valid/test02-input.svelte
@@ -0,0 +1,32 @@
+<script>
+  import { readable, writable, derived } from "svelte/store"
+
+  readable(null, function (set) {
+    set(new Date())
+    const interval = setInterval(() => set(new Date()), 1000)
+    return () => clearInterval(interval)
+  })
+
+  readable(false, function (set) {
+    /* do nothing */
+  })
+
+  writable(0, function (set) {
+    return () => {}
+  })
+  writable(null, function (set) {
+    set(0)
+    return () => {}
+  })
+
+  derived(a, function ($a) {
+    /* do nothing */
+  })
+  derived(
+    a,
+    function ($a, set) {
+      setTimeout(() => set($a), 1000)
+    },
+    "one moment...",
+  )
+</script>
diff --git a/tests/src/rules/require-store-callbacks-use-set-param.ts b/tests/src/rules/require-store-callbacks-use-set-param.ts
new file mode 100644
index 0000000..f2d3b3a
--- /dev/null
+++ b/tests/src/rules/require-store-callbacks-use-set-param.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/require-store-callbacks-use-set-param"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: "module",
+  },
+})
+
+tester.run(
+  "require-store-callbacks-use-set-param",
+  rule as any,
+  loadTestCases("require-store-callbacks-use-set-param"),
+)