Repository: ota-meshi/eslint-plugin-svelte
Pull request #225 (2022-08-14 13:55:11)

feat: implement `stores-no-async`
Related to: https://github.com/ota-meshi/eslint-plugin-svelte/issues/198 (`stores-no-async`)

This is the first ESLint rule I have written.
(So I'm sorry if I have a wrong understanding.)
I would be very grateful if you would review it.

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

Latest commit: 796551abdaf94926b6f6eea128e5780bbffe712e

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/feature/stores-no-async?filename=.changeset/twelve-radios-boil.md&value=---%0A%22eslint-plugin-svelte%22%3A%20patch%0A---%0A%0Afeat%3A%20implement%20%60stores-no-async%60%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/225/builds/281629) or the icon next to each commit SHA.

Patch:
diff --git a/.changeset/config.json b/.changeset/config.json
index 1252063..1c83b5d 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -11,5 +11,6 @@
   "access": "restricted",
   "baseBranch": "main",
   "updateInternalDependencies": "patch",
+  "bumpVersionsWithWorkspaceProtocolOnly": true,
   "ignore": []
 }
diff --git a/.changeset/curvy-ants-admire.md b/.changeset/curvy-ants-admire.md
new file mode 100644
index 0000000..db5aec1
--- /dev/null
+++ b/.changeset/curvy-ants-admire.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-svelte": minor
+---
+
+Add svelte/stores-no-async rule
diff --git a/.changeset/swift-ads-type.md b/.changeset/swift-ads-type.md
deleted file mode 100644
index e7394f1..0000000
--- a/.changeset/swift-ads-type.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"eslint-plugin-svelte": minor
----
-
-feat(html-self-closing): add configuration presets
diff --git a/.env-cmdrc b/.env-cmdrc
index 59d3db3..11f12b2 100644
--- a/.env-cmdrc
+++ b/.env-cmdrc
@@ -1,5 +1,8 @@
 {
   "version": {
     "IN_VERSION_SCRIPT": "true"
+  },
+  "debug": {
+    "DEBUG": "eslint-plugin-svelte*"
   }
 }
diff --git a/.eslintignore b/.eslintignore
index 2a12d04..2791e99 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -17,3 +17,5 @@
 /docs-svelte-kit/shim/eslint.mjs
 /docs-svelte-kit/shim/assert.mjs
 !/.*.js
+/docs-svelte-kit/src/routes/*.md
+/docs-svelte-kit/src/routes/**/*.md
diff --git a/.github/workflows/GHPages.yml b/.github/workflows/GHPages.yml
index 1405797..3517354 100644
--- a/.github/workflows/GHPages.yml
+++ b/.github/workflows/GHPages.yml
@@ -33,7 +33,7 @@ jobs:
           export NODE_OPTIONS="--max-old-space-size=8192"
           yarn docs:build
       - name: Setup Pages
-        uses: actions/configure-pages@v1
+        uses: actions/configure-pages@v2
       - name: Upload artifact
         uses: actions/upload-pages-artifact@v1
         with:
diff --git a/.gitignore b/.gitignore
index 7f4f503..5ff1bd2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -109,3 +109,5 @@ dist
 /svelte.config-dist.js
 /docs-svelte-kit/shim/eslint.mjs
 /docs-svelte-kit/shim/assert.mjs
