diff --git a/crates/biome_html_factory/src/generated/node_factory.rs b/crates/biome_html_factory/src/generated/node_factory.rs
index 01e871947e37..578134082a04 100644
--- a/crates/biome_html_factory/src/generated/node_factory.rs
+++ b/crates/biome_html_factory/src/generated/node_factory.rs
@@ -159,16 +159,34 @@ impl HtmlDirectiveBuilder {
pub fn html_element(
opening_element: HtmlOpeningElement,
children: HtmlElementList,
- closing_element: HtmlClosingElement,
-) -> HtmlElement {
- HtmlElement::unwrap_cast(SyntaxNode::new_detached(
- HtmlSyntaxKind::HTML_ELEMENT,
- [
- Some(SyntaxElement::Node(opening_element.into_syntax())),
- Some(SyntaxElement::Node(children.into_syntax())),
- Some(SyntaxElement::Node(closing_element.into_syntax())),
- ],
- ))
+) -> HtmlElementBuilder {
+ HtmlElementBuilder {
+ opening_element,
+ children,
+ closing_element: None,
+ }
+}
+pub struct HtmlElementBuilder {
+ opening_element: HtmlOpeningElement,
+ children: HtmlElementList,
+ closing_element: Option,
+}
+impl HtmlElementBuilder {
+ pub fn with_closing_element(mut self, closing_element: HtmlClosingElement) -> Self {
+ self.closing_element = Some(closing_element);
+ self
+ }
+ pub fn build(self) -> HtmlElement {
+ HtmlElement::unwrap_cast(SyntaxNode::new_detached(
+ HtmlSyntaxKind::HTML_ELEMENT,
+ [
+ Some(SyntaxElement::Node(self.opening_element.into_syntax())),
+ Some(SyntaxElement::Node(self.children.into_syntax())),
+ self.closing_element
+ .map(|token| SyntaxElement::Node(token.into_syntax())),
+ ],
+ ))
+ }
}
pub fn html_name(value_token: SyntaxToken) -> HtmlName {
HtmlName::unwrap_cast(SyntaxNode::new_detached(
diff --git a/crates/biome_html_formatter/src/html/auxiliary/element.rs b/crates/biome_html_formatter/src/html/auxiliary/element.rs
index ab15141c423d..5b47099a7d73 100644
--- a/crates/biome_html_formatter/src/html/auxiliary/element.rs
+++ b/crates/biome_html_formatter/src/html/auxiliary/element.rs
@@ -28,7 +28,6 @@ impl FormatNodeRule for FormatHtmlElement {
closing_element,
} = node.as_fields();
- let closing_element = closing_element?;
let opening_element = opening_element?;
let tag_name = opening_element.name()?;
let tag_name = tag_name
@@ -62,9 +61,13 @@ impl FormatNodeRule for FormatHtmlElement {
.last_token()
.is_some_and(|tok| tok.has_trailing_whitespace())
|| closing_element
- .l_angle_token()
- .ok()
- .is_some_and(|tok| tok.has_leading_whitespace_or_newline());
+ .as_ref()
+ .map(|e| {
+ e.l_angle_token()
+ .ok()
+ .is_some_and(|tok| tok.has_leading_whitespace_or_newline())
+ })
+ .unwrap_or_default();
// "Borrowing" in this context refers to tokens in nodes that would normally be
// formatted by that node's formatter, but are instead formatted by a sibling
@@ -98,7 +101,7 @@ impl FormatNodeRule for FormatHtmlElement {
None
};
let borrowed_closing_tag = if should_borrow_closing_tag {
- Some(closing_element.clone())
+ closing_element.clone()
} else {
None
};
@@ -147,13 +150,17 @@ impl FormatNodeRule for FormatHtmlElement {
}
}
}
- FormatNodeRule::fmt(
- &FormatHtmlClosingElement::default().with_options(FormatHtmlClosingElementOptions {
- tag_borrowed: should_borrow_closing_tag,
- }),
- &closing_element,
- f,
- )?;
+ if let Some(closing_element) = closing_element {
+ FormatNodeRule::fmt(
+ &FormatHtmlClosingElement::default().with_options(
+ FormatHtmlClosingElementOptions {
+ tag_borrowed: should_borrow_closing_tag,
+ },
+ ),
+ &closing_element,
+ f,
+ )?;
+ }
Ok(())
}
diff --git a/crates/biome_html_parser/tests/html_specs/error/element/missing-close-tag-2.html.snap b/crates/biome_html_parser/tests/html_specs/error/element/missing-close-tag-2.html.snap
index 1eecf30e88bd..4f40b897f617 100644
--- a/crates/biome_html_parser/tests/html_specs/error/element/missing-close-tag-2.html.snap
+++ b/crates/biome_html_parser/tests/html_specs/error/element/missing-close-tag-2.html.snap
@@ -27,7 +27,7 @@ HtmlRoot {
r_angle_token: R_ANGLE@4..5 ">" [] [],
},
children: HtmlElementList [],
- closing_element: missing (required),
+ closing_element: missing (optional),
},
],
eof_token: EOF@5..6 "" [Newline("\n")] [],
diff --git a/crates/biome_html_parser/tests/html_specs/error/element/missing-close-tag.html.snap b/crates/biome_html_parser/tests/html_specs/error/element/missing-close-tag.html.snap
index 59f2b4812b3e..c35621a3b8ed 100644
--- a/crates/biome_html_parser/tests/html_specs/error/element/missing-close-tag.html.snap
+++ b/crates/biome_html_parser/tests/html_specs/error/element/missing-close-tag.html.snap
@@ -31,7 +31,7 @@ HtmlRoot {
value_token: HTML_LITERAL@5..8 "foo" [] [],
},
],
- closing_element: missing (required),
+ closing_element: missing (optional),
},
],
eof_token: EOF@8..9 "" [Newline("\n")] [],
diff --git a/crates/biome_html_parser/tests/html_specs/error/element/solo-no-tag-name.html.snap b/crates/biome_html_parser/tests/html_specs/error/element/solo-no-tag-name.html.snap
index bec8b50fb679..9677a4381404 100644
--- a/crates/biome_html_parser/tests/html_specs/error/element/solo-no-tag-name.html.snap
+++ b/crates/biome_html_parser/tests/html_specs/error/element/solo-no-tag-name.html.snap
@@ -25,7 +25,7 @@ HtmlRoot {
r_angle_token: missing (required),
},
children: HtmlElementList [],
- closing_element: missing (required),
+ closing_element: missing (optional),
},
],
eof_token: EOF@1..2 "" [Newline("\n")] [],
diff --git a/crates/biome_html_syntax/src/generated/nodes.rs b/crates/biome_html_syntax/src/generated/nodes.rs
index b394db8b0259..e7250b11b3e9 100644
--- a/crates/biome_html_syntax/src/generated/nodes.rs
+++ b/crates/biome_html_syntax/src/generated/nodes.rs
@@ -371,8 +371,8 @@ impl HtmlElement {
pub fn children(&self) -> HtmlElementList {
support::list(&self.syntax, 1usize)
}
- pub fn closing_element(&self) -> SyntaxResult {
- support::required_node(&self.syntax, 2usize)
+ pub fn closing_element(&self) -> Option {
+ support::node(&self.syntax, 2usize)
}
}
impl Serialize for HtmlElement {
@@ -387,7 +387,7 @@ impl Serialize for HtmlElement {
pub struct HtmlElementFields {
pub opening_element: SyntaxResult,
pub children: HtmlElementList,
- pub closing_element: SyntaxResult,
+ pub closing_element: Option,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct HtmlName {
@@ -1114,7 +1114,7 @@ impl std::fmt::Debug for HtmlElement {
.field("children", &self.children())
.field(
"closing_element",
- &support::DebugSyntaxResult(self.closing_element()),
+ &support::DebugOptionalElement(self.closing_element()),
)
.finish()
} else {
diff --git a/crates/biome_html_syntax/src/generated/nodes_mut.rs b/crates/biome_html_syntax/src/generated/nodes_mut.rs
index 5b6db033d550..066e1a5362fc 100644
--- a/crates/biome_html_syntax/src/generated/nodes_mut.rs
+++ b/crates/biome_html_syntax/src/generated/nodes_mut.rs
@@ -168,11 +168,11 @@ impl HtmlElement {
.splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))),
)
}
- pub fn with_closing_element(self, element: HtmlClosingElement) -> Self {
- Self::unwrap_cast(
- self.syntax
- .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))),
- )
+ pub fn with_closing_element(self, element: Option) -> Self {
+ Self::unwrap_cast(self.syntax.splice_slots(
+ 2usize..=2usize,
+ once(element.map(|element| element.into_syntax().into())),
+ ))
}
}
impl HtmlName {
diff --git a/xtask/codegen/html.ungram b/xtask/codegen/html.ungram
index 4dc985b1e173..035abe4e7cfc 100644
--- a/xtask/codegen/html.ungram
+++ b/xtask/codegen/html.ungram
@@ -84,7 +84,7 @@ HtmlSelfClosingElement =
HtmlElement =
opening_element: HtmlOpeningElement
children: HtmlElementList
- closing_element: HtmlClosingElement
+ closing_element: HtmlClosingElement?
//