Skip to content

Commit 86ae349

Browse files
fix: better error messages for invalid HTML trees (#14445)
* fix: better error messages for invalid HTML trees closes #13331 * fix test * more concise * tweak * tweak messages * adjust tests * tweak message slightly, so it doesn't sound like the bad element is the one we're currently encountering * put locations in generated message * tidy up * consistency * fix --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 4e77bde commit 86ae349

File tree

25 files changed

+150
-84
lines changed

25 files changed

+150
-84
lines changed

.changeset/green-pumpkins-ring.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: better error messages for invalid HTML trees

documentation/docs/98-reference/.generated/compile-errors.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,12 +487,12 @@ A component cannot have a default export
487487
### node_invalid_placement
488488

489489
```
490-
%thing% is invalid inside `<%parent%>`
490+
%message%. The browser will 'repair' the HTML (by moving, removing, or inserting elements) which breaks Svelte's assumptions about the structure of your components.
491491
```
492492

493493
HTML restricts where certain elements can appear. In case of a violation the browser will 'repair' the HTML in a way that breaks Svelte's assumptions about the structure of your components. Some examples:
494494

495-
- `<p>hello <div>world</div></p>` will result in `<p>hello </p><div>world</div><p></p>` for example (the `<div>` autoclosed the `<p>` because `<p>` cannot contain block-level elements)
495+
- `<p>hello <div>world</div></p>` will result in `<p>hello </p><div>world</div><p></p>` (the `<div>` autoclosed the `<p>` because `<p>` cannot contain block-level elements)
496496
- `<option><div>option a</div></option>` will result in `<option>option a</option>` (the `<div>` is removed)
497497
- `<table><tr><td>cell</td></tr></table>` will result in `<table><tbody><tr><td>cell</td></tr></tbody></table>` (a `<tbody>` is auto-inserted)
498498

documentation/docs/98-reference/.generated/compile-warnings.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -643,12 +643,12 @@ Svelte 5 components are no longer classes. Instantiate them using `mount` or `hy
643643
### node_invalid_placement_ssr
644644

645645
```
646-
%thing% is invalid inside `<%parent%>`. When rendering this component on the server, the resulting HTML will be modified by the browser, likely resulting in a `hydration_mismatch` warning
646+
%message%. When rendering this component on the server, the resulting HTML will be modified by the browser (by moving, removing, or inserting elements), likely resulting in a `hydration_mismatch` warning
647647
```
648648

649649
HTML restricts where certain elements can appear. In case of a violation the browser will 'repair' the HTML in a way that breaks Svelte's assumptions about the structure of your components. Some examples:
650650

651-
- `<p>hello <div>world</div></p>` will result in `<p>hello </p><div>world</div><p></p>` for example (the `<div>` autoclosed the `<p>` because `<p>` cannot contain block-level elements)
651+
- `<p>hello <div>world</div></p>` will result in `<p>hello </p><div>world</div><p></p>` (the `<div>` autoclosed the `<p>` because `<p>` cannot contain block-level elements)
652652
- `<option><div>option a</div></option>` will result in `<option>option a</option>` (the `<div>` is removed)
653653
- `<table><tr><td>cell</td></tr></table>` will result in `<table><tbody><tr><td>cell</td></tr></tbody></table>` (a `<tbody>` is auto-inserted)
654654

packages/svelte/messages/compile-errors/template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,11 @@
190190
191191
## node_invalid_placement
192192

193-
> %thing% is invalid inside `<%parent%>`
193+
> %message%. The browser will 'repair' the HTML (by moving, removing, or inserting elements) which breaks Svelte's assumptions about the structure of your components.
194194
195195
HTML restricts where certain elements can appear. In case of a violation the browser will 'repair' the HTML in a way that breaks Svelte's assumptions about the structure of your components. Some examples:
196196

197-
- `<p>hello <div>world</div></p>` will result in `<p>hello </p><div>world</div><p></p>` for example (the `<div>` autoclosed the `<p>` because `<p>` cannot contain block-level elements)
197+
- `<p>hello <div>world</div></p>` will result in `<p>hello </p><div>world</div><p></p>` (the `<div>` autoclosed the `<p>` because `<p>` cannot contain block-level elements)
198198
- `<option><div>option a</div></option>` will result in `<option>option a</option>` (the `<div>` is removed)
199199
- `<table><tr><td>cell</td></tr></table>` will result in `<table><tbody><tr><td>cell</td></tr></tbody></table>` (a `<tbody>` is auto-inserted)
200200

packages/svelte/messages/compile-warnings/template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@
4040
4141
## node_invalid_placement_ssr
4242

43-
> %thing% is invalid inside `<%parent%>`. When rendering this component on the server, the resulting HTML will be modified by the browser, likely resulting in a `hydration_mismatch` warning
43+
> %message%. When rendering this component on the server, the resulting HTML will be modified by the browser (by moving, removing, or inserting elements), likely resulting in a `hydration_mismatch` warning
4444
4545
HTML restricts where certain elements can appear. In case of a violation the browser will 'repair' the HTML in a way that breaks Svelte's assumptions about the structure of your components. Some examples:
4646

47-
- `<p>hello <div>world</div></p>` will result in `<p>hello </p><div>world</div><p></p>` for example (the `<div>` autoclosed the `<p>` because `<p>` cannot contain block-level elements)
47+
- `<p>hello <div>world</div></p>` will result in `<p>hello </p><div>world</div><p></p>` (the `<div>` autoclosed the `<p>` because `<p>` cannot contain block-level elements)
4848
- `<option><div>option a</div></option>` will result in `<option>option a</option>` (the `<div>` is removed)
4949
- `<table><tr><td>cell</td></tr></table>` will result in `<table><tbody><tr><td>cell</td></tr></tbody></table>` (a `<tbody>` is auto-inserted)
5050

packages/svelte/src/compiler/errors.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,14 +1043,13 @@ export function mixed_event_handler_syntaxes(node, name) {
10431043
}
10441044

10451045
/**
1046-
* %thing% is invalid inside `<%parent%>`
1046+
* %message%. The browser will 'repair' the HTML (by moving, removing, or inserting elements) which breaks Svelte's assumptions about the structure of your components.
10471047
* @param {null | number | NodeLike} node
1048-
* @param {string} thing
1049-
* @param {string} parent
1048+
* @param {string} message
10501049
* @returns {never}
10511050
*/
1052-
export function node_invalid_placement(node, thing, parent) {
1053-
e(node, "node_invalid_placement", `${thing} is invalid inside \`<${parent}>\``);
1051+
export function node_invalid_placement(node, message) {
1052+
e(node, "node_invalid_placement", `${message}. The browser will 'repair' the HTML (by moving, removing, or inserting elements) which breaks Svelte's assumptions about the structure of your components.`);
10541053
}
10551054

