Repository: ota-meshi/eslint-plugin-svelte
Pull request #281 (2022-10-29 14:54:22)

feat: add no-export-load-in-svelte-module-in-kit-pages
resolve: https://github.com/ota-meshi/eslint-plugin-svelte/issues/241

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

Latest commit: bdd35804e67dd13b362231603d6be54d9a25a13e

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/no-export-load-in-svelte-module-in-kit-pages?filename=.changeset/purple-spiders-share.md&value=---%0A%22eslint-plugin-svelte%22%3A%20patch%0A---%0A%0Afeat%3A%20add%20no-export-load-in-svelte-module-in-kit-pages%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/281/builds/309978) or the icon next to each commit SHA.

Patch:
diff --git a/.changeset/orange-months-sparkle.md b/.changeset/orange-months-sparkle.md
new file mode 100644
index 0000000..0021c48
--- /dev/null
+++ b/.changeset/orange-months-sparkle.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-svelte": minor
+---
+
+feat: add `no-export-load-in-svelte-module-in-kit-pages` rule
diff --git a/README.md b/README.md
index bc6fc8f..59cd936 100644
--- a/README.md
+++ b/README.md
@@ -216,6 +216,28 @@ module.exports = {
 }
 ```
 
+#### settings.kit
+
+If you use SvelteKit with not default configuration, you need to set below configurations.
+The schema is subset of SvelteKit's configuration.
+Therefore please check [SvelteKit docs](https://kit.svelte.dev/docs/configuration) for more details.
+
+e.g.
+
+```js
+module.exports = {
+  // ...
+  settings: {
+    kit: {
+      files: {
+        routes: "src/routes",
+      },
+    },
+  },
+  // ...
+}
+```
+
 ### Running ESLint from the command line
 
 If you want to run `eslint` from the command line, make sure you include the `.svelte` extension using [the `--ext` option](https://eslint.org/docs/user-guide/configuring#specifying-file-extensions-to-lint) or a glob pattern, because ESLint targets only `.js` files by default.
@@ -266,6 +288,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
 | [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-style-properties](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dupe-style-properties/) | disallow duplicate style properties | :star: |
 | [svelte/no-dynamic-slot-name](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dynamic-slot-name/) | disallow dynamic slot name | :star::wrench: |
+| [svelte/no-export-load-in-svelte-module-in-kit-pages](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-export-load-in-svelte-module-in-kit-pages/) | disallow exporting load functions in `*.svelte` module in Svelte Kit page components. |  |
 | [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: |
diff --git a/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte b/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte
index db082d0..769a536 100644
--- a/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte
+++ b/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte
@@ -15,6 +15,7 @@
   let tsParser = null
 
   let code = ""
+  export let config = {}
   export let rules = {}
   export let fix = false
   export let language = "svelte"
@@ -75,6 +76,7 @@
         browser: true,
         es2021: true,
       },
+      ...config,
     }}
     {language}
     {options}
diff --git a/docs/rules.md b/docs/rules.md
index 946972a..ca416ad 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -19,6 +19,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
 | [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-style-properties](./rules/no-dupe-style-properties.md) | disallow duplicate style properties | :star: |
 | [svelte/no-dynamic-slot-name](./rules/no-dynamic-slot-name.md) | disallow dynamic slot name | :star::wrench: |
+| [svelte/no-export-load-in-svelte-module-in-kit-pages](./rules/no-export-load-in-svelte-module-in-kit-pages.md) | disallow exporting load functions in `*.svelte` module in Svelte Kit page components. |  |
 | [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: |
diff --git a/docs/rules/no-export-load-in-svelte-module-in-kit-pages.md b/docs/rules/no-export-load-in-svelte-module-in-kit-pages.md
new file mode 100644
index 0000000..55008c1
--- /dev/null
+++ b/docs/rules/no-export-load-in-svelte-module-in-kit-pages.md
@@ -0,0 +1,61 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "svelte/no-export-load-in-svelte-module-in-kit-pages"
+description: "disallow exporting load functions in `*.svelte` module in Svelte Kit page components."
+---
+
+# svelte/no-export-load-in-svelte-module-in-kit-pages
+
+> disallow exporting load functions in `*.svelte` module in Svelte Kit page components.
+
+- :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 reports unexpected exported `load` function at `<script context="module">`.
+At SvelteKit v1.0.0-next.405, `load` function has been moved into a separate file — `+page.js` for pages, `+layout.js` for layouts.
+And the API has changed.
+
+<script>
+  const config = {
+    settings: {
+      kit: {
+        files: {
+          routes: "",
+        },
+      },
+    },
+  }
+</script>
+
+<ESLintCodeBlock config="{config}">
+
+<!--eslint-skip-->
+
+```svelte
+<script context="module">
+  /* eslint svelte/no-export-load-in-svelte-module-in-kit-pages: "error" */
+  /* ✓ GOOD  */
+  export function foo() {}
+  export function bar() {}
+  /* ✗ BAD  */
+  export function load() {}
+  // export const load = () => {}
+</script>
+```
+
+</ESLintCodeBlock>
+
+## :wrench: Options
+
+Nothing. But if use are using not default routes folder, please set configuration according to the [user guide](../user-guide.md#settings-kit).
+
+## :books: Further Reading
+
+- [SvelteKit Migration Guide (v1.0.0-next.405)](https://github.com/sveltejs/kit/discussions/5774#discussioncomment-3292693)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-export-load-in-svelte-module-in-kit-pages.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/no-export-load-in-svelte-module-in-kit-pages.ts)
diff --git a/docs/user-guide.md b/docs/user-guide.md
index bd12959..57179db 100644
--- a/docs/user-guide.md
+++ b/docs/user-guide.md
@@ -168,6 +168,28 @@ module.exports = {
 }
 ```
 