+/docs-svelte-kit/src/routes/*.md
+/docs-svelte-kit/src/routes/**/*.md
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..4f9f9a5
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,4 @@
+.svelte-kit
+.type-coverage
+build
+lib
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..90934e9
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,7 @@
+# eslint-plugin-svelte
+
+## 2.6.0
+
+### Minor Changes
+
+- [#216](https://github.com/ota-meshi/eslint-plugin-svelte/pull/216) [`9d122ea`](https://github.com/ota-meshi/eslint-plugin-svelte/commit/9d122eaee577ffb846051b7cad15f515dbbb2ccb) Thanks [@marekvospel](https://github.com/marekvospel)! - feat(html-self-closing): add configuration presets
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..aada07a
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,71 @@
+# Contributing
+
+Thanks for contributing!
+
+## Installation
+
+```sh
+git clone https://github.com/ota-meshi/eslint-plugin-svelte.git
+cd eslint-plugin-svelte
+yarn
+```
+
+## Running the tests
+
+```sh
+yarn test
+```
+
+To run the tests with debugging log, you can use `yarn test:debug`.
+
+This is an [ESLint](http://eslint.org) plugin. Documentation for the APIs that it uses can be found on ESLint's [Working with Plugins](http://eslint.org/docs/developer-guide/working-with-plugins) page.
+
+This plugin is used to lint itself. The style is checked when `yarn lint` is run, and the build will fail if there are any linting errors. You can use `yarn lint-fix` to fix some linting errors.
+
+## Other Development Tools
+
+- `yarn test` runs tests.
+- `yarn cover` runs tests and measures coverage.
+- `yarn new [new-rule-name]` generate the files needed to implement the new rule.
+- `yarn update` runs in order to update readme and recommended configuration.
+- `yarn docs:watch` launch the document site in development mode.
+
+## Test the Rule
+
+Rule testing almost always uses fixtures.  
+For example, for an `indent` rule, the `.ts` file that runs the test is `tests/src/rules/indent.ts` and the fixture is in `tests/fixtures/rules/indent`.  
+The fixture directory has an `invalid` directory and a `valid` directory.
+
+- The `invalid` directory contains test cases where the rule reports problems.
+- The `valid` directory contains test cases where the rule does not report a problem.
+
+The fixture input file should be named `*-input.svelte`. It is automatically collected and tested.  
+If your test requires configuration, you need to add a json file with the configuration.
+
+- If you want to apply a configuration to `my-test-input.svelte`, add `my-test-config.json`.
+- If you want to apply the same configuration to all the fixtures in that directory, add `_config.json`.
+
+To verify the output of invalid test cases requires `*-errors.json`, and `*-output.svelte` (for auto-fix). However, you don't have to add them yourself. If they do not exist, they will be automatically generated when you run the test. In other words, delete them manually when you want to recreate them.
+
+**Tips**:
+
+If you want to test only one rule, run the following command (for `indent` rule):
+
+```sh
+yarn test -g indent
+```
+
+Take <https://stackoverflow.com/questions/10832031/how-to-run-a-single-test-with-mocha> as reference for details.
+
+If you want to test only `my-test-input.svelte`, add `my-test-config.json` and save `{"only": true}`.  
+(Note that `{"only": true}` must be removed before making a pull request.)
+
+## Commit messages
+
+Please view [commitlint](https://commitlint.js.org) and [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional) for more details.
+
+## Publishing
+
+We're using [changesets](https://github.com/changesets/changesets) for version management, CHANGELOG and releasing automatically.
+
+Please view [changesets-bot](https://github.com/apps/changeset-bot) and its [action](https://github.com/changesets/action) for more details.
diff --git a/README.md b/README.md
index 596f71a..7109961 100644
--- a/README.md
+++ b/README.md
@@ -269,6 +269,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
 | [svelte/no-not-function-handler](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-not-function-handler/) | disallow use of not function in event handler | :star: |
 | [svelte/no-object-in-text-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-object-in-text-mustaches/) | disallow objects in text mustache interpolation | :star: |
 | [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.md) | disallow using async/await inside svelte stores | :star: |
 | [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/valid-compile](https://ota-meshi.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | :star: |
 
@@ -347,43 +348,7 @@ Welcome contributing!
 
 Please use GitHub's Issues/PRs.
 
-### Development Tools
-
-- `yarn test` runs tests.
-- `yarn cover` runs tests and measures coverage.
-- `yarn new [new-rule-name]` generate the files needed to implement the new rule.
-- `yarn update` runs in order to update readme and recommended configuration.
-- `yarn docs:watch` launch the document site in development mode.
-
-### Test the Rule
-
-Rule testing almost always uses fixtures.  
-For example, for an `indent` rule, the `.ts` file that runs the test is `tests/src/rules/indent.ts` and the fixture is in `tests/fixtures/rules/indent`.  
-The fixture directory has an `invalid` directory and a `valid` directory.
-
-- The `invalid` directory contains test cases where the rule reports problems.
-- The `valid` directory contains test cases where the rule does not report a problem.
-
-The fixture input file should be named `*-input.svelte`. It is automatically collected and tested.  
-If your test requires configuration, you need to add a json file with the configuration.
-
-- If you want to apply a configuration to `my-test-input.svelte`, add `my-test-config.json`.
-- If you want to apply the same configuration to all the fixtures in that directory, add `_config.json`.
-
-To verify the output of invalid test cases requires `*-errors.json`, and `*-output.svelte` (for auto-fix). However, you don't have to add them yourself. If they do not exist, they will be automatically generated when you run the test. In other words, delete them manually when you want to recreate them.
-
-**Tips**:
-
-If you want to test only one rule, run the following command (for `indent` rule):
-
-```sh
-yarn test -g indent
-```
-
-Take <https://stackoverflow.com/questions/10832031/how-to-run-a-single-test-with-mocha> as reference for details.
-
-If you want to test only `my-test-input.svelte`, add `my-test-config.json` and save `{"only": true}`.  
-(Note that `{"only": true}` must be removed before making a pull request.)
+See also [CONTRIBUTING.md](./CONTRIBUTING.md)
 
 ### Working With Rules
 
diff --git a/docs-svelte-kit/src/.eslintrc.js b/docs-svelte-kit/src/.eslintrc.js
index 457395c..435fa70 100644
--- a/docs-svelte-kit/src/.eslintrc.js
+++ b/docs-svelte-kit/src/.eslintrc.js
@@ -5,5 +5,6 @@ module.exports = {
   rules: {
     "node/no-unsupported-features/es-syntax": "off",
     "require-jsdoc": "off",
+    "node/no-missing-import": "off",
   },
 }
diff --git a/docs-svelte-kit/src/lib/utils.js b/docs-svelte-kit/src/lib/utils.js
index 5cdc53b..76a57a1 100644
--- a/docs-svelte-kit/src/lib/utils.js
+++ b/docs-svelte-kit/src/lib/utils.js
@@ -1,8 +1,6 @@
 import { rules } from "../../../src/utils/rules.ts"
 import { readable, writable } from "svelte/store"
-// eslint-disable-next-line node/no-missing-import -- ignore
 import { page } from "$app/stores"
-// eslint-disable-next-line node/no-missing-import -- ignore
 import { base as baseUrl } from "$app/paths"
 
 export function stripBaseUrl(path) {
diff --git a/docs-svelte-kit/src/routes/+error.svelte b/docs-svelte-kit/src/routes/+error.svelte
new file mode 100644
index 0000000..f23afc7
--- /dev/null
+++ b/docs-svelte-kit/src/routes/+error.svelte
@@ -0,0 +1,10 @@
+<script>
+  import { base as baseUrl } from "$app/paths"
+  import { page } from "$app/stores"
+</script>
+
+<h1>{$page.status}</h1>
+<blockquote>
+  <p>{$page.error.message}</p>
+  <p>Take me <a href="{baseUrl}/">home</a></p>
+</blockquote>
diff --git a/docs-svelte-kit/src/routes/+layout.js b/docs-svelte-kit/src/routes/+layout.js
new file mode 100644
index 0000000..def92b2
--- /dev/null
+++ b/docs-svelte-kit/src/routes/+layout.js
@@ -0,0 +1,19 @@
+import { markdownPath } from "$lib/utils.js"
+const docs = import.meta.glob("../../../docs/**/*.md")
+
+/** @type {import('@sveltejs/kit').Load} */
+export async function load({ url }) {
+  const markdown = `../../../docs/${markdownPath(url.pathname)}`
+  if (docs[markdown]) {
+    return {
+      moduleData: await docs[markdown](),
+    }
+  }
+
+  // 404
+  return {
+    moduleData: {
+      frontmatter: { title: "404", hiddenMenu: true },
+    },
+  }
+}
diff --git a/docs/__layout.svelte b/docs-svelte-kit/src/routes/+layout.svelte
similarity index 67%
rename from docs/__layout.svelte
rename to docs-svelte-kit/src/routes/+layout.svelte
index 8ad08fe..046366a 100644
--- a/docs/__layout.svelte
+++ b/docs-svelte-kit/src/routes/+layout.svelte
@@ -1,40 +1,15 @@
-<script context="module">
-  import { markdownPath } from "$lib/utils.js"
-  const docs = import.meta.glob("./**/*.md")
-
-  /** @type {import('@sveltejs/kit').Load} */
-  export async function load({ url }) {
-    const markdown = `./${markdownPath(url.pathname)}`
-    if (docs[markdown]) {
-      return {
-        props: {
-          moduleData: await docs[markdown](),
-        },
-      }
-    }
-
-    // 404
-    return {
-      props: {
-        moduleData: {
-          frontmatter: { title: "404", hiddenMenu: true },
-        },
-      },
-    }
-  }
-</script>
-
 <script>
   import Header from "$lib/header/Header.svelte"
   import SideMenu from "$lib/sidemenu/SideMenu.svelte"
   import Footer from "$lib/footer/Footer.svelte"
 
-  import "../docs-svelte-kit/src/app.css"
-  import "../docs-svelte-kit/src/site.css"
+  import "../app.css"
+  import "../site.css"
   import { tocStore } from "$lib/utils"
 
-  export let moduleData
-
+  /** @type {import('./$types').PageData */
+  export let data
+  $: ({ moduleData } = data)
   $: frontmatter = moduleData.frontmatter
   $: fileInfo = moduleData.fileInfo
   $: {
diff --git a/docs/404.svelte b/docs-svelte-kit/src/routes/404/+page.svelte
similarity index 100%
rename from docs/404.svelte
rename to docs-svelte-kit/src/routes/404/+page.svelte
diff --git a/docs-svelte-kit/tools/generate-routes.mts b/docs-svelte-kit/tools/generate-routes.mts
new file mode 100644
index 0000000..b288917
--- /dev/null
+++ b/docs-svelte-kit/tools/generate-routes.mts
@@ -0,0 +1,55 @@
+import path from "path"
+import fs from "fs"
+import { fileURLToPath } from "url"
+const dirname = path.dirname(
+  fileURLToPath(
+    // @ts-expect-error -- Cannot change `module` option
+    import.meta.url,
+  ),
+)
+
+/** Generate svelte kit routes files */
+export default function generateRoutes(): void {
+  const srcRoot = path.join(dirname, "../../docs")
+  const distRoot = path.join(dirname, "../src/routes")
+
+  for (const markdownPath of iterateMarkdown(srcRoot)) {
+    const markdownRelativePath = path.relative(srcRoot, markdownPath)
+    const distPath = path.join(
+      distRoot,
+      markdownRelativePath.replace(/(?:README)?\.md$/u, "/+page.md"),
+    )
+
+    mkdirs(path.dirname(distPath))
+    fs.writeFileSync(
+      distPath,
+      `<script>
+  import MD from "${path.relative(path.dirname(distPath), markdownPath)}"
+</script>
+
+<MD />
+`,
+      "utf-8",
+    )
+  }
+}
+
+/** Iterate markdown files */
+function* iterateMarkdown(rootDir: string): IterableIterator<string> {
+  for (const filename of fs.readdirSync(rootDir)) {
+    const abs = path.join(rootDir, filename)
+    if (path.extname(filename) === ".md") {
+      yield abs
+    } else if (fs.statSync(abs).isDirectory()) {
+      yield* iterateMarkdown(abs)
+    }
+  }
+}
+
+/** Make directories */
+function mkdirs(dir: string): void {
+  if (!fs.existsSync(dir)) {
+    mkdirs(path.dirname(dir))
+    fs.mkdirSync(dir)
+  }
+}
diff --git a/docs-svelte-kit/tools/markdown-it-replace-link.mts b/docs-svelte-kit/tools/markdown-it-replace-link.mts
index c4d8ebf..a7acd89 100644
--- a/docs-svelte-kit/tools/markdown-it-replace-link.mts
+++ b/docs-svelte-kit/tools/markdown-it-replace-link.mts
@@ -24,7 +24,10 @@ export default (md: Md, options: { baseUrl: string; root: string }): void => {
 
     return `${base}/${path
       .relative(root, absolutePath)
-      .replace(/\.md$/, "")}/${hash}`.replace(/\\/gu, "/")
+      .replace(/README\.md$/, "")
+      .replace(/\.md$/, "")}/${hash}`
+      .replace(/\\/gu, "/")
+      .replace(/\/+/gu, "/")
   }
 
   md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
diff --git a/docs/__error.svelte b/docs/__error.svelte
deleted file mode 100644
index f9516ed..0000000
--- a/docs/__error.svelte
+++ /dev/null
@@ -1,19 +0,0 @@
-<script context="module">
-  /** @type {import('@sveltejs/kit').ErrorLoad} */
-  export function load({ error, status }) {
-    return {
-      props: { error, status },
-    }
-  }
-</script>
-
-<script>
-  import { base as baseUrl } from "$app/paths"
-  export let status, error
-</script>
-
-<h1>{status}</h1>
-<blockquote>
-  <p>{error.message}</p>
-  <p>Take me <a href="{baseUrl}/">home</a></p>
-</blockquote>
diff --git a/docs/index.svelte b/docs/index.svelte
deleted file mode 100644
index ef64e39..0000000
--- a/docs/index.svelte
+++ /dev/null
@@ -1,5 +0,0 @@
-<script>
-  import Md from "./README.md"
-</script>
-
-<Md />
diff --git a/docs/rules.md b/docs/rules.md
index 223d78d..cec9eb3 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -22,6 +22,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
 | [svelte/no-not-function-handler](./rules/no-not-function-handler.md) | disallow use of not function in event handler | :star: |
 | [svelte/no-object-in-text-mustaches](./rules/no-object-in-text-mustaches.md) | disallow objects in text mustache interpolation | :star: |
 | [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 | :star: |
 | [svelte/no-unknown-style-directive-property](./rules/no-unknown-style-directive-property.md) | disallow unknown `style:property` | :star: |
 | [svelte/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | :star: |
 
diff --git a/docs/rules/no-store-async.md b/docs/rules/no-store-async.md
new file mode 100644
index 0000000..29dd29c
--- /dev/null
+++ b/docs/rules/no-store-async.md
@@ -0,0 +1,56 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "svelte/no-store-async"
+description: "disallow using async/await inside svelte stores"
+---
+
+# svelte/no-store-async
+
+> disallow using async/await inside svelte stores
+
+- :gear: This rule is included in `"plugin:svelte/recommended"`.
+
+## :book: Rule Details
+
+This rule reports all uses of async/await inside svelte stores.
+Because it causes issues with the auto-unsubscribing features.
+
+<ESLintCodeBlock language="javascript">
+
+<!--eslint-skip-->
+
+```js
+/* eslint svelte/no-store-async: "error" */
+
+import { writable, readable, derived } from "svelte/store"
+
+/* ✓ GOOD */
+const w1 = writable(false, () => {})
+const r1 = readable(false, () => {})
+const d1 = derived(a1, ($a1) => {})
+
+/* ✗ BAD */
+const w2 = writable(false, async () => {})
+const r2 = readable(false, async () => {})
+const d2 = derived(a1, async ($a1) => {})
+```
+
+</ESLintCodeBlock>
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Svelte - Docs > 4. Prefix stores with $ to access their values / Store contract](https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values-store-contract)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-svelte v3.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-store-async.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/no-store-async.ts)
diff --git a/package.json b/package.json
index 9952144..090b1cf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-svelte",
-  "version": "2.5.0",
+  "version": "2.6.0",
   "description": "ESLint plugin for Svelte using AST",
   "repository": "git+https://github.com/ota-meshi/eslint-plugin-svelte.git",
   "homepage": "https://ota-meshi.github.io/eslint-plugin-svelte",
@@ -34,9 +34,9 @@
     "docs:build": "yarn svelte-kit build",
     "docs:preview": "yarn svelte-kit preview",
     "docs:watch": "yarn svelte-kit dev",
-    "eslint-fix": "eslint . --fix",
     "format-for-gen-file": "eslint src/types-for-node.ts src/utils/rules.ts src/configs --fix",
     "lint": "run-p lint:*",
+    "lint-fix": "yarn lint:es --fix && yarn lint:style --fix",
     "lint:es": "eslint --cache -f friendly .",
     "lint:style": "stylelint --cache .",
     "mocha": "yarn ts ./node_modules/mocha/bin/mocha.js",
@@ -44,11 +44,10 @@
     "prebuild": "yarn clean",
     "prepare": "simple-git-hooks && yarn-deduplicate --strategy fewer || exit 0",
     "prerelease": "yarn clean && yarn build",
-    "pretest": "yarn build",
     "release": "changeset publish",
     "svelte-kit": "node --experimental-loader ./svelte-kit-import-hook.mjs node_modules/vite/bin/vite.js",
     "test": "yarn mocha \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
-    "test:debug": "cross-env DEBUG=eslint-plugin-svelte* yarn test",
+    "test:debug": "env-cmd -e debug yarn test",
     "ts": "node -r esbuild-register",
     "typecov": "type-coverage",
     "update": "yarn ts ./tools/update.ts && yarn format-for-gen-file",
@@ -90,7 +89,7 @@
     "@ota-meshi/eslint-plugin": "^0.11.3",
     "@sindresorhus/slugify": "^2.1.0",
     "@sveltejs/adapter-static": "^1.0.0-next.26",
-    "@sveltejs/kit": "^1.0.0-next.360",
+    "@sveltejs/kit": "^1.0.0-next.406",
     "@types/babel__core": "^7.1.19",
     "@types/cross-spawn": "^6.0.2",
     "@types/escape-html": "^1.0.2",
@@ -113,7 +112,6 @@
     "@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@4",
     "assert": "^2.0.0",
     "commitlint": "^17.0.3",
-    "cross-env": "^7.0.3",
     "env-cmd": "^10.1.0",
     "esbuild": "^0.15.0",
     "esbuild-register": "^3.2.0",
@@ -148,7 +146,7 @@
     "pako": "^2.0.3",
     "postcss-nested": "^5.0.6",
     "prettier": "^2.2.1",
-    "prettier-plugin-pkg": "^0.16.0",
+    "prettier-plugin-pkg": "^0.17.0",
     "prettier-plugin-svelte": "^2.6.0",
     "prism-svelte": "^0.5.0",
     "prismjs": "^1.25.0",
@@ -156,7 +154,7 @@
     "semver": "^7.3.5",
     "simple-git-hooks": "^2.8.0",
     "stylelint": "^14.0.0",
-    "stylelint-config-standard": "^27.0.0",
+    "stylelint-config-standard": "^28.0.0",
     "stylus": "^0.59.0",
     "svelte": "^3.46.1",
     "svelte-adapter-ghpages": "0.0.2",
diff --git a/prettier.config.js b/prettier.config.js
index 59040e6..3789161 100644
--- a/prettier.config.js
+++ b/prettier.config.js
@@ -4,4 +4,13 @@ module.exports = {
   tabWidth: 2,
   semi: false,
   trailingComma: "all",
+  overrides: [
+    {
+      files: [".*rc"],
+      excludeFiles: [".browserslistrc", ".npmrc", ".nvmrc"],
+      options: {
+        parser: "json",
+      },
+    },
+  ],
 }
diff --git a/src/rules/no-store-async.ts b/src/rules/no-store-async.ts
new file mode 100644
index 0000000..d805f18
--- /dev/null
+++ b/src/rules/no-store-async.ts
@@ -0,0 +1,49 @@
+import { createRule } from "../utils"
+import { extractStoreReferences } from "./reference-helpers/svelte-store"
+
+export default createRule("no-store-async", {
+  meta: {
+    docs: {
+      description:
+        "disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features",
+      category: "Possible Errors",
+      recommended: true,
+      default: "error",
+    },
+    schema: [],
+    messages: {
+      unexpected: "Do not pass async functions to svelte stores.",
+    },
+    type: "problem",
+  },
+  create(context) {
+    return {
+      Program() {
+        for (const { node } of extractStoreReferences(context)) {
+          const [, fn] = node.arguments
+          if (
+            !fn ||
+            (fn.type !== "ArrowFunctionExpression" &&
+              fn.type !== "FunctionExpression") ||
+            !fn.async
+          ) {
+            continue
+          }
+
+          const start = fn.loc?.start ?? { line: 1, column: 0 }
+          context.report({
+            node: fn,
+            loc: {
+              start,
+              end: {
+                line: start.line,
+                column: start.column + 5,
+              },
+            },
+            messageId: "unexpected",
+          })
+        }
+      },
+    }
+  },
+})
diff --git a/src/rules/reference-helpers/svelte-store.ts b/src/rules/reference-helpers/svelte-store.ts
new file mode 100644
index 0000000..3e9edc9
--- /dev/null
+++ b/src/rules/reference-helpers/svelte-store.ts
@@ -0,0 +1,29 @@
+import type * as ESTree from "estree"
+import { ReferenceTracker } from "eslint-utils"
+import type { RuleContext } from "../../types"
+
+/** Extract 'svelte/store' references */
+export function* extractStoreReferences(
+  context: RuleContext,
+): Generator<{ node: ESTree.CallExpression; name: string }, void> {
+  const referenceTracker = new ReferenceTracker(context.getScope())
+  for (const { node, path } of referenceTracker.iterateEsmReferences({
+    "svelte/store": {
+      [ReferenceTracker.ESM]: true,
+      writable: {
+        [ReferenceTracker.CALL]: true,
+      },
+      readable: {
+        [ReferenceTracker.CALL]: true,
+      },
+      derived: {
+        [ReferenceTracker.CALL]: true,
+      },
+    },
+  })) {
+    yield {
+      node: node as ESTree.CallExpression,
+      name: path[path.length - 1],
+    }
+  }
+}
diff --git a/src/rules/require-stores-init.ts b/src/rules/require-stores-init.ts
index 5b2ac84..8245ef5 100644
--- a/src/rules/require-stores-init.ts
+++ b/src/rules/require-stores-init.ts
@@ -1,6 +1,5 @@
 import { createRule } from "../utils"
-import type * as ESTree from "estree"
-import { ReferenceTracker } from "eslint-utils"
+import { extractStoreReferences } from "./reference-helpers/svelte-store"
 
 export default createRule("require-stores-init", {
   meta: {
@@ -16,33 +15,9 @@ export default createRule("require-stores-init", {
     type: "suggestion",
   },
   create(context) {
-    /** Extract 'svelte/store' references */
-    function* extractStoreReferences() {
-      const referenceTracker = new ReferenceTracker(context.getScope())
-      for (const { node, path } of referenceTracker.iterateEsmReferences({
-        "svelte/store": {
-          [ReferenceTracker.ESM]: true,
-          writable: {
-            [ReferenceTracker.CALL]: true,
-          },
-          readable: {
-            [ReferenceTracker.CALL]: true,
-          },
-          derived: {
-            [ReferenceTracker.CALL]: true,
-          },
-        },
-      })) {
-        yield {
-          node: node as ESTree.CallExpression,
-          name: path[path.length - 1],
-        }
-      }
-    }
-
     return {
       Program() {
-        for (const { node, name } of extractStoreReferences()) {
+        for (const { node, name } of extractStoreReferences(context)) {
           const minArgs =
             name === "writable" || name === "readable"
               ? 1
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index e5776af..06382d3 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -21,6 +21,7 @@ import noReactiveFunctions from "../rules/no-reactive-functions"
 import noReactiveLiterals from "../rules/no-reactive-literals"
 import noShorthandStylePropertyOverrides from "../rules/no-shorthand-style-property-overrides"
 import noSpacesAroundEqualSignsInAttribute from "../rules/no-spaces-around-equal-signs-in-attribute"
+import noStoreAsync from "../rules/no-store-async"
 import noTargetBlank from "../rules/no-target-blank"
 import noUnknownStyleDirectiveProperty from "../rules/no-unknown-style-directive-property"
 import noUnusedSvelteIgnore from "../rules/no-unused-svelte-ignore"
@@ -59,6 +60,7 @@ export const rules = [
   noReactiveLiterals,
   noShorthandStylePropertyOverrides,
   noSpacesAroundEqualSignsInAttribute,
+  noStoreAsync,
   noTargetBlank,
   noUnknownStyleDirectiveProperty,
   noUnusedSvelteIgnore,
diff --git a/svelte.config.mjs b/svelte.config.mjs
index 6a9f61c..71bcdb4 100644
--- a/svelte.config.mjs
+++ b/svelte.config.mjs
@@ -41,7 +41,7 @@ const config = {
       default: true,
     },
     files: {
-      routes: path.join(dirname, "./docs"),
+      routes: path.join(dirname, "./docs-svelte-kit/src/routes"),
       template: path.join(dirname, "./docs-svelte-kit/src/app.html"),
       hooks: path.join(dirname, "./docs-svelte-kit/src/hooks"),
       lib: path.join(dirname, "./docs-svelte-kit/src/lib"),
diff --git a/vite.config.mts b/vite.config.mts
index 3cde8bd..8b540b9 100644
--- a/vite.config.mts
+++ b/vite.config.mts
@@ -5,9 +5,12 @@ import svelteMd from "vite-plugin-svelte-md"
 import svelteMdOption from "./docs-svelte-kit/tools/vite-plugin-svelte-md-option.mjs"
 
 import "./docs-svelte-kit/build-system/build.mts"
+import generateRoutes from "./docs-svelte-kit/tools/generate-routes.mjs"
 import type { UserConfig } from "vite"
 import { fileURLToPath } from "url"
 
+generateRoutes()
+
 const dirname = path.dirname(
   fileURLToPath(
     // @ts-expect-error -- Cannot change `module` option


Test patch:
diff --git a/tests/fixtures/rules/no-store-async/invalid/test01-errors.yaml b/tests/fixtures/rules/no-store-async/invalid/test01-errors.yaml
new file mode 100644
index 0000000..9326320
--- /dev/null
+++ b/tests/fixtures/rules/no-store-async/invalid/test01-errors.yaml
@@ -0,0 +1,12 @@
+- message: Do not pass async functions to svelte stores.
+  line: 3
+  column: 28
+  suggestions: null
+- message: Do not pass async functions to svelte stores.
+  line: 6
+  column: 28
+  suggestions: null
+- message: Do not pass async functions to svelte stores.
+  line: 9
+  column: 24
+  suggestions: null
diff --git a/tests/fixtures/rules/no-store-async/invalid/test01-input.js b/tests/fixtures/rules/no-store-async/invalid/test01-input.js
new file mode 100644
index 0000000..3ad8019
--- /dev/null
+++ b/tests/fixtures/rules/no-store-async/invalid/test01-input.js
@@ -0,0 +1,11 @@
+import { writable, readable, derived } from "svelte/store"
+
+const w2 = writable(false, async () => {
+  /** do nothing */
+})
+const r2 = readable(false, async () => {
+  /** do nothing */
+})
+const d2 = derived(a1, async ($a1) => {
+  /** do nothing */
+})
diff --git a/tests/fixtures/rules/no-store-async/invalid/test02-errors.yaml b/tests/fixtures/rules/no-store-async/invalid/test02-errors.yaml
new file mode 100644
index 0000000..b65aa40
--- /dev/null
+++ b/tests/fixtures/rules/no-store-async/invalid/test02-errors.yaml
@@ -0,0 +1,12 @@
+- message: Do not pass async functions to svelte stores.
+  line: 3
+  column: 35
+  suggestions: null
+- message: Do not pass async functions to svelte stores.
+  line: 6
+  column: 35
+  suggestions: null
+- message: Do not pass async functions to svelte stores.
+  line: 9
+  column: 31
+  suggestions: null
diff --git a/tests/fixtures/rules/no-store-async/invalid/test02-input.js b/tests/fixtures/rules/no-store-async/invalid/test02-input.js
new file mode 100644
index 0000000..a327fa9
--- /dev/null
+++ b/tests/fixtures/rules/no-store-async/invalid/test02-input.js
@@ -0,0 +1,11 @@
+import * as stores from "svelte/store"
+
+const w2 = stores.writable(false, async () => {
+  /** do nothing */
+})
+const r2 = stores.readable(false, async () => {
+  /** do nothing */
+})
+const d2 = stores.derived(a1, async ($a1) => {
+  /** do nothing */
+})
diff --git a/tests/fixtures/rules/no-store-async/invalid/test03-errors.yaml b/tests/fixtures/rules/no-store-async/invalid/test03-errors.yaml
new file mode 100644
index 0000000..3c3ed27
--- /dev/null
+++ b/tests/fixtures/rules/no-store-async/invalid/test03-errors.yaml
@@ -0,0 +1,12 @@
+- message: Do not pass async functions to svelte stores.
+  line: 3
+  column: 21
+  suggestions: null
+- message: Do not pass async functions to svelte stores.
+  line: 6
+  column: 21
+  suggestions: null
+- message: Do not pass async functions to svelte stores.
+  line: 9
+  column: 18
+  suggestions: null
diff --git a/tests/fixtures/rules/no-store-async/invalid/test03-input.js b/tests/fixtures/rules/no-store-async/invalid/test03-input.js
new file mode 100644
index 0000000..394382d
--- /dev/null
+++ b/tests/fixtures/rules/no-store-async/invalid/test03-input.js
@@ -0,0 +1,11 @@
+import { writable as A, readable as B, derived as C } from "svelte/store"
+
+const w2 = A(false, async () => {
+  /** do nothing */
+})
+const r2 = B(false, async () => {
+  /** do nothing */
+})
+const d2 = C(a1, async ($a1) => {
+  /** do nothing */
+})
diff --git a/tests/fixtures/rules/no-store-async/invalid/test04-errors.yaml b/tests/fixtures/rules/no-store-async/invalid/test04-errors.yaml
new file mode 100644
index 0000000..9326320
--- /dev/null
+++ b/tests/fixtures/rules/no-store-async/invalid/test04-errors.yaml
@@ -0,0 +1,12 @@
+- message: Do not pass async functions to svelte stores.
+  line: 3
+  column: 28
+  suggestions: null
+- message: Do not pass async functions to svelte stores.
+  line: 6
+  column: 28
+  suggestions: null
+- message: Do not pass async functions to svelte stores.
+  line: 9
+  column: 24
+  suggestions: null
diff --git a/tests/fixtures/rules/no-store-async/invalid/test04-input.js b/tests/fixtures/rules/no-store-async/invalid/test04-input.js
new file mode 100644
index 0000000..f6ca817
--- /dev/null
+++ b/tests/fixtures/rules/no-store-async/invalid/test04-input.js
@@ -0,0 +1,11 @@
+import { writable, readable, derived } from "svelte/store"
+
+const w2 = writable(false, async function () {
+  /** do nothing */
+})
+const r2 = readable(false, async function () {
+  /** do nothing */
+})
+const d2 = derived(a1, async function ($a1) {
+  /** do nothing */
+})
diff --git a/tests/fixtures/rules/no-store-async/valid/test01-input.js b/tests/fixtures/rules/no-store-async/valid/test01-input.js
new file mode 100644
index 0000000..f6e2c1a
--- /dev/null
+++ b/tests/fixtures/rules/no-store-async/valid/test01-input.js
@@ -0,0 +1,14 @@
+import { writable, readable, derived } from "svelte/store"
+
+const w1 = writable(false, () => {
+  /** do nothing */
+})
+const w2 = writable(false)
+const r1 = readable(false, () => {
+  /** do nothing */
+})
+const r2 = readable(false)
+const d1 = derived(a1, ($a1) => {
+  /** do nothing */
+})
+const d2 = derived(a1)
diff --git a/tests/src/rules/no-store-async.ts b/tests/src/rules/no-store-async.ts
new file mode 100644
index 0000000..90d4bcb
--- /dev/null
+++ b/tests/src/rules/no-store-async.ts
@@ -0,0 +1,12 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/no-store-async"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: "module",
+  },
+})
+
+tester.run("no-store-async", rule as any, loadTestCases("no-store-async"))