diff --git a/.changeset/nine-cooks-join.md b/.changeset/nine-cooks-join.md
new file mode 100644
index 0000000000..c2cc16965a
--- /dev/null
+++ b/.changeset/nine-cooks-join.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+breaking: warn on self-closing non-void HTML tags
diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js
index d86b1cb7fd..0f6f1bf6dd 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/validation.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js
@@ -15,7 +15,12 @@ import {
import { warn } from '../../warnings.js';
import fuzzymatch from '../1-parse/utils/fuzzymatch.js';
import { binding_properties } from '../bindings.js';
-import { ContentEditableBindings, EventModifiers, SVGElements } from '../constants.js';
+import {
+ ContentEditableBindings,
+ EventModifiers,
+ SVGElements,
+ VoidElements
+} from '../constants.js';
import { is_custom_element_node } from '../nodes.js';
import {
regex_illegal_attribute_character,
@@ -572,6 +577,21 @@ const validation = {
}
}
+ if (
+ context.state.analysis.source[node.end - 2] === '/' &&
+ context.state.options.namespace !== 'foreign' &&
+ !VoidElements.includes(node.name) &&
+ !SVGElements.includes(node.name)
+ ) {
+ warn(
+ context.state.analysis.warnings,
+ node,
+ context.path,
+ 'invalid-self-closing-tag',
+ node.name
+ );
+ }
+
context.next({
...context.state,
parent_element: node.name
diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js
index 26bcf1ecdd..fc5897aa93 100644
--- a/packages/svelte/src/compiler/warnings.js
+++ b/packages/svelte/src/compiler/warnings.js
@@ -250,6 +250,12 @@ const options = {
"The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?"
};
+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} ... />`
+};
+
/** @satisfies {Warnings} */
const warnings = {
...css,
@@ -261,7 +267,8 @@ const warnings = {
...components,
...legacy,
...block,
- ...options
+ ...options,
+ ...misc
};
/** @typedef {typeof warnings} AllWarnings */
diff --git a/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error-2/main.svelte b/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error-2/main.svelte
index f0ba192c4f..7143fdda50 100644
--- a/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error-2/main.svelte
+++ b/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error-2/main.svelte
@@ -5,7 +5,7 @@
diff --git a/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error-3/main.svelte b/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error-3/main.svelte
index 3031292d04..c36659274c 100644
--- a/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error-3/main.svelte
+++ b/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error-3/main.svelte
@@ -5,7 +5,7 @@
diff --git a/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error/main.svelte b/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error/main.svelte
index 5f251ea625..b0ecfcf31e 100644
--- a/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error/main.svelte
+++ b/packages/svelte/tests/compiler-errors/samples/component-slot-nested-error/main.svelte
@@ -4,6 +4,6 @@
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-await-not-exhaustive/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-await-not-exhaustive/input.svelte
index fadfc914ed..a3e4230161 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-await-not-exhaustive/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-await-not-exhaustive/input.svelte
@@ -18,24 +18,24 @@
.c ~ .g { color: green; }
-
+
{#await promise then value}
-
+
{:catch error}
-
+
{/await}
{#await promise}
-
+
{:catch error}
-
+
{/await}
{#await promise}
-
+
{:then error}
-
+
{/await}
-
+
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-await/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-await/input.svelte
index 8aeadab170..11bd358c01 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-await/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-await/input.svelte
@@ -17,14 +17,14 @@
.b ~ .d { color: green; }
-
+
{#await promise}
-
+
{:then value}
-
+
{:catch error}
-
+
{/await}
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-each-2/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-each-2/input.svelte
index 5bbdbdef66..b719ce4077 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-each-2/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-each-2/input.svelte
@@ -27,11 +27,11 @@
}
-
+
{#each array as item}
-
-
+
+
{/each}
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-each-else-nested/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-each-else-nested/input.svelte
index 63a1123708..3030f3e9ab 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-each-else-nested/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-each-else-nested/input.svelte
@@ -35,43 +35,43 @@
.e ~ .f { color: green; }
-
+
{#each array as a}
-
+
{#each array as b}
-
+
{:else}
-
+
{/each}
{/each}
{#each array as c}
{#each array as d}
-
+
{/each}
{:else}
-
+
{/each}
{#each array as x}
-
+
{#each array as y}
{#each array as z}
-
+
{/each}
{:else}
-
+
{/each}
-
+
{/each}
-
+
{#each array as item}
{#each array as item}
-
+
{:else}
-
+
{/each}
-{/each}
\ No newline at end of file
+{/each}
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-each-else/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-each-else/input.svelte
index d2916b263d..04e0d64468 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-each-else/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-each-else/input.svelte
@@ -13,12 +13,12 @@
.b ~ .c { color: green; }
-
+
{#each array as item}
-
+
{:else}
-
+
{/each}
-
+
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-each-nested/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-each-nested/input.svelte
index b7c7377015..014b9ab83f 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-each-nested/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-each-nested/input.svelte
@@ -65,39 +65,39 @@
.g ~ .i { color: green; }
-
+
{#each array as item}
-
-
+
+
{/each}
{#each array as item}
{#each array as item}
{#each array as item}
-
+
{/each}
-
+
{/each}
-
+
{/each}
{#each array as item}
-
+
{#each array as item}
-
+
{#each array as item}
-
+
{/each}
{/each}
{/each}
{#each array as item}
-
+
{#each array as item}
-
+
{#each array as item}
-
+
{/each}
{/each}
{/each}
@@ -105,9 +105,9 @@
{#each array as item}
{#each array as item}
{#each array as item}
-
+
{/each}
-
+
{/each}
-
-{/each}
\ No newline at end of file
+
+{/each}
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-each/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-each/input.svelte
index ce65da109d..9b4925e08c 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-each/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-each/input.svelte
@@ -8,13 +8,13 @@
}
-
+
{#each array as item}
-
-
-
-
+
+
+
+
{/each}
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/input.svelte
index 68a6825b82..1f86b2bbf6 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/input.svelte
@@ -18,14 +18,14 @@
.b ~ .c { color: green; }
-
+
{#if foo}
-
+
{:else}
{#each array as item}
-
+
{/each}
{/if}
-
+
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-if-not-exhaustive/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-if-not-exhaustive/input.svelte
index e5703a09fd..50de4745b6 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-if-not-exhaustive/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-if-not-exhaustive/input.svelte
@@ -14,12 +14,12 @@
.b ~ .c { color: green; }
-
+
{#if foo}
-
+
{:else if bar}
-
+
{/if}
-
+
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-if/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-if/input.svelte
index fca5499f2e..d89c9de741 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-if/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-if/input.svelte
@@ -18,14 +18,14 @@
.c ~ .d { color: green; }
-
+
{#if foo}
-
+
{:else if bar}
-
+
{:else}
-
+
{/if}
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-slot/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-slot/input.svelte
index 93b2cd73a7..afb3b5226c 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-slot/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-slot/input.svelte
@@ -15,16 +15,16 @@
.b ~ .g { color: green; }
-
+
-
+
-
+
-
+
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator-star/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator-star/input.svelte
index a069685d4f..39698972ea 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator-star/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator-star/input.svelte
@@ -8,10 +8,10 @@
\ No newline at end of file
+
+
+
diff --git a/packages/svelte/tests/css/samples/general-siblings-combinator/input.svelte b/packages/svelte/tests/css/samples/general-siblings-combinator/input.svelte
index 533702a3a3..c526151ce2 100644
--- a/packages/svelte/tests/css/samples/general-siblings-combinator/input.svelte
+++ b/packages/svelte/tests/css/samples/general-siblings-combinator/input.svelte
@@ -16,9 +16,9 @@
-
-
+
+
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/css/samples/global-with-child-combinator-2/input.svelte b/packages/svelte/tests/css/samples/global-with-child-combinator-2/input.svelte
index 146f302633..c0f2f69678 100644
--- a/packages/svelte/tests/css/samples/global-with-child-combinator-2/input.svelte
+++ b/packages/svelte/tests/css/samples/global-with-child-combinator-2/input.svelte
@@ -5,5 +5,5 @@
\ No newline at end of file
+
+
diff --git a/packages/svelte/tests/css/samples/global-with-child-combinator/input.svelte b/packages/svelte/tests/css/samples/global-with-child-combinator/input.svelte
index 3b5ff160c3..35b8774e64 100644
--- a/packages/svelte/tests/css/samples/global-with-child-combinator/input.svelte
+++ b/packages/svelte/tests/css/samples/global-with-child-combinator/input.svelte
@@ -5,5 +5,5 @@
\ No newline at end of file
+
+
diff --git a/packages/svelte/tests/css/samples/quote-mark-inside-string/input.svelte b/packages/svelte/tests/css/samples/quote-mark-inside-string/input.svelte
index 052d5949bc..e3355cd246 100644
--- a/packages/svelte/tests/css/samples/quote-mark-inside-string/input.svelte
+++ b/packages/svelte/tests/css/samples/quote-mark-inside-string/input.svelte
@@ -1,4 +1,4 @@
-
+
-
+
{#await promise then value}
-
+
{:catch error}
-
+
{/await}
{#await promise}
-
+
{:catch error}
-
+
{/await}
{#await promise}
-
+
{:then error}
-
+
{/await}
-
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-await/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-await/input.svelte
index 07698c2a30..eab88894ac 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-await/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-await/input.svelte
@@ -17,14 +17,14 @@
.b + .d { color: green; }
-
+
{#await promise}
-
+
{:then value}
-
+
{:catch error}
-
+
{/await}
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-each-2/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-each-2/input.svelte
index bbad045fbc..11876e5bac 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-each-2/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-each-2/input.svelte
@@ -28,11 +28,11 @@
}
-
+
{#each array as item}
-
-
+
+
{/each}
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-each-else-nested/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-each-else-nested/input.svelte
index d44a53c8fc..94da7d87ac 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-each-else-nested/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-each-else-nested/input.svelte
@@ -32,43 +32,43 @@
.g + .h + .i + .j { color: green; }
-
+
{#each array as a}
-
+
{#each array as b}
-
+
{:else}
-
+
{/each}
{/each}
{#each array as c}
{#each array as d}
-
+
{/each}
{:else}
-
+
{/each}
{#each array as item}
-
+
{#each array as item}
{#each array as item}
-
+
{/each}
{:else}
-
+
{/each}
-
+
{/each}
-
+
{#each array as item}
{#each array as item}
-
+
{:else}
-
+
{/each}
{/each}
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-each-else/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-each-else/input.svelte
index 04d1945461..5ea5bb0f37 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-each-else/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-each-else/input.svelte
@@ -13,12 +13,12 @@
.b + .c { color: green; }
-
+
{#each array as item}
-
+
{:else}
-
+
{/each}
-
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-each-nested/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-each-nested/input.svelte
index b5b9242675..fcc9bbd2e8 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-each-nested/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-each-nested/input.svelte
@@ -65,39 +65,39 @@
.g + .i { color: green; }
-
+
{#each array as item}
-
-
+
+
{/each}
{#each array as item}
{#each array as item}
{#each array as item}
-
+
{/each}
-
+
{/each}
-
+
{/each}
{#each array as item}
-
+
{#each array as item}
-
+
{#each array as item}
-
+
{/each}
{/each}
{/each}
{#each array as item}
-
+
{#each array as item}
-
+
{#each array as item}
-
+
{/each}
{/each}
{/each}
@@ -105,9 +105,9 @@
{#each array as item}
{#each array as item}
{#each array as item}
-
+
{/each}
-
+
{/each}
-
-{/each}
\ No newline at end of file
+
+{/each}
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-each/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-each/input.svelte
index de77a25180..e28cb5ef2d 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-each/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-each/input.svelte
@@ -8,13 +8,13 @@
}
-
+
{#each array as item}
-
-
-
-
+
+
+
+
{/each}
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-global/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-global/input.svelte
index ec06f3c015..5838977b13 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-global/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-global/input.svelte
@@ -17,5 +17,5 @@
{#each [] as _}
-
-{/each}
\ No newline at end of file
+
+{/each}
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-if-not-exhaustive-with-each/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-if-not-exhaustive-with-each/input.svelte
index 71009d0286..2a0d317103 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-if-not-exhaustive-with-each/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-if-not-exhaustive-with-each/input.svelte
@@ -18,14 +18,14 @@
.b + .c { color: green; }
-
+
{#if foo}
-
+
{:else}
{#each array as item}
-
+
{/each}
{/if}
-
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-if-not-exhaustive/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-if-not-exhaustive/input.svelte
index 41901d285e..552881f869 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-if-not-exhaustive/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-if-not-exhaustive/input.svelte
@@ -14,12 +14,12 @@
.b + .c { color: green; }
-
+
{#if foo}
-
+
{:else if bar}
-
+
{/if}
-
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-if/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-if/input.svelte
index 6cfc436876..be4c393229 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-if/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-if/input.svelte
@@ -18,14 +18,14 @@
.c + .d { color: green; }
-
+
{#if foo}
-
+
{:else if bar}
-
+
{:else}
-
+
{/if}
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-slot/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-slot/input.svelte
index 6e0df3f497..630bc2fe97 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-slot/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-slot/input.svelte
@@ -11,14 +11,14 @@
.c + .f { color: green; }
-
+
-
+
-
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator-star/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator-star/input.svelte
index ca837f2239..5bcc38f1bf 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator-star/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator-star/input.svelte
@@ -8,10 +8,10 @@
\ No newline at end of file
+
+
+
diff --git a/packages/svelte/tests/css/samples/siblings-combinator/input.svelte b/packages/svelte/tests/css/samples/siblings-combinator/input.svelte
index 3e22076d52..87682163ac 100644
--- a/packages/svelte/tests/css/samples/siblings-combinator/input.svelte
+++ b/packages/svelte/tests/css/samples/siblings-combinator/input.svelte
@@ -29,7 +29,7 @@
-
-
+
+
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/input.svelte b/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/input.svelte
index 35468de006..cd76c59e4b 100644
--- a/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/input.svelte
+++ b/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/input.svelte
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json
index 5daf29018c..2ae3acfdc7 100644
--- a/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json
+++ b/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json
@@ -2,12 +2,12 @@
"html": {
"type": "Fragment",
"start": 0,
- "end": 11,
+ "end": 16,
"children": [
{
"type": "Element",
"start": 0,
- "end": 11,
+ "end": 16,
"name": "div",
"attributes": [
{
diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/escaped-css/main.svelte b/packages/svelte/tests/runtime-browser/custom-elements-samples/escaped-css/main.svelte
index 4da86e90ca..5bc8d9ad8d 100644
--- a/packages/svelte/tests/runtime-browser/custom-elements-samples/escaped-css/main.svelte
+++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/escaped-css/main.svelte
@@ -1,6 +1,6 @@
-
+
-
\ No newline at end of file
+
diff --git a/packages/svelte/tests/validator/samples/css-mismatched-quotes/input.svelte b/packages/svelte/tests/validator/samples/css-mismatched-quotes/input.svelte
index 964aa62d7b..75a4e0de59 100644
--- a/packages/svelte/tests/validator/samples/css-mismatched-quotes/input.svelte
+++ b/packages/svelte/tests/validator/samples/css-mismatched-quotes/input.svelte
@@ -1,4 +1,4 @@
-
+