diff --git a/documentation/docs/07-misc/07-v5-migration-guide.md b/documentation/docs/07-misc/07-v5-migration-guide.md
index 87ff40cf472e..e502b7921a1e 100644
--- a/documentation/docs/07-misc/07-v5-migration-guide.md
+++ b/documentation/docs/07-misc/07-v5-migration-guide.md
@@ -10,13 +10,13 @@ You don't have to migrate to the new syntax right away - Svelte 5 still supports
At the heart of Svelte 5 is the new runes API. Runes are basically compiler instructions that inform Svelte about reactivity. Syntactically, runes are functions starting with a dollar-sign.
-### let -> $state
+### let → $state
In Svelte 4, a `let` declaration at the top level of a component was implicitly reactive. In Svelte 5, things are more explicit: a variable is reactive when created using the `$state` rune. Let's migrate the counter to runes mode by wrapping the counter in `$state`:
```svelte
```
@@ -25,14 +25,14 @@ Nothing else changes. `count` is still the number itself, and you read and write
> [!DETAILS] Why we did this
> `let` being implicitly reactive at the top level worked great, but it meant that reactivity was constrained - a `let` declaration anywhere else was not reactive. This forced you to resort to using stores when refactoring code out of the top level of components for reuse. This meant you had to learn an entirely separate reactivity model, and the result often wasn't as nice to work with. Because reactivity is more explicit in Svelte 5, you can keep using the same API outside the top level of components. Head to [the tutorial](/tutorial) to learn more.
-### $: -> $derived/$effect
+### $: → $derived/$effect
In Svelte 4, a `$:` statement at the top level of a component could be used to declare a derivation, i.e. state that is entirely defined through a computation of other state. In Svelte 5, this is achieved using the `$derived` rune:
```svelte
```
@@ -42,7 +42,8 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is
```svelte
```
@@ -105,8 +106,8 @@ In Svelte 5, the `$props` rune makes this straightforward without any additional
```svelte
@@ -192,9 +193,9 @@ This function is deprecated in Svelte 5. Instead, components should accept _call
```svelte
@@ -466,11 +467,11 @@ By now you should have a pretty good understanding of the before/after and how t
We thought the same, which is why we provide a migration script to do most of the migration automatically. You can upgrade your project by using `npx sv migrate svelte-5`. This will do the following things:
- bump core dependencies in your `package.json`
-- migrate to runes (`let` -> `$state` etc)
-- migrate to event attributes for DOM elements (`on:click` -> `onclick`)
-- migrate slot creations to render tags (`` -> `{@render children()}`)
-- migrate slot usages to snippets (`
...
` -> `{#snippet x()}
...
{/snippet}`)
-- migrate obvious component creations (`new Component(...)` -> `mount(Component, ...)`)
+- migrate to runes (`let` → `$state` etc)
+- migrate to event attributes for DOM elements (`on:click` → `onclick`)
+- migrate slot creations to render tags (`` → `{@render children()}`)
+- migrate slot usages to snippets (`
...
` → `{#snippet x()}
...
{/snippet}`)
+- migrate obvious component creations (`new Component(...)` → `mount(Component, ...)`)
You can also migrate a single component in VS Code through the `Migrate Component to Svelte 5 Syntax` command, or in our Playground through the `Migrate` button.
diff --git a/documentation/docs/07-misc/99-faq.md b/documentation/docs/07-misc/99-faq.md
index b56c27af86b6..7e25cdab5596 100644
--- a/documentation/docs/07-misc/99-faq.md
+++ b/documentation/docs/07-misc/99-faq.md
@@ -46,7 +46,7 @@ It will show up on hover.
- You can use markdown here.
- You can also use code blocks here.
- Usage:
- ```tsx
+ ```svelte
```
-->
diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md
index 2c2e0707ea12..0beb3cb9a96c 100644
--- a/documentation/docs/98-reference/.generated/client-errors.md
+++ b/documentation/docs/98-reference/.generated/client-errors.md
@@ -133,3 +133,27 @@ Reading state that was created inside the same derived is forbidden. Consider us
```
Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
```
+
+This error is thrown in a situation like this:
+
+```svelte
+
+
+
+```
+
+Here, the `$derived` updates `count`, which is `$state` and therefore forbidden to do. It is forbidden because the reactive graph could become unstable as a result, leading to subtle bugs, like values being stale or effects firing in the wrong order. To prevent this, Svelte errors when detecting an update to a `$state` variable.
+
+To fix this:
+- See if it's possible to refactor your `$derived` such that the update becomes unnecessary
+- Think about why you need to update `$state` inside a `$derived` in the first place. Maybe it's because you're using `bind:`, which leads you down a bad code path, and separating input and output path (by splitting it up to an attribute and an event, or by using [Function bindings](bind#Function-bindings)) makes it possible avoid the update
+- If it's unavoidable, you may need to use an [`$effect`]($effect) instead. This could include splitting parts of the `$derived` into an [`$effect`]($effect) which does the updates
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 65b3edd1fdad..e10a606fe48b 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,23 @@
# svelte
+## 5.23.1
+
+### Patch Changes
+
+- fix: invalidate parent effects when child effects update parent dependencies ([#15506](https://github.com/sveltejs/svelte/pull/15506))
+
+- fix: correctly match `:has()` selector during css pruning ([#15277](https://github.com/sveltejs/svelte/pull/15277))
+
+- fix: replace `undefined` with `void 0` to avoid edge case ([#15511](https://github.com/sveltejs/svelte/pull/15511))
+
+- fix: allow global-like pseudo-selectors refinement ([#15313](https://github.com/sveltejs/svelte/pull/15313))
+
+- chore: don't distribute unused types definitions ([#15473](https://github.com/sveltejs/svelte/pull/15473))
+
+- fix: add `files` and `group` to HTMLInputAttributes in elements.d.ts ([#15492](https://github.com/sveltejs/svelte/pull/15492))
+
+- fix: throw rune_invalid_arguments_length when $state.raw() is used with more than 1 arg ([#15516](https://github.com/sveltejs/svelte/pull/15516))
+
## 5.23.0
### Minor Changes
diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts
index 08687cafaf14..99d87b4c09a4 100644
--- a/packages/svelte/elements.d.ts
+++ b/packages/svelte/elements.d.ts
@@ -1076,6 +1076,7 @@ export interface HTMLInputAttributes extends HTMLAttributes {
checked?: boolean | undefined | null;
dirname?: string | undefined | null;
disabled?: boolean | undefined | null;
+ files?: FileList | undefined | null;
form?: string | undefined | null;
formaction?: string | undefined | null;
formenctype?:
@@ -1087,6 +1088,7 @@ export interface HTMLInputAttributes extends HTMLAttributes {
formmethod?: 'dialog' | 'get' | 'post' | 'DIALOG' | 'GET' | 'POST' | undefined | null;
formnovalidate?: boolean | undefined | null;
formtarget?: string | undefined | null;
+ group?: any | undefined | null;
height?: number | string | undefined | null;
indeterminate?: boolean | undefined | null;
list?: string | undefined | null;
diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md
index ce1f222c63ea..ab4d1519c18c 100644
--- a/packages/svelte/messages/client-errors/errors.md
+++ b/packages/svelte/messages/client-errors/errors.md
@@ -87,3 +87,27 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long
## state_unsafe_mutation
> Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
+
+This error is thrown in a situation like this:
+
+```svelte
+
+
+
+```
+
+Here, the `$derived` updates `count`, which is `$state` and therefore forbidden to do. It is forbidden because the reactive graph could become unstable as a result, leading to subtle bugs, like values being stale or effects firing in the wrong order. To prevent this, Svelte errors when detecting an update to a `$state` variable.
+
+To fix this:
+- See if it's possible to refactor your `$derived` such that the update becomes unnecessary
+- Think about why you need to update `$state` inside a `$derived` in the first place. Maybe it's because you're using `bind:`, which leads you down a bad code path, and separating input and output path (by splitting it up to an attribute and an event, or by using [Function bindings](bind#Function-bindings)) makes it possible avoid the update
+- If it's unavoidable, you may need to use an [`$effect`]($effect) instead. This could include splitting parts of the `$derived` into an [`$effect`]($effect) which does the updates
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index b3ac0a5b518e..6f10b2a9ea60 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,18 +2,19 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.23.0",
+ "version": "5.23.1",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
"node": ">=18"
},
"files": [
+ "*.d.ts",
"src",
"!src/**/*.test.*",
+ "!src/**/*.d.ts",
"types",
"compiler",
- "*.d.ts",
"README.md"
],
"module": "src/index-client.js",
diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
index ed228385820a..362ac9dcad50 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
@@ -133,7 +133,13 @@ const css_visitors = {
node.metadata.is_global = node.selectors.length >= 1 && is_global(node);
- if (node.selectors.length === 1) {
+ if (
+ node.selectors.length >= 1 &&
+ node.selectors.every(
+ (selector) =>
+ selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector'
+ )
+ ) {
const first = node.selectors[0];
node.metadata.is_global_like ||=
(first.type === 'PseudoClassSelector' && first.name === 'host') ||
diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
index fc8108e46e8e..070ec7cd347e 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
@@ -5,9 +5,12 @@ import { regex_ends_with_whitespace, regex_starts_with_whitespace } from '../../
import { get_attribute_chunks, is_text_attribute } from '../../../utils/ast.js';
/** @typedef {NODE_PROBABLY_EXISTS | NODE_DEFINITELY_EXISTS} NodeExistsValue */
+/** @typedef {FORWARD | BACKWARD} Direction */
const NODE_PROBABLY_EXISTS = 0;
const NODE_DEFINITELY_EXISTS = 1;
+const FORWARD = 0;
+const BACKWARD = 1;
const whitelist_attribute_selector = new Map([
['details', ['open']],
@@ -43,6 +46,27 @@ const nesting_selector = {
}
};
+/** @type {Compiler.AST.CSS.RelativeSelector} */
+const any_selector = {
+ type: 'RelativeSelector',
+ start: -1,
+ end: -1,
+ combinator: null,
+ selectors: [
+ {
+ type: 'TypeSelector',
+ name: '*',
+ start: -1,
+ end: -1
+ }
+ ],
+ metadata: {
+ is_global: false,
+ is_global_like: false,
+ scoped: false
+ }
+};
+
/**
* Snippets encountered already (avoids infinite loops)
* @type {Set}
@@ -72,7 +96,8 @@ export function prune(stylesheet, element) {
apply_selector(
selectors,
/** @type {Compiler.AST.CSS.Rule} */ (node.metadata.rule),
- element
+ element,
+ BACKWARD
)
) {
node.metadata.used = true;
@@ -159,16 +184,17 @@ function truncate(node) {
* @param {Compiler.AST.CSS.RelativeSelector[]} relative_selectors
* @param {Compiler.AST.CSS.Rule} rule
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
+ * @param {Direction} direction
* @returns {boolean}
*/
-function apply_selector(relative_selectors, rule, element) {
- const parent_selectors = relative_selectors.slice();
- const relative_selector = parent_selectors.pop();
+function apply_selector(relative_selectors, rule, element, direction) {
+ const rest_selectors = relative_selectors.slice();
+ const relative_selector = direction === FORWARD ? rest_selectors.shift() : rest_selectors.pop();
const matched =
!!relative_selector &&
- relative_selector_might_apply_to_node(relative_selector, rule, element) &&
- apply_combinator(relative_selector, parent_selectors, rule, element);
+ relative_selector_might_apply_to_node(relative_selector, rule, element, direction) &&
+ apply_combinator(relative_selector, rest_selectors, rule, element, direction);
if (matched) {
if (!is_outer_global(relative_selector)) {
@@ -183,76 +209,63 @@ function apply_selector(relative_selectors, rule, element) {
/**
* @param {Compiler.AST.CSS.RelativeSelector} relative_selector
- * @param {Compiler.AST.CSS.RelativeSelector[]} parent_selectors
+ * @param {Compiler.AST.CSS.RelativeSelector[]} rest_selectors
* @param {Compiler.AST.CSS.Rule} rule
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
+ * @param {Direction} direction
* @returns {boolean}
*/
-function apply_combinator(relative_selector, parent_selectors, rule, node) {
- if (!relative_selector.combinator) return true;
+function apply_combinator(relative_selector, rest_selectors, rule, node, direction) {
+ const combinator =
+ direction == FORWARD ? rest_selectors[0]?.combinator : relative_selector.combinator;
+ if (!combinator) return true;
- const name = relative_selector.combinator.name;
-
- switch (name) {
+ switch (combinator.name) {
case ' ':
case '>': {
+ const is_adjacent = combinator.name === '>';
+ const parents =
+ direction === FORWARD
+ ? get_descendant_elements(node, is_adjacent)
+ : get_ancestor_elements(node, is_adjacent);
let parent_matched = false;
- const path = node.metadata.path;
- let i = path.length;
-
- while (i--) {
- const parent = path[i];
-
- if (parent.type === 'SnippetBlock') {
- if (seen.has(parent)) {
- parent_matched = true;
- } else {
- seen.add(parent);
-
- for (const site of parent.metadata.sites) {
- if (apply_combinator(relative_selector, parent_selectors, rule, site)) {
- parent_matched = true;
- }
- }
- }
-
- break;
- }
-
- if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
- if (apply_selector(parent_selectors, rule, parent)) {
- parent_matched = true;
- }
-
- if (name === '>') return parent_matched;
+ for (const parent of parents) {
+ if (apply_selector(rest_selectors, rule, parent, direction)) {
+ parent_matched = true;
}
}
- return parent_matched || parent_selectors.every((selector) => is_global(selector, rule));
+ return (
+ parent_matched ||
+ (direction === BACKWARD &&
+ (!is_adjacent || parents.length === 0) &&
+ rest_selectors.every((selector) => is_global(selector, rule)))
+ );
}
case '+':
case '~': {
- const siblings = get_possible_element_siblings(node, name === '+');
+ const siblings = get_possible_element_siblings(node, direction, combinator.name === '+');
let sibling_matched = false;
for (const possible_sibling of siblings.keys()) {
if (possible_sibling.type === 'RenderTag' || possible_sibling.type === 'SlotElement') {
// `{@render foo()}
foo
` with `:global(.x) + p` is a match
- if (parent_selectors.length === 1 && parent_selectors[0].metadata.is_global) {
+ if (rest_selectors.length === 1 && rest_selectors[0].metadata.is_global) {
sibling_matched = true;
}
- } else if (apply_selector(parent_selectors, rule, possible_sibling)) {
+ } else if (apply_selector(rest_selectors, rule, possible_sibling, direction)) {
sibling_matched = true;
}
}
return (
sibling_matched ||
- (get_element_parent(node) === null &&
- parent_selectors.every((selector) => is_global(selector, rule)))
+ (direction === BACKWARD &&
+ get_element_parent(node) === null &&
+ rest_selectors.every((selector) => is_global(selector, rule)))
);
}
@@ -313,9 +326,10 @@ const regex_backslash_and_following_character = /\\(.)/g;
* @param {Compiler.AST.CSS.RelativeSelector} relative_selector
* @param {Compiler.AST.CSS.Rule} rule
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
+ * @param {Direction} direction
* @returns {boolean}
*/
-function relative_selector_might_apply_to_node(relative_selector, rule, element) {
+function relative_selector_might_apply_to_node(relative_selector, rule, element, direction) {
// Sort :has(...) selectors in one bucket and everything else into another
const has_selectors = [];
const other_selectors = [];
@@ -331,13 +345,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
// If we're called recursively from a :has(...) selector, we're on the way of checking if the other selectors match.
// In that case ignore this check (because we just came from this) to avoid an infinite loop.
if (has_selectors.length > 0) {
- /** @type {Array} */
- const child_elements = [];
- /** @type {Array} */
- const descendant_elements = [];
- /** @type {Array} */
- let sibling_elements; // do them lazy because it's rarely used and expensive to calculate
-
// If this is a :has inside a global selector, we gotta include the element itself, too,
// because the global selector might be for an element that's outside the component,
// e.g. :root:has(.scoped), :global(.foo):has(.scoped), or :root { &:has(.scoped) {} }
@@ -353,46 +360,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
)
)
);
- if (include_self) {
- child_elements.push(element);
- descendant_elements.push(element);
- }
-
- const seen = new Set();
-
- /**
- * @param {Compiler.AST.SvelteNode} node
- * @param {{ is_child: boolean }} state
- */
- function walk_children(node, state) {
- walk(node, state, {
- _(node, context) {
- if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
- descendant_elements.push(node);
-
- if (context.state.is_child) {
- child_elements.push(node);
- context.state.is_child = false;
- context.next();
- context.state.is_child = true;
- } else {
- context.next();
- }
- } else if (node.type === 'RenderTag') {
- for (const snippet of node.metadata.snippets) {
- if (seen.has(snippet)) continue;
-
- seen.add(snippet);
- walk_children(snippet.body, context.state);
- }
- } else {
- context.next();
- }
- }
- });
- }
-
- walk_children(element.fragment, { is_child: true });
// :has(...) is special in that it means "look downwards in the CSS tree". Since our matching algorithm goes
// upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the
@@ -403,37 +370,34 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
let matched = false;
for (const complex_selector of complex_selectors) {
- const selectors = truncate(complex_selector);
- const left_most_combinator = selectors[0]?.combinator ?? descendant_combinator;
- // In .x:has(> y), we want to search for y, ignoring the left-most combinator
- // (else it would try to walk further up and fail because there are no selectors left)
- if (selectors.length > 0) {
- selectors[0] = {
- ...selectors[0],
- combinator: null
- };
+ const [first, ...rest] = truncate(complex_selector);
+ // if it was just a :global(...)
+ if (!first) {
+ complex_selector.metadata.used = true;
+ matched = true;
+ continue;
}
- const descendants =
- left_most_combinator.name === '+' || left_most_combinator.name === '~'
- ? (sibling_elements ??= get_following_sibling_elements(element, include_self))
- : left_most_combinator.name === '>'
- ? child_elements
- : descendant_elements;
-
- let selector_matched = false;
-
- // Iterate over all descendant elements and check if the selector inside :has matches
- for (const element of descendants) {
- if (
- selectors.length === 0 /* is :global(...) */ ||
- (element.metadata.scoped && selector_matched) ||
- apply_selector(selectors, rule, element)
- ) {
+ if (include_self) {
+ const selector_including_self = [
+ first.combinator ? { ...first, combinator: null } : first,
+ ...rest
+ ];
+ if (apply_selector(selector_including_self, rule, element, FORWARD)) {
complex_selector.metadata.used = true;
- selector_matched = matched = true;
+ matched = true;
}
}
+
+ const selector_excluding_self = [
+ any_selector,
+ first.combinator ? first : { ...first, combinator: descendant_combinator },
+ ...rest
+ ];
+ if (apply_selector(selector_excluding_self, rule, element, FORWARD)) {
+ complex_selector.metadata.used = true;
+ matched = true;
+ }
}
if (!matched) {
@@ -458,7 +422,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
) {
const args = selector.args;
const complex_selector = args.children[0];
- return apply_selector(complex_selector.children, rule, element);
+ return apply_selector(complex_selector.children, rule, element, BACKWARD);
}
// We came across a :global, everything beyond it is global and therefore a potential match
@@ -507,7 +471,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
if (is_global) {
complex_selector.metadata.used = true;
matched = true;
- } else if (apply_selector(relative, rule, element)) {
+ } else if (apply_selector(relative, rule, element, BACKWARD)) {
complex_selector.metadata.used = true;
matched = true;
} else if (complex_selector.children.length > 1 && (name == 'is' || name == 'where')) {
@@ -591,7 +555,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
for (const complex_selector of parent.prelude.children) {
if (
- apply_selector(get_relative_selectors(complex_selector), parent, element) ||
+ apply_selector(get_relative_selectors(complex_selector), parent, element, direction) ||
complex_selector.children.every((s) => is_global(s, parent))
) {
complex_selector.metadata.used = true;
@@ -612,80 +576,6 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
return true;
}
-/**
- * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
- * @param {boolean} include_self
- */
-function get_following_sibling_elements(element, include_self) {
- const path = element.metadata.path;
- let i = path.length;
-
- /** @type {Compiler.AST.SvelteNode} */
- let start = element;
- let nodes = /** @type {Compiler.AST.SvelteNode[]} */ (
- /** @type {Compiler.AST.Fragment} */ (path[0]).nodes
- );
-
- // find the set of nodes to walk...
- while (i--) {
- const node = path[i];
-
- if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
- nodes = node.fragment.nodes;
- break;
- }
-
- if (node.type !== 'Fragment') {
- start = node;
- }
- }
-
- /** @type {Array} */
- const siblings = [];
-
- // ...then walk them, starting from the node containing the element in question
- // skipping nodes that appears before the element
-
- const seen = new Set();
- let skip = true;
-
- /** @param {Compiler.AST.SvelteNode} node */
- function get_siblings(node) {
- walk(node, null, {
- RegularElement(node) {
- if (node === element) {
- skip = false;
- if (include_self) siblings.push(node);
- } else if (!skip) {
- siblings.push(node);
- }
- },
- SvelteElement(node) {
- if (node === element) {
- skip = false;
- if (include_self) siblings.push(node);
- } else if (!skip) {
- siblings.push(node);
- }
- },
- RenderTag(node) {
- for (const snippet of node.metadata.snippets) {
- if (seen.has(snippet)) continue;
-
- seen.add(snippet);
- get_siblings(snippet.body);
- }
- }
- });
- }
-
- for (const node of nodes.slice(nodes.indexOf(start))) {
- get_siblings(node);
- }
-
- return siblings;
-}
-
/**
* @param {any} operator
* @param {any} expected_value
@@ -822,6 +712,84 @@ function unquote(str) {
return str;
}
+/**
+ * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
+ * @param {boolean} adjacent_only
+ * @param {Set} seen
+ */
+function get_ancestor_elements(node, adjacent_only, seen = new Set()) {
+ /** @type {Array} */
+ const ancestors = [];
+
+ const path = node.metadata.path;
+ let i = path.length;
+
+ while (i--) {
+ const parent = path[i];
+
+ if (parent.type === 'SnippetBlock') {
+ if (!seen.has(parent)) {
+ seen.add(parent);
+
+ for (const site of parent.metadata.sites) {
+ ancestors.push(...get_ancestor_elements(site, adjacent_only, seen));
+ }
+ }
+
+ break;
+ }
+
+ if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
+ ancestors.push(parent);
+ if (adjacent_only) {
+ break;
+ }
+ }
+ }
+
+ return ancestors;
+}
+
+/**
+ * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
+ * @param {boolean} adjacent_only
+ * @param {Set} seen
+ */
+function get_descendant_elements(node, adjacent_only, seen = new Set()) {
+ /** @type {Array} */
+ const descendants = [];
+
+ /**
+ * @param {Compiler.AST.SvelteNode} node
+ */
+ function walk_children(node) {
+ walk(node, null, {
+ _(node, context) {
+ if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
+ descendants.push(node);
+
+ if (!adjacent_only) {
+ context.next();
+ }
+ } else if (node.type === 'RenderTag') {
+ for (const snippet of node.metadata.snippets) {
+ if (seen.has(snippet)) continue;
+
+ seen.add(snippet);
+ walk_children(snippet.body);
+ }
+ } else {
+ context.next();
+ }
+ }
+ });
+ }
+
+ walk_children(node.type === 'RenderTag' ? node : node.fragment);
+
+ return descendants;
+}
+
/**
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
* @returns {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | null}
@@ -843,11 +811,12 @@ function get_element_parent(node) {
/**
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
+ * @param {Direction} direction
* @param {boolean} adjacent_only
* @param {Set} seen
* @returns {Map}
*/
-function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
+function get_possible_element_siblings(node, direction, adjacent_only, seen = new Set()) {
/** @type {Map} */
const result = new Map();
const path = node.metadata.path;
@@ -859,9 +828,9 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
while (i--) {
const fragment = /** @type {Compiler.AST.Fragment} */ (path[i--]);
- let j = fragment.nodes.indexOf(current);
+ let j = fragment.nodes.indexOf(current) + (direction === FORWARD ? 1 : -1);
- while (j--) {
+ while (j >= 0 && j < fragment.nodes.length) {
const node = fragment.nodes[j];
if (node.type === 'RegularElement') {
@@ -876,21 +845,28 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
return result;
}
}
+ // Special case: slots, render tags and svelte:element tags could resolve to no siblings,
+ // so we want to continue until we find a definite sibling even with the adjacent-only combinator
} else if (is_block(node)) {
if (node.type === 'SlotElement') {
result.set(node, NODE_PROBABLY_EXISTS);
}
- const possible_last_child = get_possible_last_child(node, adjacent_only);
+ const possible_last_child = get_possible_nested_siblings(node, direction, adjacent_only);
add_to_map(possible_last_child, result);
if (adjacent_only && has_definite_elements(possible_last_child)) {
return result;
}
- } else if (node.type === 'RenderTag' || node.type === 'SvelteElement') {
+ } else if (node.type === 'SvelteElement') {
result.set(node, NODE_PROBABLY_EXISTS);
- // Special case: slots, render tags and svelte:element tags could resolve to no siblings,
- // so we want to continue until we find a definite sibling even with the adjacent-only combinator
+ } else if (node.type === 'RenderTag') {
+ result.set(node, NODE_PROBABLY_EXISTS);
+ for (const snippet of node.metadata.snippets) {
+ add_to_map(get_possible_nested_siblings(snippet, direction, adjacent_only), result);
+ }
}
+
+ j = direction === FORWARD ? j + 1 : j - 1;
}
current = path[i];
@@ -910,7 +886,7 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
seen.add(current);
for (const site of current.metadata.sites) {
- const siblings = get_possible_element_siblings(site, adjacent_only, seen);
+ const siblings = get_possible_element_siblings(site, direction, adjacent_only, seen);
add_to_map(siblings, result);
if (adjacent_only && current.metadata.sites.size === 1 && has_definite_elements(siblings)) {
@@ -923,7 +899,7 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
if (current.type === 'EachBlock' && fragment === current.body) {
// `{#each ...}{/each}` — `` can be previous sibling of ``
- add_to_map(get_possible_last_child(current, adjacent_only), result);
+ add_to_map(get_possible_nested_siblings(current, direction, adjacent_only), result);
}
}
@@ -931,11 +907,13 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) {
}
/**
- * @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement} node
+ * @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement | Compiler.AST.SnippetBlock} node
+ * @param {Direction} direction
* @param {boolean} adjacent_only
+ * @param {Set} seen
* @returns {Map}
*/
-function get_possible_last_child(node, adjacent_only) {
+function get_possible_nested_siblings(node, direction, adjacent_only, seen = new Set()) {
/** @type {Array} */
let fragments = [];
@@ -956,12 +934,20 @@ function get_possible_last_child(node, adjacent_only) {
case 'SlotElement':
fragments.push(node.fragment);
break;
+
+ case 'SnippetBlock':
+ if (seen.has(node)) {
+ return new Map();
+ }
+ seen.add(node);
+ fragments.push(node.body);
+ break;
}
/** @type {Map} NodeMap */
const result = new Map();
- let exhaustive = node.type !== 'SlotElement';
+ let exhaustive = node.type !== 'SlotElement' && node.type !== 'SnippetBlock';
for (const fragment of fragments) {
if (fragment == null) {
@@ -969,7 +955,7 @@ function get_possible_last_child(node, adjacent_only) {
continue;
}
- const map = loop_child(fragment.nodes, adjacent_only);
+ const map = loop_child(fragment.nodes, direction, adjacent_only, seen);
exhaustive &&= has_definite_elements(map);
add_to_map(map, result);
@@ -1012,27 +998,28 @@ function add_to_map(from, to) {
}
/**
- * @param {NodeExistsValue | undefined} exist1
+ * @param {NodeExistsValue} exist1
* @param {NodeExistsValue | undefined} exist2
* @returns {NodeExistsValue}
*/
function higher_existence(exist1, exist2) {
- // @ts-expect-error TODO figure out if this is a bug
- if (exist1 === undefined || exist2 === undefined) return exist1 || exist2;
+ if (exist2 === undefined) return exist1;
return exist1 > exist2 ? exist1 : exist2;
}
/**
* @param {Compiler.AST.SvelteNode[]} children
+ * @param {Direction} direction
* @param {boolean} adjacent_only
+ * @param {Set} seen
*/
-function loop_child(children, adjacent_only) {
+function loop_child(children, direction, adjacent_only, seen) {
/** @type {Map} */
const result = new Map();
- let i = children.length;
+ let i = direction === FORWARD ? 0 : children.length - 1;
- while (i--) {
+ while (i >= 0 && i < children.length) {
const child = children[i];
if (child.type === 'RegularElement') {
@@ -1042,13 +1029,19 @@ function loop_child(children, adjacent_only) {
}
} else if (child.type === 'SvelteElement') {
result.set(child, NODE_PROBABLY_EXISTS);
+ } else if (child.type === 'RenderTag') {
+ for (const snippet of child.metadata.snippets) {
+ add_to_map(get_possible_nested_siblings(snippet, direction, adjacent_only, seen), result);
+ }
} else if (is_block(child)) {
- const child_result = get_possible_last_child(child, adjacent_only);
+ const child_result = get_possible_nested_siblings(child, direction, adjacent_only, seen);
add_to_map(child_result, result);
if (adjacent_only && has_definite_elements(child_result)) {
break;
}
}
+
+ i = direction === FORWARD ? i + 1 : i - 1;
}
return result;
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
index 6c2171785244..6ef323725b3f 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js
@@ -117,7 +117,7 @@ export function CallExpression(node, context) {
if ((rune === '$derived' || rune === '$derived.by') && node.arguments.length !== 1) {
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
- } else if (rune === '$state' && node.arguments.length > 1) {
+ } else if (node.arguments.length > 1) {
e.rune_invalid_arguments_length(node, rune, 'zero or one arguments');
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index cf5ba285cbf3..ac8263b91669 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -596,7 +596,7 @@ export function client_component(analysis, options) {
/** @type {ESTree.Property[]} */ (
[
prop_def.attribute ? b.init('attribute', b.literal(prop_def.attribute)) : undefined,
- prop_def.reflect ? b.init('reflect', b.literal(true)) : undefined,
+ prop_def.reflect ? b.init('reflect', b.true) : undefined,
prop_def.type ? b.init('type', b.literal(prop_def.type)) : undefined
].filter(Boolean)
)
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AnimateDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AnimateDirective.js
index 510f32cde502..2e051ec67465 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AnimateDirective.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AnimateDirective.js
@@ -11,7 +11,7 @@ import { parse_directive_name } from './shared/utils.js';
export function AnimateDirective(node, context) {
const expression =
node.expression === null
- ? b.literal(null)
+ ? b.null
: b.thunk(/** @type {Expression} */ (context.visit(node.expression)));
// in after_update to ensure it always happens after bind:this
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
index e0aef2d316a7..7588b24280d8 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
@@ -60,7 +60,7 @@ export function AwaitBlock(node, context) {
expression,
node.pending
? b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.pending)))
- : b.literal(null),
+ : b.null,
then_block,
catch_block
)
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BinaryExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BinaryExpression.js
index c8c54a5a599b..c5639208553d 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BinaryExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BinaryExpression.js
@@ -16,7 +16,7 @@ export function BinaryExpression(node, context) {
'$.strict_equals',
/** @type {Expression} */ (context.visit(node.left)),
/** @type {Expression} */ (context.visit(node.right)),
- operator === '!==' && b.literal(false)
+ operator === '!==' && b.false
);
}
@@ -25,7 +25,7 @@ export function BinaryExpression(node, context) {
'$.equals',
/** @type {Expression} */ (context.visit(node.left)),
/** @type {Expression} */ (context.visit(node.right)),
- operator === '!=' && b.literal(false)
+ operator === '!=' && b.false
);
}
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js
index 0876fa30b6a5..fdd21b2b7ed8 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/IfBlock.js
@@ -40,9 +40,7 @@ export function IfBlock(node, context) {
b.if(
/** @type {Expression} */ (context.visit(node.test)),
b.stmt(b.call(b.id('$$render'), b.id(consequent_id))),
- alternate_id
- ? b.stmt(b.call(b.id('$$render'), b.id(alternate_id), b.literal(false)))
- : undefined
+ alternate_id ? b.stmt(b.call(b.id('$$render'), b.id(alternate_id), b.false)) : undefined
)
])
)
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
index 9b3ecc922d89..45a594af1f06 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
@@ -689,7 +689,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
'=',
b.member(node_id, 'value'),
b.conditional(
- b.binary('==', b.literal(null), b.assignment('=', b.member(node_id, '__value'), value)),
+ b.binary('==', b.null, b.assignment('=', b.member(node_id, '__value'), value)),
b.literal(''), // render null/undefined values as empty string to support placeholder options
value
)
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
index fdd705e32e75..c6f4ba1ed383 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
@@ -59,7 +59,7 @@ export function SlotElement(node, context) {
const fallback =
node.fragment.nodes.length === 0
- ? b.literal(null)
+ ? b.null
: b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.fragment)));
const slot = b.call(
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js
index 31e712cdcc4d..baffc5dec374 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js
@@ -116,8 +116,7 @@ export function VariableDeclaration(node, context) {
}
const args = /** @type {CallExpression} */ (init).arguments;
- const value =
- args.length === 0 ? b.id('undefined') : /** @type {Expression} */ (context.visit(args[0]));
+ const value = args.length > 0 ? /** @type {Expression} */ (context.visit(args[0])) : b.void0;
if (rune === '$state' || rune === '$state.raw') {
/**
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
index 084c1e7c675e..97cec7a729cd 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
@@ -95,7 +95,7 @@ export function build_set_attributes(
const call = b.call(
'$.set_attributes',
element_id,
- is_dynamic ? attributes_id : b.literal(null),
+ is_dynamic ? attributes_id : b.null,
b.object(values),
element.metadata.scoped &&
context.state.analysis.css.hash !== '' &&
@@ -120,7 +120,7 @@ export function build_set_attributes(
*/
export function build_attribute_value(value, context, memoize = (value) => value) {
if (value === true) {
- return { value: b.literal(true), has_state: false };
+ return { value: b.true, has_state: false };
}
if (!Array.isArray(value) || value.length === 1) {
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js
index 386c6b6ff393..a425bc5ec430 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js
@@ -13,11 +13,11 @@ export function CallExpression(node, context) {
const rune = get_rune(node, context.state.scope);
if (rune === '$host') {
- return b.id('undefined');
+ return b.void0;
}
if (rune === '$effect.tracking') {
- return b.literal(false);
+ return b.false;
}
if (rune === '$effect.root') {
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js
index 7ece04ae3d66..e7925071cd2f 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js
@@ -38,7 +38,7 @@ export function SlotElement(node, context) {
const fallback =
node.fragment.nodes.length === 0
- ? b.literal(null)
+ ? b.null
: b.thunk(/** @type {BlockStatement} */ (context.visit(node.fragment)));
const slot = b.call(
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js
index c4c31d7eb304..a9c9777335ff 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js
@@ -45,7 +45,7 @@ export function VariableDeclaration(node, context) {
) {
const right = node.right.arguments.length
? /** @type {Expression} */ (context.visit(node.right.arguments[0]))
- : b.id('undefined');
+ : b.void0;
return b.assignment_pattern(node.left, right);
}
}
@@ -75,8 +75,7 @@ export function VariableDeclaration(node, context) {
}
const args = /** @type {CallExpression} */ (init).arguments;
- const value =
- args.length === 0 ? b.id('undefined') : /** @type {Expression} */ (context.visit(args[0]));
+ const value = args.length > 0 ? /** @type {Expression} */ (context.visit(args[0])) : b.void0;
if (rune === '$derived.by') {
declarations.push(
diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js
index ecb595d74dbd..736738d19f15 100644
--- a/packages/svelte/src/compiler/utils/builders.js
+++ b/packages/svelte/src/compiler/utils/builders.js
@@ -154,6 +154,8 @@ export function unary(operator, argument) {
return { type: 'UnaryExpression', argument, operator, prefix: true };
}
+export const void0 = unary('void', literal(0));
+
/**
* @param {ESTree.Expression} test
* @param {ESTree.Expression} consequent
@@ -483,7 +485,7 @@ export function do_while(test, body) {
const true_instance = literal(true);
const false_instance = literal(false);
-const null_instane = literal(null);
+const null_instance = literal(null);
/** @type {ESTree.DebuggerStatement} */
const debugger_builder = {
@@ -645,7 +647,7 @@ export {
return_builder as return,
if_builder as if,
this_instance as this,
- null_instane as null,
+ null_instance as null,
debugger_builder as debugger
};
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index aa0a41e71fd9..0a65c6e45a13 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -460,6 +460,14 @@ export function update_reaction(reaction) {
// the same version
if (previous_reaction !== null) {
read_version++;
+
+ if (untracked_writes !== null) {
+ if (previous_untracked_writes === null) {
+ previous_untracked_writes = untracked_writes;
+ } else {
+ previous_untracked_writes.push(.../** @type {Source[]} */ (untracked_writes));
+ }
+ }
}
return result;
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 5f06fd07536e..32a50f3bcec5 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.23.0';
+export const VERSION = '5.23.1';
export const PUBLIC_VERSION = '5';
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/_config.js
new file mode 100644
index 000000000000..af226559d11b
--- /dev/null
+++ b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/_config.js
@@ -0,0 +1,8 @@
+import { test } from '../../test';
+
+export default test({
+ error: {
+ code: 'rune_invalid_arguments_length',
+ message: '`$state.raw` must be called with zero or one arguments'
+ }
+});
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte
new file mode 100644
index 000000000000..2b50b43b9a2b
--- /dev/null
+++ b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte
@@ -0,0 +1,3 @@
+
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte.js
new file mode 100644
index 000000000000..442aaad14289
--- /dev/null
+++ b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-raw-args/main.svelte.js
@@ -0,0 +1 @@
+const foo = $state.raw(1, 2, 3);
diff --git a/packages/svelte/tests/css/samples/has/_config.js b/packages/svelte/tests/css/samples/has/_config.js
index 8d89d98cbdb0..5700a09b9627 100644
--- a/packages/svelte/tests/css/samples/has/_config.js
+++ b/packages/svelte/tests/css/samples/has/_config.js
@@ -6,210 +6,238 @@ export default test({
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused:has(y)"',
start: {
- line: 33,
+ line: 41,
column: 1,
- character: 330
+ character: 378
},
end: {
- line: 33,
+ line: 41,
column: 15,
- character: 344
+ character: 392
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused:has(:global(y))"',
start: {
- line: 36,
+ line: 44,
column: 1,
- character: 365
+ character: 413
},
end: {
- line: 36,
+ line: 44,
column: 24,
- character: 388
+ character: 436
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(.unused)"',
start: {
- line: 39,
+ line: 47,
column: 1,
- character: 409
+ character: 457
},
end: {
- line: 39,
+ line: 47,
column: 15,
- character: 423
+ character: 471
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ":global(.foo):has(.unused)"',
start: {
- line: 42,
+ line: 50,
column: 1,
- character: 444
+ character: 492
},
end: {
- line: 42,
+ line: 50,
column: 27,
- character: 470
+ character: 518
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(y):has(.unused)"',
start: {
- line: 52,
+ line: 60,
column: 1,
- character: 578
+ character: 626
},
end: {
- line: 52,
+ line: 60,
column: 22,
- character: 599
+ character: 647
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused"',
start: {
- line: 71,
+ line: 79,
column: 2,
- character: 804
+ character: 852
},
end: {
- line: 71,
+ line: 79,
column: 9,
- character: 811
+ character: 859
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused x:has(y)"',
start: {
- line: 87,
+ line: 95,
column: 1,
- character: 958
+ character: 1006
},
end: {
- line: 87,
+ line: 95,
column: 17,
- character: 974
+ character: 1022
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused:has(.unused)"',
start: {
- line: 90,
+ line: 98,
column: 1,
- character: 995
+ character: 1043
},
end: {
- line: 90,
+ line: 98,
column: 21,
- character: 1015
+ character: 1063
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(> z)"',
start: {
- line: 100,
+ line: 108,
column: 1,
- character: 1115
+ character: 1163
},
end: {
- line: 100,
+ line: 108,
column: 11,
- character: 1125
+ character: 1173
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(> d)"',
start: {
- line: 103,
+ line: 111,
column: 1,
- character: 1146
+ character: 1194
},
end: {
- line: 103,
+ line: 111,
column: 11,
- character: 1156
+ character: 1204
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "x:has(~ y)"',
start: {
- line: 123,
+ line: 131,
column: 1,
- character: 1348
+ character: 1396
},
end: {
- line: 123,
+ line: 131,
column: 11,
- character: 1358
+ character: 1406
+ }
+ },
+ {
+ code: 'css_unused_selector',
+ message: 'Unused CSS selector "d:has(+ f)"',
+ start: {
+ line: 141,
+ column: 1,
+ character: 1494
+ },
+ end: {
+ line: 141,
+ column: 11,
+ character: 1504
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "f:has(~ d)"',
start: {
- line: 133,
+ line: 144,
column: 1,
- character: 1446
+ character: 1525
},
end: {
- line: 133,
+ line: 144,
column: 11,
- character: 1456
+ character: 1535
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ":has(.unused)"',
start: {
- line: 141,
+ line: 152,
column: 2,
- character: 1529
+ character: 1608
},
end: {
- line: 141,
+ line: 152,
column: 15,
- character: 1542
+ character: 1621
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector "&:has(.unused)"',
start: {
- line: 147,
+ line: 158,
column: 2,
- character: 1600
+ character: 1679
},
end: {
- line: 147,
+ line: 158,
column: 16,
- character: 1614
+ character: 1693
}
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ":global(.foo):has(.unused)"',
start: {
- line: 155,
+ line: 166,
column: 1,
- character: 1684
+ character: 1763
},
end: {
- line: 155,
+ line: 166,
column: 27,
- character: 1710
+ character: 1789
+ }
+ },
+ {
+ code: 'css_unused_selector',
+ message: 'Unused CSS selector "h:has(> h > i)"',
+ start: {
+ line: 173,
+ column: 1,
+ character: 1848
+ },
+ end: {
+ line: 173,
+ column: 15,
+ character: 1862
}
}
]
diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css
index b257370d61f3..2ce4d2bec5cd 100644
--- a/packages/svelte/tests/css/samples/has/expected.css
+++ b/packages/svelte/tests/css/samples/has/expected.css
@@ -118,6 +118,9 @@
d.svelte-xyz:has(~ f:where(.svelte-xyz)) {
color: green;
}
+ /* (unused) d:has(+ f) {
+ color: red;
+ }*/
/* (unused) f:has(~ d) {
color: red;
}*/
@@ -143,3 +146,13 @@
/* (unused) :global(.foo):has(.unused) {
color: red;
}*/
+
+ g.svelte-xyz:has(> h:where(.svelte-xyz) > i:where(.svelte-xyz)) {
+ color: green;
+ }
+ /* (unused) h:has(> h > i) {
+ color: red;
+ }*/
+ g.svelte-xyz:has(+ j:where(.svelte-xyz) > k:where(.svelte-xyz)) {
+ color: green;
+ }
\ No newline at end of file
diff --git a/packages/svelte/tests/css/samples/has/input.svelte b/packages/svelte/tests/css/samples/has/input.svelte
index 9b254996bf30..033471bc1696 100644
--- a/packages/svelte/tests/css/samples/has/input.svelte
+++ b/packages/svelte/tests/css/samples/has/input.svelte
@@ -9,6 +9,14 @@
+
+
+
+
+
+
+
+
diff --git a/packages/svelte/tests/css/samples/render-tag-loop/_config.js b/packages/svelte/tests/css/samples/render-tag-loop/_config.js
index f623b92cc38b..292c6c49ac9d 100644
--- a/packages/svelte/tests/css/samples/render-tag-loop/_config.js
+++ b/packages/svelte/tests/css/samples/render-tag-loop/_config.js
@@ -1,20 +1,5 @@
import { test } from '../../test';
export default test({
- warnings: [
- {
- code: 'css_unused_selector',
- message: 'Unused CSS selector "div + div"',
- start: {
- line: 19,
- column: 1,
- character: 185
- },
- end: {
- line: 19,
- column: 10,
- character: 194
- }
- }
- ]
+ warnings: []
});
diff --git a/packages/svelte/tests/css/samples/render-tag-loop/expected.css b/packages/svelte/tests/css/samples/render-tag-loop/expected.css
index 9ced15e96407..3e449286c997 100644
--- a/packages/svelte/tests/css/samples/render-tag-loop/expected.css
+++ b/packages/svelte/tests/css/samples/render-tag-loop/expected.css
@@ -2,9 +2,12 @@
div.svelte-xyz div:where(.svelte-xyz) {
color: green;
}
- /* (unused) div + div {
- color: red; /* this is marked as unused, but only because we've written an infinite loop - worth fixing? *\/
- }*/
+ div.svelte-xyz + div:where(.svelte-xyz) {
+ color: green;
+ }
div.svelte-xyz:has(div:where(.svelte-xyz)) {
color: green;
}
+ span.svelte-xyz:has(~span:where(.svelte-xyz)) {
+ color: green;
+ }
diff --git a/packages/svelte/tests/css/samples/render-tag-loop/input.svelte b/packages/svelte/tests/css/samples/render-tag-loop/input.svelte
index ade8df574489..3c55261f1845 100644
--- a/packages/svelte/tests/css/samples/render-tag-loop/input.svelte
+++ b/packages/svelte/tests/css/samples/render-tag-loop/input.svelte
@@ -12,14 +12,22 @@
{/snippet}
+{#snippet c()}
+
+ {@render c()}
+{/snippet}
+
diff --git a/packages/svelte/tests/css/samples/view-transition/expected.css b/packages/svelte/tests/css/samples/view-transition/expected.css
index afc84d52ebf5..e216a4d3ad9b 100644
--- a/packages/svelte/tests/css/samples/view-transition/expected.css
+++ b/packages/svelte/tests/css/samples/view-transition/expected.css
@@ -8,9 +8,15 @@
::view-transition-old {
animation-duration: 0.5s;
}
+ ::view-transition-old:only-child {
+ animation-duration: 0.5s;
+ }
::view-transition-new {
animation-duration: 0.5s;
}
+ ::view-transition-new:only-child {
+ animation-duration: 0.5s;
+ }
::view-transition-image-pair {
animation-duration: 0.5s;
}
diff --git a/packages/svelte/tests/css/samples/view-transition/input.svelte b/packages/svelte/tests/css/samples/view-transition/input.svelte
index ebb2b3fd88e0..345213ccd3f5 100644
--- a/packages/svelte/tests/css/samples/view-transition/input.svelte
+++ b/packages/svelte/tests/css/samples/view-transition/input.svelte
@@ -8,9 +8,15 @@
::view-transition-old {
animation-duration: 0.5s;
}
+ ::view-transition-old:only-child {
+ animation-duration: 0.5s;
+ }
::view-transition-new {
animation-duration: 0.5s;
}
+ ::view-transition-new:only-child {
+ animation-duration: 0.5s;
+ }
::view-transition-image-pair {
animation-duration: 0.5s;
}
diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/_config.js b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/_config.js
new file mode 100644
index 000000000000..0310ec4fbb52
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/_config.js
@@ -0,0 +1,7 @@
+import { test } from '../../test';
+
+export default test({
+ test({ assert, target, logs }) {
+ assert.deepEqual(logs, ['Outer', 'Inner', 'Outer', 'Inner']);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/main.svelte b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/main.svelte
new file mode 100644
index 000000000000..5e95dbfd411a
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/main.svelte
@@ -0,0 +1,13 @@
+
pFad - Phonifier reborn
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.