From df6750508193557a0ad27b5a2aba422546c172f8 Mon Sep 17 00:00:00 2001 From: 7nik Date: Thu, 15 May 2025 23:26:30 +0300 Subject: [PATCH] warn on implicitly closed tags --- .../.generated/compile-warnings.md | 6 +++++ .../messages/compile-warnings/template.md | 4 +++ .../compiler/phases/1-parse/state/element.js | 9 ++++++- packages/svelte/src/compiler/warnings.js | 10 +++++++ .../implicitly-closed-tag/input.svelte | 6 +++++ .../implicitly-closed-tag/warnings.json | 26 +++++++++++++++++++ 6 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 packages/svelte/tests/validator/samples/implicitly-closed-tag/input.svelte create mode 100644 packages/svelte/tests/validator/samples/implicitly-closed-tag/warnings.json diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md index 7069f90206..e232076765 100644 --- a/documentation/docs/98-reference/.generated/compile-warnings.md +++ b/documentation/docs/98-reference/.generated/compile-warnings.md @@ -632,6 +632,12 @@ In some situations a selector may target an element that is not 'visible' to the ``` +### element_implicitly_closed + +``` +The tag `<%name%>` was implicitly closed by the parent or a next element. This may cause DOM structure being other than expected one. +``` + ### element_invalid_self_closing_tag ``` diff --git a/packages/svelte/messages/compile-warnings/template.md b/packages/svelte/messages/compile-warnings/template.md index c1675e5995..84b9c4a791 100644 --- a/packages/svelte/messages/compile-warnings/template.md +++ b/packages/svelte/messages/compile-warnings/template.md @@ -30,6 +30,10 @@ > `<%name%>` will be treated as an HTML element unless it begins with a capital letter +## element_implicitly_closed + +> The tag `<%name%>` was implicitly closed by the parent or a next element. This may cause DOM structure being other than expected one. + ## element_invalid_self_closing_tag > Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...>` rather than `<%name% ... />` diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index d20369b0d9..1c46bf17ba 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -93,7 +93,12 @@ export default function element(parser) { } } - if (parent.type !== 'RegularElement' && !parser.loose) { + if (parent.type === 'RegularElement') { + if (!parser.last_auto_closed_tag || parser.last_auto_closed_tag.tag !== name) { + const opening_tag_end = parent.fragment.nodes[0]?.start ?? parent.start + parent.name.length + 2; + w.element_implicitly_closed({ start: parent.start, end: opening_tag_end }, parent.name); + } + } else if (!parser.loose) { if (parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name) { e.element_invalid_closing_tag_autoclosed(start, name, parser.last_auto_closed_tag.reason); } else { @@ -186,6 +191,8 @@ export default function element(parser) { parser.allow_whitespace(); if (parent.type === 'RegularElement' && closing_tag_omitted(parent.name, name)) { + const opening_tag_end = parent.fragment.nodes[0]?.start ?? parent.start + parent.name.length + 2; + w.element_implicitly_closed({ start: parent.start, end: opening_tag_end }, parent.name); parent.end = start; parser.pop(); parser.last_auto_closed_tag = { diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index c281433213..661575eddf 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -114,6 +114,7 @@ export const codes = [ 'bind_invalid_each_rest', 'block_empty', 'component_name_lowercase', + 'element_implicitly_closed', 'element_invalid_self_closing_tag', 'event_directive_deprecated', 'node_invalid_placement_ssr', @@ -746,6 +747,15 @@ export function component_name_lowercase(node, name) { w(node, 'component_name_lowercase', `\`<${name}>\` will be treated as an HTML element unless it begins with a capital letter\nhttps://svelte.dev/e/component_name_lowercase`); } +/** + * The tag `<%name%>` was implicitly closed by the parent or a next element. This may cause DOM structure being other than expected one. + * @param {null | NodeLike} node + * @param {string} name + */ +export function element_implicitly_closed(node, name) { + w(node, 'element_implicitly_closed', `The tag \`<${name}>\` was implicitly closed by the parent or a next element. This may cause DOM structure being other than expected one.\nhttps://svelte.dev/e/element_implicitly_closed`); +} + /** * Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...>` rather than `<%name% ... />` * @param {null | NodeLike} node diff --git a/packages/svelte/tests/validator/samples/implicitly-closed-tag/input.svelte b/packages/svelte/tests/validator/samples/implicitly-closed-tag/input.svelte new file mode 100644 index 0000000000..02d7a1a7aa --- /dev/null +++ b/packages/svelte/tests/validator/samples/implicitly-closed-tag/input.svelte @@ -0,0 +1,6 @@ +
+ +
+

+

+
diff --git a/packages/svelte/tests/validator/samples/implicitly-closed-tag/warnings.json b/packages/svelte/tests/validator/samples/implicitly-closed-tag/warnings.json new file mode 100644 index 0000000000..7a3873842d --- /dev/null +++ b/packages/svelte/tests/validator/samples/implicitly-closed-tag/warnings.json @@ -0,0 +1,26 @@ +[ + { + "code": "element_implicitly_closed", + "message": "The tag `

` was implicitly closed by the parent or a next element. This may cause DOM structure being other than expected one.", + "start": { + "line": 4, + "column": 4 + }, + "end": { + "line": 4, + "column": 7 + } + }, + { + "code": "element_implicitly_closed", + "message": "The tag `

` was implicitly closed by the parent or a next element. This may cause DOM structure being other than expected one.", + "start": { + "line": 3, + "column": 4 + }, + "end": { + "line": 3, + "column": 9 + } + } +]