10561055
/**

packages/svelte/src/compiler/phases/2-analyze/visitors/ExpressionTag.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ export function ExpressionTag(node, context) {
1212
const in_template = context.path.at(-1)?.type === 'Fragment';
1313

1414
if (in_template && context.state.parent_element) {
15-
if (!is_tag_valid_with_parent('#text', context.state.parent_element)) {
16-
e.node_invalid_placement(node, '`{expression}`', context.state.parent_element);
15+
const message = is_tag_valid_with_parent('#text', context.state.parent_element);
16+
if (message) {
17+
e.node_invalid_placement(node, message);
1718
}
1819
}
1920

packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,12 @@ export function RegularElement(node, context) {
114114

115115
if (!past_parent) {
116116
if (ancestor.type === 'RegularElement' && ancestor.name === context.state.parent_element) {
117-
if (!is_tag_valid_with_parent(node.name, context.state.parent_element)) {
117+
const message = is_tag_valid_with_parent(node.name, context.state.parent_element);
118+
if (message) {
118119
if (only_warn) {
119-
w.node_invalid_placement_ssr(
120-
node,
121-
`\`<${node.name}>\``,
122-
context.state.parent_element
123-
);
120+
w.node_invalid_placement_ssr(node, message);
124121
} else {
125-
e.node_invalid_placement(node, `\`<${node.name}>\``, context.state.parent_element);
122+
e.node_invalid_placement(node, message);
126123
}
127124
}
128125

@@ -131,11 +128,12 @@ export function RegularElement(node, context) {
131128
} else if (ancestor.type === 'RegularElement') {
132129
ancestors.push(ancestor.name);
133130

134-
if (!is_tag_valid_with_ancestor(node.name, ancestors)) {
131+
const message = is_tag_valid_with_ancestor(node.name, ancestors);
132+
if (message) {
135133
if (only_warn) {
136-
w.node_invalid_placement_ssr(node, `\`<${node.name}>\``, ancestor.name);
134+
w.node_invalid_placement_ssr(node, message);
137135
} else {
138-
e.node_invalid_placement(node, `\`<${node.name}>\``, ancestor.name);
136+
e.node_invalid_placement(node, message);
139137
}
140138
}
141139
} else if (

packages/svelte/src/compiler/phases/2-analyze/visitors/Text.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ export function Text(node, context) {
1212
const in_template = context.path.at(-1)?.type === 'Fragment';
1313

1414
if (in_template && context.state.parent_element && regex_not_whitespace.test(node.data)) {
15-
if (!is_tag_valid_with_parent('#text', context.state.parent_element)) {
16-
e.node_invalid_placement(node, 'Text node', context.state.parent_element);
15+
const message = is_tag_valid_with_parent('#text', context.state.parent_element);
16+
if (message) {
17+
e.node_invalid_placement(node, message);
1718
}
1819
}
1920
}

packages/svelte/src/compiler/warnings.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -754,13 +754,12 @@ export function event_directive_deprecated(node, name) {
754754
}
755755

756756
/**
757-
* %thing% is invalid inside `<%parent%>`. When rendering this component on the server, the resulting HTML will be modified by the browser, likely resulting in a `hydration_mismatch` warning
757+
* %message%. When rendering this component on the server, the resulting HTML will be modified by the browser (by moving, removing, or inserting elements), likely resulting in a `hydration_mismatch` warning
758758
* @param {null | NodeLike} node
759-
* @param {string} thing
760-
* @param {string} parent
759+
* @param {string} message
761760
*/
762-
export function node_invalid_placement_ssr(node, thing, parent) {
763-
w(node, "node_invalid_placement_ssr", `${thing} is invalid inside \`<${parent}>\`. When rendering this component on the server, the resulting HTML will be modified by the browser, likely resulting in a \`hydration_mismatch\` warning`);
761+
export function node_invalid_placement_ssr(node, message) {
762+
w(node, "node_invalid_placement_ssr", `${message}. When rendering this component on the server, the resulting HTML will be modified by the browser (by moving, removing, or inserting elements), likely resulting in a \`hydration_mismatch\` warning`);
764763
}
765764

766765
/**

0 commit comments

Comments
 (0)