+#### settings.kit
+
+If you use SvelteKit with not default configuration, you need to set below configurations.
+The schema is subset of SvelteKit's configuration.
+Therefore please check [SvelteKit docs](https://kit.svelte.dev/docs/configuration) for more details.
+
+e.g.
+
+```js
+module.exports = {
+  // ...
+  settings: {
+    kit: {
+      files: {
+        routes: "src/routes",
+      },
+    },
+  },
+  // ...
+}
+```
+
 ### Running ESLint from the command line
 
 If you want to run `eslint` from the command line, make sure you include the `.svelte` extension using [the `--ext` option](https://eslint.org/docs/user-guide/configuring#specifying-file-extensions-to-lint) or a glob pattern, because ESLint targets only `.js` files by default.
diff --git a/package.json b/package.json
index 2d5a742..af9850e 100644
--- a/package.json
+++ b/package.json
@@ -174,7 +174,7 @@
     "access": "public"
   },
   "typeCoverage": {
-    "atLeast": 98.72,
+    "atLeast": 98.69,
     "cache": true,
     "detail": true,
     "ignoreAsAssertion": true,
diff --git a/src/rules/no-export-load-in-svelte-module-in-kit-pages.ts b/src/rules/no-export-load-in-svelte-module-in-kit-pages.ts
new file mode 100644
index 0000000..40ce3a6
--- /dev/null
+++ b/src/rules/no-export-load-in-svelte-module-in-kit-pages.ts
@@ -0,0 +1,51 @@
+import type * as ESTree from "estree"
+import { createRule } from "../utils"
+import { isKitPageComponent } from "../utils/svelte-kit"
+
+export default createRule("no-export-load-in-svelte-module-in-kit-pages", {
+  meta: {
+    docs: {
+      description:
+        "disallow exporting load functions in `*.svelte` module in Svelte Kit page components.",
+      category: "Possible Errors",
+      // TODO Switch to recommended in the major version.
+      recommended: false,
+    },
+    schema: [],
+    messages: {
+      unexpected:
+        "disallow exporting load functions in `*.svelte` module in Svelte Kit page components.",
+    },
+    type: "problem",
+  },
+  create(context) {
+    if (!isKitPageComponent(context)) {
+      return {}
+    }
+    let isModule = false
+    return {
+      // <script context="module">
+      [`Program > SvelteScriptElement > SvelteStartTag > SvelteAttribute[key.name="context"] > SvelteLiteral[value="module"]`]:
+        () => {
+          isModule = true
+        },
+
+      // </script>
+      "Program > SvelteScriptElement:exit": () => {
+        isModule = false
+      },
+
+      // export function load() {}
+      // export const load = () => {}
+      [`:matches(ExportNamedDeclaration > FunctionDeclaration, ExportNamedDeclaration > VariableDeclaration > VariableDeclarator) > Identifier.id[name="load"]`]:
+        (node: ESTree.Identifier) => {
+          if (!isModule) return {}
+          return context.report({
+            node,
+            loc: node.loc!,
+            messageId: "unexpected",
+          })
+        },
+    }
+  },
+})
diff --git a/src/types.ts b/src/types.ts
index 2b991c3..3be8cfd 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -128,6 +128,11 @@ export type RuleContext = {
         postcss?: false | { configFilePath?: unknown }
       }
     }
+    ["kit"]?: {
+      files?: {
+        routes?: string
+      }
+    }
   }
   parserPath: string
   parserOptions: Linter.ParserOptions
