diff --git a/crates/biome_configuration/src/html.rs b/crates/biome_configuration/src/html.rs index 485d4a244948..ea9b23a43942 100644 --- a/crates/biome_configuration/src/html.rs +++ b/crates/biome_configuration/src/html.rs @@ -48,6 +48,7 @@ pub type HtmlFormatterEnabled = Bool; // Keep it disabled by default whil pub type HtmlLinterEnabled = Bool; pub type HtmlAssistEnabled = Bool; pub type HtmlParseInterpolation = Bool; +pub type HtmlAngularEnabled = Bool; /// Options that changes how the HTML parser behaves #[derive( @@ -58,6 +59,10 @@ pub type HtmlParseInterpolation = Bool; pub struct HtmlParserConfiguration { /// Enables the parsing of double text expressions such as `{{ expression }}` inside `.html` files pub interpolation: Option, + + /// Enables parsing of Angular syntax + #[bpaf(long("html-parse-angular"), argument("true|false"))] + pub angular: Option, } /// Options that changes how the HTML formatter behaves diff --git a/crates/biome_html_factory/src/generated/node_factory.rs b/crates/biome_html_factory/src/generated/node_factory.rs index 3b06cd697918..c52c1cfe4608 100644 --- a/crates/biome_html_factory/src/generated/node_factory.rs +++ b/crates/biome_html_factory/src/generated/node_factory.rs @@ -6,6 +6,164 @@ use biome_html_syntax::{ HtmlSyntaxToken as SyntaxToken, *, }; use biome_rowan::AstNode; +pub fn angular_binding_name(value_token: SyntaxToken) -> AngularBindingName { + AngularBindingName::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::ANGULAR_BINDING_NAME, + [Some(SyntaxElement::Token(value_token))], + )) +} +pub fn angular_event_binding( + l_paren_token: SyntaxToken, + name: AngularBindingName, + r_paren_token: SyntaxToken, +) -> AngularEventBindingBuilder { + AngularEventBindingBuilder { + l_paren_token, + name, + r_paren_token, + initializer: None, + } +} +pub struct AngularEventBindingBuilder { + l_paren_token: SyntaxToken, + name: AngularBindingName, + r_paren_token: SyntaxToken, + initializer: Option, +} +impl AngularEventBindingBuilder { + pub fn with_initializer(mut self, initializer: HtmlAttributeInitializerClause) -> Self { + self.initializer = Some(initializer); + self + } + pub fn build(self) -> AngularEventBinding { + AngularEventBinding::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::ANGULAR_EVENT_BINDING, + [ + Some(SyntaxElement::Token(self.l_paren_token)), + Some(SyntaxElement::Node(self.name.into_syntax())), + Some(SyntaxElement::Token(self.r_paren_token)), + self.initializer + .map(|token| SyntaxElement::Node(token.into_syntax())), + ], + )) + } +} +pub fn angular_property_binding( + l_brack_token: SyntaxToken, + name: AngularBindingName, + r_brack_token: SyntaxToken, +) -> AngularPropertyBindingBuilder { + AngularPropertyBindingBuilder { + l_brack_token, + name, + r_brack_token, + initializer: None, + } +} +pub struct AngularPropertyBindingBuilder { + l_brack_token: SyntaxToken, + name: AngularBindingName, + r_brack_token: SyntaxToken, + initializer: Option, +} +impl AngularPropertyBindingBuilder { + pub fn with_initializer(mut self, initializer: HtmlAttributeInitializerClause) -> Self { + self.initializer = Some(initializer); + self + } + pub fn build(self) -> AngularPropertyBinding { + AngularPropertyBinding::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::ANGULAR_PROPERTY_BINDING, + [ + Some(SyntaxElement::Token(self.l_brack_token)), + Some(SyntaxElement::Node(self.name.into_syntax())), + Some(SyntaxElement::Token(self.r_brack_token)), + self.initializer + .map(|token| SyntaxElement::Node(token.into_syntax())), + ], + )) + } +} +pub fn angular_structural_directive( + star_token: SyntaxToken, + name: AngularBindingName, +) -> AngularStructuralDirectiveBuilder { + AngularStructuralDirectiveBuilder { + star_token, + name, + initializer: None, + } +} +pub struct AngularStructuralDirectiveBuilder { + star_token: SyntaxToken, + name: AngularBindingName, + initializer: Option, +} +impl AngularStructuralDirectiveBuilder { + pub fn with_initializer(mut self, initializer: HtmlAttributeInitializerClause) -> Self { + self.initializer = Some(initializer); + self + } + pub fn build(self) -> AngularStructuralDirective { + AngularStructuralDirective::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::ANGULAR_STRUCTURAL_DIRECTIVE, + [ + Some(SyntaxElement::Token(self.star_token)), + Some(SyntaxElement::Node(self.name.into_syntax())), + self.initializer + .map(|token| SyntaxElement::Node(token.into_syntax())), + ], + )) + } +} +pub fn angular_template_ref_variable( + hash_token: SyntaxToken, + name: AngularBindingName, +) -> AngularTemplateRefVariable { + AngularTemplateRefVariable::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::ANGULAR_TEMPLATE_REF_VARIABLE, + [ + Some(SyntaxElement::Token(hash_token)), + Some(SyntaxElement::Node(name.into_syntax())), + ], + )) +} +pub fn angular_two_way_binding( + l_bracket_paren_token: SyntaxToken, + name: AngularBindingName, + r_bracket_paren_token: SyntaxToken, +) -> AngularTwoWayBindingBuilder { + AngularTwoWayBindingBuilder { + l_bracket_paren_token, + name, + r_bracket_paren_token, + initializer: None, + } +} +pub struct AngularTwoWayBindingBuilder { + l_bracket_paren_token: SyntaxToken, + name: AngularBindingName, + r_bracket_paren_token: SyntaxToken, + initializer: Option, +} +impl AngularTwoWayBindingBuilder { + pub fn with_initializer(mut self, initializer: HtmlAttributeInitializerClause) -> Self { + self.initializer = Some(initializer); + self + } + pub fn build(self) -> AngularTwoWayBinding { + AngularTwoWayBinding::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::ANGULAR_TWO_WAY_BINDING, + [ + Some(SyntaxElement::Token(self.l_bracket_paren_token)), + Some(SyntaxElement::Node(self.name.into_syntax())), + Some(SyntaxElement::Token(self.r_bracket_paren_token)), + self.initializer + .map(|token| SyntaxElement::Node(token.into_syntax())), + ], + )) + } +} pub fn astro_class_directive( class_token: SyntaxToken, value: AstroDirectiveValue, diff --git a/crates/biome_html_factory/src/generated/syntax_factory.rs b/crates/biome_html_factory/src/generated/syntax_factory.rs index 501677bd9213..634dcd7098cd 100644 --- a/crates/biome_html_factory/src/generated/syntax_factory.rs +++ b/crates/biome_html_factory/src/generated/syntax_factory.rs @@ -24,6 +24,204 @@ impl SyntaxFactory for HtmlSyntaxFactory { | VUE_BOGUS_DIRECTIVE_ARGUMENT => { RawSyntaxNode::new(kind, children.into_iter().map(Some)) } + ANGULAR_BINDING_NAME => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<1usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == HTML_LITERAL + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + ANGULAR_BINDING_NAME.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(ANGULAR_BINDING_NAME, children) + } + ANGULAR_EVENT_BINDING => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T!['('] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && AngularBindingName::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T![')'] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && HtmlAttributeInitializerClause::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + ANGULAR_EVENT_BINDING.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(ANGULAR_EVENT_BINDING, children) + } + ANGULAR_PROPERTY_BINDING => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T!['['] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && AngularBindingName::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T![']'] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && HtmlAttributeInitializerClause::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + ANGULAR_PROPERTY_BINDING.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(ANGULAR_PROPERTY_BINDING, children) + } + ANGULAR_STRUCTURAL_DIRECTIVE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T ! [*] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && AngularBindingName::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && HtmlAttributeInitializerClause::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + ANGULAR_STRUCTURAL_DIRECTIVE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(ANGULAR_STRUCTURAL_DIRECTIVE, children) + } + ANGULAR_TEMPLATE_REF_VARIABLE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T ! [#] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && AngularBindingName::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + ANGULAR_TEMPLATE_REF_VARIABLE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(ANGULAR_TEMPLATE_REF_VARIABLE, children) + } + ANGULAR_TWO_WAY_BINDING => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T!["[("] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && AngularBindingName::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T![")]"] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && HtmlAttributeInitializerClause::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + ANGULAR_TWO_WAY_BINDING.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(ANGULAR_TWO_WAY_BINDING, children) + } ASTRO_CLASS_DIRECTIVE => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); diff --git a/crates/biome_html_formatter/src/angular/any/attribute.rs b/crates/biome_html_formatter/src/angular/any/attribute.rs new file mode 100644 index 000000000000..fe0b0f35cf6e --- /dev/null +++ b/crates/biome_html_formatter/src/angular/any/attribute.rs @@ -0,0 +1,18 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_html_syntax::AnyAngularAttribute; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyAngularAttribute; +impl FormatRule for FormatAnyAngularAttribute { + type Context = HtmlFormatContext; + fn fmt(&self, node: &AnyAngularAttribute, f: &mut HtmlFormatter) -> FormatResult<()> { + match node { + AnyAngularAttribute::AngularEventBinding(node) => node.format().fmt(f), + AnyAngularAttribute::AngularPropertyBinding(node) => node.format().fmt(f), + AnyAngularAttribute::AngularStructuralDirective(node) => node.format().fmt(f), + AnyAngularAttribute::AngularTemplateRefVariable(node) => node.format().fmt(f), + AnyAngularAttribute::AngularTwoWayBinding(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_html_formatter/src/angular/any/mod.rs b/crates/biome_html_formatter/src/angular/any/mod.rs new file mode 100644 index 000000000000..dc8396fec6b3 --- /dev/null +++ b/crates/biome_html_formatter/src/angular/any/mod.rs @@ -0,0 +1,3 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +pub(crate) mod attribute; diff --git a/crates/biome_html_formatter/src/angular/auxiliary/binding_name.rs b/crates/biome_html_formatter/src/angular/auxiliary/binding_name.rs new file mode 100644 index 000000000000..a0c33df2eb8e --- /dev/null +++ b/crates/biome_html_formatter/src/angular/auxiliary/binding_name.rs @@ -0,0 +1,11 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{AngularBindingName, AngularBindingNameFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAngularBindingName; +impl FormatNodeRule for FormatAngularBindingName { + fn fmt_fields(&self, node: &AngularBindingName, f: &mut HtmlFormatter) -> FormatResult<()> { + let AngularBindingNameFields { value_token } = node.as_fields(); + write!(f, [value_token.format()]) + } +} diff --git a/crates/biome_html_formatter/src/angular/auxiliary/event_binding.rs b/crates/biome_html_formatter/src/angular/auxiliary/event_binding.rs new file mode 100644 index 000000000000..1a1528917cec --- /dev/null +++ b/crates/biome_html_formatter/src/angular/auxiliary/event_binding.rs @@ -0,0 +1,24 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{AngularEventBinding, AngularEventBindingFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAngularEventBinding; +impl FormatNodeRule for FormatAngularEventBinding { + fn fmt_fields(&self, node: &AngularEventBinding, f: &mut HtmlFormatter) -> FormatResult<()> { + let AngularEventBindingFields { + l_paren_token, + name, + initializer, + r_paren_token, + } = node.as_fields(); + write!( + f, + [ + l_paren_token.format(), + name.format(), + initializer.format(), + r_paren_token.format() + ] + ) + } +} diff --git a/crates/biome_html_formatter/src/angular/auxiliary/mod.rs b/crates/biome_html_formatter/src/angular/auxiliary/mod.rs new file mode 100644 index 000000000000..10f3d2602699 --- /dev/null +++ b/crates/biome_html_formatter/src/angular/auxiliary/mod.rs @@ -0,0 +1,8 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +pub(crate) mod binding_name; +pub(crate) mod event_binding; +pub(crate) mod property_binding; +pub(crate) mod structural_directive; +pub(crate) mod template_ref_variable; +pub(crate) mod two_way_binding; diff --git a/crates/biome_html_formatter/src/angular/auxiliary/property_binding.rs b/crates/biome_html_formatter/src/angular/auxiliary/property_binding.rs new file mode 100644 index 000000000000..775430e3d7c8 --- /dev/null +++ b/crates/biome_html_formatter/src/angular/auxiliary/property_binding.rs @@ -0,0 +1,24 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{AngularPropertyBinding, AngularPropertyBindingFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAngularPropertyBinding; +impl FormatNodeRule for FormatAngularPropertyBinding { + fn fmt_fields(&self, node: &AngularPropertyBinding, f: &mut HtmlFormatter) -> FormatResult<()> { + let AngularPropertyBindingFields { + l_brack_token, + name, + initializer, + r_brack_token, + } = node.as_fields(); + write!( + f, + [ + l_brack_token.format(), + name.format(), + initializer.format(), + r_brack_token.format() + ] + ) + } +} diff --git a/crates/biome_html_formatter/src/angular/auxiliary/structural_directive.rs b/crates/biome_html_formatter/src/angular/auxiliary/structural_directive.rs new file mode 100644 index 000000000000..58b55d67359e --- /dev/null +++ b/crates/biome_html_formatter/src/angular/auxiliary/structural_directive.rs @@ -0,0 +1,22 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{AngularStructuralDirective, AngularStructuralDirectiveFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAngularStructuralDirective; +impl FormatNodeRule for FormatAngularStructuralDirective { + fn fmt_fields( + &self, + node: &AngularStructuralDirective, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + let AngularStructuralDirectiveFields { + star_token, + name, + initializer, + } = node.as_fields(); + write!( + f, + [star_token.format(), name.format(), initializer.format()] + ) + } +} diff --git a/crates/biome_html_formatter/src/angular/auxiliary/template_ref_variable.rs b/crates/biome_html_formatter/src/angular/auxiliary/template_ref_variable.rs new file mode 100644 index 000000000000..0333ddeacc05 --- /dev/null +++ b/crates/biome_html_formatter/src/angular/auxiliary/template_ref_variable.rs @@ -0,0 +1,15 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{AngularTemplateRefVariable, AngularTemplateRefVariableFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAngularTemplateRefVariable; +impl FormatNodeRule for FormatAngularTemplateRefVariable { + fn fmt_fields( + &self, + node: &AngularTemplateRefVariable, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + let AngularTemplateRefVariableFields { hash_token, name } = node.as_fields(); + write!(f, [hash_token.format(), name.format()]) + } +} diff --git a/crates/biome_html_formatter/src/angular/auxiliary/two_way_binding.rs b/crates/biome_html_formatter/src/angular/auxiliary/two_way_binding.rs new file mode 100644 index 000000000000..bc2a57807587 --- /dev/null +++ b/crates/biome_html_formatter/src/angular/auxiliary/two_way_binding.rs @@ -0,0 +1,24 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{AngularTwoWayBinding, AngularTwoWayBindingFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAngularTwoWayBinding; +impl FormatNodeRule for FormatAngularTwoWayBinding { + fn fmt_fields(&self, node: &AngularTwoWayBinding, f: &mut HtmlFormatter) -> FormatResult<()> { + let AngularTwoWayBindingFields { + l_bracket_paren_token, + name, + initializer, + r_bracket_paren_token, + } = node.as_fields(); + write!( + f, + [ + l_bracket_paren_token.format(), + name.format(), + initializer.format(), + r_bracket_paren_token.format() + ] + ) + } +} diff --git a/crates/biome_html_formatter/src/angular/mod.rs b/crates/biome_html_formatter/src/angular/mod.rs new file mode 100644 index 000000000000..6d1b2615796e --- /dev/null +++ b/crates/biome_html_formatter/src/angular/mod.rs @@ -0,0 +1,4 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +pub(crate) mod any; +pub(crate) mod auxiliary; diff --git a/crates/biome_html_formatter/src/generated.rs b/crates/biome_html_formatter/src/generated.rs index c435f5cc3e6f..088522178e4b 100644 --- a/crates/biome_html_formatter/src/generated.rs +++ b/crates/biome_html_formatter/src/generated.rs @@ -6,6 +6,222 @@ use crate::{ AsFormat, FormatBogusNodeRule, FormatNodeRule, HtmlFormatContext, HtmlFormatter, IntoFormat, }; use biome_formatter::{FormatOwnedWithRule, FormatRefWithRule, FormatResult, FormatRule}; +impl FormatRule + for crate::angular::auxiliary::binding_name::FormatAngularBindingName +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::AngularBindingName, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::AngularBindingName { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::AngularBindingName, + crate::angular::auxiliary::binding_name::FormatAngularBindingName, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::angular::auxiliary::binding_name::FormatAngularBindingName::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::AngularBindingName { + type Format = FormatOwnedWithRule< + biome_html_syntax::AngularBindingName, + crate::angular::auxiliary::binding_name::FormatAngularBindingName, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::angular::auxiliary::binding_name::FormatAngularBindingName::default(), + ) + } +} +impl FormatRule + for crate::angular::auxiliary::event_binding::FormatAngularEventBinding +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::AngularEventBinding, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::AngularEventBinding { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::AngularEventBinding, + crate::angular::auxiliary::event_binding::FormatAngularEventBinding, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::angular::auxiliary::event_binding::FormatAngularEventBinding::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::AngularEventBinding { + type Format = FormatOwnedWithRule< + biome_html_syntax::AngularEventBinding, + crate::angular::auxiliary::event_binding::FormatAngularEventBinding, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::angular::auxiliary::event_binding::FormatAngularEventBinding::default(), + ) + } +} +impl FormatRule + for crate::angular::auxiliary::property_binding::FormatAngularPropertyBinding +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::AngularPropertyBinding, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::AngularPropertyBinding { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::AngularPropertyBinding, + crate::angular::auxiliary::property_binding::FormatAngularPropertyBinding, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::angular::auxiliary::property_binding::FormatAngularPropertyBinding::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::AngularPropertyBinding { + type Format = FormatOwnedWithRule< + biome_html_syntax::AngularPropertyBinding, + crate::angular::auxiliary::property_binding::FormatAngularPropertyBinding, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::angular::auxiliary::property_binding::FormatAngularPropertyBinding::default(), + ) + } +} +impl FormatRule + for crate::angular::auxiliary::structural_directive::FormatAngularStructuralDirective +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::AngularStructuralDirective, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::AngularStructuralDirective { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::AngularStructuralDirective, + crate::angular::auxiliary::structural_directive::FormatAngularStructuralDirective, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule :: new (self , crate :: angular :: auxiliary :: structural_directive :: FormatAngularStructuralDirective :: default ()) + } +} +impl IntoFormat for biome_html_syntax::AngularStructuralDirective { + type Format = FormatOwnedWithRule< + biome_html_syntax::AngularStructuralDirective, + crate::angular::auxiliary::structural_directive::FormatAngularStructuralDirective, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule :: new (self , crate :: angular :: auxiliary :: structural_directive :: FormatAngularStructuralDirective :: default ()) + } +} +impl FormatRule + for crate::angular::auxiliary::template_ref_variable::FormatAngularTemplateRefVariable +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::AngularTemplateRefVariable, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::AngularTemplateRefVariable { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::AngularTemplateRefVariable, + crate::angular::auxiliary::template_ref_variable::FormatAngularTemplateRefVariable, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule :: new (self , crate :: angular :: auxiliary :: template_ref_variable :: FormatAngularTemplateRefVariable :: default ()) + } +} +impl IntoFormat for biome_html_syntax::AngularTemplateRefVariable { + type Format = FormatOwnedWithRule< + biome_html_syntax::AngularTemplateRefVariable, + crate::angular::auxiliary::template_ref_variable::FormatAngularTemplateRefVariable, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule :: new (self , crate :: angular :: auxiliary :: template_ref_variable :: FormatAngularTemplateRefVariable :: default ()) + } +} +impl FormatRule + for crate::angular::auxiliary::two_way_binding::FormatAngularTwoWayBinding +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::AngularTwoWayBinding, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::AngularTwoWayBinding { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::AngularTwoWayBinding, + crate::angular::auxiliary::two_way_binding::FormatAngularTwoWayBinding, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::angular::auxiliary::two_way_binding::FormatAngularTwoWayBinding::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::AngularTwoWayBinding { + type Format = FormatOwnedWithRule< + biome_html_syntax::AngularTwoWayBinding, + crate::angular::auxiliary::two_way_binding::FormatAngularTwoWayBinding, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::angular::auxiliary::two_way_binding::FormatAngularTwoWayBinding::default(), + ) + } +} impl FormatRule for crate::astro::auxiliary::class_directive::FormatAstroClassDirective { @@ -3557,6 +3773,31 @@ impl IntoFormat for biome_html_syntax::VueBogusDirectiveArgum ) } } +impl AsFormat for biome_html_syntax::AnyAngularAttribute { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::AnyAngularAttribute, + crate::angular::any::attribute::FormatAnyAngularAttribute, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::angular::any::attribute::FormatAnyAngularAttribute::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::AnyAngularAttribute { + type Format = FormatOwnedWithRule< + biome_html_syntax::AnyAngularAttribute, + crate::angular::any::attribute::FormatAnyAngularAttribute, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::angular::any::attribute::FormatAnyAngularAttribute::default(), + ) + } +} impl AsFormat for biome_html_syntax::AnyAstroDirective { type Format<'a> = FormatRefWithRule< 'a, diff --git a/crates/biome_html_formatter/src/html/any/attribute.rs b/crates/biome_html_formatter/src/html/any/attribute.rs index efba245a7099..d64ca56528c9 100644 --- a/crates/biome_html_formatter/src/html/any/attribute.rs +++ b/crates/biome_html_formatter/src/html/any/attribute.rs @@ -8,6 +8,7 @@ impl FormatRule for FormatAnyHtmlAttribute { type Context = HtmlFormatContext; fn fmt(&self, node: &AnyHtmlAttribute, f: &mut HtmlFormatter) -> FormatResult<()> { match node { + AnyHtmlAttribute::AnyAngularAttribute(node) => node.format().fmt(f), AnyHtmlAttribute::AnyAstroDirective(node) => node.format().fmt(f), AnyHtmlAttribute::AnySvelteDirective(node) => node.format().fmt(f), AnyHtmlAttribute::AnyVueDirective(node) => node.format().fmt(f), diff --git a/crates/biome_html_formatter/src/html/lists/attribute_list.rs b/crates/biome_html_formatter/src/html/lists/attribute_list.rs index f70d6d189f64..b9e84f09b00e 100644 --- a/crates/biome_html_formatter/src/html/lists/attribute_list.rs +++ b/crates/biome_html_formatter/src/html/lists/attribute_list.rs @@ -83,6 +83,9 @@ impl FormatRule for FormatHtmlAttributeList { AnyHtmlAttribute::AnyAstroDirective(attr) => { attr.format().fmt(f) } + AnyHtmlAttribute::AnyAngularAttribute(attr) => { + attr.format().fmt(f) + } }) })) .finish()?; diff --git a/crates/biome_html_formatter/src/lib.rs b/crates/biome_html_formatter/src/lib.rs index 515cc971a19a..ce696cc338bc 100644 --- a/crates/biome_html_formatter/src/lib.rs +++ b/crates/biome_html_formatter/src/lib.rs @@ -13,6 +13,7 @@ use context::HtmlFormatContext; pub use context::HtmlFormatOptions; use cst::FormatHtmlSyntaxNode; +mod angular; mod astro; mod comments; pub mod context; diff --git a/crates/biome_html_parser/src/parser.rs b/crates/biome_html_parser/src/parser.rs index 4c14725d4646..fe62a6bc13ff 100644 --- a/crates/biome_html_parser/src/parser.rs +++ b/crates/biome_html_parser/src/parser.rs @@ -118,6 +118,7 @@ pub struct HtmlParserOptions { pub(crate) vue: bool, pub(crate) svelte: bool, pub(crate) is_html: bool, + pub(crate) angular: bool, } impl HtmlParserOptions { @@ -158,6 +159,11 @@ impl HtmlParserOptions { self } + pub fn with_angular(mut self) -> Self { + self.angular = true; + self + } + pub fn is_html(&self) -> bool { self.is_html } @@ -186,6 +192,9 @@ impl From<&HtmlFileSource> for HtmlParserOptions { HtmlVariant::Svelte => { options = options.with_single_text_expression().with_svelte(); } + HtmlVariant::Angular => { + options = options.with_single_text_expression().with_angular(); + } } options diff --git a/crates/biome_html_parser/src/token_source.rs b/crates/biome_html_parser/src/token_source.rs index b8768347a905..a42c80688720 100644 --- a/crates/biome_html_parser/src/token_source.rs +++ b/crates/biome_html_parser/src/token_source.rs @@ -148,7 +148,7 @@ impl LexContext for HtmlLexContext { #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub(crate) enum HtmlReLexContext { - /// Specialised relex that manages certain characters such as commas, etc. + /// Specialized relex that manages certain characters such as commas, etc. Svelte, /// Relex tokens using `HtmlLexer::consume_html_text` HtmlText, diff --git a/crates/biome_html_parser/tests/html_specs/ok/angular/event-binding.component.html b/crates/biome_html_parser/tests/html_specs/ok/angular/event-binding.component.html new file mode 100644 index 000000000000..fd9813bfe4c5 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/angular/event-binding.component.html @@ -0,0 +1 @@ + diff --git a/crates/biome_html_parser/tests/html_specs/ok/angular/property-binding.component.html b/crates/biome_html_parser/tests/html_specs/ok/angular/property-binding.component.html new file mode 100644 index 000000000000..48784561480d --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/angular/property-binding.component.html @@ -0,0 +1 @@ +Save diff --git a/crates/biome_html_parser/tests/html_specs/ok/angular/structual-directive.component.html b/crates/biome_html_parser/tests/html_specs/ok/angular/structual-directive.component.html new file mode 100644 index 000000000000..4d3d70332c5d --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/angular/structual-directive.component.html @@ -0,0 +1,3 @@ + + default + diff --git a/crates/biome_html_parser/tests/html_specs/ok/angular/template-ref.component.html b/crates/biome_html_parser/tests/html_specs/ok/angular/template-ref.component.html new file mode 100644 index 000000000000..31b82fba6b9c --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/angular/template-ref.component.html @@ -0,0 +1,5 @@ +This is a normal element + + + This is a template fragment + diff --git a/crates/biome_html_parser/tests/html_specs/ok/angular/two-way-binding.component.html b/crates/biome_html_parser/tests/html_specs/ok/angular/two-way-binding.component.html new file mode 100644 index 000000000000..164880de1f54 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/angular/two-way-binding.component.html @@ -0,0 +1 @@ + diff --git a/crates/biome_html_syntax/src/file_source.rs b/crates/biome_html_syntax/src/file_source.rs index 530dd49425ce..427aedfc0d57 100644 --- a/crates/biome_html_syntax/src/file_source.rs +++ b/crates/biome_html_syntax/src/file_source.rs @@ -31,6 +31,8 @@ pub enum HtmlVariant { Vue, /// Use this variant to parse a Svelte file Svelte, + /// Use this variant to parse a Angular file + Angular, } impl Default for HtmlVariant { @@ -64,6 +66,10 @@ impl HtmlFileSource { matches!(self.variant, HtmlVariant::Astro) } + pub const fn is_angular(&self) -> bool { + matches!(self.variant, HtmlVariant::Angular) + } + pub const fn supports_components(&self) -> bool { self.is_vue() || self.is_svelte() || self.is_astro() } @@ -97,12 +103,23 @@ impl HtmlFileSource { variant: HtmlVariant::Vue, } } + pub fn svelte() -> Self { Self { variant: HtmlVariant::Svelte, } } + pub fn angular() -> Self { + Self { + variant: HtmlVariant::Angular, + } + } + + pub fn set_variant(&mut self, variant: HtmlVariant) { + self.variant = variant; + } + /// Try to return the HTML file source corresponding to this file name from well-known files pub fn try_from_well_known(path: &Utf8Path) -> Result { let Some(extension) = path.extension() else { @@ -120,6 +137,7 @@ impl HtmlFileSource { "astro" => Ok(Self::astro()), "vue" => Ok(Self::vue()), "svelte" => Ok(Self::svelte()), + "component.html" => Ok(Self::angular()), _ => Err(FileSourceError::UnknownExtension), } } diff --git a/crates/biome_html_syntax/src/generated/kind.rs b/crates/biome_html_syntax/src/generated/kind.rs index 1d27cf2801f8..c79da80a8ea8 100644 --- a/crates/biome_html_syntax/src/generated/kind.rs +++ b/crates/biome_html_syntax/src/generated/kind.rs @@ -38,8 +38,11 @@ pub enum HtmlSyntaxKind { HASH, L_PAREN, R_PAREN, + L_BRACKET_PAREN, + R_BRACKET_PAREN, DOT3, PIPE, + STAR, NULL_KW, TRUE_KW, FALSE_KW, @@ -171,6 +174,12 @@ pub enum HtmlSyntaxKind { VUE_DYNAMIC_ARGUMENT, VUE_MODIFIER_LIST, VUE_MODIFIER, + ANGULAR_BINDING_NAME, + ANGULAR_EVENT_BINDING, + ANGULAR_PROPERTY_BINDING, + ANGULAR_TWO_WAY_BINDING, + ANGULAR_STRUCTURAL_DIRECTIVE, + ANGULAR_TEMPLATE_REF_VARIABLE, HTML_BOGUS, HTML_BOGUS_ELEMENT, HTML_BOGUS_ATTRIBUTE, @@ -213,8 +222,11 @@ impl HtmlSyntaxKind { | HASH | L_PAREN | R_PAREN + | L_BRACKET_PAREN + | R_BRACKET_PAREN | DOT3 | PIPE + | STAR ) } pub const fn is_literal(self) -> bool { @@ -298,8 +310,11 @@ impl HtmlSyntaxKind { HASH => "#", L_PAREN => "(", R_PAREN => ")", + L_BRACKET_PAREN => "[(", + R_BRACKET_PAREN => ")]", DOT3 => "...", PIPE => "|", + STAR => "*", NULL_KW => "null", TRUE_KW => "true", FALSE_KW => "false", @@ -340,4 +355,4 @@ impl HtmlSyntaxKind { } #[doc = r" Utility macro for creating a SyntaxKind through simple macro syntax"] #[macro_export] -macro_rules ! T { [<] => { $ crate :: HtmlSyntaxKind :: L_ANGLE } ; [>] => { $ crate :: HtmlSyntaxKind :: R_ANGLE } ; [/] => { $ crate :: HtmlSyntaxKind :: SLASH } ; [=] => { $ crate :: HtmlSyntaxKind :: EQ } ; [!] => { $ crate :: HtmlSyntaxKind :: BANG } ; [-] => { $ crate :: HtmlSyntaxKind :: MINUS } ; [" { $ crate :: HtmlSyntaxKind :: CDATA_START } ; ["]]>"] => { $ crate :: HtmlSyntaxKind :: CDATA_END } ; [---] => { $ crate :: HtmlSyntaxKind :: FENCE } ; ['{'] => { $ crate :: HtmlSyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: HtmlSyntaxKind :: R_CURLY } ; ["{{"] => { $ crate :: HtmlSyntaxKind :: L_DOUBLE_CURLY } ; ["}}"] => { $ crate :: HtmlSyntaxKind :: R_DOUBLE_CURLY } ; ["{@"] => { $ crate :: HtmlSyntaxKind :: SV_CURLY_AT } ; ["{#"] => { $ crate :: HtmlSyntaxKind :: SV_CURLY_HASH } ; ["{/"] => { $ crate :: HtmlSyntaxKind :: SV_CURLY_SLASH } ; ["{:"] => { $ crate :: HtmlSyntaxKind :: SV_CURLY_COLON } ; [,] => { $ crate :: HtmlSyntaxKind :: COMMA } ; [:] => { $ crate :: HtmlSyntaxKind :: COLON } ; [@] => { $ crate :: HtmlSyntaxKind :: AT } ; [.] => { $ crate :: HtmlSyntaxKind :: DOT } ; ['['] => { $ crate :: HtmlSyntaxKind :: L_BRACKET } ; [']'] => { $ crate :: HtmlSyntaxKind :: R_BRACKET } ; [#] => { $ crate :: HtmlSyntaxKind :: HASH } ; ['('] => { $ crate :: HtmlSyntaxKind :: L_PAREN } ; [')'] => { $ crate :: HtmlSyntaxKind :: R_PAREN } ; [...] => { $ crate :: HtmlSyntaxKind :: DOT3 } ; [|] => { $ crate :: HtmlSyntaxKind :: PIPE } ; [null] => { $ crate :: HtmlSyntaxKind :: NULL_KW } ; [true] => { $ crate :: HtmlSyntaxKind :: TRUE_KW } ; [false] => { $ crate :: HtmlSyntaxKind :: FALSE_KW } ; [doctype] => { $ crate :: HtmlSyntaxKind :: DOCTYPE_KW } ; [html] => { $ crate :: HtmlSyntaxKind :: HTML_KW } ; [debug] => { $ crate :: HtmlSyntaxKind :: DEBUG_KW } ; [key] => { $ crate :: HtmlSyntaxKind :: KEY_KW } ; [render] => { $ crate :: HtmlSyntaxKind :: RENDER_KW } ; [const] => { $ crate :: HtmlSyntaxKind :: CONST_KW } ; [attach] => { $ crate :: HtmlSyntaxKind :: ATTACH_KW } ; [else] => { $ crate :: HtmlSyntaxKind :: ELSE_KW } ; [if] => { $ crate :: HtmlSyntaxKind :: IF_KW } ; [as] => { $ crate :: HtmlSyntaxKind :: AS_KW } ; [each] => { $ crate :: HtmlSyntaxKind :: EACH_KW } ; [then] => { $ crate :: HtmlSyntaxKind :: THEN_KW } ; [await] => { $ crate :: HtmlSyntaxKind :: AWAIT_KW } ; [catch] => { $ crate :: HtmlSyntaxKind :: CATCH_KW } ; [snippet] => { $ crate :: HtmlSyntaxKind :: SNIPPET_KW } ; [bind] => { $ crate :: HtmlSyntaxKind :: BIND_KW } ; [transition] => { $ crate :: HtmlSyntaxKind :: TRANSITION_KW } ; [use] => { $ crate :: HtmlSyntaxKind :: USE_KW } ; [animate] => { $ crate :: HtmlSyntaxKind :: ANIMATE_KW } ; [in] => { $ crate :: HtmlSyntaxKind :: IN_KW } ; [out] => { $ crate :: HtmlSyntaxKind :: OUT_KW } ; [style] => { $ crate :: HtmlSyntaxKind :: STYLE_KW } ; [class] => { $ crate :: HtmlSyntaxKind :: CLASS_KW } ; [client] => { $ crate :: HtmlSyntaxKind :: CLIENT_KW } ; [set] => { $ crate :: HtmlSyntaxKind :: SET_KW } ; [server] => { $ crate :: HtmlSyntaxKind :: SERVER_KW } ; [is] => { $ crate :: HtmlSyntaxKind :: IS_KW } ; [define] => { $ crate :: HtmlSyntaxKind :: DEFINE_KW } ; [ident] => { $ crate :: HtmlSyntaxKind :: IDENT } ; [EOF] => { $ crate :: HtmlSyntaxKind :: EOF } ; [UNICODE_BOM] => { $ crate :: HtmlSyntaxKind :: UNICODE_BOM } ; [#] => { $ crate :: HtmlSyntaxKind :: HASH } ; } +macro_rules ! T { [<] => { $ crate :: HtmlSyntaxKind :: L_ANGLE } ; [>] => { $ crate :: HtmlSyntaxKind :: R_ANGLE } ; [/] => { $ crate :: HtmlSyntaxKind :: SLASH } ; [=] => { $ crate :: HtmlSyntaxKind :: EQ } ; [!] => { $ crate :: HtmlSyntaxKind :: BANG } ; [-] => { $ crate :: HtmlSyntaxKind :: MINUS } ; [" { $ crate :: HtmlSyntaxKind :: CDATA_START } ; ["]]>"] => { $ crate :: HtmlSyntaxKind :: CDATA_END } ; [---] => { $ crate :: HtmlSyntaxKind :: FENCE } ; ['{'] => { $ crate :: HtmlSyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: HtmlSyntaxKind :: R_CURLY } ; ["{{"] => { $ crate :: HtmlSyntaxKind :: L_DOUBLE_CURLY } ; ["}}"] => { $ crate :: HtmlSyntaxKind :: R_DOUBLE_CURLY } ; ["{@"] => { $ crate :: HtmlSyntaxKind :: SV_CURLY_AT } ; ["{#"] => { $ crate :: HtmlSyntaxKind :: SV_CURLY_HASH } ; ["{/"] => { $ crate :: HtmlSyntaxKind :: SV_CURLY_SLASH } ; ["{:"] => { $ crate :: HtmlSyntaxKind :: SV_CURLY_COLON } ; [,] => { $ crate :: HtmlSyntaxKind :: COMMA } ; [:] => { $ crate :: HtmlSyntaxKind :: COLON } ; [@] => { $ crate :: HtmlSyntaxKind :: AT } ; [.] => { $ crate :: HtmlSyntaxKind :: DOT } ; ['['] => { $ crate :: HtmlSyntaxKind :: L_BRACKET } ; [']'] => { $ crate :: HtmlSyntaxKind :: R_BRACKET } ; [#] => { $ crate :: HtmlSyntaxKind :: HASH } ; ['('] => { $ crate :: HtmlSyntaxKind :: L_PAREN } ; [')'] => { $ crate :: HtmlSyntaxKind :: R_PAREN } ; ["[("] => { $ crate :: HtmlSyntaxKind :: L_BRACKET_PAREN } ; [")]"] => { $ crate :: HtmlSyntaxKind :: R_BRACKET_PAREN } ; [...] => { $ crate :: HtmlSyntaxKind :: DOT3 } ; [|] => { $ crate :: HtmlSyntaxKind :: PIPE } ; [*] => { $ crate :: HtmlSyntaxKind :: STAR } ; [null] => { $ crate :: HtmlSyntaxKind :: NULL_KW } ; [true] => { $ crate :: HtmlSyntaxKind :: TRUE_KW } ; [false] => { $ crate :: HtmlSyntaxKind :: FALSE_KW } ; [doctype] => { $ crate :: HtmlSyntaxKind :: DOCTYPE_KW } ; [html] => { $ crate :: HtmlSyntaxKind :: HTML_KW } ; [debug] => { $ crate :: HtmlSyntaxKind :: DEBUG_KW } ; [key] => { $ crate :: HtmlSyntaxKind :: KEY_KW } ; [render] => { $ crate :: HtmlSyntaxKind :: RENDER_KW } ; [const] => { $ crate :: HtmlSyntaxKind :: CONST_KW } ; [attach] => { $ crate :: HtmlSyntaxKind :: ATTACH_KW } ; [else] => { $ crate :: HtmlSyntaxKind :: ELSE_KW } ; [if] => { $ crate :: HtmlSyntaxKind :: IF_KW } ; [as] => { $ crate :: HtmlSyntaxKind :: AS_KW } ; [each] => { $ crate :: HtmlSyntaxKind :: EACH_KW } ; [then] => { $ crate :: HtmlSyntaxKind :: THEN_KW } ; [await] => { $ crate :: HtmlSyntaxKind :: AWAIT_KW } ; [catch] => { $ crate :: HtmlSyntaxKind :: CATCH_KW } ; [snippet] => { $ crate :: HtmlSyntaxKind :: SNIPPET_KW } ; [bind] => { $ crate :: HtmlSyntaxKind :: BIND_KW } ; [transition] => { $ crate :: HtmlSyntaxKind :: TRANSITION_KW } ; [use] => { $ crate :: HtmlSyntaxKind :: USE_KW } ; [animate] => { $ crate :: HtmlSyntaxKind :: ANIMATE_KW } ; [in] => { $ crate :: HtmlSyntaxKind :: IN_KW } ; [out] => { $ crate :: HtmlSyntaxKind :: OUT_KW } ; [style] => { $ crate :: HtmlSyntaxKind :: STYLE_KW } ; [class] => { $ crate :: HtmlSyntaxKind :: CLASS_KW } ; [client] => { $ crate :: HtmlSyntaxKind :: CLIENT_KW } ; [set] => { $ crate :: HtmlSyntaxKind :: SET_KW } ; [server] => { $ crate :: HtmlSyntaxKind :: SERVER_KW } ; [is] => { $ crate :: HtmlSyntaxKind :: IS_KW } ; [define] => { $ crate :: HtmlSyntaxKind :: DEFINE_KW } ; [ident] => { $ crate :: HtmlSyntaxKind :: IDENT } ; [EOF] => { $ crate :: HtmlSyntaxKind :: EOF } ; [UNICODE_BOM] => { $ crate :: HtmlSyntaxKind :: UNICODE_BOM } ; [#] => { $ crate :: HtmlSyntaxKind :: HASH } ; } diff --git a/crates/biome_html_syntax/src/generated/macros.rs b/crates/biome_html_syntax/src/generated/macros.rs index 1d1a73974324..48f2e826ccfb 100644 --- a/crates/biome_html_syntax/src/generated/macros.rs +++ b/crates/biome_html_syntax/src/generated/macros.rs @@ -16,6 +16,32 @@ macro_rules! map_syntax_node { ($ node : expr , $ pattern : pat => $ body : expr) => { match $node { node => match $crate::HtmlSyntaxNode::kind(&node) { + $crate::HtmlSyntaxKind::ANGULAR_BINDING_NAME => { + let $pattern = unsafe { $crate::AngularBindingName::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::ANGULAR_EVENT_BINDING => { + let $pattern = unsafe { $crate::AngularEventBinding::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::ANGULAR_PROPERTY_BINDING => { + let $pattern = unsafe { $crate::AngularPropertyBinding::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::ANGULAR_STRUCTURAL_DIRECTIVE => { + let $pattern = + unsafe { $crate::AngularStructuralDirective::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::ANGULAR_TEMPLATE_REF_VARIABLE => { + let $pattern = + unsafe { $crate::AngularTemplateRefVariable::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::ANGULAR_TWO_WAY_BINDING => { + let $pattern = unsafe { $crate::AngularTwoWayBinding::new_unchecked(node) }; + $body + } $crate::HtmlSyntaxKind::ASTRO_CLASS_DIRECTIVE => { let $pattern = unsafe { $crate::AstroClassDirective::new_unchecked(node) }; $body diff --git a/crates/biome_html_syntax/src/generated/nodes.rs b/crates/biome_html_syntax/src/generated/nodes.rs index c408eee07478..47ab05cdc0aa 100644 --- a/crates/biome_html_syntax/src/generated/nodes.rs +++ b/crates/biome_html_syntax/src/generated/nodes.rs @@ -20,6 +20,276 @@ use std::fmt::{Debug, Formatter}; #[doc = r" the slots are not statically known."] pub(crate) const SLOT_MAP_EMPTY_VALUE: u8 = u8::MAX; #[derive(Clone, PartialEq, Eq, Hash)] +pub struct AngularBindingName { + pub(crate) syntax: SyntaxNode, +} +impl AngularBindingName { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> AngularBindingNameFields { + AngularBindingNameFields { + value_token: self.value_token(), + } + } + pub fn value_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } +} +impl Serialize for AngularBindingName { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct AngularBindingNameFields { + pub value_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct AngularEventBinding { + pub(crate) syntax: SyntaxNode, +} +impl AngularEventBinding { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> AngularEventBindingFields { + AngularEventBindingFields { + l_paren_token: self.l_paren_token(), + name: self.name(), + r_paren_token: self.r_paren_token(), + initializer: self.initializer(), + } + } + pub fn l_paren_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } + pub fn r_paren_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 2usize) + } + pub fn initializer(&self) -> Option { + support::node(&self.syntax, 3usize) + } +} +impl Serialize for AngularEventBinding { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct AngularEventBindingFields { + pub l_paren_token: SyntaxResult, + pub name: SyntaxResult, + pub r_paren_token: SyntaxResult, + pub initializer: Option, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct AngularPropertyBinding { + pub(crate) syntax: SyntaxNode, +} +impl AngularPropertyBinding { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> AngularPropertyBindingFields { + AngularPropertyBindingFields { + l_brack_token: self.l_brack_token(), + name: self.name(), + r_brack_token: self.r_brack_token(), + initializer: self.initializer(), + } + } + pub fn l_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } + pub fn r_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 2usize) + } + pub fn initializer(&self) -> Option { + support::node(&self.syntax, 3usize) + } +} +impl Serialize for AngularPropertyBinding { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct AngularPropertyBindingFields { + pub l_brack_token: SyntaxResult, + pub name: SyntaxResult, + pub r_brack_token: SyntaxResult, + pub initializer: Option, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct AngularStructuralDirective { + pub(crate) syntax: SyntaxNode, +} +impl AngularStructuralDirective { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> AngularStructuralDirectiveFields { + AngularStructuralDirectiveFields { + star_token: self.star_token(), + name: self.name(), + initializer: self.initializer(), + } + } + pub fn star_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } + pub fn initializer(&self) -> Option { + support::node(&self.syntax, 2usize) + } +} +impl Serialize for AngularStructuralDirective { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct AngularStructuralDirectiveFields { + pub star_token: SyntaxResult, + pub name: SyntaxResult, + pub initializer: Option, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct AngularTemplateRefVariable { + pub(crate) syntax: SyntaxNode, +} +impl AngularTemplateRefVariable { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> AngularTemplateRefVariableFields { + AngularTemplateRefVariableFields { + hash_token: self.hash_token(), + name: self.name(), + } + } + pub fn hash_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } +} +impl Serialize for AngularTemplateRefVariable { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct AngularTemplateRefVariableFields { + pub hash_token: SyntaxResult, + pub name: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct AngularTwoWayBinding { + pub(crate) syntax: SyntaxNode, +} +impl AngularTwoWayBinding { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> AngularTwoWayBindingFields { + AngularTwoWayBindingFields { + l_bracket_paren_token: self.l_bracket_paren_token(), + name: self.name(), + r_bracket_paren_token: self.r_bracket_paren_token(), + initializer: self.initializer(), + } + } + pub fn l_bracket_paren_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } + pub fn r_bracket_paren_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 2usize) + } + pub fn initializer(&self) -> Option { + support::node(&self.syntax, 3usize) + } +} +impl Serialize for AngularTwoWayBinding { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct AngularTwoWayBindingFields { + pub l_bracket_paren_token: SyntaxResult, + pub name: SyntaxResult, + pub r_bracket_paren_token: SyntaxResult, + pub initializer: Option, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct AstroClassDirective { pub(crate) syntax: SyntaxNode, } @@ -3775,6 +4045,46 @@ pub struct VueVSlotShorthandDirectiveFields { pub initializer: Option, } #[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub enum AnyAngularAttribute { + AngularEventBinding(AngularEventBinding), + AngularPropertyBinding(AngularPropertyBinding), + AngularStructuralDirective(AngularStructuralDirective), + AngularTemplateRefVariable(AngularTemplateRefVariable), + AngularTwoWayBinding(AngularTwoWayBinding), +} +impl AnyAngularAttribute { + pub fn as_angular_event_binding(&self) -> Option<&AngularEventBinding> { + match &self { + Self::AngularEventBinding(item) => Some(item), + _ => None, + } + } + pub fn as_angular_property_binding(&self) -> Option<&AngularPropertyBinding> { + match &self { + Self::AngularPropertyBinding(item) => Some(item), + _ => None, + } + } + pub fn as_angular_structural_directive(&self) -> Option<&AngularStructuralDirective> { + match &self { + Self::AngularStructuralDirective(item) => Some(item), + _ => None, + } + } + pub fn as_angular_template_ref_variable(&self) -> Option<&AngularTemplateRefVariable> { + match &self { + Self::AngularTemplateRefVariable(item) => Some(item), + _ => None, + } + } + pub fn as_angular_two_way_binding(&self) -> Option<&AngularTwoWayBinding> { + match &self { + Self::AngularTwoWayBinding(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] pub enum AnyAstroDirective { AstroClassDirective(AstroClassDirective), AstroClientDirective(AstroClientDirective), @@ -3842,6 +4152,7 @@ impl AnyAstroFrontmatterElement { } #[derive(Clone, PartialEq, Eq, Hash, Serialize)] pub enum AnyHtmlAttribute { + AnyAngularAttribute(AnyAngularAttribute), AnyAstroDirective(AnyAstroDirective), AnySvelteDirective(AnySvelteDirective), AnyVueDirective(AnyVueDirective), @@ -3853,6 +4164,12 @@ pub enum AnyHtmlAttribute { SvelteAttachAttribute(SvelteAttachAttribute), } impl AnyHtmlAttribute { + pub fn as_any_angular_attribute(&self) -> Option<&AnyAngularAttribute> { + match &self { + Self::AnyAngularAttribute(item) => Some(item), + _ => None, + } + } pub fn as_any_astro_directive(&self) -> Option<&AnyAstroDirective> { match &self { Self::AnyAstroDirective(item) => Some(item), @@ -4309,109 +4626,436 @@ impl AnySvelteDirective { _ => None, } } - pub fn as_svelte_transition_directive(&self) -> Option<&SvelteTransitionDirective> { - match &self { - Self::SvelteTransitionDirective(item) => Some(item), - _ => None, - } + pub fn as_svelte_transition_directive(&self) -> Option<&SvelteTransitionDirective> { + match &self { + Self::SvelteTransitionDirective(item) => Some(item), + _ => None, + } + } + pub fn as_svelte_use_directive(&self) -> Option<&SvelteUseDirective> { + match &self { + Self::SvelteUseDirective(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub enum AnySvelteEachName { + AnySvelteDestructuredName(AnySvelteDestructuredName), + HtmlTextExpression(HtmlTextExpression), + SvelteName(SvelteName), +} +impl AnySvelteEachName { + pub fn as_any_svelte_destructured_name(&self) -> Option<&AnySvelteDestructuredName> { + match &self { + Self::AnySvelteDestructuredName(item) => Some(item), + _ => None, + } + } + pub fn as_html_text_expression(&self) -> Option<&HtmlTextExpression> { + match &self { + Self::HtmlTextExpression(item) => Some(item), + _ => None, + } + } + pub fn as_svelte_name(&self) -> Option<&SvelteName> { + match &self { + Self::SvelteName(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub enum AnyVueDirective { + VueBogusDirective(VueBogusDirective), + VueDirective(VueDirective), + VueVBindShorthandDirective(VueVBindShorthandDirective), + VueVOnShorthandDirective(VueVOnShorthandDirective), + VueVSlotShorthandDirective(VueVSlotShorthandDirective), +} +impl AnyVueDirective { + pub fn as_vue_bogus_directive(&self) -> Option<&VueBogusDirective> { + match &self { + Self::VueBogusDirective(item) => Some(item), + _ => None, + } + } + pub fn as_vue_directive(&self) -> Option<&VueDirective> { + match &self { + Self::VueDirective(item) => Some(item), + _ => None, + } + } + pub fn as_vue_v_bind_shorthand_directive(&self) -> Option<&VueVBindShorthandDirective> { + match &self { + Self::VueVBindShorthandDirective(item) => Some(item), + _ => None, + } + } + pub fn as_vue_v_on_shorthand_directive(&self) -> Option<&VueVOnShorthandDirective> { + match &self { + Self::VueVOnShorthandDirective(item) => Some(item), + _ => None, + } + } + pub fn as_vue_v_slot_shorthand_directive(&self) -> Option<&VueVSlotShorthandDirective> { + match &self { + Self::VueVSlotShorthandDirective(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub enum AnyVueDirectiveArgument { + VueBogusDirectiveArgument(VueBogusDirectiveArgument), + VueDynamicArgument(VueDynamicArgument), + VueStaticArgument(VueStaticArgument), +} +impl AnyVueDirectiveArgument { + pub fn as_vue_bogus_directive_argument(&self) -> Option<&VueBogusDirectiveArgument> { + match &self { + Self::VueBogusDirectiveArgument(item) => Some(item), + _ => None, + } + } + pub fn as_vue_dynamic_argument(&self) -> Option<&VueDynamicArgument> { + match &self { + Self::VueDynamicArgument(item) => Some(item), + _ => None, + } + } + pub fn as_vue_static_argument(&self) -> Option<&VueStaticArgument> { + match &self { + Self::VueStaticArgument(item) => Some(item), + _ => None, + } + } +} +impl AstNode for AngularBindingName { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(ANGULAR_BINDING_NAME as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == ANGULAR_BINDING_NAME + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for AngularBindingName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("AngularBindingName") + .field( + "value_token", + &support::DebugSyntaxResult(self.value_token()), + ) + .finish() + } else { + f.debug_struct("AngularBindingName").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: AngularBindingName) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: AngularBindingName) -> Self { + n.syntax.into() + } +} +impl AstNode for AngularEventBinding { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(ANGULAR_EVENT_BINDING as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == ANGULAR_EVENT_BINDING + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for AngularEventBinding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("AngularEventBinding") + .field( + "l_paren_token", + &support::DebugSyntaxResult(self.l_paren_token()), + ) + .field("name", &support::DebugSyntaxResult(self.name())) + .field( + "r_paren_token", + &support::DebugSyntaxResult(self.r_paren_token()), + ) + .field( + "initializer", + &support::DebugOptionalElement(self.initializer()), + ) + .finish() + } else { + f.debug_struct("AngularEventBinding").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: AngularEventBinding) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: AngularEventBinding) -> Self { + n.syntax.into() + } +} +impl AstNode for AngularPropertyBinding { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(ANGULAR_PROPERTY_BINDING as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == ANGULAR_PROPERTY_BINDING + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for AngularPropertyBinding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("AngularPropertyBinding") + .field( + "l_brack_token", + &support::DebugSyntaxResult(self.l_brack_token()), + ) + .field("name", &support::DebugSyntaxResult(self.name())) + .field( + "r_brack_token", + &support::DebugSyntaxResult(self.r_brack_token()), + ) + .field( + "initializer", + &support::DebugOptionalElement(self.initializer()), + ) + .finish() + } else { + f.debug_struct("AngularPropertyBinding").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: AngularPropertyBinding) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: AngularPropertyBinding) -> Self { + n.syntax.into() + } +} +impl AstNode for AngularStructuralDirective { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(ANGULAR_STRUCTURAL_DIRECTIVE as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == ANGULAR_STRUCTURAL_DIRECTIVE + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax } - pub fn as_svelte_use_directive(&self) -> Option<&SvelteUseDirective> { - match &self { - Self::SvelteUseDirective(item) => Some(item), - _ => None, - } + fn into_syntax(self) -> SyntaxNode { + self.syntax } } -#[derive(Clone, PartialEq, Eq, Hash, Serialize)] -pub enum AnySvelteEachName { - AnySvelteDestructuredName(AnySvelteDestructuredName), - HtmlTextExpression(HtmlTextExpression), - SvelteName(SvelteName), +impl std::fmt::Debug for AngularStructuralDirective { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("AngularStructuralDirective") + .field("star_token", &support::DebugSyntaxResult(self.star_token())) + .field("name", &support::DebugSyntaxResult(self.name())) + .field( + "initializer", + &support::DebugOptionalElement(self.initializer()), + ) + .finish() + } else { + f.debug_struct("AngularStructuralDirective").finish() + }; + DEPTH.set(current_depth); + result + } } -impl AnySvelteEachName { - pub fn as_any_svelte_destructured_name(&self) -> Option<&AnySvelteDestructuredName> { - match &self { - Self::AnySvelteDestructuredName(item) => Some(item), - _ => None, - } +impl From for SyntaxNode { + fn from(n: AngularStructuralDirective) -> Self { + n.syntax } - pub fn as_html_text_expression(&self) -> Option<&HtmlTextExpression> { - match &self { - Self::HtmlTextExpression(item) => Some(item), - _ => None, - } +} +impl From for SyntaxElement { + fn from(n: AngularStructuralDirective) -> Self { + n.syntax.into() } - pub fn as_svelte_name(&self) -> Option<&SvelteName> { - match &self { - Self::SvelteName(item) => Some(item), - _ => None, +} +impl AstNode for AngularTemplateRefVariable { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(ANGULAR_TEMPLATE_REF_VARIABLE as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == ANGULAR_TEMPLATE_REF_VARIABLE + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None } } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } } -#[derive(Clone, PartialEq, Eq, Hash, Serialize)] -pub enum AnyVueDirective { - VueBogusDirective(VueBogusDirective), - VueDirective(VueDirective), - VueVBindShorthandDirective(VueVBindShorthandDirective), - VueVOnShorthandDirective(VueVOnShorthandDirective), - VueVSlotShorthandDirective(VueVSlotShorthandDirective), +impl std::fmt::Debug for AngularTemplateRefVariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("AngularTemplateRefVariable") + .field("hash_token", &support::DebugSyntaxResult(self.hash_token())) + .field("name", &support::DebugSyntaxResult(self.name())) + .finish() + } else { + f.debug_struct("AngularTemplateRefVariable").finish() + }; + DEPTH.set(current_depth); + result + } } -impl AnyVueDirective { - pub fn as_vue_bogus_directive(&self) -> Option<&VueBogusDirective> { - match &self { - Self::VueBogusDirective(item) => Some(item), - _ => None, - } +impl From for SyntaxNode { + fn from(n: AngularTemplateRefVariable) -> Self { + n.syntax } - pub fn as_vue_directive(&self) -> Option<&VueDirective> { - match &self { - Self::VueDirective(item) => Some(item), - _ => None, - } +} +impl From for SyntaxElement { + fn from(n: AngularTemplateRefVariable) -> Self { + n.syntax.into() } - pub fn as_vue_v_bind_shorthand_directive(&self) -> Option<&VueVBindShorthandDirective> { - match &self { - Self::VueVBindShorthandDirective(item) => Some(item), - _ => None, - } +} +impl AstNode for AngularTwoWayBinding { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(ANGULAR_TWO_WAY_BINDING as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == ANGULAR_TWO_WAY_BINDING } - pub fn as_vue_v_on_shorthand_directive(&self) -> Option<&VueVOnShorthandDirective> { - match &self { - Self::VueVOnShorthandDirective(item) => Some(item), - _ => None, + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None } } - pub fn as_vue_v_slot_shorthand_directive(&self) -> Option<&VueVSlotShorthandDirective> { - match &self { - Self::VueVSlotShorthandDirective(item) => Some(item), - _ => None, - } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax } } -#[derive(Clone, PartialEq, Eq, Hash, Serialize)] -pub enum AnyVueDirectiveArgument { - VueBogusDirectiveArgument(VueBogusDirectiveArgument), - VueDynamicArgument(VueDynamicArgument), - VueStaticArgument(VueStaticArgument), -} -impl AnyVueDirectiveArgument { - pub fn as_vue_bogus_directive_argument(&self) -> Option<&VueBogusDirectiveArgument> { - match &self { - Self::VueBogusDirectiveArgument(item) => Some(item), - _ => None, - } +impl std::fmt::Debug for AngularTwoWayBinding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("AngularTwoWayBinding") + .field( + "l_bracket_paren_token", + &support::DebugSyntaxResult(self.l_bracket_paren_token()), + ) + .field("name", &support::DebugSyntaxResult(self.name())) + .field( + "r_bracket_paren_token", + &support::DebugSyntaxResult(self.r_bracket_paren_token()), + ) + .field( + "initializer", + &support::DebugOptionalElement(self.initializer()), + ) + .finish() + } else { + f.debug_struct("AngularTwoWayBinding").finish() + }; + DEPTH.set(current_depth); + result } - pub fn as_vue_dynamic_argument(&self) -> Option<&VueDynamicArgument> { - match &self { - Self::VueDynamicArgument(item) => Some(item), - _ => None, - } +} +impl From for SyntaxNode { + fn from(n: AngularTwoWayBinding) -> Self { + n.syntax } - pub fn as_vue_static_argument(&self) -> Option<&VueStaticArgument> { - match &self { - Self::VueStaticArgument(item) => Some(item), - _ => None, - } +} +impl From for SyntaxElement { + fn from(n: AngularTwoWayBinding) -> Self { + n.syntax.into() } } impl AstNode for AstroClassDirective { @@ -8939,6 +9583,112 @@ impl From for SyntaxElement { n.syntax.into() } } +impl From for AnyAngularAttribute { + fn from(node: AngularEventBinding) -> Self { + Self::AngularEventBinding(node) + } +} +impl From for AnyAngularAttribute { + fn from(node: AngularPropertyBinding) -> Self { + Self::AngularPropertyBinding(node) + } +} +impl From for AnyAngularAttribute { + fn from(node: AngularStructuralDirective) -> Self { + Self::AngularStructuralDirective(node) + } +} +impl From for AnyAngularAttribute { + fn from(node: AngularTemplateRefVariable) -> Self { + Self::AngularTemplateRefVariable(node) + } +} +impl From for AnyAngularAttribute { + fn from(node: AngularTwoWayBinding) -> Self { + Self::AngularTwoWayBinding(node) + } +} +impl AstNode for AnyAngularAttribute { + type Language = Language; + const KIND_SET: SyntaxKindSet = AngularEventBinding::KIND_SET + .union(AngularPropertyBinding::KIND_SET) + .union(AngularStructuralDirective::KIND_SET) + .union(AngularTemplateRefVariable::KIND_SET) + .union(AngularTwoWayBinding::KIND_SET); + fn can_cast(kind: SyntaxKind) -> bool { + matches!( + kind, + ANGULAR_EVENT_BINDING + | ANGULAR_PROPERTY_BINDING + | ANGULAR_STRUCTURAL_DIRECTIVE + | ANGULAR_TEMPLATE_REF_VARIABLE + | ANGULAR_TWO_WAY_BINDING + ) + } + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + ANGULAR_EVENT_BINDING => Self::AngularEventBinding(AngularEventBinding { syntax }), + ANGULAR_PROPERTY_BINDING => { + Self::AngularPropertyBinding(AngularPropertyBinding { syntax }) + } + ANGULAR_STRUCTURAL_DIRECTIVE => { + Self::AngularStructuralDirective(AngularStructuralDirective { syntax }) + } + ANGULAR_TEMPLATE_REF_VARIABLE => { + Self::AngularTemplateRefVariable(AngularTemplateRefVariable { syntax }) + } + ANGULAR_TWO_WAY_BINDING => Self::AngularTwoWayBinding(AngularTwoWayBinding { syntax }), + _ => return None, + }; + Some(res) + } + fn syntax(&self) -> &SyntaxNode { + match self { + Self::AngularEventBinding(it) => it.syntax(), + Self::AngularPropertyBinding(it) => it.syntax(), + Self::AngularStructuralDirective(it) => it.syntax(), + Self::AngularTemplateRefVariable(it) => it.syntax(), + Self::AngularTwoWayBinding(it) => it.syntax(), + } + } + fn into_syntax(self) -> SyntaxNode { + match self { + Self::AngularEventBinding(it) => it.into_syntax(), + Self::AngularPropertyBinding(it) => it.into_syntax(), + Self::AngularStructuralDirective(it) => it.into_syntax(), + Self::AngularTemplateRefVariable(it) => it.into_syntax(), + Self::AngularTwoWayBinding(it) => it.into_syntax(), + } + } +} +impl std::fmt::Debug for AnyAngularAttribute { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::AngularEventBinding(it) => std::fmt::Debug::fmt(it, f), + Self::AngularPropertyBinding(it) => std::fmt::Debug::fmt(it, f), + Self::AngularStructuralDirective(it) => std::fmt::Debug::fmt(it, f), + Self::AngularTemplateRefVariable(it) => std::fmt::Debug::fmt(it, f), + Self::AngularTwoWayBinding(it) => std::fmt::Debug::fmt(it, f), + } + } +} +impl From for SyntaxNode { + fn from(n: AnyAngularAttribute) -> Self { + match n { + AnyAngularAttribute::AngularEventBinding(it) => it.into_syntax(), + AnyAngularAttribute::AngularPropertyBinding(it) => it.into_syntax(), + AnyAngularAttribute::AngularStructuralDirective(it) => it.into_syntax(), + AnyAngularAttribute::AngularTemplateRefVariable(it) => it.into_syntax(), + AnyAngularAttribute::AngularTwoWayBinding(it) => it.into_syntax(), + } + } +} +impl From for SyntaxElement { + fn from(n: AnyAngularAttribute) -> Self { + let node: SyntaxNode = n.into(); + node.into() + } +} impl From for AnyAstroDirective { fn from(node: AstroClassDirective) -> Self { Self::AstroClassDirective(node) @@ -9147,7 +9897,8 @@ impl From for AnyHtmlAttribute { } impl AstNode for AnyHtmlAttribute { type Language = Language; - const KIND_SET: SyntaxKindSet = AnyAstroDirective::KIND_SET + const KIND_SET: SyntaxKindSet = AnyAngularAttribute::KIND_SET + .union(AnyAstroDirective::KIND_SET) .union(AnySvelteDirective::KIND_SET) .union(AnyVueDirective::KIND_SET) .union(HtmlAttribute::KIND_SET) @@ -9164,6 +9915,7 @@ impl AstNode for AnyHtmlAttribute { | HTML_BOGUS_ATTRIBUTE | HTML_SPREAD_ATTRIBUTE | SVELTE_ATTACH_ATTRIBUTE => true, + k if AnyAngularAttribute::can_cast(k) => true, k if AnyAstroDirective::can_cast(k) => true, k if AnySvelteDirective::can_cast(k) => true, k if AnyVueDirective::can_cast(k) => true, @@ -9189,6 +9941,12 @@ impl AstNode for AnyHtmlAttribute { Self::SvelteAttachAttribute(SvelteAttachAttribute { syntax }) } _ => { + let syntax = match AnyAngularAttribute::try_cast(syntax) { + Ok(any_angular_attribute) => { + return Some(Self::AnyAngularAttribute(any_angular_attribute)); + } + Err(syntax) => syntax, + }; let syntax = match AnyAstroDirective::try_cast(syntax) { Ok(any_astro_directive) => { return Some(Self::AnyAstroDirective(any_astro_directive)); @@ -9217,6 +9975,7 @@ impl AstNode for AnyHtmlAttribute { Self::HtmlBogusAttribute(it) => it.syntax(), Self::HtmlSpreadAttribute(it) => it.syntax(), Self::SvelteAttachAttribute(it) => it.syntax(), + Self::AnyAngularAttribute(it) => it.syntax(), Self::AnyAstroDirective(it) => it.syntax(), Self::AnySvelteDirective(it) => it.syntax(), Self::AnyVueDirective(it) => it.syntax(), @@ -9230,6 +9989,7 @@ impl AstNode for AnyHtmlAttribute { Self::HtmlBogusAttribute(it) => it.into_syntax(), Self::HtmlSpreadAttribute(it) => it.into_syntax(), Self::SvelteAttachAttribute(it) => it.into_syntax(), + Self::AnyAngularAttribute(it) => it.into_syntax(), Self::AnyAstroDirective(it) => it.into_syntax(), Self::AnySvelteDirective(it) => it.into_syntax(), Self::AnyVueDirective(it) => it.into_syntax(), @@ -9239,6 +9999,7 @@ impl AstNode for AnyHtmlAttribute { impl std::fmt::Debug for AnyHtmlAttribute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::AnyAngularAttribute(it) => std::fmt::Debug::fmt(it, f), Self::AnyAstroDirective(it) => std::fmt::Debug::fmt(it, f), Self::AnySvelteDirective(it) => std::fmt::Debug::fmt(it, f), Self::AnyVueDirective(it) => std::fmt::Debug::fmt(it, f), @@ -9254,6 +10015,7 @@ impl std::fmt::Debug for AnyHtmlAttribute { impl From for SyntaxNode { fn from(n: AnyHtmlAttribute) -> Self { match n { + AnyHtmlAttribute::AnyAngularAttribute(it) => it.into_syntax(), AnyHtmlAttribute::AnyAstroDirective(it) => it.into_syntax(), AnyHtmlAttribute::AnySvelteDirective(it) => it.into_syntax(), AnyHtmlAttribute::AnyVueDirective(it) => it.into_syntax(), @@ -10628,6 +11390,11 @@ impl From for SyntaxElement { node.into() } } +impl std::fmt::Display for AnyAngularAttribute { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for AnyAstroDirective { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -10723,6 +11490,36 @@ impl std::fmt::Display for AnyVueDirectiveArgument { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for AngularBindingName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for AngularEventBinding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for AngularPropertyBinding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for AngularStructuralDirective { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for AngularTemplateRefVariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for AngularTwoWayBinding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for AstroClassDirective { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/biome_html_syntax/src/generated/nodes_mut.rs b/crates/biome_html_syntax/src/generated/nodes_mut.rs index a5335d678ee0..776468d037fe 100644 --- a/crates/biome_html_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_html_syntax/src/generated/nodes_mut.rs @@ -3,6 +3,126 @@ use crate::{HtmlSyntaxToken as SyntaxToken, generated::nodes::*}; use biome_rowan::AstNode; use std::iter::once; +impl AngularBindingName { + pub fn with_value_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } +} +impl AngularEventBinding { + pub fn with_l_paren_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: AngularBindingName) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_paren_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into()))), + ) + } + pub fn with_initializer(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 3usize..=3usize, + once(element.map(|element| element.into_syntax().into())), + )) + } +} +impl AngularPropertyBinding { + pub fn with_l_brack_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: AngularBindingName) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_brack_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into()))), + ) + } + pub fn with_initializer(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 3usize..=3usize, + once(element.map(|element| element.into_syntax().into())), + )) + } +} +impl AngularStructuralDirective { + pub fn with_star_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: AngularBindingName) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_initializer(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 2usize..=2usize, + once(element.map(|element| element.into_syntax().into())), + )) + } +} +impl AngularTemplateRefVariable { + pub fn with_hash_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: AngularBindingName) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } +} +impl AngularTwoWayBinding { + pub fn with_l_bracket_paren_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: AngularBindingName) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_bracket_paren_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into()))), + ) + } + pub fn with_initializer(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 3usize..=3usize, + once(element.map(|element| element.into_syntax().into())), + )) + } +} impl AstroClassDirective { pub fn with_class_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( diff --git a/crates/biome_service/src/file_handlers/html.rs b/crates/biome_service/src/file_handlers/html.rs index 4577084144ee..073dc0c9903e 100644 --- a/crates/biome_service/src/file_handlers/html.rs +++ b/crates/biome_service/src/file_handlers/html.rs @@ -20,8 +20,9 @@ use crate::{ }; use biome_analyze::{AnalysisFilter, AnalyzerConfiguration, AnalyzerOptions, ControlFlow, Never}; use biome_configuration::html::{ - HtmlAssistConfiguration, HtmlAssistEnabled, HtmlFormatterConfiguration, HtmlFormatterEnabled, - HtmlLinterConfiguration, HtmlLinterEnabled, HtmlParseInterpolation, HtmlParserConfiguration, + HtmlAngularEnabled, HtmlAssistConfiguration, HtmlAssistEnabled, HtmlFormatterConfiguration, + HtmlFormatterEnabled, HtmlLinterConfiguration, HtmlLinterEnabled, HtmlParseInterpolation, + HtmlParserConfiguration, }; use biome_css_parser::{CssModulesKind, parse_css_with_offset_and_cache}; use biome_css_syntax::{CssFileSource, CssLanguage}; @@ -65,12 +66,14 @@ use tracing::{debug_span, error, instrument, trace_span}; #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct HtmlParserSettings { pub interpolation: Option, + pub angular: Option, } impl From for HtmlParserSettings { fn from(configuration: HtmlParserConfiguration) -> Self { Self { interpolation: configuration.interpolation, + angular: configuration.angular, } } } @@ -881,6 +884,7 @@ fn parse_embedded_nodes( } } } + HtmlVariant::Angular => {} } ParseEmbedResult { nodes } diff --git a/crates/biome_service/src/settings.rs b/crates/biome_service/src/settings.rs index 9eb58af8d5be..231c3a24eb24 100644 --- a/crates/biome_service/src/settings.rs +++ b/crates/biome_service/src/settings.rs @@ -1855,6 +1855,9 @@ impl OverrideSettingPattern { if let Some(interpolation) = html_parser.interpolation { options.set_double_text_expression(interpolation.value()); } + if let Some(angular) = html_parser.angular { + options.angular = angular.value(); + } } fn apply_overrides_to_css_parser_options(&self, options: &mut CssParserOptions) { diff --git a/crates/biome_service/src/workspace/server.rs b/crates/biome_service/src/workspace/server.rs index d427170aa8f2..f310d8ee62fa 100644 --- a/crates/biome_service/src/workspace/server.rs +++ b/crates/biome_service/src/workspace/server.rs @@ -48,7 +48,7 @@ use biome_diagnostics::{ use biome_formatter::Printed; use biome_fs::{BiomePath, ConfigName, PathKind}; use biome_grit_patterns::{CompilePatternOptions, GritQuery, compile_pattern_with_options}; -use biome_html_syntax::HtmlRoot; +use biome_html_syntax::{HtmlRoot, HtmlVariant}; use biome_js_syntax::{AnyJsRoot, LanguageVariant, ModuleKind}; use biome_json_parser::JsonParserOptions; use biome_json_syntax::JsonFileSource; @@ -401,6 +401,19 @@ impl WorkspaceServer { } } + if let DocumentFileSource::Html(html) = &mut source { + if settings + .languages + .html + .parser + .angular + .unwrap_or_default() + .into() + { + html.set_variant(HtmlVariant::Angular) + } + } + let (content, version) = match content { FileContent::FromClient { content, version } => (content, Some(version)), FileContent::FromServer => (self.fs.read_file_from_path(&path)?, None), diff --git a/xtask/codegen/html.ungram b/xtask/codegen/html.ungram index 5894b1550f28..06ed4523364f 100644 --- a/xtask/codegen/html.ungram +++ b/xtask/codegen/html.ungram @@ -186,6 +186,7 @@ AnyHtmlAttribute = | HtmlAttributeSingleTextExpression | SvelteAttachAttribute | HtmlSpreadAttribute + | AnyAngularAttribute | AnySvelteDirective | AnyVueDirective | AnyAstroDirective @@ -757,3 +758,55 @@ AstroDirectiveValue = colon_token: ':' name: HtmlAttributeName initializer: HtmlAttributeInitializerClause? + +// ================================== +// Angular +// ================================== + +// reference: https://angular.dev/guide/templates + +AnyAngularAttribute = + AngularEventBinding + | AngularPropertyBinding + | AngularTwoWayBinding + | AngularStructuralDirective + | AngularTemplateRefVariable + +// +// ^^^^^^^^^^^^^^^^^^ +AngularEventBinding = + '(' + name: AngularBindingName + ')' + initializer: HtmlAttributeInitializerClause? + +// +// ^^^^^^^^^^^^^^ +AngularPropertyBinding = + '[' + name: AngularBindingName + ']' + initializer: HtmlAttributeInitializerClause? + +// +// ^^^^^^^^^^^^^^^^^^^ +AngularTwoWayBinding = + '[(' + name: AngularBindingName + ')]' + initializer: HtmlAttributeInitializerClause? + +// +// ^^^^^^^^^^^^^ +AngularStructuralDirective = + '*' + name: AngularBindingName + initializer: HtmlAttributeInitializerClause? + +// +// ^^^^^ +AngularTemplateRefVariable = + '#' + name: AngularBindingName + +AngularBindingName = value: 'html_literal' diff --git a/xtask/codegen/src/formatter.rs b/xtask/codegen/src/formatter.rs index ef867c694679..59fb7beaf394 100644 --- a/xtask/codegen/src/formatter.rs +++ b/xtask/codegen/src/formatter.rs @@ -572,6 +572,7 @@ enum NodeDialect { Tailwind, Yaml, Markdown, + Angular, } impl NodeDialect { @@ -612,6 +613,7 @@ impl NodeDialect { Self::Tailwind => "tailwind", Self::Yaml => "yaml", Self::Markdown => "markdown", + Self::Angular => "angular", } } @@ -628,6 +630,7 @@ impl NodeDialect { "Html" => Self::Html, "Astro" => Self::Astro, "Svelte" => Self::Svelte, + "Angular" => Self::Angular, "Vue" => Self::Vue, "Tw" => Self::Tailwind, "Yaml" => Self::Yaml, diff --git a/xtask/codegen/src/generate_nodes.rs b/xtask/codegen/src/generate_nodes.rs index e6142471b07f..9125d48798a0 100644 --- a/xtask/codegen/src/generate_nodes.rs +++ b/xtask/codegen/src/generate_nodes.rs @@ -1201,5 +1201,7 @@ pub fn should_token_be_quoted(token: &str) -> bool { | "{#" | "{/" | "{:" + | "[(" + | ")]" ) } diff --git a/xtask/codegen/src/html_kinds_src.rs b/xtask/codegen/src/html_kinds_src.rs index 82c75c38b174..4329743a52e1 100644 --- a/xtask/codegen/src/html_kinds_src.rs +++ b/xtask/codegen/src/html_kinds_src.rs @@ -28,8 +28,11 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { ("#", "HASH"), ("(", "L_PAREN"), (")", "R_PAREN"), + ("[(", "L_BRACKET_PAREN"), + (")]", "R_BRACKET_PAREN"), ("...", "DOT3"), ("|", "PIPE"), + ("*", "STAR"), ], keywords: &[ "null", @@ -169,6 +172,13 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { "VUE_DYNAMIC_ARGUMENT", "VUE_MODIFIER_LIST", "VUE_MODIFIER", + // Angular nodes + "ANGULAR_BINDING_NAME", + "ANGULAR_EVENT_BINDING", + "ANGULAR_PROPERTY_BINDING", + "ANGULAR_TWO_WAY_BINDING", + "ANGULAR_STRUCTURAL_DIRECTIVE", + "ANGULAR_TEMPLATE_REF_VARIABLE", // Bogus nodes "HTML_BOGUS", "HTML_BOGUS_ELEMENT", diff --git a/xtask/codegen/src/js_kinds_src.rs b/xtask/codegen/src/js_kinds_src.rs index 7a428d7fcf20..c239715f61a3 100644 --- a/xtask/codegen/src/js_kinds_src.rs +++ b/xtask/codegen/src/js_kinds_src.rs @@ -705,6 +705,8 @@ impl Field { ("{#", LanguageKind::Html) => "sv_curly_hash", ("{/", LanguageKind::Html) => "sv_curly_slash", ("{:", LanguageKind::Html) => "sv_curly_colon", + ("[(", LanguageKind::Html) => "l_bracket_paren", + (")]", LanguageKind::Html) => "r_bracket_paren", _ => name, };
This is a normal element
This is a template fragment