pull/18893/merge
aeTunga 3 days ago committed by GitHub
commit 609e7755e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: don't warn about content in a void `<svelte:element>` when it only has an optional render tag that renders nothing

@ -5,7 +5,7 @@ import { dev, locator } from '../../../../state.js';
import { is_text_attribute } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { async_thunk } from '../utils.js';
import { determine_namespace_for_children } from '../../utils.js';
import { build_void_element_content_check, determine_namespace_for_children } from '../../utils.js';
import {
build_attribute_value,
build_attribute_effect,
@ -121,7 +121,18 @@ export function SvelteElement(node, context) {
if (dev) {
statements.push(b.stmt(b.call('$.validate_dynamic_element_tag', get_tag)));
if (node.fragment.nodes.length > 0) {
statements.push(b.stmt(b.call('$.validate_void_dynamic_element', get_tag)));
statements.push(
b.stmt(
b.call(
'$.validate_void_dynamic_element',
get_tag,
build_void_element_content_check(
node,
(n) => /** @type {Expression} */ (context.visit(n))
)
)
)
);
}
}

@ -3,7 +3,7 @@
/** @import { ComponentContext } from '../types.js' */
import { dev, locator } from '../../../../state.js';
import * as b from '#compiler/builders';
import { determine_namespace_for_children } from '../../utils.js';
import { build_void_element_content_check, determine_namespace_for_children } from '../../utils.js';
import { build_element_attributes } from './shared/element.js';
import { build_template, create_child_block, PromiseOptimiser } from './shared/utils.js';
@ -25,7 +25,18 @@ export function SvelteElement(node, context) {
context.state.init.push(b.stmt(b.call('$.validate_dynamic_element_tag', b.thunk(tag))));
if (node.fragment.nodes.length > 0) {
context.state.init.push(b.stmt(b.call('$.validate_void_dynamic_element', b.thunk(tag))));
context.state.init.push(
b.stmt(
b.call(
'$.validate_void_dynamic_element',
b.thunk(tag),
build_void_element_content_check(
node,
(n) => /** @type {Expression} */ (context.visit(n))
)
)
)
);
}
}

@ -8,7 +8,8 @@ import {
} from '../patterns.js';
import * as e from '../../errors.js';
import { walk } from 'zimmerframe';
import { extract_identifiers } from '../../utils/ast.js';
import { extract_identifiers, unwrap_optional } from '../../utils/ast.js';
import * as b from '#compiler/builders';
import check_graph_for_cycles from '../../utils/check_graph_for_cycles.js';
import is_reference from 'is-reference';
import { set_scope } from '../scope.js';
@ -450,3 +451,32 @@ export function get_inspect_args(rune, node, visit) {
rune === '$inspect' ? 'console.log' : /** @type {Expression} */ (visit(node.arguments[0]))
};
}
/**
* If the content of a `<svelte:element>` consists only of optional render tags like
* `{@render children?.()}`, returns a thunk that checks whether any of those snippets exist,
* so that we don't warn about void elements having content that will never be rendered
* @param {AST.SvelteElement} node
* @param {(node: Expression) => Expression} visit
* @returns {Expression | undefined}
*/
export function build_void_element_content_check(node, visit) {
/** @type {Expression[]} */
const checks = [];
for (const child of node.fragment.nodes) {
if (child.type === 'Comment') continue;
if (child.type === 'Text' && !regex_not_whitespace.test(child.data)) continue;
if (child.type !== 'RenderTag' || child.metadata.expression.is_async()) return;
const call = unwrap_optional(child.expression);
if (!call.optional) return;
checks.push(b.binary('!=', visit(/** @type {Expression} */ (call.callee)), b.null));
}
if (checks.length === 0) return;
return b.thunk(checks.reduce((a, c) => b.logical('||', a, c)));
}

@ -6,11 +6,12 @@ export { invalid_default_snippet } from './errors.js';
/**
* @param {() => string} tag_fn
* @param {() => boolean} [has_content]
* @returns {void}
*/
export function validate_void_dynamic_element(tag_fn) {
export function validate_void_dynamic_element(tag_fn, has_content) {
const tag = tag_fn();
if (tag && is_void(tag)) {
if (tag && is_void(tag) && (has_content?.() ?? true)) {
w.dynamic_void_element_content(tag);
}
}

@ -0,0 +1,7 @@
<script>
let { tag, children } = $props();
</script>
<svelte:element this={tag}>
{@render children?.()}
</svelte:element>

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
html: '<input>',
warnings: ['`<svelte:element this="input">` is a void element — it cannot have content']
});

@ -0,0 +1,5 @@
<script>
import Child from './Child.svelte';
</script>
<Child tag="input">foo</Child>

@ -0,0 +1,7 @@
<script>
let { tag, children } = $props();
</script>
<svelte:element this={tag}>
{@render children?.()}
</svelte:element>

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
html: '<input>',
warnings: []
});

@ -0,0 +1,5 @@
<script>
import Child from './Child.svelte';
</script>
<Child tag="input" />
Loading…
Cancel
Save