diff --git a/.changeset/dull-roses-relate.md b/.changeset/dull-roses-relate.md
new file mode 100644
index 000000000..b0da71fe5
--- /dev/null
+++ b/.changeset/dull-roses-relate.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: infer `svg` namespace correctly
diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js
index b7592f6f8..8ce6c0fd1 100644
--- a/packages/svelte/src/compiler/phases/3-transform/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/utils.js
@@ -211,23 +211,67 @@ export function infer_namespace(namespace, parent, nodes, path) {
parent_node.type === 'SvelteFragment' ||
parent_node.type === 'SnippetBlock')
) {
- // Heuristic: Keep current namespace, unless we find a regular element,
- // in which case we always want html, or we only find svg nodes,
- // in which case we assume svg.
- let only_svg = true;
- for (const node of nodes) {
- if (node.type === 'RegularElement') {
- if (!node.metadata.svg) {
- namespace = 'html';
- only_svg = false;
- break;
- }
- } else if (node.type !== 'Text' || node.data.trim() !== '') {
- only_svg = false;
- }
+ const new_namespace = check_nodes_for_namespace(nodes, 'keep');
+ if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {
+ namespace = new_namespace;
}
- if (only_svg) {
- namespace = 'svg';
+ }
+
+ return namespace;
+}
+
+/**
+ * Heuristic: Keep current namespace, unless we find a regular element,
+ * in which case we always want html, or we only find svg nodes,
+ * in which case we assume svg.
+ * @param {import('#compiler').SvelteNode[]} nodes
+ * @param {import('#compiler').Namespace | 'keep' | 'maybe_html'} namespace
+ */
+function check_nodes_for_namespace(nodes, namespace) {
+ for (const node of nodes) {
+ if (node.type === 'RegularElement') {
+ if (!node.metadata.svg) {
+ namespace = 'html';
+ break;
+ } else if (namespace === 'keep') {
+ namespace = 'svg';
+ }
+ } else if (
+ (node.type === 'Text' && node.data.trim() !== '') ||
+ node.type === 'HtmlTag' ||
+ node.type === 'RenderTag'
+ ) {
+ namespace = 'maybe_html';
+ } else if (node.type === 'EachBlock') {
+ namespace = check_nodes_for_namespace(node.body.nodes, namespace);
+ if (namespace === 'html') break;
+ if (node.fallback) {
+ namespace = check_nodes_for_namespace(node.fallback.nodes, namespace);
+ if (namespace === 'html') break;
+ }
+ } else if (node.type === 'IfBlock') {
+ namespace = check_nodes_for_namespace(node.consequent.nodes, namespace);
+ if (namespace === 'html') break;
+ if (node.alternate) {
+ namespace = check_nodes_for_namespace(node.alternate.nodes, namespace);
+ if (namespace === 'html') break;
+ }
+ } else if (node.type === 'AwaitBlock') {
+ if (node.pending) {
+ namespace = check_nodes_for_namespace(node.pending.nodes, namespace);
+ if (namespace === 'html') break;
+ }
+ if (node.then) {
+ namespace = check_nodes_for_namespace(node.then.nodes, namespace);
+ if (namespace === 'html') break;
+ }
+ if (node.catch) {
+ namespace = check_nodes_for_namespace(node.catch.nodes, namespace);
+ if (namespace === 'html') break;
+ }
+ } else if (node.type === 'KeyBlock') {
+ namespace = check_nodes_for_namespace(node.fragment.nodes, namespace);
+ if (namespace === 'html') break;
}
}
diff --git a/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte
index 2d2a6538d..0e3eec9d9 100644
--- a/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte
@@ -34,6 +34,10 @@
return Promise.resolve([24, 25]);
}
+ const some = {
+ fn: () => {}
+ }
+
const update = async () => {
[a, b] = [1, await Promise.resolve(2)];
({ c = await Promise.resolve(3), d } = { d: 4 });
@@ -49,7 +53,7 @@
[l = obj[await Promise.resolve("prop")] + 1] = [];
[m] = [`${1}${await Promise.resolve("3")}`];
[n] = [-(await Promise.resolve(-14))];
- [o] = [(console.log(15), await Promise.resolve(15))];
+ [o] = [(some.fn(), await Promise.resolve(15))];
({ anotherprop: p = await Promise.resolve(16) } = obj);
let val1, val2;
({ val1 = (async function (x) { return await x; })(Promise.resolve(18)), r = await val1 }
diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte
new file mode 100644
index 000000000..41f3f38fa
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte
@@ -0,0 +1,13 @@
+outside
+
+{#if true}
+ true
+{:else}
+ false
+{/if}
+
+{#each Array(3).fill(0) as item, idx}
+ {idx}
+{/each}
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js
new file mode 100644
index 000000000..46118026a
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js
@@ -0,0 +1,26 @@
+import { test, ok } from '../../test';
+
+export default test({
+ html: `
+
+`,
+ test({ assert, target }) {
+ const svg = target.querySelector('svg');
+ ok(svg);
+
+ assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
+
+ const text_elements = target.querySelectorAll('text');
+
+ assert.equal(text_elements.length, 5);
+
+ for (const { namespaceURI } of text_elements)
+ assert.equal(namespaceURI, 'http://www.w3.org/2000/svg');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte
new file mode 100644
index 000000000..7c1253ea7
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte
@@ -0,0 +1,7 @@
+
+
+