Skip to content

Commit 538060e

Browse files
authored
fix: ensure visit is called with correct state (#11798)
Some of our `visit` calls have the wrong current state associated with it. To fix it, we need to pass the real current one. fixes #11722
1 parent 68263c8 commit 538060e

File tree

9 files changed

+78
-62
lines changed

9 files changed

+78
-62
lines changed

.changeset/flat-olives-live.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: set correct scope for `@const` tags within slots

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

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,13 @@ function serialize_inline_component(node, component_name, context) {
847847
/** @type {import('estree').Property[]} */
848848
const serialized_slots = [];
849849
for (const slot_name of Object.keys(children)) {
850-
const body = create_block(node, `${node.name}_${slot_name}`, children[slot_name], context);
850+
const body = create_block(
851+
node,
852+
node.fragment,
853+
`${node.name}_${slot_name}`,
854+
children[slot_name],
855+
context
856+
);
851857
if (body.length === 0) continue;
852858

853859
const slot_fn = b.arrow(
@@ -1023,13 +1029,14 @@ function serialize_locations(locations) {
10231029
* ```
10241030
* Adds the hoisted parts to `context.state.hoisted` and returns the statements of the main block.
10251031
* @param {import('#compiler').SvelteNode} parent
1032+
* @param {import('#compiler').Fragment} fragment
10261033
* @param {string} name
10271034
* @param {import('#compiler').SvelteNode[]} nodes
10281035
* @param {import('../types.js').ComponentContext} context
10291036
* @returns {import('estree').Statement[]}
10301037
*/
1031-
function create_block(parent, name, nodes, context) {
1032-
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes, context.path);
1038+
function create_block(parent, fragment, name, nodes, context) {
1039+
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes);
10331040

10341041
const { hoisted, trimmed } = clean_nodes(
10351042
parent,
@@ -1060,6 +1067,7 @@ function create_block(parent, name, nodes, context) {
10601067
/** @type {import('../types').ComponentClientTransformState} */
10611068
const state = {
10621069
...context.state,
1070+
scope: context.state.scopes.get(fragment) ?? context.state.scope,
10631071
before_init: [],
10641072
init: [],
10651073
update: [],
@@ -1616,7 +1624,7 @@ function serialize_attribute_value(attribute_value, context) {
16161624

16171625
/**
16181626
* @param {Array<import('#compiler').Text | import('#compiler').ExpressionTag>} values
1619-
* @param {(node: import('#compiler').SvelteNode) => any} visit
1627+
* @param {(node: import('#compiler').SvelteNode, state: any) => any} visit
16201628
* @param {import("../types.js").ComponentClientTransformState} state
16211629
* @returns {[boolean, import('estree').TemplateLiteral]}
16221630
*/
@@ -1661,13 +1669,13 @@ function serialize_template_literal(values, visit, state) {
16611669
id,
16621670
create_derived(
16631671
state,
1664-
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression)))
1672+
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression, state)))
16651673
)
16661674
)
16671675
);
16681676
expressions.push(b.call('$.get', id));
16691677
} else {
1670-
expressions.push(b.call('$.stringify', visit(node.expression)));
1678+
expressions.push(b.call('$.stringify', visit(node.expression, state)));
16711679
}
16721680
quasis.push(b.quasi('', i + 1 === values.length));
16731681
}
@@ -1680,7 +1688,7 @@ function serialize_template_literal(values, visit, state) {
16801688
/** @type {import('../types').ComponentVisitors} */
16811689
export const template_visitors = {
16821690
Fragment(node, context) {
1683-
const body = create_block(node, 'root', node.nodes, context);
1691+
const body = create_block(context.path.at(-1) ?? node, node, 'root', node.nodes, context);
16841692
return b.block(body);
16851693
},
16861694
Comment(node, context) {
@@ -2221,7 +2229,7 @@ export const template_visitors = {
22212229
}
22222230
inner.push(...inner_context.state.after_update);
22232231
inner.push(
2224-
...create_block(node, 'dynamic_element', node.fragment.nodes, {
2232+
...create_block(node, node.fragment, 'dynamic_element', node.fragment.nodes, {
22252233
...context,
22262234
state: {
22272235
...context.state,
@@ -2449,7 +2457,7 @@ export const template_visitors = {
24492457
}
24502458

24512459
// TODO should use context.visit?
2452-
const children = create_block(node, 'each_block', node.body.nodes, context);
2460+
const children = create_block(node, node.body, 'each_block', node.body.nodes, context);
24532461

24542462
const key_function = node.key
24552463
? b.arrow(
@@ -3017,22 +3025,14 @@ export const template_visitors = {
30173025
}
30183026
}
30193027

3020-
const state = {
3021-
...context.state,
3022-
// TODO this logic eventually belongs in create_block, when fragments are used everywhere
3023-
scope: /** @type {import('../../../scope').Scope} */ (context.state.scopes.get(node.fragment))
3024-
};
3025-
30263028
context.state.init.push(...lets);
30273029
context.state.init.push(
30283030
...create_block(
30293031
node,
3032+
node.fragment,
30303033
'slot_template',
30313034
/** @type {import('#compiler').SvelteNode[]} */ (node.fragment.nodes),
3032-
{
3033-
...context,
3034-
state
3035-
}
3035+
context
30363036
)
30373037
);
30383038
},
@@ -3088,7 +3088,7 @@ export const template_visitors = {
30883088
? b.literal(null)
30893089
: b.arrow(
30903090
[b.id('$$anchor')],
3091-
b.block(create_block(node, 'fallback', node.fragment.nodes, context))
3091+
b.block(create_block(node, node.fragment, 'fallback', node.fragment.nodes, context))
30923092
);
30933093

30943094
const expression = is_default
@@ -3106,7 +3106,7 @@ export const template_visitors = {
31063106
'$.head',
31073107
b.arrow(
31083108
[b.id('$$anchor')],
3109-
b.block(create_block(node, 'head', node.fragment.nodes, context))
3109+
b.block(create_block(node, node.fragment, 'head', node.fragment.nodes, context))
31103110
)
31113111
)
31123112
)

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -240,13 +240,14 @@ function process_children(nodes, parent, { visit, state }) {
240240

241241
/**
242242
* @param {import('#compiler').SvelteNode} parent
243+
* @param {import('#compiler').Fragment} fragment
243244
* @param {import('#compiler').SvelteNode[]} nodes
244245
* @param {import('./types').ComponentContext} context
245246
* @param {import('./types').Anchor} [anchor]
246247
* @returns {import('estree').Statement[]}
247248
*/
248-
function create_block(parent, nodes, context, anchor) {
249-
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes, context.path);
249+
function create_block(parent, fragment, nodes, context, anchor) {
250+
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes);
250251

251252
const { hoisted, trimmed } = clean_nodes(
252253
parent,
@@ -264,6 +265,7 @@ function create_block(parent, nodes, context, anchor) {
264265
/** @type {import('./types').ComponentServerTransformState} */
265266
const state = {
266267
...context.state,
268+
scope: context.state.scopes.get(fragment) ?? context.state.scope,
267269
init: [],
268270
template: [],
269271
metadata: {
@@ -1085,7 +1087,7 @@ function serialize_inline_component(node, component_name, context) {
10851087
const serialized_slots = [];
10861088

10871089
for (const slot_name of Object.keys(children)) {
1088-
const body = create_block(node, children[slot_name], context);
1090+
const body = create_block(node, node.fragment, children[slot_name], context);
10891091
if (body.length === 0) continue;
10901092

10911093
const slot_fn = b.arrow(
@@ -1268,7 +1270,7 @@ const javascript_visitors_legacy = {
12681270
/** @type {import('./types').ComponentVisitors} */
12691271
const template_visitors = {
12701272
Fragment(node, context) {
1271-
const body = create_block(node, node.nodes, context);
1273+
const body = create_block(context.path.at(-1) ?? node, node, node.nodes, context);
12721274
return b.block(body);
12731275
},
12741276
HtmlTag(node, context) {
@@ -1472,7 +1474,7 @@ const template_visitors = {
14721474

14731475
context.state.template.push(block_open);
14741476

1475-
const main = create_block(node, node.fragment.nodes, {
1477+
const main = create_block(node, node.fragment, node.fragment.nodes, {
14761478
...context,
14771479
state: { ...context.state, metadata }
14781480
});
@@ -1541,7 +1543,9 @@ const template_visitors = {
15411543
each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_open.value))));
15421544

15431545
each.push(
1544-
.../** @type {import('estree').Statement[]} */ (create_block(node, children, context))
1546+
.../** @type {import('estree').Statement[]} */ (
1547+
create_block(node, node.body, children, context)
1548+
)
15451549
);
15461550

15471551
each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_close.value))));
@@ -1556,7 +1560,7 @@ const template_visitors = {
15561560
const close = b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE)));
15571561

15581562
if (node.fallback) {
1559-
const fallback = create_block(node, node.fallback.nodes, context);
1563+
const fallback = create_block(node, node.fallback, node.fallback.nodes, context);
15601564

15611565
fallback.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE))));
15621566

@@ -1577,8 +1581,10 @@ const template_visitors = {
15771581
const state = context.state;
15781582
state.template.push(block_open);
15791583

1580-
const consequent = create_block(node, node.consequent.nodes, context);
1581-
const alternate = node.alternate ? create_block(node, node.alternate.nodes, context) : [];
1584+
const consequent = create_block(node, node.consequent, node.consequent.nodes, context);
1585+
const alternate = node.alternate
1586+
? create_block(node, node.alternate, node.alternate.nodes, context)
1587+
: [];
15821588

15831589
consequent.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE))));
15841590
alternate.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE))));
@@ -1634,7 +1640,7 @@ const template_visitors = {
16341640
KeyBlock(node, context) {
16351641
const state = context.state;
16361642
state.template.push(block_open);
1637-
const body = create_block(node, node.fragment.nodes, context);
1643+
const body = create_block(node, node.fragment, node.fragment.nodes, context);
16381644
state.template.push(t_statement(b.block(body)));
16391645
state.template.push(block_close);
16401646
},
@@ -1724,15 +1730,7 @@ const template_visitors = {
17241730
}
17251731
}
17261732

1727-
const state = {
1728-
...context.state,
1729-
// TODO this logic eventually belongs in create_block, when fragments are used everywhere
1730-
scope: /** @type {import('../../scope').Scope} */ (context.state.scopes.get(node.fragment))
1731-
};
1732-
const body = create_block(node, node.fragment.nodes, {
1733-
...context,
1734-
state
1735-
});
1733+
const body = create_block(node, node.fragment, node.fragment.nodes, context);
17361734

17371735
context.state.template.push(t_statement(b.block(body)));
17381736
},
@@ -1802,15 +1800,15 @@ const template_visitors = {
18021800
const fallback =
18031801
node.fragment.nodes.length === 0
18041802
? b.literal(null)
1805-
: b.thunk(b.block(create_block(node, node.fragment.nodes, context)));
1803+
: b.thunk(b.block(create_block(node, node.fragment, node.fragment.nodes, context)));
18061804
const slot = b.call('$.slot', b.id('$$payload'), expression, props_expression, fallback);
18071805

18081806
state.template.push(t_statement(b.stmt(slot)));
18091807
state.template.push(block_close);
18101808
},
18111809
SvelteHead(node, context) {
18121810
const state = context.state;
1813-
const body = create_block(node, node.fragment.nodes, context);
1811+
const body = create_block(node, node.fragment, node.fragment.nodes, context);
18141812
state.template.push(
18151813
t_statement(
18161814
b.stmt(b.call('$.head', b.id('$$payload'), b.arrow([b.id('$$payload')], b.block(body))))

packages/svelte/src/compiler/phases/3-transform/utils.js

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -155,35 +155,28 @@ export function clean_nodes(
155155
* @param {import('#compiler').Namespace} namespace
156156
* @param {import('#compiler').SvelteNode} parent
157157
* @param {import('#compiler').SvelteNode[]} nodes
158-
* @param {import('#compiler').SvelteNode[]} path
159158
*/
160-
export function infer_namespace(namespace, parent, nodes, path) {
161-
const parent_node =
162-
parent.type === 'Fragment'
163-
? // Messy: We know that Fragment calls create_block directly, so we can do this here
164-
path.at(-1)
165-
: parent;
166-
159+
export function infer_namespace(namespace, parent, nodes) {
167160
if (namespace !== 'foreign') {
168-
if (parent_node?.type === 'RegularElement' && parent_node.name === 'foreignObject') {
161+
if (parent.type === 'RegularElement' && parent.name === 'foreignObject') {
169162
return 'html';
170163
}
171164

172-
if (parent_node?.type === 'RegularElement' || parent_node?.type === 'SvelteElement') {
173-
if (parent_node.metadata.svg) {
165+
if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
166+
if (parent.metadata.svg) {
174167
return 'svg';
175168
}
176-
return parent_node.metadata.mathml ? 'mathml' : 'html';
169+
return parent.metadata.mathml ? 'mathml' : 'html';
177170
}
178171

179172
// Re-evaluate the namespace inside slot nodes that reset the namespace
180173
if (
181-
parent_node === undefined ||
182-
parent_node.type === 'Root' ||
183-
parent_node.type === 'Component' ||
184-
parent_node.type === 'SvelteComponent' ||
185-
parent_node.type === 'SvelteFragment' ||
186-
parent_node.type === 'SnippetBlock'
174+
parent.type === 'Fragment' ||
175+
parent.type === 'Root' ||
176+
parent.type === 'Component' ||
177+
parent.type === 'SvelteComponent' ||
178+
parent.type === 'SvelteFragment' ||
179+
parent.type === 'SnippetBlock'
187180
) {
188181
const new_namespace = check_nodes_for_namespace(nodes, 'keep');
189182
if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,6 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
280280
next({ scope });
281281
};
282282

283-
const skip = () => {};
284-
285283
/**
286284
* @type {import('zimmerframe').Visitor<import('#compiler').ElementLike, State, import('#compiler').SvelteNode>}
287285
*/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<slot name="inner" text="hello" />
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<slot name="footer" />
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: `
5+
<div slot="footer">hello hello</div>
6+
`
7+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
import Nested from "./Nested.svelte"
3+
import Nested2 from "./Nested2.svelte"
4+
</script>
5+
6+
<Nested>
7+
<Nested2 slot="inner" let:text>
8+
<div slot="footer">
9+
{@const text2 = text}
10+
{text} {text2}
11+
</div>
12+
</Nested2>
13+
</Nested>

0 commit comments

Comments
 (0)