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 4 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[]} */
const serialized_slots = [];
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;
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.
* @param {import('#compiler').SvelteNode} parent
* @param {import('#compiler').Fragment} fragment
* @param {string} name
* @param {import('#compiler').SvelteNode[]} nodes
* @param {import('../types.js').ComponentContext} context
* @returns {import('estree').Statement[]}
*/
function create_block(parent, name, nodes, context) {
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes, context.path);
function create_block(parent, fragment, name, nodes, context) {
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes);
const { hoisted, trimmed } = clean_nodes(
parent,
@ -1060,6 +1067,7 @@ function create_block(parent, name, nodes, context) {
/** @type {import('../types').ComponentClientTransformState} */
const state = {
...context.state,
scope: context.state.scopes.get(fragment) ?? context.state.scope,
before_init: [],
init: [],
update: [],
@ -1616,7 +1624,7 @@ function serialize_attribute_value(attribute_value, context) {
/**
* @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
* @returns {[boolean, import('estree').TemplateLiteral]}
*/
@ -1661,13 +1669,13 @@ function serialize_template_literal(values, visit, state) {
id,
create_derived(
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));
} 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));
}
@ -1680,7 +1688,7 @@ function serialize_template_literal(values, visit, state) {
/** @type {import('../types').ComponentVisitors} */
export const template_visitors = {
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);
},
Comment(node, context) {
@ -2221,7 +2229,7 @@ export const template_visitors = {
}
inner.push(...inner_context.state.after_update);
inner.push(
...create_block(node, 'dynamic_element', node.fragment.nodes, {
...create_block(node, node.fragment, 'dynamic_element', node.fragment.nodes, {
...context,
state: {
...context.state,
@ -2449,7 +2457,7 @@ export const template_visitors = {
}
// 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
? 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(
...create_block(
node,
node.fragment,
'slot_template',
/** @type {import('#compiler').SvelteNode[]} */ (node.fragment.nodes),
{
...context,
state
}
context
)
);
},
@ -3088,7 +3088,7 @@ export const template_visitors = {
? b.literal(null)
: b.arrow(
[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
@ -3106,7 +3106,7 @@ export const template_visitors = {
'$.head',
b.arrow(
[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').Fragment} fragment
* @param {import('#compiler').SvelteNode[]} nodes
* @param {import('./types').ComponentContext} context
* @param {import('./types').Anchor} [anchor]
* @returns {import('estree').Statement[]}
*/
function create_block(parent, nodes, context, anchor) {
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes, context.path);
function create_block(parent, fragment, nodes, context, anchor) {
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes);
const { hoisted, trimmed } = clean_nodes(
parent,
@ -264,6 +265,7 @@ function create_block(parent, nodes, context, anchor) {
/** @type {import('./types').ComponentServerTransformState} */
const state = {
...context.state,
scope: context.state.scopes.get(fragment) ?? context.state.scope,
init: [],
template: [],
metadata: {
@ -1085,7 +1087,7 @@ function serialize_inline_component(node, component_name, context) {
const serialized_slots = [];
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;
const slot_fn = b.arrow(
@ -1268,7 +1270,7 @@ const javascript_visitors_legacy = {
/** @type {import('./types').ComponentVisitors} */
const template_visitors = {
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);
},
HtmlTag(node, context) {
@ -1472,7 +1474,7 @@ const template_visitors = {
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,
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(
.../** @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))));
@ -1556,7 +1560,7 @@ const template_visitors = {
const close = b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE)));
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))));
@ -1577,8 +1581,10 @@ const template_visitors = {
const state = context.state;
state.template.push(block_open);
const consequent = create_block(node, node.consequent.nodes, context);
const alternate = node.alternate ? create_block(node, node.alternate.nodes, context) : [];
const consequent = create_block(node, node.consequent, node.consequent.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))));
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) {
const state = context.state;
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(block_close);
},
@ -1724,15 +1730,7 @@ 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))
};
const body = create_block(node, node.fragment.nodes, {
...context,
state
});
const body = create_block(node, node.fragment, node.fragment.nodes, context);
context.state.template.push(t_statement(b.block(body)));
},
@ -1802,7 +1800,7 @@ const template_visitors = {
const fallback =
node.fragment.nodes.length === 0
? 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);
state.template.push(t_statement(b.stmt(slot)));
@ -1810,7 +1808,7 @@ const template_visitors = {
},
SvelteHead(node, context) {
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(
t_statement(
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').SvelteNode} parent
* @param {import('#compiler').SvelteNode[]} nodes
* @param {import('#compiler').SvelteNode[]} path
*/
export function infer_namespace(namespace, parent, nodes, path) {
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;
export function infer_namespace(namespace, parent, nodes) {
if (namespace !== 'foreign') {
if (parent_node?.type === 'RegularElement' && parent_node.name === 'foreignObject') {
if (parent.type === 'RegularElement' && parent.name === 'foreignObject') {
return 'html';
}
if (parent_node?.type === 'RegularElement' || parent_node?.type === 'SvelteElement') {
if (parent_node.metadata.svg) {
if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
if (parent.metadata.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
if (
parent_node === undefined ||
parent_node.type === 'Root' ||
parent_node.type === 'Component' ||
parent_node.type === 'SvelteComponent' ||
parent_node.type === 'SvelteFragment' ||
parent_node.type === 'SnippetBlock'
parent.type === 'Fragment' ||
parent.type === 'Root' ||
parent.type === 'Component' ||
parent.type === 'SvelteComponent' ||
parent.type === 'SvelteFragment' ||
parent.type === 'SnippetBlock'
) {
const new_namespace = check_nodes_for_namespace(nodes, 'keep');
if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {

@ -280,8 +280,6 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
next({ scope });
};
const skip = () => {};
/**
* @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