Skip to content

Commit 0166bc4

Browse files
committed
Merge branch 'main' into opaque-rune
2 parents 8b38272 + ab1f7f4 commit 0166bc4

File tree

22 files changed

+189
-33
lines changed

22 files changed

+189
-33
lines changed

.changeset/chatty-windows-own.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: ensure snippet hoisting works in the correct scope

.changeset/cyan-ducks-train.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: restore input binding selection position

.changeset/red-worms-suffer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: transform everything that is not a selector inside `:global`

.changeset/thick-dryers-kiss.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: don't emit assignment warnings for bindings

packages/svelte/src/compiler/phases/2-analyze/visitors/SnippetBlock.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ function can_hoist_snippet(scope, scopes, visited = new Set()) {
9999
if (binding.initial?.type === 'SnippetBlock') {
100100
if (visited.has(binding)) continue;
101101
visited.add(binding);
102+
const snippet_scope = /** @type {Scope} */ (scopes.get(binding.initial));
102103

103-
if (can_hoist_snippet(binding.scope, scopes, visited)) {
104+
if (can_hoist_snippet(snippet_scope, scopes, visited)) {
104105
continue;
105106
}
106107
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,17 @@ function build_assignment(operator, left, right, context) {
169169
}
170170
}
171171

172+
// special case — ignore `bind:prop={getter, (v) => (...)}` / `bind:value={x.y}`
173+
if (
174+
path.at(-1) === 'Component' ||
175+
path.at(-1) === 'SvelteComponent' ||
176+
(path.at(-1) === 'ArrowFunctionExpression' &&
177+
path.at(-2) === 'SequenceExpression' &&
178+
(path.at(-3) === 'Component' || path.at(-3) === 'SvelteComponent'))
179+
) {
180+
should_transform = false;
181+
}
182+
172183
if (left.type === 'MemberExpression' && should_transform) {
173184
const callee = callees[operator];
174185

packages/svelte/src/compiler/phases/3-transform/css/index.js

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const visitors = {
7878
context.state.code.addSourcemapLocation(node.end);
7979
context.next();
8080
},
81-
Atrule(node, { state, next }) {
81+
Atrule(node, { state, next, path }) {
8282
if (is_keyframes_node(node)) {
8383
let start = node.start + node.name.length + 1;
8484
while (state.code.original[start] === ' ') start += 1;
@@ -87,7 +87,7 @@ const visitors = {
8787

8888
if (node.prelude.startsWith('-global-')) {
8989
state.code.remove(start, start + 8);
90-
} else {
90+
} else if (!is_in_global_block(path)) {
9191
state.code.prependRight(start, `${state.hash}-`);
9292
}
9393

@@ -134,7 +134,7 @@ const visitors = {
134134
}
135135
}
136136
},
137-
Rule(node, { state, next, visit }) {
137+
Rule(node, { state, next, visit, path }) {
138138
if (state.minify) {
139139
remove_preceding_whitespace(node.start, state);
140140
remove_preceding_whitespace(node.block.end - 1, state);
@@ -154,7 +154,7 @@ const visitors = {
154154
return;
155155
}
156156

157-
if (!is_used(node)) {
157+
if (!is_used(node) && !is_in_global_block(path)) {
158158
if (state.minify) {
159159
state.code.remove(node.start, node.end);
160160
} else {
@@ -182,20 +182,20 @@ const visitors = {
182182
state.code.appendLeft(node.block.end, '*/');
183183
}
184184

185-
// don't recurse into selector or body
185+
// don't recurse into selectors but visit the body
186+
visit(node.block);
186187
return;
187188
}
188-
189-
// don't recurse into body
190-
visit(node.prelude);
191-
return;
192189
}
193190

194191
next();
195192
},
196193
SelectorList(node, { state, next, path }) {
197-
// Only add comments if we're not inside a complex selector that itself is unused
198-
if (!path.find((n) => n.type === 'ComplexSelector' && !n.metadata.used)) {
194+
// Only add comments if we're not inside a complex selector that itself is unused or a global block
195+
if (
196+
!is_in_global_block(path) &&
197+
!path.find((n) => n.type === 'ComplexSelector' && !n.metadata.used)
198+
) {
199199
const children = node.children;
200200
let pruning = false;
201201
let prune_start = children[0].start;
@@ -359,6 +359,14 @@ const visitors = {
359359
}
360360
};
361361

362+
/**
363+
*
364+
* @param {Array<Css.Node>} path
365+
*/
366+
function is_in_global_block(path) {
367+
return path.some((node) => node.type === 'Rule' && node.metadata.is_global_block);
368+
}
369+
362370
/**
363371
* @param {Css.PseudoClassSelector} selector
364372
* @param {Css.Combinator | null} combinator

packages/svelte/src/compiler/phases/scope.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -628,12 +628,8 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
628628

629629
SnippetBlock(node, context) {
630630
const state = context.state;
631-
// Special-case for root-level snippets: they become part of the instance scope
632-
const is_top_level = !context.path.at(-2);
633631
let scope = state.scope;
634-
if (is_top_level) {
635-
scope = /** @type {Scope} */ (parent);
636-
}
632+
637633
scope.declare(node.expression, 'normal', 'function', node);
638634

639635
const child_scope = state.scope.child();

packages/svelte/src/internal/client/dev/assign.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { untrack } from '../runtime.js';
12
import * as w from '../warnings.js';
23
import { sanitize_location } from './location.js';
34

@@ -23,7 +24,12 @@ function compare(a, b, property, location) {
2324
* @param {string} location
2425
*/
2526
export function assign(object, property, value, location) {
26-
return compare((object[property] = value), object[property], property, location);
27+
return compare(
28+
(object[property] = value),
29+
untrack(() => object[property]),
30+
property,
31+
location
32+
);
2733
}
2834

2935
/**
@@ -33,7 +39,12 @@ export function assign(object, property, value, location) {
3339
* @param {string} location
3440
*/
3541
export function assign_and(object, property, value, location) {
36-
return compare((object[property] &&= value), object[property], property, location);
42+
return compare(
43+
(object[property] &&= value),
44+
untrack(() => object[property]),
45+
property,
46+
location
47+
);
3748
}
3849

3950
/**
@@ -43,7 +54,12 @@ export function assign_and(object, property, value, location) {
4354
* @param {string} location
4455
*/
4556
export function assign_or(object, property, value, location) {
46-
return compare((object[property] ||= value), object[property], property, location);
57+
return compare(
58+
(object[property] ||= value),
59+
untrack(() => object[property]),
60+
property,
61+
location
62+
);
4763
}
4864

4965
/**
@@ -53,5 +69,10 @@ export function assign_or(object, property, value, location) {
5369
* @param {string} location
5470
*/
5571
export function assign_nullish(object, property, value, location) {
56-
return compare((object[property] ??= value), object[property], property, location);
72+
return compare(
73+
(object[property] ??= value),
74+
untrack(() => object[property]),
75+
property,
76+
location
77+
);
5778
}

packages/svelte/src/internal/client/dom/elements/bindings/input.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,17 @@ export function bind_value(input, get, set = get) {
3030
// In runes mode, respect any validation in accessors (doesn't apply in legacy mode,
3131
// because we use mutable state which ensures the render effect always runs)
3232
if (runes && value !== (value = get())) {
33+
var start = input.selectionStart;
34+
var end = input.selectionEnd;
35+
3336
// the value is coerced on assignment
3437
input.value = value ?? '';
38+
39+
// Restore selection
40+
if (end !== null) {
41+
input.selectionStart = start;
42+
input.selectionEnd = Math.min(end, input.value.length);
43+
}
3544
}
3645
});
3746

packages/svelte/tests/css/samples/global-block/expected.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,20 @@
6969
color: red;
7070
}
7171
}*/
72+
/* :global{*/
73+
.x{
74+
animation: svelte-xyz-test 1s;
75+
}
76+
77+
@keyframes test-in{
78+
to{
79+
opacity: 1;
80+
}
81+
}
82+
/*}*/
83+
84+
@keyframes svelte-xyz-test{
85+
to{
86+
opacity: 1;
87+
}
88+
}

packages/svelte/tests/css/samples/global-block/input.svelte

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,21 @@
7171
color: red;
7272
}
7373
}
74+
:global{
75+
.x{
76+
animation: test 1s;
77+
}
78+
79+
@keyframes test-in{
80+
to{
81+
opacity: 1;
82+
}
83+
}
84+
}
85+
86+
@keyframes test{
87+
to{
88+
opacity: 1;
89+
}
90+
}
7491
</style>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
let { x = $bindable() } = $props();
3+
4+
$effect(() => {
5+
x = {};
6+
});
7+
8+
export function soThatTestReturnsAnObject() {
9+
return x;
10+
}
11+
</script>
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ export default test({
66
dev: true
77
},
88

9-
html: `<button>items: null</button>`,
9+
html: `<button>items: null</button> <div>x</div>`,
1010

1111
test({ assert, target, warnings }) {
1212
const btn = target.querySelector('button');
1313

1414
flushSync(() => btn?.click());
15-
assert.htmlEqual(target.innerHTML, `<button>items: []</button>`);
15+
assert.htmlEqual(target.innerHTML, `<button>items: []</button> <div>x</div>`);
1616

1717
flushSync(() => btn?.click());
18-
assert.htmlEqual(target.innerHTML, `<button>items: [0]</button>`);
18+
assert.htmlEqual(target.innerHTML, `<button>items: [0]</button> <div>x</div>`);
1919

2020
assert.deepEqual(warnings, [
21-
'Assignment to `items` property (main.svelte:5:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.'
21+
'Assignment to `items` property (main.svelte:8:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.'
2222
]);
2323
}
2424
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
import Test from './Test.svelte';
3+
4+
let entries = $state([]);
5+
let object = $state({ items: null });
6+
</script>
7+
8+
<button onclick={() => (object.items ??= []).push(object.items.length)}>
9+
items: {JSON.stringify(object.items)}
10+
</button>
11+
12+
<!-- these should not emit warnings -->
13+
<div bind:this={entries[0]}>x</div>
14+
<Test bind:this={entries[1]}></Test>
15+
<Test bind:this={() => entries[2], (v) => (entries[2] = v)}></Test>
16+
<Test bind:x={entries[3]}></Test>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default test({
55
html: `<button>items: null</button>`,
66

77
test({ assert, target }) {
8-
const [btn1, btn2] = target.querySelectorAll('button');
8+
const [btn1] = target.querySelectorAll('button');
99

1010
flushSync(() => btn1.click());
1111
assert.htmlEqual(target.innerHTML, `<button>items: [0]</button>`);

packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment-warning/main.svelte

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: 'a'
5+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
let abc = 'a'
3+
</script>
4+
5+
{@render b()}
6+
7+
{#snippet a()}
8+
{abc}
9+
{/snippet}
10+
11+
{#snippet b()}
12+
{@render a()}
13+
{/snippet}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: '<h1>Hello world!</h1>'
5+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script module>
2+
export { foo }
3+
</script>
4+
5+
<script>
6+
let name = 'world';
7+
</script>
8+
9+
<h1>Hello {name}!</h1>
10+
11+
{#snippet foo()}
12+
oo
13+
{/snippet}

0 commit comments

Comments
 (0)