Skip to content

Commit eefc5ab

Browse files
authored
fix(parse/html): improve the diagnostics when void elements have a closing tag (#10157)
1 parent aa055cd commit eefc5ab

6 files changed

Lines changed: 376 additions & 25 deletions

File tree

.changeset/clean-rice-sleep.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed [#7882](https://github.com/biomejs/biome/issues/7882): The HTML parser will now emit better diagnostics when it encounters a void element with a closing tag, such as `<br></br>`. Previously, the parser would emit multiple diagnostics with conflicting advice. Now it emits a single diagnostic that clearly states that void elements should not have closing tags.

crates/biome_html_parser/src/syntax/mod.rs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,11 +313,19 @@ fn parse_element(p: &mut HtmlParser) -> ParsedSyntax {
313313
ElementList.parse_list(p);
314314
if let Some(mut closing) =
315315
parse_closing_tag(p).or_add_diagnostic(p, expected_closing_tag)
316-
&& !closing.text(p).contains(opening_tag_name.as_str())
317316
{
318-
p.error(expected_matching_closing_tag(p, closing.range(p)).into_diagnostic(p));
319-
closing.change_to_bogus(p);
320-
continue;
317+
if is_void_closing_tag(p, &closing) {
318+
closing.change_to_bogus(p);
319+
continue;
320+
}
321+
322+
if !closing.text(p).contains(opening_tag_name.as_str()) {
323+
p.error(
324+
expected_matching_closing_tag(p, closing.range(p)).into_diagnostic(p),
325+
);
326+
closing.change_to_bogus(p);
327+
continue;
328+
}
321329
}
322330
break;
323331
}
@@ -335,13 +343,10 @@ fn parse_closing_tag(p: &mut HtmlParser) -> ParsedSyntax {
335343
let m = p.start();
336344
p.bump_with_context(T![<], inside_tag_context(p));
337345
p.bump_with_context(T![/], inside_tag_context(p));
338-
let should_be_self_closing = VOID_ELEMENTS
346+
let is_void_element = VOID_ELEMENTS
339347
.iter()
340348
.any(|tag| tag.eq_ignore_ascii_case(p.cur_text()))
341349
&& !is_possible_component(p, p.cur_text());
342-
if should_be_self_closing {
343-
p.error(void_element_should_not_have_closing_tag(p, p.cur_range()).into_diagnostic(p));
344-
}
345350
let _name = parse_any_tag_name(p);
346351

347352
// There shouldn't be any attributes in a closing tag.
@@ -350,7 +355,29 @@ fn parse_closing_tag(p: &mut HtmlParser) -> ParsedSyntax {
350355
p.bump_remap_with_context(HTML_BOGUS, HtmlLexContext::InsideTag);
351356
}
352357
p.expect(T![>]);
353-
Present(m.complete(p, HTML_CLOSING_ELEMENT))
358+
let closing = m.complete(p, HTML_CLOSING_ELEMENT);
359+
360+
if is_void_element {
361+
p.error(void_element_should_not_have_closing_tag(p, closing.range(p)).into_diagnostic(p));
362+
}
363+
364+
Present(closing)
365+
}
366+
367+
fn is_void_closing_tag(p: &HtmlParser, closing: &CompletedMarker) -> bool {
368+
let text = closing.text(p);
369+
let Some(name) = text
370+
.strip_prefix("</")
371+
.and_then(|text| text.strip_suffix('>'))
372+
.map(|text| text.trim())
373+
else {
374+
return false;
375+
};
376+
377+
VOID_ELEMENTS
378+
.iter()
379+
.any(|tag| tag.eq_ignore_ascii_case(name))
380+
&& !is_possible_component(p, name)
354381
}
355382

356383
#[inline]

crates/biome_html_parser/src/syntax/parse_error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ pub(crate) fn void_element_should_not_have_closing_tag(
9191
_p: &HtmlParser,
9292
range: TextRange,
9393
) -> ParseDiagnostic {
94-
ParseDiagnostic::new("Void elements should not have a closing tag.", range)
94+
ParseDiagnostic::new(
95+
"Void elements should not have a closing tag. Remove the closing tag.",
96+
range,
97+
)
9598
}
9699

97100
pub(crate) fn closing_tag_should_not_have_attributes(

crates/biome_html_parser/tests/html_specs/error/element/br-with-end.html.snap

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -125,23 +125,9 @@ HtmlRoot {
125125
## Diagnostics
126126

127127
```
128-
br-with-end.html:1:39 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
129-
130-
× Void elements should not have a closing tag.
131-
132-
> 1 │ <span>foo<br>This text is inside br.</br>bar</span>
133-
│ ^^
134-
2 │
135-
136128
br-with-end.html:1:37 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
137129
138-
× Expected a matching closing tag but instead found '</br>'.
139-
140-
> 1 │ <span>foo<br>This text is inside br.</br>bar</span>
141-
│ ^^^^^
142-
2 │
143-
144-
i Expected a matching closing tag here.
130+
× Void elements should not have a closing tag. Remove the closing tag.
145131
146132
> 1 │ <span>foo<br>This text is inside br.</br>bar</span>
147133
│ ^^^^^
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<fieldset>
2+
<summary>Alg <button id="set-search-alg">Apply alg to default pattern and set as search goal</button></summary>
3+
<br>
4+
<input id="search-alg" placeholder="Alg"></input>
5+
<br></br >
6+
</fieldset>

0 commit comments

Comments
 (0)