Skip to content

Commit 1fdb5c2

Browse files
authored
feat(lint/js/svelte): add useSvelteKitRuneImports lint rule (#11960)
1 parent faa8b37 commit 1fdb5c2

18 files changed

Lines changed: 386 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Added the nursery rule [`useSvelteKitRuneImports`](https://biomejs.dev/linter/rules/use-svelte-kit-rune-imports/), which reports imports from the deprecated `$app/stores` module and suggests `$app/state` instead.
6+
7+
```js
8+
import { page } from "$app/stores";
9+
```

‎crates/biome_configuration/src/analyzer/linter/rules.rs‎

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/biome_configuration/src/generated/domain_selector.rs‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/biome_configuration/src/generated/linter_options_check.rs‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/biome_diagnostics_categories/src/categories.rs‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use biome_analyze::{
2+
Ast, Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule,
3+
};
4+
use biome_console::markup;
5+
use biome_diagnostics::Severity;
6+
use biome_js_syntax::AnyJsImportLike;
7+
use biome_rowan::TextRange;
8+
use biome_rule_options::use_svelte_kit_rune_imports::UseSvelteKitRuneImportsOptions;
9+
10+
declare_lint_rule! {
11+
/// Require importing SvelteKit's app state from `$app/state` instead of `$app/stores`.
12+
///
13+
/// SvelteKit 2.12 deprecated `$app/stores` in favor of `$app/state`. The `$app/state` module
14+
/// exposes `page`, `navigating`, and `updated` as reactive objects built on Svelte 5 runes,
15+
/// so they can be read directly instead of through a store subscription.
16+
///
17+
/// Switching modules also changes how the values are read. For example, `$page.url` becomes
18+
/// `page.url`, and `$updated` becomes `updated.current`. For this reason, the rule doesn't
19+
/// provide a code fix.
20+
///
21+
/// ## Examples
22+
///
23+
/// ### Invalid
24+
///
25+
/// ```js,expect_diagnostic
26+
/// import { page } from "$app/stores";
27+
/// ```
28+
///
29+
/// ```svelte,expect_diagnostic
30+
/// <script>
31+
/// import { page } from "$app/stores";
32+
/// </script>
33+
///
34+
/// <h1>{$page.url.pathname}</h1>
35+
/// ```
36+
///
37+
/// ### Valid
38+
///
39+
/// ```js
40+
/// import { page } from "$app/state";
41+
/// ```
42+
///
43+
/// ```svelte
44+
/// <script>
45+
/// import { page } from "$app/state";
46+
/// </script>
47+
///
48+
/// <h1>{page.url.pathname}</h1>
49+
/// ```
50+
///
51+
pub UseSvelteKitRuneImports {
52+
version: "next",
53+
name: "useSvelteKitRuneImports",
54+
language: "js",
55+
domains: &[RuleDomain::Svelte],
56+
recommended: true,
57+
severity: Severity::Warning,
58+
}
59+
}
60+
61+
impl Rule for UseSvelteKitRuneImports {
62+
type Query = Ast<AnyJsImportLike>;
63+
type State = TextRange;
64+
type Signals = Option<Self::State>;
65+
type Options = UseSvelteKitRuneImportsOptions;
66+
67+
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
68+
let node = ctx.query();
69+
let module_name_token = node.module_name_token()?;
70+
let import_path = node.inner_string_text()?;
71+
72+
(import_path.text() == DEPRECATED_MODULE).then_some(module_name_token.text_trimmed_range())
73+
}
74+
75+
fn diagnostic(_ctx: &RuleContext<Self>, range: &Self::State) -> Option<RuleDiagnostic> {
76+
Some(
77+
RuleDiagnostic::new(
78+
rule_category!(),
79+
range,
80+
markup! {
81+
"The "<Emphasis>"$app/stores"</Emphasis>" module is deprecated."
82+
},
83+
)
84+
.note(markup! {
85+
"SvelteKit deprecated "<Emphasis>"$app/stores"</Emphasis>" in favor of "<Emphasis>"$app/state"</Emphasis>", which provides the same values as reactive objects built on runes."
86+
})
87+
.note(markup! {
88+
"Import from "<Emphasis>"$app/state"</Emphasis>" instead, and read its values directly, for example "<Emphasis>"page.url"</Emphasis>" instead of "<Emphasis>"$page.url"</Emphasis>"."
89+
}),
90+
)
91+
}
92+
}
93+
94+
const DEPRECATED_MODULE: &str = "$app/stores";
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/* should generate diagnostics */
2+
import { page } from "$app/stores";
3+
import { navigating, updated } from '$app/stores';
4+
import * as stores from "$app/stores";
5+
export { page as currentPage } from "$app/stores";
6+
const { getStores } = require("$app/stores");
7+
await import("$app/stores");
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: invalid.js
4+
---
5+
# Input
6+
```js
7+
/* should generate diagnostics */
8+
import { page } from "$app/stores";
9+
import { navigating, updated } from '$app/stores';
10+
import * as stores from "$app/stores";
11+
export { page as currentPage } from "$app/stores";
12+
const { getStores } = require("$app/stores");
13+
await import("$app/stores");
14+
15+
```
16+
17+
# Diagnostics
18+
```
19+
invalid.js:2:22 lint/nursery/useSvelteKitRuneImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
20+
21+
! The $app/stores module is deprecated.
22+
23+
1 │ /* should generate diagnostics */
24+
> 2 │ import { page } from "$app/stores";
25+
│ ^^^^^^^^^^^^^
26+
3 │ import { navigating, updated } from '$app/stores';
27+
4 │ import * as stores from "$app/stores";
28+
29+
i SvelteKit deprecated $app/stores in favor of $app/state, which provides the same values as reactive objects built on runes.
30+
31+
i Import from $app/state instead, and read its values directly, for example page.url instead of $page.url.
32+
33+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
34+
35+
36+
```
37+
38+
```
39+
invalid.js:3:37 lint/nursery/useSvelteKitRuneImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
40+
41+
! The $app/stores module is deprecated.
42+
43+
1 │ /* should generate diagnostics */
44+
2 │ import { page } from "$app/stores";
45+
> 3 │ import { navigating, updated } from '$app/stores';
46+
│ ^^^^^^^^^^^^^
47+
4 │ import * as stores from "$app/stores";
48+
5 │ export { page as currentPage } from "$app/stores";
49+
50+
i SvelteKit deprecated $app/stores in favor of $app/state, which provides the same values as reactive objects built on runes.
51+
52+
i Import from $app/state instead, and read its values directly, for example page.url instead of $page.url.
53+
54+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
55+
56+
57+
```
58+
59+
```
60+
invalid.js:4:25 lint/nursery/useSvelteKitRuneImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
61+
62+
! The $app/stores module is deprecated.
63+
64+
2 │ import { page } from "$app/stores";
65+
3 │ import { navigating, updated } from '$app/stores';
66+
> 4 │ import * as stores from "$app/stores";
67+
│ ^^^^^^^^^^^^^
68+
5 │ export { page as currentPage } from "$app/stores";
69+
6 │ const { getStores } = require("$app/stores");
70+
71+
i SvelteKit deprecated $app/stores in favor of $app/state, which provides the same values as reactive objects built on runes.
72+
73+
i Import from $app/state instead, and read its values directly, for example page.url instead of $page.url.
74+
75+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
76+
77+
78+
```
79+
80+
```
81+
invalid.js:5:37 lint/nursery/useSvelteKitRuneImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
82+
83+
! The $app/stores module is deprecated.
84+
85+
3 │ import { navigating, updated } from '$app/stores';
86+
4 │ import * as stores from "$app/stores";
87+
> 5 │ export { page as currentPage } from "$app/stores";
88+
│ ^^^^^^^^^^^^^
89+
6 │ const { getStores } = require("$app/stores");
90+
7 │ await import("$app/stores");
91+
92+
i SvelteKit deprecated $app/stores in favor of $app/state, which provides the same values as reactive objects built on runes.
93+
94+
i Import from $app/state instead, and read its values directly, for example page.url instead of $page.url.
95+
96+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
97+
98+
99+
```
100+
101+
```
102+
invalid.js:6:31 lint/nursery/useSvelteKitRuneImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
103+
104+
! The $app/stores module is deprecated.
105+
106+
4 │ import * as stores from "$app/stores";
107+
5 │ export { page as currentPage } from "$app/stores";
108+
> 6 │ const { getStores } = require("$app/stores");
109+
│ ^^^^^^^^^^^^^
110+
7 │ await import("$app/stores");
111+
8 │
112+
113+
i SvelteKit deprecated $app/stores in favor of $app/state, which provides the same values as reactive objects built on runes.
114+
115+
i Import from $app/state instead, and read its values directly, for example page.url instead of $page.url.
116+
117+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
118+
119+
120+
```
121+
122+
```
123+
invalid.js:7:14 lint/nursery/useSvelteKitRuneImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
124+
125+
! The $app/stores module is deprecated.
126+
127+
5 │ export { page as currentPage } from "$app/stores";
128+
6 │ const { getStores } = require("$app/stores");
129+
> 7 │ await import("$app/stores");
130+
│ ^^^^^^^^^^^^^
131+
8 │
132+
133+
i SvelteKit deprecated $app/stores in favor of $app/state, which provides the same values as reactive objects built on runes.
134+
135+
i Import from $app/state instead, and read its values directly, for example page.url instead of $page.url.
136+
137+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
138+
139+
140+
```
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!-- should generate diagnostics -->
2+
<script>
3+
import { page } from "$app/stores";
4+
</script>
5+
6+
<h1>{$page.url.pathname}</h1>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: invalid.svelte
4+
---
5+
# Input
6+
```svelte
7+
<!-- should generate diagnostics -->
8+
<script>
9+
import { page } from "$app/stores";
10+
</script>
11+
12+
<h1>{$page.url.pathname}</h1>
13+
14+
```
15+
16+
# Diagnostics
17+
```
18+
invalid.svelte:3:23 lint/nursery/useSvelteKitRuneImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
19+
20+
! The $app/stores module is deprecated.
21+
22+
1 │ <!-- should generate diagnostics -->
23+
2 │ <script>
24+
> 3 │ import { page } from "$app/stores";
25+
│ ^^^^^^^^^^^^^
26+
4 │ </script>
27+
5 │
28+
29+
i SvelteKit deprecated $app/stores in favor of $app/state, which provides the same values as reactive objects built on runes.
30+
31+
i Import from $app/state instead, and read its values directly, for example page.url instead of $page.url.
32+
33+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
34+
35+
36+
```

0 commit comments

Comments
 (0)