diff --git a/src/utils/cache.ts b/src/utils/cache.ts
new file mode 100644
index 0000000..e188f36
--- /dev/null
+++ b/src/utils/cache.ts
@@ -0,0 +1,63 @@
+/**
+ * Simple cache manager.
+ *
+ * refer: https://github.com/mysticatea/eslint-plugin-node/blob/f45c6149be7235c0f7422d1179c25726afeecd83/lib/util/cache.js
+ */
+
+const SKIP_TIME = 5000
+
+type CacheValue<T> = {
+  expire: number
+  value: T
+}
+
+/**
+ * The cache will dispose of each value if the value has not been accessed
+ * during 5 seconds.
+ * @returns getter and setter ofr the cache.
+ */
+export function createCache<T>(): {
+  get: (key: string) => T | null
+  set: (key: string, value: T) => void
+} {
+  const map: Map<string, CacheValue<T>> = new Map()
+
+  /**
+   * Get the cached value of the given key.
+   * @param key The key to get.
+   * @returns The cached value or null.
+   */
+  function get(key: string): T | null {
+    const entry = map.get(key)
+    const now = Date.now()
+
+    if (entry) {
+      if (entry.expire > now) {
+        entry.expire = now + SKIP_TIME
+        return entry.value
+      }
+      map.delete(key)
+    }
+    return null
+  }
+
+  /**
+   * Set the value of the given key.
+   * @param key The key to set.
+   * @param value The value to set.
+   * @returns
+   */
+  function set(key: string, value: T): void {
+    const entry = map.get(key)
+    const expire = Date.now() + SKIP_TIME
+
+    if (entry) {
+      entry.value = value
+      entry.expire = expire
+    } else {
+      map.set(key, { value, expire })
+    }
+  }
+
+  return { get, set }
+}
diff --git a/src/utils/get-package-json.ts b/src/utils/get-package-json.ts
new file mode 100644
index 0000000..88e1c50
--- /dev/null
+++ b/src/utils/get-package-json.ts
@@ -0,0 +1,77 @@
+/**
+ * refer: https://github.com/mysticatea/eslint-plugin-node/blob/f45c6149be7235c0f7422d1179c25726afeecd83/lib/util/get-package-json.js
+ */
+
+import fs from "fs"
+import path from "path"
+import { createCache } from "./cache"
+
+type PackageJson = Record<string, any> & { filePath: string }
+
+const isRunOnBrowser = !fs.readFileSync
+const cache = createCache<PackageJson | null>()
+
+/**
+ * Reads the `package.json` data in a given path.
+ *
+ * Don't cache the data.
+ *
+ * @param dir The path to a directory to read.
+ * @returns The read `package.json` data, or null.
+ */
+function readPackageJson(dir: string): PackageJson | null {
+  if (isRunOnBrowser) return null
+  const filePath = path.join(dir, "package.json")
+  try {
+    const text = fs.readFileSync(filePath, "utf8")
+    const data = JSON.parse(text)
+
+    if (typeof data === "object" && data !== null) {
+      data.filePath = filePath
+      return data
+    }
+  } catch (_err) {
+    // do nothing.
+  }
+
+  return null
+}
+
+/**
+ * Gets a `package.json` data.
+ * The data is cached if found, then it's used after.
+ * @param startPath A file path to lookup.
+ * @returns A found `package.json` data or `null`.
+ *      This object have additional property `filePath`.
+ */
+export function getPackageJson(startPath = "a.js"): PackageJson | null {
+  if (isRunOnBrowser) return null
+  const startDir = path.dirname(path.resolve(startPath))
+  let dir = startDir
+  let prevDir = ""
+  let data = null
+
+  do {
+    data = cache.get(dir)
+    if (data) {
+      if (dir !== startDir) {
+        cache.set(startDir, data)
+      }
+      return data
+    }
+
+    data = readPackageJson(dir)
+    if (data) {
+      cache.set(dir, data)
+      cache.set(startDir, data)
+      return data
+    }
+
+    // Go to next.
+    prevDir = dir
+    dir = path.resolve(dir, "..")
+  } while (dir !== prevDir)
+
+  cache.set(startDir, null)
+  return null
+}
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index f4bc55b..5308fb1 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -15,6 +15,7 @@ import noAtHtmlTags from "../rules/no-at-html-tags"
 import noDupeElseIfBlocks from "../rules/no-dupe-else-if-blocks"
 import noDupeStyleProperties from "../rules/no-dupe-style-properties"
 import noDynamicSlotName from "../rules/no-dynamic-slot-name"
