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: ` + + outside + true + 0 + 1 + 2 + +`, + 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 @@ + + + + +