diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 1601e73fb8..b7cf1aef51 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -277,8 +277,11 @@ export default class ElementWrapper extends Wrapper { const tag = this.node.tag_expr.manipulate(block); block.add_variable(previous_tag, tag); + const has_children_of_dynamic_element = block.get_unique_name('has_children_of_dynamic_element'); + block.add_variable(has_children_of_dynamic_element, x`${this.node.children.length > 0 ? 'true' : 'false'}`); + block.chunks.init.push(b` - ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag}, ${has_children_of_dynamic_element});`} let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx); `); @@ -309,7 +312,7 @@ export default class ElementWrapper extends Wrapper { ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); } else if (${not_equal}(${previous_tag}, ${tag})) { ${this.var}.d(1); - ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag}, ${has_children_of_dynamic_element});`} ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); ${this.var}.c(); ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 0ba47f7e86..2c31256193 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -1,5 +1,6 @@ import { custom_event, append, append_hydration, insert, insert_hydration, detach, listen, attr } from './dom'; import { SvelteComponent } from './Component'; +import { is_void } from '../../shared/utils/names'; export function dispatch_dev(type: string, detail?: T) { document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }, { bubbles: true })); @@ -107,10 +108,14 @@ export function validate_slots(name, slot, keys) { } } -export function validate_dynamic_element(tag: unknown) { - if (tag && typeof tag !== 'string') { +export function validate_dynamic_element(tag: unknown, has_children: boolean) { + const is_string = typeof tag === 'string'; + if (tag && !is_string) { throw new Error(' expects "this" attribute to be a string.'); } + if (is_string && is_void(tag as string) && has_children) { + throw new Error(`<${tag}> element is self-closing and cannot have content.`); + } } type Props = Record; diff --git a/test/runtime/samples/dynamic-element-void-with-content1/_config.js b/test/runtime/samples/dynamic-element-void-with-content1/_config.js new file mode 100644 index 0000000000..dc5a954a0e --- /dev/null +++ b/test/runtime/samples/dynamic-element-void-with-content1/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + props: { + tag: 'br' + }, + error: '
element is self-closing and cannot have content.' +}; diff --git a/test/runtime/samples/dynamic-element-void-with-content1/main.svelte b/test/runtime/samples/dynamic-element-void-with-content1/main.svelte new file mode 100644 index 0000000000..622e82d901 --- /dev/null +++ b/test/runtime/samples/dynamic-element-void-with-content1/main.svelte @@ -0,0 +1,5 @@ + + +foo diff --git a/test/runtime/samples/dynamic-element-void-with-content2/_config.js b/test/runtime/samples/dynamic-element-void-with-content2/_config.js new file mode 100644 index 0000000000..dc5a954a0e --- /dev/null +++ b/test/runtime/samples/dynamic-element-void-with-content2/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + props: { + tag: 'br' + }, + error: '
element is self-closing and cannot have content.' +}; diff --git a/test/runtime/samples/dynamic-element-void-with-content2/main.svelte b/test/runtime/samples/dynamic-element-void-with-content2/main.svelte new file mode 100644 index 0000000000..2b8c083efd --- /dev/null +++ b/test/runtime/samples/dynamic-element-void-with-content2/main.svelte @@ -0,0 +1,5 @@ + + +
bar
diff --git a/test/runtime/samples/dynamic-element-void-with-content3/Nested.svelte b/test/runtime/samples/dynamic-element-void-with-content3/Nested.svelte new file mode 100644 index 0000000000..e62294a135 --- /dev/null +++ b/test/runtime/samples/dynamic-element-void-with-content3/Nested.svelte @@ -0,0 +1 @@ +
This is nested
diff --git a/test/runtime/samples/dynamic-element-void-with-content3/_config.js b/test/runtime/samples/dynamic-element-void-with-content3/_config.js new file mode 100644 index 0000000000..dc5a954a0e --- /dev/null +++ b/test/runtime/samples/dynamic-element-void-with-content3/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + props: { + tag: 'br' + }, + error: '
element is self-closing and cannot have content.' +}; diff --git a/test/runtime/samples/dynamic-element-void-with-content3/main.svelte b/test/runtime/samples/dynamic-element-void-with-content3/main.svelte new file mode 100644 index 0000000000..320f50ea91 --- /dev/null +++ b/test/runtime/samples/dynamic-element-void-with-content3/main.svelte @@ -0,0 +1,6 @@ + + +