+import noExportLoadInSvelteModuleInKitPages from "../rules/no-export-load-in-svelte-module-in-kit-pages"
 import noExtraReactiveCurlies from "../rules/no-extra-reactive-curlies"
 import noInnerDeclarations from "../rules/no-inner-declarations"
 import noNotFunctionHandler from "../rules/no-not-function-handler"
@@ -59,6 +60,7 @@ export const rules = [
   noDupeElseIfBlocks,
   noDupeStyleProperties,
   noDynamicSlotName,
+  noExportLoadInSvelteModuleInKitPages,
   noExtraReactiveCurlies,
   noInnerDeclarations,
   noNotFunctionHandler,
diff --git a/src/utils/svelte-kit.ts b/src/utils/svelte-kit.ts
new file mode 100644
index 0000000..a8b8d80
--- /dev/null
+++ b/src/utils/svelte-kit.ts
@@ -0,0 +1,65 @@
+/**
+ * refer: https://github.com/mysticatea/eslint-plugin-node/blob/f45c6149be7235c0f7422d1179c25726afeecd83/lib/util/get-package-json.js
+ */
+
+import type { RuleContext } from "../types"
+import fs from "fs"
+import path from "path"
+import { getPackageJson } from "./get-package-json"
+
+const isRunOnBrowser = !fs.readFileSync
+
+/**
+ * return true if it's a Svelte Kit page component.
+ * @param context
+ * @returns
+ */
+export function isKitPageComponent(context: RuleContext): boolean {
+  // Hack: if it runs on browser, it regards as Svelte Kit project.
+  if (isRunOnBrowser) return true
+  if (!hasSvelteKit(context.getFilename())) return false
+  const routes =
+    context.settings?.kit?.files?.routes?.replace(/^\//, "") ?? "src/routes"
+  const filePath = context.getFilename()
+  const projectRootDir = getProjectRootDir(context.getFilename()) ?? ""
+  return filePath.startsWith(path.join(projectRootDir, routes))
+}
+
+/**
+ * Check givin file is under SvelteKit project.
+ *
+ * If it runs on browser, it always returns true.
+ *
+ * @param filePath A file path.
+ * @returns
+ */
+function hasSvelteKit(filePath: string): boolean {
+  // Hack: if it runs on browser, it regards as Svelte Kit project.
+  if (isRunOnBrowser) return true
+  try {
+    const packageJson = getPackageJson(filePath)
+    if (!packageJson) return false
+    if (packageJson.name === "eslint-plugin-svelte")
+      // Hack: CI removes `@sveltejs/kit` and it returns false and test failed.
+      // So always it returns true if it runs on the package.
+      return true
+    return Boolean(
+      packageJson.dependencies["@sveltejs/kit"] ??
+        packageJson.devDependencies["@sveltejs/kit"],
+    )
+  } catch (_e) {
+    return false
+  }
+}
+
+/**
+ * Gets a  project root folder path.
+ * @param filePath A file path to lookup.
+ * @returns A found project root folder path or null.
+ */
+function getProjectRootDir(filePath: string): string | null {
+  if (isRunOnBrowser) return null
+  const packageJsonFilePath = getPackageJson(filePath)?.filePath
+  if (!packageJsonFilePath) return null
+  return path.dirname(path.resolve(packageJsonFilePath))
+}


Test patch:
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/_config.json b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/_config.json
new file mode 100644
index 0000000..75fb9d9
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/_config.json
@@ -0,0 +1,9 @@
+{
+  "settings": {
+    "kit": {
+      "files": {
+        "routes": "tests/fixtures"
+      }
+    }
+  }
+}
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test01-errors.yaml b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test01-errors.yaml
new file mode 100644
index 0000000..3dfa29f
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test01-errors.yaml
@@ -0,0 +1,4 @@
+- message: "disallow exporting load functions in `*.svelte` module in Svelte Kit page components."
+  line: 2
+  column: 19
+  suggestions: null
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test01-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test01-input.svelte
new file mode 100644
index 0000000..e3461ce
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test01-input.svelte
@@ -0,0 +1,3 @@
+<script context="module">
+  export function load() {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test02-errors.yaml b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test02-errors.yaml
new file mode 100644
index 0000000..9917a63
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test02-errors.yaml
@@ -0,0 +1,4 @@
+- message: "disallow exporting load functions in `*.svelte` module in Svelte Kit page components."
+  line: 2
+  column: 16
+  suggestions: null
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test02-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test02-input.svelte
new file mode 100644
index 0000000..dbc677c
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/invalid/test02-input.svelte
@@ -0,0 +1,3 @@
+<script context="module">
+  export const load = () => {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/_config.json b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/_config.json
new file mode 100644
index 0000000..75fb9d9
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/_config.json
@@ -0,0 +1,9 @@
+{
+  "settings": {
+    "kit": {
+      "files": {
+        "routes": "tests/fixtures"
+      }
+    }
+  }
+}
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/not-page/_config.json b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/not-page/_config.json
new file mode 100644
index 0000000..2955b4e
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/not-page/_config.json
@@ -0,0 +1,9 @@
+{
+  "settings": {
+    "kit": {
+      "files": {
+        "routes": "some-path"
+      }
+    }
+  }
+}
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/not-page/test01-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/not-page/test01-input.svelte
new file mode 100644
index 0000000..e3461ce
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/not-page/test01-input.svelte
@@ -0,0 +1,3 @@
+<script context="module">
+  export function load() {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/not-page/test02-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/not-page/test02-input.svelte
new file mode 100644
index 0000000..dbc677c
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/not-page/test02-input.svelte
@@ -0,0 +1,3 @@
+<script context="module">
+  export const load = () => {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test01-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test01-input.svelte
new file mode 100644
index 0000000..5c80bb8
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test01-input.svelte
@@ -0,0 +1,3 @@
+<script>
+  export function load() {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test010-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test010-input.svelte
new file mode 100644
index 0000000..e364a91
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test010-input.svelte
@@ -0,0 +1,5 @@
+<script context="module">
+  export function fn() {
+    const load = () => {}
+  }
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test02-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test02-input.svelte
new file mode 100644
index 0000000..73e9175
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test02-input.svelte
@@ -0,0 +1,3 @@
+<script>
+  export const load = () => {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test03-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test03-input.svelte
new file mode 100644
index 0000000..64139d6
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test03-input.svelte
@@ -0,0 +1,3 @@
+<script context="module">
+  function load() {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test04-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test04-input.svelte
new file mode 100644
index 0000000..f2990d5
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test04-input.svelte
@@ -0,0 +1,3 @@
+<script context="module">
+  const load = () => {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test05-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test05-input.svelte
new file mode 100644
index 0000000..304b081
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test05-input.svelte
@@ -0,0 +1,3 @@
+<script context="module">
+  export function foo() {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test06-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test06-input.svelte
new file mode 100644
index 0000000..8c752e6
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test06-input.svelte
@@ -0,0 +1,7 @@
+<script context="module">
+  export function foo() {}
+</script>
+
+<script>
+  export function load() {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test07-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test07-input.svelte
new file mode 100644
index 0000000..1908775
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test07-input.svelte
@@ -0,0 +1,4 @@
+<script context="module">
+  function load() {}
+  export function foo(load) {}
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test08-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test08-input.svelte
new file mode 100644
index 0000000..b9d71aa
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test08-input.svelte
@@ -0,0 +1,4 @@
+<script context="module">
+  function load() {}
+  export const foo = load
+</script>
diff --git a/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test09-input.svelte b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test09-input.svelte
new file mode 100644
index 0000000..6daa28d
--- /dev/null
+++ b/tests/fixtures/rules/no-export-load-in-svelte-module-in-kit-pages/valid/test09-input.svelte
@@ -0,0 +1,5 @@
+<script context="module">
+  export function fn() {
+    function load() {}
+  }
+</script>
diff --git a/tests/src/rules/no-export-load-in-svelte-module-in-kit-pages.ts b/tests/src/rules/no-export-load-in-svelte-module-in-kit-pages.ts
new file mode 100644
index 0000000..fc83d0b
--- /dev/null
+++ b/tests/src/rules/no-export-load-in-svelte-module-in-kit-pages.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/no-export-load-in-svelte-module-in-kit-pages"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: "module",
+  },
+})
+
+tester.run(
+  "no-export-load-in-svelte-module-in-kit-pages",
+  rule as any,
+  loadTestCases("no-export-load-in-svelte-module-in-kit-pages"),
+)