Skip to content

Commit 8da870f

Browse files
feat: add no-export-load-in-svelte-module-in-kit-pages (#281)
* feat: add no-export-load-in-svelte-module-in-kit-pages * chore: add changeset * chore: add test * fix: check devDependencies also * fix: adjust for test * feat: add config * chore: fix typo * fix: bug * fix: bug * chore: uupdate docus * chore: update docs * chore: add kit helper * fix: fix for demo * chore: adjust doc style * chore: revert .eslintrc.js * chore: update uppercase -> lowercase * fix: update recommended * fix: i did not know :exit * fix: project root path finding logic * chore: utilize * chore: yarn update * fix: remove needless logic * chore: simplify * fix: false positive Co-authored-by: Yosuke Ota <[email protected]>
1 parent 1240968 commit 8da870f

33 files changed

+481
-1
lines changed

.changeset/orange-months-sparkle.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 `no-export-load-in-svelte-module-in-kit-pages` rule

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,28 @@ module.exports = {
216216
}
217217
```
218218

219+
#### settings.kit
220+
221+
If you use SvelteKit with not default configuration, you need to set below configurations.
222+
The schema is subset of SvelteKit's configuration.
223+
Therefore please check [SvelteKit docs](https://kit.svelte.dev/docs/configuration) for more details.
224+
225+
e.g.
226+
227+
```js
228+
module.exports = {
229+
// ...
230+
settings: {
231+
kit: {
232+
files: {
233+
routes: "src/routes",
234+
},
235+
},
236+
},
237+
// ...
238+
}
239+
```
240+
219241
### Running ESLint from the command line
220242

221243
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:
266288
| [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: |
267289
| [svelte/no-dupe-style-properties](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dupe-style-properties/) | disallow duplicate style properties | :star: |
268290
| [svelte/no-dynamic-slot-name](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dynamic-slot-name/) | disallow dynamic slot name | :star::wrench: |
291+
| [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. | |
269292
| [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: |
270293
| [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: |
271294
| [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: |

docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
let tsParser = null
1616
1717
let code = ""
18+
export let config = {}
1819
export let rules = {}
1920
export let fix = false
2021
export let language = "svelte"
@@ -75,6 +76,7 @@
7576
browser: true,
7677
es2021: true,
7778
},
79+
...config,
7880
}}
7981
{language}
8082
{options}

docs/rules.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
1919
| [svelte/no-dupe-else-if-blocks](./rules/no-dupe-else-if-blocks.md) | disallow duplicate conditions in `{#if}` / `{:else if}` chains | :star: |
2020
| [svelte/no-dupe-style-properties](./rules/no-dupe-style-properties.md) | disallow duplicate style properties | :star: |
2121
| [svelte/no-dynamic-slot-name](./rules/no-dynamic-slot-name.md) | disallow dynamic slot name | :star::wrench: |
22+
| [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. | |
2223
| [svelte/no-not-function-handler](./rules/no-not-function-handler.md) | disallow use of not function in event handler | :star: |
2324
| [svelte/no-object-in-text-mustaches](./rules/no-object-in-text-mustaches.md) | disallow objects in text mustache interpolation | :star: |
2425
| [svelte/no-shorthand-style-property-overrides](./rules/no-shorthand-style-property-overrides.md) | disallow shorthand style properties that override related longhand properties | :star: |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "svelte/no-export-load-in-svelte-module-in-kit-pages"
5+
description: "disallow exporting load functions in `*.svelte` module in Svelte Kit page components."
6+
---
7+
8+
# svelte/no-export-load-in-svelte-module-in-kit-pages
9+
10+
> disallow exporting load functions in `*.svelte` module in Svelte Kit page components.
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
13+
14+
## :book: Rule Details
15+
16+
This rule reports unexpected exported `load` function at `<script context="module">`.
17+
At SvelteKit v1.0.0-next.405, `load` function has been moved into a separate file — `+page.js` for pages, `+layout.js` for layouts.
18+
And the API has changed.
19+
20+
<script>
21+
const config = {
22+
settings: {
23+
kit: {
24+
files: {
25+
routes: "",
26+
},
27+
},
28+
},
29+
}
30+
</script>
31+
32+
<ESLintCodeBlock config="{config}">
33+
34+
<!--eslint-skip-->
35+
36+
```svelte
37+
<script context="module">
38+
/* eslint svelte/no-export-load-in-svelte-module-in-kit-pages: "error" */
39+
/* ✓ GOOD */
40+
export function foo() {}
41+
export function bar() {}
42+
/* ✗ BAD */
43+
export function load() {}
44+
// export const load = () => {}
45+
</script>
46+
```
47+
48+
</ESLintCodeBlock>
49+
50+
## :wrench: Options
51+
52+
Nothing. But if use are using not default routes folder, please set configuration according to the [user guide](../user-guide.md#settings-kit).
53+
54+
## :books: Further Reading
55+
56+
- [SvelteKit Migration Guide (v1.0.0-next.405)](https://github.com/sveltejs/kit/discussions/5774#discussioncomment-3292693)
57+
58+
## :mag: Implementation
59+
60+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-export-load-in-svelte-module-in-kit-pages.ts)
61+
- [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)

docs/user-guide.md

+22
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,28 @@ module.exports = {
168168
}
169169
```
170170

171+
#### settings.kit
172+
173+
If you use SvelteKit with not default configuration, you need to set below configurations.
174+
The schema is subset of SvelteKit's configuration.
175+
Therefore please check [SvelteKit docs](https://kit.svelte.dev/docs/configuration) for more details.
176+
177+
e.g.
178+
179+
```js
180+
module.exports = {
181+
// ...
182+
settings: {
183+
kit: {
184+
files: {
185+
routes: "src/routes",
186+
},
187+
},
188+
},
189+
// ...
190+
}
191+
```
192+
171193
### Running ESLint from the command line
172194

173195
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.

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.72,
177+
"atLeast": 98.69,
178178
"cache": true,
179179
"detail": true,
180180
"ignoreAsAssertion": true,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type * as ESTree from "estree"
2+
import { createRule } from "../utils"
3+
import { isKitPageComponent } from "../utils/svelte-kit"
4+
5+
export default createRule("no-export-load-in-svelte-module-in-kit-pages", {
6+
meta: {
7+
docs: {
8+
description:
9+
"disallow exporting load functions in `*.svelte` module in Svelte Kit page components.",
10+
category: "Possible Errors",
11+
// TODO Switch to recommended in the major version.
12+
recommended: false,
13+
},
14+
schema: [],
15+
messages: {
16+
unexpected:
17+
"disallow exporting load functions in `*.svelte` module in Svelte Kit page components.",
18+
},
19+
type: "problem",
20+
},
21+
create(context) {
22+
if (!isKitPageComponent(context)) {
23+
return {}
24+
}
25+
let isModule = false
26+
return {
27+
// <script context="module">
28+
[`Program > SvelteScriptElement > SvelteStartTag > SvelteAttribute[key.name="context"] > SvelteLiteral[value="module"]`]:
29+
() => {
30+
isModule = true
31+
},
32+
33+
// </script>
34+
"Program > SvelteScriptElement:exit": () => {
35+
isModule = false
36+
},
37+
38+
// export function load() {}
39+
// export const load = () => {}
40+
[`:matches(ExportNamedDeclaration > FunctionDeclaration, ExportNamedDeclaration > VariableDeclaration > VariableDeclarator) > Identifier.id[name="load"]`]:
41+
(node: ESTree.Identifier) => {
42+
if (!isModule) return {}
43+
return context.report({
44+
node,
45+
loc: node.loc!,
46+
messageId: "unexpected",
47+
})
48+
},
49+
}
50+
},
51+
})

src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ export type RuleContext = {
128128
postcss?: false | { configFilePath?: unknown }
129129
}
130130
}
131+
["kit"]?: {
132+
files?: {
133+
routes?: string
134+
}
135+
}
131136
}
132137
parserPath: string
133138
parserOptions: Linter.ParserOptions

src/utils/cache.ts

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Simple cache manager.
3+
*
4+
* refer: https://github.com/mysticatea/eslint-plugin-node/blob/f45c6149be7235c0f7422d1179c25726afeecd83/lib/util/cache.js
5+
*/
6+
7+
const SKIP_TIME = 5000
8+
9+
type CacheValue<T> = {
10+
expire: number
11+
value: T
12+
}
13+
14+
/**
15+
* The cache will dispose of each value if the value has not been accessed
16+
* during 5 seconds.
17+
* @returns getter and setter ofr the cache.
18+
*/
19+
export function createCache<T>(): {
20+
get: (key: string) => T | null
21+
set: (key: string, value: T) => void
22+
} {
23+
const map: Map<string, CacheValue<T>> = new Map()
24+
25+
/**
26+
* Get the cached value of the given key.
27+
* @param key The key to get.
28+
* @returns The cached value or null.
29+
*/
30+
function get(key: string): T | null {
31+
const entry = map.get(key)
32+
const now = Date.now()
33+
34+
if (entry) {
35+
if (entry.expire > now) {
36+
entry.expire = now + SKIP_TIME
37+
return entry.value
38+
}
39+
map.delete(key)
40+
}
41+
return null
42+
}
43+
44+
/**
45+
* Set the value of the given key.
46+
* @param key The key to set.
47+
* @param value The value to set.
48+
* @returns
49+
*/
50+
function set(key: string, value: T): void {
51+
const entry = map.get(key)
52+
const expire = Date.now() + SKIP_TIME
53+
54+
if (entry) {
55+
entry.value = value
56+
entry.expire = expire
57+
} else {
58+
map.set(key, { value, expire })
59+
}
60+
}
61+
62+
return { get, set }
63+
}

src/utils/get-package-json.ts

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* refer: https://github.com/mysticatea/eslint-plugin-node/blob/f45c6149be7235c0f7422d1179c25726afeecd83/lib/util/get-package-json.js
3+
*/
4+
5+
import fs from "fs"
6+
import path from "path"
7+
import { createCache } from "./cache"
8+
9+
type PackageJson = Record<string, any> & { filePath: string }
10+
11+
const isRunOnBrowser = !fs.readFileSync
12+
const cache = createCache<PackageJson | null>()
13+
14+
/**
15+
* Reads the `package.json` data in a given path.
16+
*
17+
* Don't cache the data.
18+
*
19+
* @param dir The path to a directory to read.
20+
* @returns The read `package.json` data, or null.
21+
*/
22+
function readPackageJson(dir: string): PackageJson | null {
23+
if (isRunOnBrowser) return null
24+
const filePath = path.join(dir, "package.json")
25+
try {
26+
const text = fs.readFileSync(filePath, "utf8")
27+
const data = JSON.parse(text)
28+
29+
if (typeof data === "object" && data !== null) {
30+
data.filePath = filePath
31+
return data
32+
}
33+
} catch (_err) {
34+
// do nothing.
35+
}
36+
37+
return null
38+
}
39+
40+
/**
41+
* Gets a `package.json` data.
42+
* The data is cached if found, then it's used after.
43+
* @param startPath A file path to lookup.
44+
* @returns A found `package.json` data or `null`.
45+
* This object have additional property `filePath`.
46+
*/
47+
export function getPackageJson(startPath = "a.js"): PackageJson | null {
48+
if (isRunOnBrowser) return null
49+
const startDir = path.dirname(path.resolve(startPath))
50+
let dir = startDir
51+
let prevDir = ""
52+
let data = null
53+
54+
do {
55+
data = cache.get(dir)
56+
if (data) {
57+
if (dir !== startDir) {
58+
cache.set(startDir, data)
59+
}
60+
return data
61+
}
62+
63+
data = readPackageJson(dir)
64+
if (data) {
65+
cache.set(dir, data)
66+
cache.set(startDir, data)
67+
return data
68+
}
69+
70+
// Go to next.
71+
prevDir = dir
72+
dir = path.resolve(dir, "..")
73+
} while (dir !== prevDir)
74+
75+
cache.set(startDir, null)
76+
return null
77+
}

src/utils/rules.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import noAtHtmlTags from "../rules/no-at-html-tags"
1515
import noDupeElseIfBlocks from "../rules/no-dupe-else-if-blocks"
1616
import noDupeStyleProperties from "../rules/no-dupe-style-properties"
1717
import noDynamicSlotName from "../rules/no-dynamic-slot-name"
18+
import noExportLoadInSvelteModuleInKitPages from "../rules/no-export-load-in-svelte-module-in-kit-pages"
1819
import noExtraReactiveCurlies from "../rules/no-extra-reactive-curlies"
1920
import noInnerDeclarations from "../rules/no-inner-declarations"
2021
import noNotFunctionHandler from "../rules/no-not-function-handler"
@@ -59,6 +60,7 @@ export const rules = [
5960
noDupeElseIfBlocks,
6061
noDupeStyleProperties,
6162
noDynamicSlotName,
63+
noExportLoadInSvelteModuleInKitPages,
6264
noExtraReactiveCurlies,
6365
noInnerDeclarations,
6466
noNotFunctionHandler,

0 commit comments

Comments
 (0)