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
pull/11812/head
Simon H 7 months ago committed by GitHub
parent 68263c8615
commit 538060ebda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: set correct scope for `@const` tags within slots

@ -847,7 +847,13 @@ function serialize_inline_component(node, component_name, context) {
/** @type {import('estree').Property[]} */ /** @type {import('estree').Property[]} */
const serialized_slots = []; const serialized_slots = [];
for (const slot_name of Object.keys(children)) { for (const slot_name of Object.keys(children)) {
const body = create_block(node, `${node.name}_${slot_name}`, children[slot_name], context); const body = create_block(
node,
node.fragment,
`${node.name}_${slot_name}`,
children[slot_name],
context
);
if (body.length === 0) continue; if (body.length === 0) continue;
const slot_fn = b.arrow( const slot_fn = b.arrow(
@ -1023,13 +1029,14 @@ function serialize_locations(locations) {
* ``` * ```
* Adds the hoisted parts to `context.state.hoisted` and returns the statements of the main block. * Adds the hoisted parts to `context.state.hoisted` and returns the statements of the main block.
* @param {import('#compiler').SvelteNode} parent * @param {import('#compiler').SvelteNode} parent
* @param {import('#compiler').Fragment} fragment
* @param {string} name * @param {string} name
* @param {import('#compiler').SvelteNode[]} nodes * @param {import('#compiler').SvelteNode[]} nodes
* @param {import('../types.js').ComponentContext} context * @param {import('../types.js').ComponentContext} context
* @returns {import('estree').Statement[]} * @returns {import('estree').Statement[]}
*/ */
function create_block(parent, name, nodes, context) { function create_block(parent, fragment, name, nodes, context) {
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes, context.path); const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes);
const { hoisted, trimmed } = clean_nodes( const { hoisted, trimmed } = clean_nodes(
parent, parent,
@ -1060,6 +1067,7 @@ function create_block(parent, name, nodes, context) {
/** @type {import('../types').ComponentClientTransformState} */ /** @type {import('../types').ComponentClientTransformState} */
const state = { const state = {
...context.state, ...context.state,
scope: context.state.scopes.get(fragment) ?? context.state.scope,
before_init: [], before_init: [],
init: [], init: [],
update: [], update: [],
@ -1616,7 +1624,7 @@ function serialize_attribute_value(attribute_value, context) {
/** /**
* @param {Array<import('#compiler').Text | import('#compiler').ExpressionTag>} values * @param {Array<import('#compiler').Text | import('#compiler').ExpressionTag>} values
* @param {(node: import('#compiler').SvelteNode) => any} visit * @param {(node: import('#compiler').SvelteNode, state: any) => any} visit
* @param {import("../types.js").ComponentClientTransformState} state * @param {import("../types.js").ComponentClientTransformState} state
* @returns {[boolean, import('estree').TemplateLiteral]} * @returns {[boolean, import('estree').TemplateLiteral]}
*/ */
@ -1661,13 +1669,13 @@ function serialize_template_literal(values, visit, state) {
id, id,
create_derived( create_derived(
state, state,
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression))) b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression, state)))
) )
) )
); );
expressions.push(b.call('$.get', id)); expressions.push(b.call('$.get', id));
} else { } else {
expressions.push(b.call('$.stringify', visit(node.expression))); expressions.push(b.call('$.stringify', visit(node.expression, state)));
} }
quasis.push(b.quasi('', i + 1 === values.length)); quasis.push(b.quasi('', i + 1 === values.length));
} }
@ -1680,7 +1688,7 @@ function serialize_template_literal(values, visit, state) {
/** @type {import('../types').ComponentVisitors} */ /** @type {import('../types').ComponentVisitors} */
export const template_visitors = { export const template_visitors = {
Fragment(node, context) { Fragment(node, context) {
const body = create_block(node, 'root', node.nodes, context); const body = create_block(context.path.at(-1) ?? node, node, 'root', node.nodes, context);
return b.block(body); return b.block(body);
}, },
Comment(node, context) { Comment(node, context) {
@ -2221,7 +2229,7 @@ export const template_visitors = {
} }
inner.push(...inner_context.state.after_update); inner.push(...inner_context.state.after_update);
inner.push( inner.push(
...create_block(node, 'dynamic_element', node.fragment.nodes, { ...create_block(node, node.fragment, 'dynamic_element', node.fragment.nodes, {
...context, ...context,
state: { state: {
...context.state, ...context.state,
@ -2449,7 +2457,7 @@ export const template_visitors = {
} }
// TODO should use context.visit? // TODO should use context.visit?
const children = create_block(node, 'each_block', node.body.nodes, context); const children = create_block(node, node.body, 'each_block', node.body.nodes, context);
const key_function = node.key const key_function = node.key
? b.arrow( ? b.arrow(
@ -3017,22 +3025,14 @@ export const template_visitors = {
} }
} }
const state = {
...context.state,
// TODO this logic eventually belongs in create_block, when fragments are used everywhere
scope: /** @type {import('../../../scope').Scope} */ (context.state.scopes.get(node.fragment))
};
context.state.init.push(...lets); context.state.init.push(...lets);
context.state.init.push( context.state.init.push(
...create_block( ...create_block(
node, node,
node.fragment,
'slot_template', 'slot_template',
/** @type {import('#compiler').SvelteNode[]} */ (node.fragment.nodes), /** @type {import('#compiler').SvelteNode[]} */ (node.fragment.nodes),
{ context
...context,
state
}
) )
); );
}, },
@ -3088,7 +3088,7 @@ export const template_visitors = {
? b.literal(null) ? b.literal(null)
: b.arrow( : b.arrow(
[b.id('$$anchor')], [b.id('$$anchor')],
b.block(create_block(node, 'fallback', node.fragment.nodes, context)) b.block(create_block(node, node.fragment, 'fallback', node.fragment.nodes, context))
); );
const expression = is_default const expression = is_default
@ -3106,7 +3106,7 @@ export const template_visitors = {
'$.head', '$.head',
b.arrow( b.arrow(
[b.id('$$anchor')], [b.id('$$anchor')],
b.block(create_block(node, 'head', node.fragment.nodes, context)) b.block(create_block(node, node.fragment, 'head', node.fragment.nodes, context))
) )
) )
) )

@ -240,13 +240,14 @@ function process_children(nodes, parent, { visit, state }) {
/** /**
* @param {import('#compiler').SvelteNode} parent * @param {import('#compiler').SvelteNode} parent
* @param {import('#compiler').Fragment} fragment
* @param {import('#compiler').SvelteNode[]} nodes * @param {import('#compiler').SvelteNode[]} nodes
* @param {import('./types').ComponentContext} context * @param {import('./types').ComponentContext} context
* @param {import('./types').Anchor} [anchor] * @param {import('./types').Anchor} [anchor]
* @returns {import('estree').Statement[]} * @returns {import('estree').Statement[]}
*/ */
function create_block(parent, nodes, context, anchor) { function create_block(parent, fragment, nodes, context, anchor) {
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes, context.path); const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes);
const { hoisted, trimmed } = clean_nodes( const { hoisted, trimmed } = clean_nodes(
parent, parent,
@ -264,6 +265,7 @@ function create_block(parent, nodes, context, anchor) {
/** @type {import('./types').ComponentServerTransformState} */ /** @type {import('./types').ComponentServerTransformState} */
const state = { const state = {
...context.state, ...context.state,
scope: context.state.scopes.get(fragment) ?? context.state.scope,
init: [], init: [],
template: [], template: [],
metadata: { metadata: {
@ -1085,7 +1087,7 @@ function serialize_inline_component(node, component_name, context) {
const serialized_slots = []; const serialized_slots = [];
for (const slot_name of Object.keys(children)) { for (const slot_name of Object.keys(children)) {
const body = create_block(node, children[slot_name], context); const body = create_block(node, node.fragment, children[slot_name], context);
if (body.length === 0) continue; if (body.length === 0) continue;
const slot_fn = b.arrow( const slot_fn = b.arrow(
@ -1268,7 +1270,7 @@ const javascript_visitors_legacy = {
/** @type {import('./types').ComponentVisitors} */ /** @type {import('./types').ComponentVisitors} */
const template_visitors = { const template_visitors = {
Fragment(node, context) { Fragment(node, context) {
const body = create_block(node, node.nodes, context); const body = create_block(context.path.at(-1) ?? node, node, node.nodes, context);
return b.block(body); return b.block(body);
}, },
HtmlTag(node, context) { HtmlTag(node, context) {
@ -1472,7 +1474,7 @@ const template_visitors = {
context.state.template.push(block_open); context.state.template.push(block_open);
const main = create_block(node, node.fragment.nodes, { const main = create_block(node, node.fragment, node.fragment.nodes, {
...context, ...context,
state: { ...context.state, metadata } state: { ...context.state, metadata }
}); });
@ -1541,7 +1543,9 @@ const template_visitors = {
each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_open.value)))); each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_open.value))));
each.push( each.push(
.../** @type {import('estree').Statement[]} */ (create_block(node, children, context)) .../** @type {import('estree').Statement[]} */ (
create_block(node, node.body, children, context)
)
); );
each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_close.value)))); each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_close.value))));
@ -1556,7 +1560,7 @@ const template_visitors = {
const close = b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE))); const close = b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE)));
if (node.fallback) { if (node.fallback) {
const fallback = create_block(node, node.fallback.nodes, context); const fallback = create_block(node, node.fallback, node.fallback.nodes, context);
fallback.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE)))); fallback.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE))));
@ -1577,8 +1581,10 @@ const template_visitors = {
const state = context.state; const state = context.state;
state.template.push(block_open); state.template.push(block_open);
const consequent = create_block(node, node.consequent.nodes, context); const consequent = create_block(node, node.consequent, node.consequent.nodes, context);
const alternate = node.alternate ? create_block(node, node.alternate.nodes, context) : []; const alternate = node.alternate
? create_block(node, node.alternate, node.alternate.nodes, context)
: [];
consequent.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE)))); consequent.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE))));
alternate.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE)))); alternate.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE))));
@ -1634,7 +1640,7 @@ const template_visitors = {
KeyBlock(node, context) { KeyBlock(node, context) {
const state = context.state; const state = context.state;
state.template.push(block_open); state.template.push(block_open);
const body = create_block(node, node.fragment.nodes, context); const body = create_block(node, node.fragment, node.fragment.nodes, context);
state.template.push(t_statement(b.block(body))); state.template.push(t_statement(b.block(body)));
state.template.push(block_close); state.template.push(block_close);
}, },
@ -1724,15 +1730,7 @@ const template_visitors = {
} }
} }
const state = { const body = create_block(node, node.fragment, node.fragment.nodes, context);
...context.state,
// TODO this logic eventually belongs in create_block, when fragments are used everywhere
scope: /** @type {import('../../scope').Scope} */ (context.state.scopes.get(node.fragment))
};
const body = create_block(node, node.fragment.nodes, {
...context,
state
});
context.state.template.push(t_statement(b.block(body))); context.state.template.push(t_statement(b.block(body)));
}, },
@ -1802,7 +1800,7 @@ const template_visitors = {
const fallback = const fallback =
node.fragment.nodes.length === 0 node.fragment.nodes.length === 0
? b.literal(null) ? b.literal(null)
: b.thunk(b.block(create_block(node, node.fragment.nodes, context))); : b.thunk(b.block(create_block(node, node.fragment, node.fragment.nodes, context)));
const slot = b.call('$.slot', b.id('$$payload'), expression, props_expression, fallback); const slot = b.call('$.slot', b.id('$$payload'), expression, props_expression, fallback);
state.template.push(t_statement(b.stmt(slot))); state.template.push(t_statement(b.stmt(slot)));
@ -1810,7 +1808,7 @@ const template_visitors = {
}, },
SvelteHead(node, context) { SvelteHead(node, context) {
const state = context.state; const state = context.state;
const body = create_block(node, node.fragment.nodes, context); const body = create_block(node, node.fragment, node.fragment.nodes, context);
state.template.push( state.template.push(
t_statement( t_statement(
b.stmt(b.call('$.head', b.id('$$payload'), b.arrow([b.id('$$payload')], b.block(body)))) b.stmt(b.call('$.head', b.id('$$payload'), b.arrow([b.id('$$payload')], b.block(body))))

@ -155,35 +155,28 @@ export function clean_nodes(
* @param {import('#compiler').Namespace} namespace * @param {import('#compiler').Namespace} namespace
* @param {import('#compiler').SvelteNode} parent * @param {import('#compiler').SvelteNode} parent
* @param {import('#compiler').SvelteNode[]} nodes * @param {import('#compiler').SvelteNode[]} nodes
* @param {import('#compiler').SvelteNode[]} path
*/ */
export function infer_namespace(namespace, parent, nodes, path) { export function infer_namespace(namespace, parent, nodes) {
const parent_node =
parent.type === 'Fragment'
? // Messy: We know that Fragment calls create_block directly, so we can do this here
path.at(-1)
: parent;
if (namespace !== 'foreign') { if (namespace !== 'foreign') {
if (parent_node?.type === 'RegularElement' && parent_node.name === 'foreignObject') { if (parent.type === 'RegularElement' && parent.name === 'foreignObject') {
return 'html'; return 'html';
} }
if (parent_node?.type === 'RegularElement' || parent_node?.type === 'SvelteElement') { if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
if (parent_node.metadata.svg) { if (parent.metadata.svg) {
return 'svg'; return 'svg';
} }
return parent_node.metadata.mathml ? 'mathml' : 'html'; return parent.metadata.mathml ? 'mathml' : 'html';
} }
// Re-evaluate the namespace inside slot nodes that reset the namespace // Re-evaluate the namespace inside slot nodes that reset the namespace
if ( if (
parent_node === undefined || parent.type === 'Fragment' ||
parent_node.type === 'Root' || parent.type === 'Root' ||
parent_node.type === 'Component' || parent.type === 'Component' ||
parent_node.type === 'SvelteComponent' || parent.type === 'SvelteComponent' ||
parent_node.type === 'SvelteFragment' || parent.type === 'SvelteFragment' ||
parent_node.type === 'SnippetBlock' parent.type === 'SnippetBlock'
) { ) {
const new_namespace = check_nodes_for_namespace(nodes, 'keep'); const new_namespace = check_nodes_for_namespace(nodes, 'keep');
if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') { if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {

@ -280,8 +280,6 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
next({ scope }); next({ scope });
}; };
const skip = () => {};
/** /**
* @type {import('zimmerframe').Visitor<import('#compiler').ElementLike, State, import('#compiler').SvelteNode>} * @type {import('zimmerframe').Visitor<import('#compiler').ElementLike, State, import('#compiler').SvelteNode>}
*/ */

@ -0,0 +1,7 @@
import { test } from '../../test';
export default test({
html: `
<div slot="footer">hello hello</div>
`
});

@ -0,0 +1,13 @@
<script>
import Nested from "./Nested.svelte"
import Nested2 from "./Nested2.svelte"
</script>
<Nested>
<Nested2 slot="inner" let:text>
<div slot="footer">
{@const text2 = text}
{text} {text2}
</div>
</Nested2>
</Nested>
Loading…
Cancel
Save