diff --git a/.changeset/gentle-hairs-confess.md b/.changeset/gentle-hairs-confess.md
new file mode 100644
index 000000000000..44e88e7c202a
--- /dev/null
+++ b/.changeset/gentle-hairs-confess.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+breaking: warn on self-closing non-void special elements
diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js
index 0f6f1bf6dd30..6b01f3430950 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/validation.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js
@@ -676,11 +676,21 @@ const validation = {
error(node, 'invalid-style-directive-modifier');
}
},
- SvelteHead(node) {
+ SvelteHead(node, context) {
const attribute = node.attributes[0];
if (attribute) {
error(attribute, 'illegal-svelte-head-attribute');
}
+
+ if (context.state.analysis.source[node.end - 2] === '/') {
+ warn(
+ context.state.analysis.warnings,
+ node,
+ context.path,
+ 'invalid-self-closing-tag',
+ node.name
+ );
+ }
},
SvelteElement(node, context) {
validate_element(node, context);
@@ -704,6 +714,16 @@ const validation = {
error(attribute, 'invalid-svelte-fragment-attribute');
}
}
+
+ if (context.state.analysis.source[node.end - 2] === '/') {
+ warn(
+ context.state.analysis.warnings,
+ node,
+ context.path,
+ 'invalid-self-closing-tag',
+ node.name
+ );
+ }
},
SlotElement(node) {
for (const attribute of node.attributes) {
diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js
index fc5897aa9300..d81cd5728453 100644
--- a/packages/svelte/src/compiler/warnings.js
+++ b/packages/svelte/src/compiler/warnings.js
@@ -253,7 +253,7 @@ const options = {
const misc = {
/** @param {string} name */
'invalid-self-closing-tag': (name) =>
- `Self-closing HTML tags for non-void elements are ambiguous — use <${name} ...>${name}> rather than <${name} ... />`
+ `Self-closing tags for non-void elements are ambiguous — use <${name} ...>${name}> rather than <${name} ... />`
};
/** @satisfies {Warnings} */
diff --git a/packages/svelte/tests/validator/samples/invalid-self-closing-tag/input.svelte b/packages/svelte/tests/validator/samples/invalid-self-closing-tag/input.svelte
index 8b09b6f9a3e3..bbfe9cfd7909 100644
--- a/packages/svelte/tests/validator/samples/invalid-self-closing-tag/input.svelte
+++ b/packages/svelte/tests/validator/samples/invalid-self-closing-tag/input.svelte
@@ -1,7 +1,31 @@
-
-
+
-
+
+
+
+
+ null} />
+ null} />
+ null} />
+
+
+
+
+
+{#if true}
+
+{/if}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/svelte/tests/validator/samples/invalid-self-closing-tag/warnings.json b/packages/svelte/tests/validator/samples/invalid-self-closing-tag/warnings.json
index 772140c44ec6..9a9182364e58 100644
--- a/packages/svelte/tests/validator/samples/invalid-self-closing-tag/warnings.json
+++ b/packages/svelte/tests/validator/samples/invalid-self-closing-tag/warnings.json
@@ -1,26 +1,50 @@
[
{
"code": "invalid-self-closing-tag",
- "message": "Self-closing HTML tags for non-void elements are ambiguous — use rather than ",
+ "message": "Self-closing tags for non-void elements are ambiguous — use rather than ",
"start": {
- "line": 6,
+ "line": 26,
"column": 0
},
"end": {
- "line": 6,
+ "line": 26,
"column": 7
}
},
{
"code": "invalid-self-closing-tag",
- "message": "Self-closing HTML tags for non-void elements are ambiguous — use rather than ",
+ "message": "Self-closing tags for non-void elements are ambiguous — use rather than ",
"start": {
- "line": 7,
+ "line": 27,
"column": 0
},
"end": {
- "line": 7,
+ "line": 27,
"column": 12
}
+ },
+ {
+ "code": "invalid-self-closing-tag",
+ "message": "Self-closing tags for non-void elements are ambiguous — use rather than ",
+ "start": {
+ "line": 28,
+ "column": 0
+ },
+ "end": {
+ "line": 28,
+ "column": 15
+ }
+ },
+ {
+ "code": "invalid-self-closing-tag",
+ "message": "Self-closing tags for non-void elements are ambiguous — use rather than ",
+ "start": {
+ "line": 30,
+ "column": 1
+ },
+ "end": {
+ "line": 30,
+ "column": 34
+ }
}
]