From 411f441d37ff2417c6e069b27b5f1dd032ac9d65 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Sun, 9 Nov 2025 17:36:01 +0000 Subject: [PATCH 1/6] wip --- .../src/generated/node_factory.rs | 52 ++- .../src/generated/syntax_factory.rs | 124 +++++- crates/biome_html_formatter/src/generated.rs | 114 ++++++ .../src/svelte/any/block.rs | 1 + .../src/svelte/auxiliary/each_block.rs | 10 + .../svelte/auxiliary/each_closing_block.rs | 10 + .../svelte/auxiliary/each_opening_block.rs | 10 + .../src/svelte/auxiliary/mod.rs | 3 + .../src/svelte/auxiliary/name.rs | 4 +- crates/biome_html_parser/src/lexer/mod.rs | 98 +++-- crates/biome_html_parser/src/lexer/tests.rs | 12 +- crates/biome_html_parser/src/syntax/mod.rs | 16 +- crates/biome_html_parser/src/syntax/svelte.rs | 85 ++-- crates/biome_html_parser/src/syntax/vue.rs | 4 +- crates/biome_html_parser/src/token_source.rs | 2 + .../html_specs/error/svelte/debug.svelte.snap | 16 +- .../html_specs/ok/svelte/debug.svelte.snap | 28 +- .../arg-modifiers-no-value.vue.snap | 12 +- .../arg-no-modifiers-no-value.vue.snap | 12 +- .../directive-only.vue.snap | 12 +- .../modifiers-no-arg-no-value.vue.snap | 12 +- .../tests/html_specs/ok/vue/modifier.vue.snap | 4 +- .../ok/vue/modifiers-all-variants.vue.snap | 4 +- .../html_specs/ok/vue/v-bind-mixed.vue.snap | 16 +- .../tests/html_specs/ok/vue/v-bind.vue.snap | 4 +- .../ok/vue/v-dynamic-chains.vue.snap | 32 +- .../html_specs/ok/vue/v-else-if.vue.snap | 44 +-- .../tests/html_specs/ok/vue/v-else.vue.snap | 8 +- .../tests/html_specs/ok/vue/v-for.vue.snap | 68 ++-- .../html_specs/ok/vue/v-html-text.vue.snap | 48 +-- .../tests/html_specs/ok/vue/v-if.vue.snap | 8 +- .../ok/vue/v-mixed-complex.vue.snap | 36 +- .../html_specs/ok/vue/v-model-mixed.vue.snap | 80 ++-- .../html_specs/ok/vue/v-on-mixed.vue.snap | 32 +- crates/biome_html_parser/tests/quick_test.rs | 4 +- .../biome_html_syntax/src/generated/kind.rs | 16 +- .../biome_html_syntax/src/generated/macros.rs | 12 + .../biome_html_syntax/src/generated/nodes.rs | 362 +++++++++++++++++- .../src/generated/nodes_mut.rs | 80 +++- xtask/codegen/html.ungram | 33 +- xtask/codegen/src/html_kinds_src.rs | 19 +- 41 files changed, 1198 insertions(+), 349 deletions(-) create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/each_block.rs create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/each_closing_block.rs create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/each_opening_block.rs diff --git a/crates/biome_html_factory/src/generated/node_factory.rs b/crates/biome_html_factory/src/generated/node_factory.rs index 71fa27b7a86d..8b03e2afe11e 100644 --- a/crates/biome_html_factory/src/generated/node_factory.rs +++ b/crates/biome_html_factory/src/generated/node_factory.rs @@ -395,6 +395,54 @@ pub fn svelte_debug_block( ], )) } +pub fn svelte_each_block( + opening_block: SvelteEachOpeningBlock, + children: HtmlElementList, + closing_block: SvelteEachClosingBlock, +) -> SvelteEachBlock { + SvelteEachBlock::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_EACH_BLOCK, + [ + Some(SyntaxElement::Node(opening_block.into_syntax())), + Some(SyntaxElement::Node(children.into_syntax())), + Some(SyntaxElement::Node(closing_block.into_syntax())), + ], + )) +} +pub fn svelte_each_closing_block( + sv_curly_slash_token: SyntaxToken, + each_token: SyntaxToken, + r_curly_token: SyntaxToken, +) -> SvelteEachClosingBlock { + SvelteEachClosingBlock::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_EACH_CLOSING_BLOCK, + [ + Some(SyntaxElement::Token(sv_curly_slash_token)), + Some(SyntaxElement::Token(each_token)), + Some(SyntaxElement::Token(r_curly_token)), + ], + )) +} +pub fn svelte_each_opening_block( + sv_curly_hash_token: SyntaxToken, + each_token: SyntaxToken, + list: HtmlTextExpression, + as_token: SyntaxToken, + item: HtmlTextExpression, + r_curly_token: SyntaxToken, +) -> SvelteEachOpeningBlock { + SvelteEachOpeningBlock::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_EACH_OPENING_BLOCK, + [ + Some(SyntaxElement::Token(sv_curly_hash_token)), + Some(SyntaxElement::Token(each_token)), + Some(SyntaxElement::Node(list.into_syntax())), + Some(SyntaxElement::Token(as_token)), + Some(SyntaxElement::Node(item.into_syntax())), + Some(SyntaxElement::Token(r_curly_token)), + ], + )) +} pub fn svelte_else_clause( sv_curly_colon_token: SyntaxToken, else_token: SyntaxToken, @@ -559,10 +607,10 @@ pub fn svelte_key_opening_block( ], )) } -pub fn svelte_name(svelte_ident_token: SyntaxToken) -> SvelteName { +pub fn svelte_name(ident_token: SyntaxToken) -> SvelteName { SvelteName::unwrap_cast(SyntaxNode::new_detached( HtmlSyntaxKind::SVELTE_NAME, - [Some(SyntaxElement::Token(svelte_ident_token))], + [Some(SyntaxElement::Token(ident_token))], )) } pub fn svelte_render_block( diff --git a/crates/biome_html_factory/src/generated/syntax_factory.rs b/crates/biome_html_factory/src/generated/syntax_factory.rs index 707279080f39..75a2845695e0 100644 --- a/crates/biome_html_factory/src/generated/syntax_factory.rs +++ b/crates/biome_html_factory/src/generated/syntax_factory.rs @@ -736,6 +736,126 @@ impl SyntaxFactory for HtmlSyntaxFactory { } slots.into_node(SVELTE_DEBUG_BLOCK, children) } + SVELTE_EACH_BLOCK => { + 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 + && SvelteEachOpeningBlock::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && HtmlElementList::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && SvelteEachClosingBlock::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + SVELTE_EACH_BLOCK.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_EACH_BLOCK, children) + } + SVELTE_EACH_CLOSING_BLOCK => { + 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 + && element.kind() == T![each] + { + 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 current_element.is_some() { + return RawSyntaxNode::new( + SVELTE_EACH_CLOSING_BLOCK.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_EACH_CLOSING_BLOCK, children) + } + SVELTE_EACH_OPENING_BLOCK => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<6usize> = 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 + && element.kind() == T![each] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && HtmlTextExpression::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T![as] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && HtmlTextExpression::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 current_element.is_some() { + return RawSyntaxNode::new( + SVELTE_EACH_OPENING_BLOCK.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_EACH_OPENING_BLOCK, children) + } SVELTE_ELSE_CLAUSE => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); @@ -1101,7 +1221,7 @@ impl SyntaxFactory for HtmlSyntaxFactory { let mut slots: RawNodeSlots<1usize> = RawNodeSlots::default(); let mut current_element = elements.next(); if let Some(element) = ¤t_element - && element.kind() == SVELTE_IDENT + && element.kind() == IDENT { slots.mark_present(); current_element = elements.next(); @@ -1160,7 +1280,7 @@ impl SyntaxFactory for HtmlSyntaxFactory { let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); let mut current_element = elements.next(); if let Some(element) = ¤t_element - && element.kind() == VUE_IDENT + && element.kind() == IDENT { slots.mark_present(); current_element = elements.next(); diff --git a/crates/biome_html_formatter/src/generated.rs b/crates/biome_html_formatter/src/generated.rs index b7b12149873b..81c4fb9d6cf5 100644 --- a/crates/biome_html_formatter/src/generated.rs +++ b/crates/biome_html_formatter/src/generated.rs @@ -830,6 +830,120 @@ impl IntoFormat for biome_html_syntax::SvelteDebugBlock { ) } } +impl FormatRule + for crate::svelte::auxiliary::each_block::FormatSvelteEachBlock +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteEachBlock, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteEachBlock { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteEachBlock, + crate::svelte::auxiliary::each_block::FormatSvelteEachBlock, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::each_block::FormatSvelteEachBlock::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteEachBlock { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteEachBlock, + crate::svelte::auxiliary::each_block::FormatSvelteEachBlock, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::each_block::FormatSvelteEachBlock::default(), + ) + } +} +impl FormatRule + for crate::svelte::auxiliary::each_closing_block::FormatSvelteEachClosingBlock +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteEachClosingBlock, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteEachClosingBlock { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteEachClosingBlock, + crate::svelte::auxiliary::each_closing_block::FormatSvelteEachClosingBlock, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::each_closing_block::FormatSvelteEachClosingBlock::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteEachClosingBlock { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteEachClosingBlock, + crate::svelte::auxiliary::each_closing_block::FormatSvelteEachClosingBlock, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::each_closing_block::FormatSvelteEachClosingBlock::default(), + ) + } +} +impl FormatRule + for crate::svelte::auxiliary::each_opening_block::FormatSvelteEachOpeningBlock +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteEachOpeningBlock, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteEachOpeningBlock { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteEachOpeningBlock, + crate::svelte::auxiliary::each_opening_block::FormatSvelteEachOpeningBlock, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::each_opening_block::FormatSvelteEachOpeningBlock::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteEachOpeningBlock { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteEachOpeningBlock, + crate::svelte::auxiliary::each_opening_block::FormatSvelteEachOpeningBlock, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::each_opening_block::FormatSvelteEachOpeningBlock::default(), + ) + } +} impl FormatRule for crate::svelte::auxiliary::else_clause::FormatSvelteElseClause { diff --git a/crates/biome_html_formatter/src/svelte/any/block.rs b/crates/biome_html_formatter/src/svelte/any/block.rs index 469abf858f68..f04e8ac58155 100644 --- a/crates/biome_html_formatter/src/svelte/any/block.rs +++ b/crates/biome_html_formatter/src/svelte/any/block.rs @@ -11,6 +11,7 @@ impl FormatRule for FormatAnySvelteBlock { AnySvelteBlock::SvelteBogusBlock(node) => node.format().fmt(f), AnySvelteBlock::SvelteConstBlock(node) => node.format().fmt(f), AnySvelteBlock::SvelteDebugBlock(node) => node.format().fmt(f), + AnySvelteBlock::SvelteEachBlock(node) => node.format().fmt(f), AnySvelteBlock::SvelteHtmlBlock(node) => node.format().fmt(f), AnySvelteBlock::SvelteIfBlock(node) => node.format().fmt(f), AnySvelteBlock::SvelteKeyBlock(node) => node.format().fmt(f), diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_block.rs new file mode 100644 index 000000000000..9d6ea9bb431c --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_block.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_html_syntax::SvelteEachBlock; +use biome_rowan::AstNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteEachBlock; +impl FormatNodeRule for FormatSvelteEachBlock { + fn fmt_fields(&self, node: &SvelteEachBlock, f: &mut HtmlFormatter) -> FormatResult<()> { + format_html_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_closing_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_closing_block.rs new file mode 100644 index 000000000000..41f49437b6fb --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_closing_block.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_html_syntax::SvelteEachClosingBlock; +use biome_rowan::AstNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteEachClosingBlock; +impl FormatNodeRule for FormatSvelteEachClosingBlock { + fn fmt_fields(&self, node: &SvelteEachClosingBlock, f: &mut HtmlFormatter) -> FormatResult<()> { + format_html_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_opening_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_opening_block.rs new file mode 100644 index 000000000000..1f8faba28b8d --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_opening_block.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_html_syntax::SvelteEachOpeningBlock; +use biome_rowan::AstNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteEachOpeningBlock; +impl FormatNodeRule for FormatSvelteEachOpeningBlock { + fn fmt_fields(&self, node: &SvelteEachOpeningBlock, f: &mut HtmlFormatter) -> FormatResult<()> { + format_html_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/mod.rs b/crates/biome_html_formatter/src/svelte/auxiliary/mod.rs index 2d93e9dd71d2..8676b43b4764 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/mod.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/mod.rs @@ -3,6 +3,9 @@ pub(crate) mod attach_attribute; pub(crate) mod const_block; pub(crate) mod debug_block; +pub(crate) mod each_block; +pub(crate) mod each_closing_block; +pub(crate) mod each_opening_block; pub(crate) mod else_clause; pub(crate) mod else_if_clause; pub(crate) mod html_block; diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/name.rs b/crates/biome_html_formatter/src/svelte/auxiliary/name.rs index ff67192f76c5..7140c93c3cf4 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/name.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/name.rs @@ -6,7 +6,7 @@ use biome_html_syntax::{SvelteName, SvelteNameFields}; pub(crate) struct FormatSvelteName; impl FormatNodeRule for FormatSvelteName { fn fmt_fields(&self, node: &SvelteName, f: &mut HtmlFormatter) -> FormatResult<()> { - let SvelteNameFields { svelte_ident_token } = node.as_fields(); - write!(f, [svelte_ident_token.format()]) + let SvelteNameFields { ident_token } = node.as_fields(); + write!(f, [ident_token.format()]) } } diff --git a/crates/biome_html_parser/src/lexer/mod.rs b/crates/biome_html_parser/src/lexer/mod.rs index b9f0819e5001..48757a62fd87 100644 --- a/crates/biome_html_parser/src/lexer/mod.rs +++ b/crates/biome_html_parser/src/lexer/mod.rs @@ -4,9 +4,9 @@ use crate::token_source::{ HtmlEmbeddedLanguage, HtmlLexContext, HtmlReLexContext, TextExpressionKind, }; use biome_html_syntax::HtmlSyntaxKind::{ - ATTACH_KW, COMMENT, CONST_KW, DEBUG_KW, DOCTYPE_KW, ELSE_KW, EOF, ERROR_TOKEN, HTML_KW, - HTML_LITERAL, HTML_STRING_LITERAL, IF_KW, KEY_KW, NEWLINE, RENDER_KW, SVELTE_IDENT, TOMBSTONE, - UNICODE_BOM, WHITESPACE, + AS_KW, ATTACH_KW, COMMENT, CONST_KW, DEBUG_KW, DOCTYPE_KW, EACH_KW, ELSE_KW, EOF, ERROR_TOKEN, + HTML_KW, HTML_LITERAL, HTML_STRING_LITERAL, IDENT, IF_KW, KEY_KW, NEWLINE, RENDER_KW, + TOMBSTONE, UNICODE_BOM, WHITESPACE, }; use biome_html_syntax::{HtmlSyntaxKind, T, TextLen, TextSize}; use biome_parser::diagnostic::ParseDiagnostic; @@ -33,7 +33,6 @@ pub(crate) struct HtmlLexer<'src> { enum IdentifierContext { None, Doctype, - Svelte, Vue, VueDirectiveArgument, } @@ -42,10 +41,6 @@ impl IdentifierContext { const fn is_doctype(&self) -> bool { matches!(self, Self::Doctype) } - - const fn is_svelte(&self) -> bool { - matches!(self, Self::Svelte) - } } impl<'src> HtmlLexer<'src> { @@ -96,9 +91,9 @@ impl<'src> HtmlLexer<'src> { _ if self.current_kind != T![<] && is_attribute_name_byte(current) => { self.consume_identifier(current, IdentifierContext::None) } - _ if is_at_svelte_start_identifier(current) => { - self.consume_identifier(current, IdentifierContext::Svelte) - } + _ if is_at_start_identifier(current) => self + .consume_language_identifier(current) + .unwrap_or_else(|| self.consume_unexpected_character()), _ => { if self.position == 0 && let Some((bom, bom_size)) = self.consume_potential_bom(UNICODE_BOM) @@ -201,6 +196,9 @@ impl<'src> HtmlLexer<'src> { self.consume_byte(HTML_LITERAL) } } + _ if is_at_start_identifier(current) => self + .consume_language_identifier(current) + .unwrap_or_else(|| self.consume_html_text(current)), _ => { if self.position == 0 && let Some((bom, bom_size)) = self.consume_potential_bom(UNICODE_BOM) @@ -401,9 +399,9 @@ impl<'src> HtmlLexer<'src> { b',' => self.consume_byte(T![,]), b'{' if self.at_svelte_opening_block() => self.consume_svelte_opening_block(), b'{' => self.consume_byte(T!['{']), - _ if is_at_svelte_start_identifier(current) => { - self.consume_identifier(current, IdentifierContext::Svelte) - } + _ if is_at_start_identifier(current) => self + .consume_language_identifier(current) + .unwrap_or_else(|| self.consume_single_text_expression()), _ => self.consume_single_text_expression(), } } @@ -434,6 +432,48 @@ impl<'src> HtmlLexer<'src> { debug_assert!(self.source.is_char_boundary(self.position)); } + /// Attempts to consume HTML-ish languages identifiers. If none is found, the function + /// restores the position of the lexer and returns [None]. + fn consume_language_identifier(&mut self, first: u8) -> Option { + self.assert_current_char_boundary(); + let starting_position = self.position; + const BUFFER_SIZE: usize = 14; + let mut buffer = [0u8; BUFFER_SIZE]; + buffer[0] = first; + let mut len = 1; + + self.advance_byte_or_char(first); + + while let Some(byte) = self.current_byte() { + if is_at_continue_identifier(byte) { + if len < BUFFER_SIZE { + buffer[len] = byte; + len += 1; + } + self.advance(1) + } else { + break; + } + } + + Some(match &buffer[..len] { + b"debug" => DEBUG_KW, + b"attach" => ATTACH_KW, + b"const" => CONST_KW, + b"render" => RENDER_KW, + b"html" => HTML_KW, + b"key" => KEY_KW, + b"if" => IF_KW, + b"else" => ELSE_KW, + b"each" => EACH_KW, + b"as" => AS_KW, + _ => { + self.position = starting_position; + return None; + } + }) + } + fn consume_identifier(&mut self, first: u8, context: IdentifierContext) -> HtmlSyntaxKind { self.assert_current_char_boundary(); @@ -458,17 +498,7 @@ impl<'src> HtmlLexer<'src> { break; } } - IdentifierContext::Svelte => { - if is_at_svelte_continue_identifier(byte) { - if len < BUFFER_SIZE { - buffer[len] = byte; - len += 1; - } - self.advance(1) - } else { - break; - } - } + IdentifierContext::Vue => { if is_attribute_name_byte_vue(byte) { if len < BUFFER_SIZE { @@ -497,19 +527,8 @@ impl<'src> HtmlLexer<'src> { } match &buffer[..len] { - b"doctype" | b"DOCTYPE" if !context.is_svelte() => DOCTYPE_KW, + b"doctype" | b"DOCTYPE" => DOCTYPE_KW, b"html" | b"HTML" if context.is_doctype() => HTML_KW, - buffer if context.is_svelte() => match buffer { - b"debug" => DEBUG_KW, - b"attach" => ATTACH_KW, - b"const" => CONST_KW, - b"render" => RENDER_KW, - b"html" => HTML_KW, - b"key" => KEY_KW, - b"if" => IF_KW, - b"else" => ELSE_KW, - _ => SVELTE_IDENT, - }, _ => HTML_LITERAL, } } @@ -1053,6 +1072,7 @@ impl<'src> ReLexer<'src> for HtmlLexer<'src> { Some(current) => match context { HtmlReLexContext::Svelte => self.consume_svelte(current), HtmlReLexContext::SingleTextExpression => self.consume_single_text_expression(), + HtmlReLexContext::HtmlText => self.consume_html_text(current), }, None => EOF, }; @@ -1096,12 +1116,12 @@ fn is_attribute_name_byte_vue(byte: u8) -> bool { } /// Identifiers can contain letters, numbers and `_` -fn is_at_svelte_continue_identifier(byte: u8) -> bool { +fn is_at_continue_identifier(byte: u8) -> bool { byte.is_ascii_alphanumeric() || byte == b'_' } /// Identifiers should start with letters or `_` -fn is_at_svelte_start_identifier(byte: u8) -> bool { +fn is_at_start_identifier(byte: u8) -> bool { byte.is_ascii_alphabetic() || byte == b'_' } diff --git a/crates/biome_html_parser/src/lexer/tests.rs b/crates/biome_html_parser/src/lexer/tests.rs index 9e0e5491f08e..700b2951a439 100644 --- a/crates/biome_html_parser/src/lexer/tests.rs +++ b/crates/biome_html_parser/src/lexer/tests.rs @@ -338,28 +338,32 @@ fn svelte_openings() { HtmlLexContext::Regular, "{@debug}", SV_CURLY_AT: 2, - HTML_LITERAL: 6, + DEBUG_KW: 5, + R_CURLY: 1 } assert_lex! { HtmlLexContext::Regular, "{/debug}", SV_CURLY_SLASH: 2, - HTML_LITERAL: 6, + DEBUG_KW: 5, + R_CURLY: 1 } assert_lex! { HtmlLexContext::Regular, "{:debug}", SV_CURLY_COLON: 2, - HTML_LITERAL: 6, + DEBUG_KW: 5, + R_CURLY: 1 } assert_lex! { HtmlLexContext::Regular, "{#debug}", SV_CURLY_HASH: 2, - HTML_LITERAL: 6, + DEBUG_KW: 5, + R_CURLY: 1 } } diff --git a/crates/biome_html_parser/src/syntax/mod.rs b/crates/biome_html_parser/src/syntax/mod.rs index 8412efcf10a2..ed76b59893dc 100644 --- a/crates/biome_html_parser/src/syntax/mod.rs +++ b/crates/biome_html_parser/src/syntax/mod.rs @@ -8,7 +8,7 @@ use crate::syntax::HtmlSyntaxFeatures::{DoubleTextExpressions, SingleTextExpress use crate::syntax::astro::parse_astro_fence; use crate::syntax::parse_error::*; use crate::syntax::svelte::{ - parse_attach_attribute, parse_svelte_at_block, parse_svelte_hash_block, + is_at_svelte_keyword, parse_attach_attribute, parse_svelte_at_block, parse_svelte_hash_block, }; use crate::syntax::vue::{ parse_vue_directive, parse_vue_v_bind_shorthand_directive, parse_vue_v_on_shorthand_directive, @@ -261,6 +261,14 @@ pub(crate) fn parse_html_element(p: &mut HtmlParser) -> ParsedSyntax { p.bump_remap(HTML_LITERAL); Present(m.complete(p, HTML_CONTENT)) } + // At this position, we shouldn't have svelte keyword, so we relex everything + // as text + _ if is_at_svelte_keyword(p) => { + let m = p.start(); + p.re_lex(HtmlReLexContext::HtmlText); + p.bump_with_context(HTML_LITERAL, HtmlLexContext::Regular); + Present(m.complete(p, HTML_CONTENT)) + } HTML_LITERAL => { let m = p.start(); p.bump_with_context(HTML_LITERAL, HtmlLexContext::Regular); @@ -634,11 +642,7 @@ fn parse_single_text_expression_content(p: &mut HtmlParser) -> ParsedSyntax { } let m = p.start(); - if p.at(SVELTE_IDENT) { - p.re_lex(HtmlReLexContext::SingleTextExpression); - } else { - p.bump_remap(HTML_LITERAL); - } + p.bump_remap(HTML_LITERAL); Present(m.complete(p, HTML_TEXT_EXPRESSION)) } diff --git a/crates/biome_html_parser/src/syntax/svelte.rs b/crates/biome_html_parser/src/syntax/svelte.rs index 4648b8f26273..a13ec0fe85ae 100644 --- a/crates/biome_html_parser/src/syntax/svelte.rs +++ b/crates/biome_html_parser/src/syntax/svelte.rs @@ -5,11 +5,12 @@ use crate::syntax::parse_error::{ use crate::syntax::{parse_html_element, parse_single_text_expression_content}; use crate::token_source::{HtmlLexContext, HtmlReLexContext}; use biome_html_syntax::HtmlSyntaxKind::{ - EOF, HTML_BOGUS_ELEMENT, HTML_ELEMENT_LIST, SVELTE_ATTACH_ATTRIBUTE, SVELTE_BINDING_LIST, - SVELTE_BOGUS_BLOCK, SVELTE_CONST_BLOCK, SVELTE_DEBUG_BLOCK, SVELTE_ELSE_CLAUSE, - SVELTE_ELSE_IF_CLAUSE, SVELTE_ELSE_IF_CLAUSE_LIST, SVELTE_HTML_BLOCK, SVELTE_IDENT, - SVELTE_IF_BLOCK, SVELTE_IF_CLOSING_BLOCK, SVELTE_IF_OPENING_BLOCK, SVELTE_KEY_BLOCK, - SVELTE_KEY_CLOSING_BLOCK, SVELTE_KEY_OPENING_BLOCK, SVELTE_NAME, SVELTE_RENDER_BLOCK, + EOF, HTML_BOGUS_ELEMENT, HTML_ELEMENT_LIST, IDENT, SVELTE_ATTACH_ATTRIBUTE, + SVELTE_BINDING_LIST, SVELTE_BOGUS_BLOCK, SVELTE_CONST_BLOCK, SVELTE_DEBUG_BLOCK, + SVELTE_EACH_BLOCK, SVELTE_EACH_CLOSING_BLOCK, SVELTE_EACH_OPENING_BLOCK, SVELTE_ELSE_CLAUSE, + SVELTE_ELSE_IF_CLAUSE, SVELTE_ELSE_IF_CLAUSE_LIST, SVELTE_HTML_BLOCK, SVELTE_IF_BLOCK, + SVELTE_IF_CLOSING_BLOCK, SVELTE_IF_OPENING_BLOCK, SVELTE_KEY_BLOCK, SVELTE_KEY_CLOSING_BLOCK, + SVELTE_KEY_OPENING_BLOCK, SVELTE_NAME, SVELTE_RENDER_BLOCK, }; use biome_html_syntax::{HtmlSyntaxKind, T}; use biome_parser::parse_lists::{ParseNodeList, ParseSeparatedList}; @@ -28,6 +29,7 @@ pub(crate) fn parse_svelte_hash_block(p: &mut HtmlParser) -> ParsedSyntax { match p.cur() { T![key] => parse_key_block(p, m), T![if] => parse_if_block(p, m), + T![each] => parse_each_block(p, m), _ => { m.abandon(p); Absent @@ -148,6 +150,44 @@ pub(crate) fn parse_else_clause(p: &mut HtmlParser) -> ParsedSyntax { Present(m.complete(p, SVELTE_ELSE_CLAUSE)) } +fn parse_each_block(p: &mut HtmlParser, parent_marker: Marker) -> ParsedSyntax { + if !p.at(T![each]) { + parent_marker.abandon(p); + return Absent; + } + + let result = parse_each_opening_block(p, parent_marker); + let m = result.precede(p); + + SvelteElementList::new().parse_list(p); + + parse_closing_block(p, T![each], SVELTE_EACH_CLOSING_BLOCK).or_add_diagnostic(p, |p, range| { + expected_svelte_closing_block(p, range) + .with_detail(range.sub(m.start()), "This is where the block started.") + }); + Present(m.complete(p, SVELTE_EACH_BLOCK)) +} + +fn parse_each_opening_block(p: &mut HtmlParser, parent_marker: Marker) -> ParsedSyntax { + if !p.at(T![each]) { + parent_marker.abandon(p); + return Absent; + } + + p.bump_with_context(T![each], HtmlLexContext::single_expression()); + + // It should stop at `as` + parse_single_text_expression_content(p).ok(); + + p.expect_with_context(T![as], HtmlLexContext::single_expression()); + + parse_single_text_expression_content(p).ok(); + + p.expect(T!['}']); + + Present(parent_marker.complete(p, SVELTE_EACH_OPENING_BLOCK)) +} + /// Parses a `{# expression }` block. /// /// `node` is the name of the node to emit @@ -330,7 +370,7 @@ impl ParseSeparatedList for BindingList { fn parse_name(p: &mut HtmlParser) -> ParsedSyntax { let m = p.start(); - p.bump_remap_with_context(SVELTE_IDENT, HtmlLexContext::Svelte); + p.bump_remap_with_context(IDENT, HtmlLexContext::Svelte); Present(m.complete(p, SVELTE_NAME)) } @@ -400,22 +440,8 @@ impl ParseNodeList for SvelteElseIfClauseLit { if closing { return true; } - // Here we need to get creative. At the moment svelte keywords are correctly lexed - // only when we use the `Svelte` context. To retrieve them, we use the relex - // feature to bump two tokens, and relex them with the proper context. - // Once we retrieved the relexed tokens, we rewind the parser. - let curly_colon = p.at(T!["{:"]); - let checkpoint = p.checkpoint(); - p.bump_any(); - p.re_lex(HtmlReLexContext::Svelte); - let at_else = p.at(T![else]); - p.bump_any(); - p.re_lex(HtmlReLexContext::Svelte); - let at_if = p.at(T![if]); - - let condition = curly_colon && at_else && !at_if; - p.rewind(checkpoint); - condition + + p.at(T!["{:"]) && p.nth_at(1, T![else]) && !p.nth_at(2, T![if]) } fn recover( @@ -433,3 +459,18 @@ impl ParseNodeList for SvelteElseIfClauseLit { ) } } + +pub(crate) fn is_at_svelte_keyword(p: &HtmlParser) -> bool { + matches!( + p.cur(), + T![if] + | T![else] + | T![each] + | T![debug] + | T![const] + | T![attach] + | T![render] + | T![key] + | T![as] + ) +} diff --git a/crates/biome_html_parser/src/syntax/vue.rs b/crates/biome_html_parser/src/syntax/vue.rs index 75e534c91d37..577fe5bc123b 100644 --- a/crates/biome_html_parser/src/syntax/vue.rs +++ b/crates/biome_html_parser/src/syntax/vue.rs @@ -20,8 +20,8 @@ pub(crate) fn parse_vue_directive(p: &mut HtmlParser) -> ParsedSyntax { let m = p.start(); let pos = p.source().position(); - // FIXME: Ideally, the lexer would just lex VUE_IDENT directly - p.bump_remap_with_context(VUE_IDENT, HtmlLexContext::InsideTagVue); + // FIXME: Ideally, the lexer would just lex IDENT directly + p.bump_remap_with_context(IDENT, HtmlLexContext::InsideTagVue); if p.at(T![:]) { // is there any trivia after the directive name and before the colon? if let Some(last_trivia) = p.source().trivia_list.last() diff --git a/crates/biome_html_parser/src/token_source.rs b/crates/biome_html_parser/src/token_source.rs index b98f1850b6b3..b21055ed1233 100644 --- a/crates/biome_html_parser/src/token_source.rs +++ b/crates/biome_html_parser/src/token_source.rs @@ -103,6 +103,8 @@ impl LexContext for HtmlLexContext { pub(crate) enum HtmlReLexContext { Svelte, SingleTextExpression, + /// Relex tokens using `HtmlLexer::consume_html_text` + HtmlText, } pub(crate) type HtmlTokenSourceCheckpoint = TokenSourceCheckpoint; diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/debug.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/debug.svelte.snap index cd1bf42a420b..0162b0bdbd1b 100644 --- a/crates/biome_html_parser/tests/html_specs/error/svelte/debug.svelte.snap +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/debug.svelte.snap @@ -25,15 +25,15 @@ HtmlRoot { debug_token: DEBUG_KW@2..7 "debug" [] [], bindings: SvelteBindingList [ SvelteName { - svelte_ident_token: SVELTE_IDENT@7..10 "{@" [Newline("\n")] [], + ident_token: IDENT@7..10 "{@" [Newline("\n")] [], }, missing separator, SvelteName { - svelte_ident_token: SVELTE_IDENT@10..16 "debug" [] [Whitespace(" ")], + ident_token: IDENT@10..16 "debug" [] [Whitespace(" ")], }, missing separator, SvelteName { - svelte_ident_token: SVELTE_IDENT@16..25 "something" [] [], + ident_token: IDENT@16..25 "something" [] [], }, ], r_curly_token: R_CURLY@25..26 "}" [] [], @@ -43,7 +43,7 @@ HtmlRoot { debug_token: DEBUG_KW@29..35 "debug" [] [Whitespace(" ")], bindings: SvelteBindingList [ SvelteName { - svelte_ident_token: SVELTE_IDENT@35..40 "debug" [] [], + ident_token: IDENT@35..40 "debug" [] [], }, ], r_curly_token: R_CURLY@40..41 "}" [] [], @@ -66,20 +66,20 @@ HtmlRoot { 1: DEBUG_KW@2..7 "debug" [] [] 2: SVELTE_BINDING_LIST@7..25 0: SVELTE_NAME@7..10 - 0: SVELTE_IDENT@7..10 "{@" [Newline("\n")] [] + 0: IDENT@7..10 "{@" [Newline("\n")] [] 1: (empty) 2: SVELTE_NAME@10..16 - 0: SVELTE_IDENT@10..16 "debug" [] [Whitespace(" ")] + 0: IDENT@10..16 "debug" [] [Whitespace(" ")] 3: (empty) 4: SVELTE_NAME@16..25 - 0: SVELTE_IDENT@16..25 "something" [] [] + 0: IDENT@16..25 "something" [] [] 3: R_CURLY@25..26 "}" [] [] 1: SVELTE_DEBUG_BLOCK@26..41 0: SV_CURLY_AT@26..29 "{@" [Newline("\n")] [] 1: DEBUG_KW@29..35 "debug" [] [Whitespace(" ")] 2: SVELTE_BINDING_LIST@35..40 0: SVELTE_NAME@35..40 - 0: SVELTE_IDENT@35..40 "debug" [] [] + 0: IDENT@35..40 "debug" [] [] 3: R_CURLY@40..41 "}" [] [] 4: EOF@41..42 "" [Newline("\n")] [] diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/debug.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/debug.svelte.snap index 0887f3cffe57..63d26d253685 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/svelte/debug.svelte.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/debug.svelte.snap @@ -32,7 +32,7 @@ HtmlRoot { debug_token: DEBUG_KW@11..17 "debug" [] [Whitespace(" ")], bindings: SvelteBindingList [ SvelteName { - svelte_ident_token: SVELTE_IDENT@17..26 "something" [] [], + ident_token: IDENT@17..26 "something" [] [], }, ], r_curly_token: R_CURLY@26..27 "}" [] [], @@ -42,7 +42,7 @@ HtmlRoot { debug_token: DEBUG_KW@30..36 "debug" [] [Whitespace(" ")], bindings: SvelteBindingList [ SvelteName { - svelte_ident_token: SVELTE_IDENT@36..41 "debug" [] [], + ident_token: IDENT@36..41 "debug" [] [], }, ], r_curly_token: R_CURLY@41..42 "}" [] [], @@ -52,15 +52,7 @@ HtmlRoot { debug_token: DEBUG_KW@45..51 "debug" [] [Whitespace(" ")], bindings: SvelteBindingList [ SvelteName { - svelte_ident_token: SVELTE_IDENT@51..60 "something" [] [], - }, - COMMA@60..62 "," [] [Whitespace(" ")], - SvelteName { - svelte_ident_token: SVELTE_IDENT@62..71 "something" [] [], - }, - COMMA@71..73 "," [] [Whitespace(" ")], - SvelteName { - svelte_ident_token: SVELTE_IDENT@73..82 "something" [] [], + ident_token: IDENT@51..82 "something, something, something" [] [], }, ], r_curly_token: R_CURLY@82..83 "}" [] [], @@ -88,27 +80,21 @@ HtmlRoot { 1: DEBUG_KW@11..17 "debug" [] [Whitespace(" ")] 2: SVELTE_BINDING_LIST@17..26 0: SVELTE_NAME@17..26 - 0: SVELTE_IDENT@17..26 "something" [] [] + 0: IDENT@17..26 "something" [] [] 3: R_CURLY@26..27 "}" [] [] 2: SVELTE_DEBUG_BLOCK@27..42 0: SV_CURLY_AT@27..30 "{@" [Newline("\n")] [] 1: DEBUG_KW@30..36 "debug" [] [Whitespace(" ")] 2: SVELTE_BINDING_LIST@36..41 0: SVELTE_NAME@36..41 - 0: SVELTE_IDENT@36..41 "debug" [] [] + 0: IDENT@36..41 "debug" [] [] 3: R_CURLY@41..42 "}" [] [] 3: SVELTE_DEBUG_BLOCK@42..83 0: SV_CURLY_AT@42..45 "{@" [Newline("\n")] [] 1: DEBUG_KW@45..51 "debug" [] [Whitespace(" ")] 2: SVELTE_BINDING_LIST@51..82 - 0: SVELTE_NAME@51..60 - 0: SVELTE_IDENT@51..60 "something" [] [] - 1: COMMA@60..62 "," [] [Whitespace(" ")] - 2: SVELTE_NAME@62..71 - 0: SVELTE_IDENT@62..71 "something" [] [] - 3: COMMA@71..73 "," [] [Whitespace(" ")] - 4: SVELTE_NAME@73..82 - 0: SVELTE_IDENT@73..82 "something" [] [] + 0: SVELTE_NAME@51..82 + 0: IDENT@51..82 "something, something, something" [] [] 3: R_CURLY@82..83 "}" [] [] 4: EOF@83..84 "" [Newline("\n")] [] diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/arg-modifiers-no-value.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/arg-modifiers-no-value.vue.snap index d3bafea86237..cf694b039806 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/arg-modifiers-no-value.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/arg-modifiers-no-value.vue.snap @@ -28,7 +28,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@3..8 "v-foo" [] [], + name_token: IDENT@3..8 "v-foo" [] [], arg: VueDirectiveArgument { colon_token: COLON@8..9 ":" [] [], arg: VueStaticArgument { @@ -75,7 +75,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@38..43 "v-foo" [] [], + name_token: IDENT@38..43 "v-foo" [] [], arg: VueDirectiveArgument { colon_token: COLON@43..44 ":" [] [], arg: VueStaticArgument { @@ -111,7 +111,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@60..65 "v-foo" [] [], + name_token: IDENT@60..65 "v-foo" [] [], arg: VueDirectiveArgument { colon_token: COLON@65..66 ":" [] [], arg: VueStaticArgument { @@ -170,7 +170,7 @@ HtmlRoot { 0: HTML_LITERAL@1..3 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@3..16 0: VUE_DIRECTIVE@3..16 - 0: VUE_IDENT@3..8 "v-foo" [] [] + 0: IDENT@3..8 "v-foo" [] [] 1: VUE_DIRECTIVE_ARGUMENT@8..12 0: COLON@8..9 ":" [] [] 1: VUE_STATIC_ARGUMENT@9..12 @@ -202,7 +202,7 @@ HtmlRoot { 1: HTML_STRING@31..38 0: HTML_STRING_LITERAL@31..38 "\"flex\"" [] [Whitespace(" ")] 1: VUE_DIRECTIVE@38..51 - 0: VUE_IDENT@38..43 "v-foo" [] [] + 0: IDENT@38..43 "v-foo" [] [] 1: VUE_DIRECTIVE_ARGUMENT@43..47 0: COLON@43..44 ":" [] [] 1: VUE_STATIC_ARGUMENT@44..47 @@ -227,7 +227,7 @@ HtmlRoot { 0: HTML_LITERAL@58..60 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@60..86 0: VUE_DIRECTIVE@60..74 - 0: VUE_IDENT@60..65 "v-foo" [] [] + 0: IDENT@60..65 "v-foo" [] [] 1: VUE_DIRECTIVE_ARGUMENT@65..69 0: COLON@65..66 ":" [] [] 1: VUE_STATIC_ARGUMENT@66..69 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/arg-no-modifiers-no-value.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/arg-no-modifiers-no-value.vue.snap index 08e288756c29..1759bd180b43 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/arg-no-modifiers-no-value.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/arg-no-modifiers-no-value.vue.snap @@ -28,7 +28,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@3..8 "v-foo" [] [], + name_token: IDENT@3..8 "v-foo" [] [], arg: VueDirectiveArgument { colon_token: COLON@8..9 ":" [] [], arg: VueStaticArgument { @@ -70,7 +70,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@34..39 "v-foo" [] [], + name_token: IDENT@34..39 "v-foo" [] [], arg: VueDirectiveArgument { colon_token: COLON@39..40 ":" [] [], arg: VueStaticArgument { @@ -101,7 +101,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@52..57 "v-foo" [] [], + name_token: IDENT@52..57 "v-foo" [] [], arg: VueDirectiveArgument { colon_token: COLON@57..58 ":" [] [], arg: VueStaticArgument { @@ -155,7 +155,7 @@ HtmlRoot { 0: HTML_LITERAL@1..3 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@3..12 0: VUE_DIRECTIVE@3..12 - 0: VUE_IDENT@3..8 "v-foo" [] [] + 0: IDENT@3..8 "v-foo" [] [] 1: VUE_DIRECTIVE_ARGUMENT@8..12 0: COLON@8..9 ":" [] [] 1: VUE_STATIC_ARGUMENT@9..12 @@ -184,7 +184,7 @@ HtmlRoot { 1: HTML_STRING@27..34 0: HTML_STRING_LITERAL@27..34 "\"flex\"" [] [Whitespace(" ")] 1: VUE_DIRECTIVE@34..43 - 0: VUE_IDENT@34..39 "v-foo" [] [] + 0: IDENT@34..39 "v-foo" [] [] 1: VUE_DIRECTIVE_ARGUMENT@39..43 0: COLON@39..40 ":" [] [] 1: VUE_STATIC_ARGUMENT@40..43 @@ -206,7 +206,7 @@ HtmlRoot { 0: HTML_LITERAL@50..52 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@52..74 0: VUE_DIRECTIVE@52..62 - 0: VUE_IDENT@52..57 "v-foo" [] [] + 0: IDENT@52..57 "v-foo" [] [] 1: VUE_DIRECTIVE_ARGUMENT@57..62 0: COLON@57..58 ":" [] [] 1: VUE_STATIC_ARGUMENT@58..62 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/directive-only.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/directive-only.vue.snap index 096f193df199..a803ad757641 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/directive-only.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/directive-only.vue.snap @@ -28,7 +28,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@3..8 "v-foo" [] [], + name_token: IDENT@3..8 "v-foo" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: missing (optional), @@ -65,7 +65,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@30..35 "v-foo" [] [], + name_token: IDENT@30..35 "v-foo" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: missing (optional), @@ -91,7 +91,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@44..50 "v-foo" [] [Whitespace(" ")], + name_token: IDENT@44..50 "v-foo" [] [Whitespace(" ")], arg: missing (optional), modifiers: VueModifierList [], initializer: missing (optional), @@ -140,7 +140,7 @@ HtmlRoot { 0: HTML_LITERAL@1..3 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@3..8 0: VUE_DIRECTIVE@3..8 - 0: VUE_IDENT@3..8 "v-foo" [] [] + 0: IDENT@3..8 "v-foo" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@8..8 3: (empty) @@ -166,7 +166,7 @@ HtmlRoot { 1: HTML_STRING@23..30 0: HTML_STRING_LITERAL@23..30 "\"flex\"" [] [Whitespace(" ")] 1: VUE_DIRECTIVE@30..35 - 0: VUE_IDENT@30..35 "v-foo" [] [] + 0: IDENT@30..35 "v-foo" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@35..35 3: (empty) @@ -185,7 +185,7 @@ HtmlRoot { 0: HTML_LITERAL@42..44 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@44..62 0: VUE_DIRECTIVE@44..50 - 0: VUE_IDENT@44..50 "v-foo" [] [Whitespace(" ")] + 0: IDENT@44..50 "v-foo" [] [Whitespace(" ")] 1: (empty) 2: VUE_MODIFIER_LIST@50..50 3: (empty) diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/modifiers-no-arg-no-value.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/modifiers-no-arg-no-value.vue.snap index fc9402d0cd3d..ad9e3db72616 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/modifiers-no-arg-no-value.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/generic-directives/modifiers-no-arg-no-value.vue.snap @@ -28,7 +28,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@3..8 "v-foo" [] [], + name_token: IDENT@3..8 "v-foo" [] [], arg: missing (optional), modifiers: VueModifierList [ VueModifier { @@ -70,7 +70,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@34..39 "v-foo" [] [], + name_token: IDENT@34..39 "v-foo" [] [], arg: missing (optional), modifiers: VueModifierList [ VueModifier { @@ -101,7 +101,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@52..57 "v-foo" [] [], + name_token: IDENT@52..57 "v-foo" [] [], arg: missing (optional), modifiers: VueModifierList [ VueModifier { @@ -155,7 +155,7 @@ HtmlRoot { 0: HTML_LITERAL@1..3 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@3..12 0: VUE_DIRECTIVE@3..12 - 0: VUE_IDENT@3..8 "v-foo" [] [] + 0: IDENT@3..8 "v-foo" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@8..12 0: VUE_MODIFIER@8..12 @@ -184,7 +184,7 @@ HtmlRoot { 1: HTML_STRING@27..34 0: HTML_STRING_LITERAL@27..34 "\"flex\"" [] [Whitespace(" ")] 1: VUE_DIRECTIVE@34..43 - 0: VUE_IDENT@34..39 "v-foo" [] [] + 0: IDENT@34..39 "v-foo" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@39..43 0: VUE_MODIFIER@39..43 @@ -206,7 +206,7 @@ HtmlRoot { 0: HTML_LITERAL@50..52 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@52..74 0: VUE_DIRECTIVE@52..62 - 0: VUE_IDENT@52..57 "v-foo" [] [] + 0: IDENT@52..57 "v-foo" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@57..62 0: VUE_MODIFIER@57..62 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/modifier.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/modifier.vue.snap index 49182fb9fae0..64b157e499f8 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/modifier.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/modifier.vue.snap @@ -37,7 +37,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@17..23 "v-bind" [] [], + name_token: IDENT@17..23 "v-bind" [] [], arg: VueDirectiveArgument { colon_token: COLON@23..24 ":" [] [], arg: VueStaticArgument { @@ -108,7 +108,7 @@ HtmlRoot { 0: HTML_LITERAL@13..17 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@17..41 0: VUE_DIRECTIVE@17..41 - 0: VUE_IDENT@17..23 "v-bind" [] [] + 0: IDENT@17..23 "v-bind" [] [] 1: VUE_DIRECTIVE_ARGUMENT@23..27 0: COLON@23..24 ":" [] [] 1: VUE_STATIC_ARGUMENT@24..27 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/modifiers-all-variants.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/modifiers-all-variants.vue.snap index ad4e1946ed51..18f589126a69 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/modifiers-all-variants.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/modifiers-all-variants.vue.snap @@ -39,7 +39,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@17..22 "v-foo" [] [], + name_token: IDENT@17..22 "v-foo" [] [], arg: VueDirectiveArgument { colon_token: COLON@22..23 ":" [] [], arg: VueStaticArgument { @@ -188,7 +188,7 @@ HtmlRoot { 0: HTML_LITERAL@13..17 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@17..34 0: VUE_DIRECTIVE@17..34 - 0: VUE_IDENT@17..22 "v-foo" [] [] + 0: IDENT@17..22 "v-foo" [] [] 1: VUE_DIRECTIVE_ARGUMENT@22..26 0: COLON@22..23 ":" [] [] 1: VUE_STATIC_ARGUMENT@23..26 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-bind-mixed.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-bind-mixed.vue.snap index 93eef26a7fad..67c09d2e31a9 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-bind-mixed.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-bind-mixed.vue.snap @@ -55,7 +55,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@46..52 "v-bind" [] [], + name_token: IDENT@46..52 "v-bind" [] [], arg: VueDirectiveArgument { colon_token: COLON@52..53 ":" [] [], arg: VueStaticArgument { @@ -111,7 +111,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@171..177 "v-bind" [] [], + name_token: IDENT@171..177 "v-bind" [] [], arg: VueDirectiveArgument { colon_token: COLON@177..178 ":" [] [], arg: VueDynamicArgument { @@ -186,7 +186,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@343..349 "v-bind" [] [], + name_token: IDENT@343..349 "v-bind" [] [], arg: VueDirectiveArgument { colon_token: COLON@349..350 ":" [] [], arg: VueDynamicArgument { @@ -243,7 +243,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@453..459 "v-bind" [] [], + name_token: IDENT@453..459 "v-bind" [] [], arg: VueDirectiveArgument { colon_token: COLON@459..460 ":" [] [], arg: VueDynamicArgument { @@ -333,7 +333,7 @@ HtmlRoot { 0: HTML_LITERAL@40..46 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@46..62 0: VUE_DIRECTIVE@46..62 - 0: VUE_IDENT@46..52 "v-bind" [] [] + 0: IDENT@46..52 "v-bind" [] [] 1: VUE_DIRECTIVE_ARGUMENT@52..62 0: COLON@52..53 ":" [] [] 1: VUE_STATIC_ARGUMENT@53..62 @@ -372,7 +372,7 @@ HtmlRoot { 0: HTML_LITERAL@167..171 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@171..199 0: VUE_DIRECTIVE@171..199 - 0: VUE_IDENT@171..177 "v-bind" [] [] + 0: IDENT@171..177 "v-bind" [] [] 1: VUE_DIRECTIVE_ARGUMENT@177..187 0: COLON@177..178 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@178..187 @@ -425,7 +425,7 @@ HtmlRoot { 0: HTML_LITERAL@333..343 "component" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@343..372 0: VUE_DIRECTIVE@343..372 - 0: VUE_IDENT@343..349 "v-bind" [] [] + 0: IDENT@343..349 "v-bind" [] [] 1: VUE_DIRECTIVE_ARGUMENT@349..360 0: COLON@349..350 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@350..360 @@ -464,7 +464,7 @@ HtmlRoot { 1: HTML_STRING@444..453 0: HTML_STRING_LITERAL@444..453 "\"imgSrc\"" [] [Whitespace(" ")] 1: VUE_DIRECTIVE@453..479 - 0: VUE_IDENT@453..459 "v-bind" [] [] + 0: IDENT@453..459 "v-bind" [] [] 1: VUE_DIRECTIVE_ARGUMENT@459..468 0: COLON@459..460 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@460..468 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-bind.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-bind.vue.snap index 27a4be15df84..b97122d1298a 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-bind.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-bind.vue.snap @@ -38,7 +38,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@17..23 "v-bind" [] [], + name_token: IDENT@17..23 "v-bind" [] [], arg: VueDirectiveArgument { colon_token: COLON@23..24 ":" [] [], arg: VueStaticArgument { @@ -104,7 +104,7 @@ HtmlRoot { 0: HTML_LITERAL@13..17 "Foo" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@17..32 0: VUE_DIRECTIVE@17..32 - 0: VUE_IDENT@17..23 "v-bind" [] [] + 0: IDENT@17..23 "v-bind" [] [] 1: VUE_DIRECTIVE_ARGUMENT@23..28 0: COLON@23..24 ":" [] [] 1: VUE_STATIC_ARGUMENT@24..28 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-dynamic-chains.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-dynamic-chains.vue.snap index ad7b677ab1bc..c930a25338b1 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-dynamic-chains.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-dynamic-chains.vue.snap @@ -106,7 +106,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@234..244 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [], + name_token: IDENT@234..244 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@244..245 ":" [] [], arg: VueDynamicArgument { @@ -166,7 +166,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@323..331 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [], + name_token: IDENT@323..331 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@331..332 ":" [] [], arg: VueDynamicArgument { @@ -301,7 +301,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@623..631 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [], + name_token: IDENT@623..631 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@631..632 ":" [] [], arg: VueDynamicArgument { @@ -335,7 +335,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@753..763 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [], + name_token: IDENT@753..763 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@763..764 ":" [] [], arg: VueDynamicArgument { @@ -358,7 +358,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@790..800 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [], + name_token: IDENT@790..800 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@800..801 ":" [] [], arg: VueDynamicArgument { @@ -381,7 +381,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@828..836 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [], + name_token: IDENT@828..836 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@836..837 ":" [] [], arg: VueDynamicArgument { @@ -459,7 +459,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@1022..1032 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [], + name_token: IDENT@1022..1032 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@1032..1033 ":" [] [], arg: VueDynamicArgument { @@ -595,7 +595,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@1277..1285 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [], + name_token: IDENT@1277..1285 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@1285..1286 ":" [] [], arg: VueDynamicArgument { @@ -751,7 +751,7 @@ HtmlRoot { 0: HTML_LITERAL@228..234 "button" [] [] 2: HTML_ATTRIBUTE_LIST@234..425 0: VUE_DIRECTIVE@234..274 - 0: VUE_IDENT@234..244 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [] + 0: IDENT@234..244 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@244..254 0: COLON@244..245 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@245..254 @@ -792,7 +792,7 @@ HtmlRoot { 1: HTML_STRING@318..323 0: HTML_STRING_LITERAL@318..323 "\"'0'\"" [] [] 3: VUE_DIRECTIVE@323..359 - 0: VUE_IDENT@323..331 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [] + 0: IDENT@323..331 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@331..337 0: COLON@331..332 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@332..337 @@ -885,7 +885,7 @@ HtmlRoot { 1: HTML_STRING@614..623 0: HTML_STRING_LITERAL@614..623 "\"onEnter\"" [] [] 2: VUE_DIRECTIVE@623..659 - 0: VUE_IDENT@623..631 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [] + 0: IDENT@623..631 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@631..640 0: COLON@631..632 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@632..640 @@ -909,7 +909,7 @@ HtmlRoot { 0: HTML_LITERAL@742..753 "component-x" [] [] 2: HTML_ATTRIBUTE_LIST@753..912 0: VUE_DIRECTIVE@753..790 - 0: VUE_IDENT@753..763 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [] + 0: IDENT@753..763 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@763..774 0: COLON@763..764 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@764..774 @@ -925,7 +925,7 @@ HtmlRoot { 1: HTML_STRING@781..790 0: HTML_STRING_LITERAL@781..790 "\"propVal\"" [] [] 1: VUE_DIRECTIVE@790..828 - 0: VUE_IDENT@790..800 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [] + 0: IDENT@790..800 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@800..811 0: COLON@800..801 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@801..811 @@ -941,7 +941,7 @@ HtmlRoot { 1: HTML_STRING@818..828 0: HTML_STRING_LITERAL@818..828 "\"styleVal\"" [] [] 2: VUE_DIRECTIVE@828..863 - 0: VUE_IDENT@828..836 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [] + 0: IDENT@828..836 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@836..848 0: COLON@836..837 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@837..848 @@ -995,7 +995,7 @@ HtmlRoot { 0: HTML_LITERAL@1021..1022 "a" [] [] 2: HTML_ATTRIBUTE_LIST@1022..1129 0: VUE_DIRECTIVE@1022..1054 - 0: VUE_IDENT@1022..1032 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [] + 0: IDENT@1022..1032 "v-bind" [Newline("\n"), Whitespace("\t\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@1032..1042 0: COLON@1032..1033 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@1033..1042 @@ -1088,7 +1088,7 @@ HtmlRoot { 1: HTML_STRING@1271..1277 0: HTML_STRING_LITERAL@1271..1277 "\"valB\"" [] [] 2: VUE_DIRECTIVE@1277..1319 - 0: VUE_IDENT@1277..1285 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [] + 0: IDENT@1277..1285 "v-on" [Newline("\n"), Whitespace("\t\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@1285..1296 0: COLON@1285..1286 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@1286..1296 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-else-if.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-else-if.vue.snap index 35e5120b3b3b..ffb595793c69 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-else-if.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-else-if.vue.snap @@ -66,7 +66,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@43..47 "v-if" [] [], + name_token: IDENT@43..47 "v-if" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -101,7 +101,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@78..87 "v-else-if" [] [], + name_token: IDENT@78..87 "v-else-if" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -136,7 +136,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@118..124 "v-else" [] [], + name_token: IDENT@118..124 "v-else" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: missing (optional), @@ -166,7 +166,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@180..186 "v-bind" [] [], + name_token: IDENT@180..186 "v-bind" [] [], arg: VueDirectiveArgument { colon_token: COLON@186..187 ":" [] [], arg: VueStaticArgument { @@ -257,7 +257,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@259..265 "v-bind" [] [], + name_token: IDENT@259..265 "v-bind" [] [], arg: VueDirectiveArgument { colon_token: COLON@265..266 ":" [] [], arg: VueDynamicArgument { @@ -332,7 +332,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@399..403 "v-on" [] [], + name_token: IDENT@399..403 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@403..404 ":" [] [], arg: VueStaticArgument { @@ -391,7 +391,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@462..466 "v-on" [] [], + name_token: IDENT@462..466 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@466..467 ":" [] [], arg: VueDynamicArgument { @@ -509,7 +509,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@619..626 "v-model" [] [], + name_token: IDENT@619..626 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -530,7 +530,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@643..650 "v-model" [] [], + name_token: IDENT@643..650 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [ VueModifier { @@ -561,7 +561,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@696..702 "v-show" [] [], + name_token: IDENT@696..702 "v-show" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -602,7 +602,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@758..763 "v-for" [] [], + name_token: IDENT@758..763 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -692,7 +692,7 @@ HtmlRoot { 0: HTML_LITERAL@39..43 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@43..61 0: VUE_DIRECTIVE@43..61 - 0: VUE_IDENT@43..47 "v-if" [] [] + 0: IDENT@43..47 "v-if" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@47..47 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@47..61 @@ -716,7 +716,7 @@ HtmlRoot { 0: HTML_LITERAL@74..78 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@78..101 0: VUE_DIRECTIVE@78..101 - 0: VUE_IDENT@78..87 "v-else-if" [] [] + 0: IDENT@78..87 "v-else-if" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@87..87 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@87..101 @@ -740,7 +740,7 @@ HtmlRoot { 0: HTML_LITERAL@114..118 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@118..124 0: VUE_DIRECTIVE@118..124 - 0: VUE_IDENT@118..124 "v-else" [] [] + 0: IDENT@118..124 "v-else" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@124..124 3: (empty) @@ -761,7 +761,7 @@ HtmlRoot { 0: HTML_LITERAL@176..180 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@180..200 0: VUE_DIRECTIVE@180..200 - 0: VUE_IDENT@180..186 "v-bind" [] [] + 0: IDENT@180..186 "v-bind" [] [] 1: VUE_DIRECTIVE_ARGUMENT@186..192 0: COLON@186..187 ":" [] [] 1: VUE_STATIC_ARGUMENT@187..192 @@ -824,7 +824,7 @@ HtmlRoot { 0: HTML_LITERAL@255..259 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@259..288 0: VUE_DIRECTIVE@259..288 - 0: VUE_IDENT@259..265 "v-bind" [] [] + 0: IDENT@259..265 "v-bind" [] [] 1: VUE_DIRECTIVE_ARGUMENT@265..276 0: COLON@265..266 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@266..276 @@ -877,7 +877,7 @@ HtmlRoot { 0: HTML_LITERAL@392..399 "button" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@399..419 0: VUE_DIRECTIVE@399..419 - 0: VUE_IDENT@399..403 "v-on" [] [] + 0: IDENT@399..403 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@403..409 0: COLON@403..404 ":" [] [] 1: VUE_STATIC_ARGUMENT@404..409 @@ -918,7 +918,7 @@ HtmlRoot { 0: HTML_LITERAL@458..462 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@462..482 0: VUE_DIRECTIVE@462..482 - 0: VUE_IDENT@462..466 "v-on" [] [] + 0: IDENT@462..466 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@466..472 0: COLON@466..467 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@467..472 @@ -1001,7 +1001,7 @@ HtmlRoot { 0: HTML_LITERAL@613..619 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@619..633 0: VUE_DIRECTIVE@619..633 - 0: VUE_IDENT@619..626 "v-model" [] [] + 0: IDENT@619..626 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@626..626 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@626..633 @@ -1016,7 +1016,7 @@ HtmlRoot { 0: HTML_LITERAL@637..643 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@643..670 0: VUE_DIRECTIVE@643..670 - 0: VUE_IDENT@643..650 "v-model" [] [] + 0: IDENT@643..650 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@650..662 0: VUE_MODIFIER@650..655 @@ -1038,7 +1038,7 @@ HtmlRoot { 0: HTML_LITERAL@692..696 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@696..712 0: VUE_DIRECTIVE@696..712 - 0: VUE_IDENT@696..702 "v-show" [] [] + 0: IDENT@696..702 "v-show" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@702..702 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@702..712 @@ -1068,7 +1068,7 @@ HtmlRoot { 0: HTML_LITERAL@755..758 "li" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@758..801 0: VUE_DIRECTIVE@758..789 - 0: VUE_IDENT@758..763 "v-for" [] [] + 0: IDENT@758..763 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@763..763 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@763..789 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-else.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-else.vue.snap index 305546c8e2cc..e454b5d9d8d7 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-else.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-else.vue.snap @@ -39,7 +39,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@16..20 "v-if" [] [], + name_token: IDENT@16..20 "v-if" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -85,7 +85,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@56..63 "v-else" [] [Whitespace(" ")], + name_token: IDENT@56..63 "v-else" [] [Whitespace(" ")], arg: missing (optional), modifiers: VueModifierList [], initializer: missing (optional), @@ -156,7 +156,7 @@ HtmlRoot { 0: HTML_LITERAL@14..16 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@16..40 0: VUE_DIRECTIVE@16..28 - 0: VUE_IDENT@16..20 "v-if" [] [] + 0: IDENT@16..20 "v-if" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@20..20 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@20..28 @@ -187,7 +187,7 @@ HtmlRoot { 0: HTML_LITERAL@54..56 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@56..75 0: VUE_DIRECTIVE@56..63 - 0: VUE_IDENT@56..63 "v-else" [] [Whitespace(" ")] + 0: IDENT@56..63 "v-else" [] [Whitespace(" ")] 1: (empty) 2: VUE_MODIFIER_LIST@63..63 3: (empty) diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-for.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-for.vue.snap index 894bd6e1118f..f622cc8bf760 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-for.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-for.vue.snap @@ -90,7 +90,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@23..28 "v-for" [] [], + name_token: IDENT@23..28 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -164,7 +164,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@118..123 "v-for" [] [], + name_token: IDENT@118..123 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -203,7 +203,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@153..158 "v-for" [] [], + name_token: IDENT@153..158 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -252,7 +252,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@219..224 "v-for" [] [], + name_token: IDENT@219..224 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -263,7 +263,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@244..248 "v-if" [] [], + name_token: IDENT@244..248 "v-if" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -312,7 +312,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@323..328 "v-for" [] [], + name_token: IDENT@323..328 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -371,7 +371,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@418..423 "v-for" [] [], + name_token: IDENT@418..423 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -445,7 +445,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@538..543 "v-for" [] [], + name_token: IDENT@538..543 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -494,7 +494,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@669..674 "v-for" [] [], + name_token: IDENT@669..674 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -558,7 +558,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@808..813 "v-for" [] [], + name_token: IDENT@808..813 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -632,7 +632,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@926..931 "v-for" [] [], + name_token: IDENT@926..931 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -694,7 +694,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@1000..1005 "v-for" [] [], + name_token: IDENT@1000..1005 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -731,7 +731,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@1056..1061 "v-for" [] [], + name_token: IDENT@1056..1061 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -805,7 +805,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@1173..1178 "v-for" [] [], + name_token: IDENT@1173..1178 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -816,7 +816,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@1189..1193 "v-if" [] [], + name_token: IDENT@1189..1193 "v-if" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -870,7 +870,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@1247..1253 "v-else" [] [], + name_token: IDENT@1247..1253 "v-else" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: missing (optional), @@ -900,7 +900,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@1329..1334 "v-for" [] [], + name_token: IDENT@1329..1334 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -992,7 +992,7 @@ HtmlRoot { 0: HTML_LITERAL@20..23 "li" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@23..68 0: VUE_DIRECTIVE@23..54 - 0: VUE_IDENT@23..28 "v-for" [] [] + 0: IDENT@23..28 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@28..28 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@28..54 @@ -1042,7 +1042,7 @@ HtmlRoot { 0: HTML_LITERAL@114..118 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@118..132 0: VUE_DIRECTIVE@118..132 - 0: VUE_IDENT@118..123 "v-for" [] [] + 0: IDENT@118..123 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@123..123 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@123..132 @@ -1069,7 +1069,7 @@ HtmlRoot { 0: HTML_LITERAL@149..153 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@153..183 0: VUE_DIRECTIVE@153..183 - 0: VUE_IDENT@153..158 "v-for" [] [] + 0: IDENT@153..158 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@158..158 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@158..183 @@ -1103,7 +1103,7 @@ HtmlRoot { 0: HTML_LITERAL@215..219 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@219..262 0: VUE_DIRECTIVE@219..244 - 0: VUE_IDENT@219..224 "v-for" [] [] + 0: IDENT@219..224 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@224..224 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@224..244 @@ -1111,7 +1111,7 @@ HtmlRoot { 1: HTML_STRING@225..244 0: HTML_STRING_LITERAL@225..244 "\"(foo, i) of foos\"" [] [Whitespace(" ")] 1: VUE_DIRECTIVE@244..262 - 0: VUE_IDENT@244..248 "v-if" [] [] + 0: IDENT@244..248 "v-if" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@248..248 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@248..262 @@ -1146,7 +1146,7 @@ HtmlRoot { 0: HTML_LITERAL@320..323 "li" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@323..344 0: VUE_DIRECTIVE@323..344 - 0: VUE_IDENT@323..328 "v-for" [] [] + 0: IDENT@323..328 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@328..328 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@328..344 @@ -1187,7 +1187,7 @@ HtmlRoot { 0: HTML_LITERAL@415..418 "li" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@418..461 0: VUE_DIRECTIVE@418..449 - 0: VUE_IDENT@418..423 "v-for" [] [] + 0: IDENT@418..423 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@423..423 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@423..449 @@ -1237,7 +1237,7 @@ HtmlRoot { 0: HTML_LITERAL@534..538 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@538..565 0: VUE_DIRECTIVE@538..565 - 0: VUE_IDENT@538..543 "v-for" [] [] + 0: IDENT@538..543 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@543..543 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@543..565 @@ -1271,7 +1271,7 @@ HtmlRoot { 0: HTML_LITERAL@665..669 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@669..728 0: VUE_DIRECTIVE@669..704 - 0: VUE_IDENT@669..674 "v-for" [] [] + 0: IDENT@669..674 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@674..674 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@674..704 @@ -1315,7 +1315,7 @@ HtmlRoot { 0: HTML_LITERAL@799..808 "template" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@808..833 0: VUE_DIRECTIVE@808..833 - 0: VUE_IDENT@808..813 "v-for" [] [] + 0: IDENT@808..813 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@813..813 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@813..833 @@ -1365,7 +1365,7 @@ HtmlRoot { 0: HTML_LITERAL@916..926 "component" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@926..961 0: VUE_DIRECTIVE@926..942 - 0: VUE_IDENT@926..931 "v-for" [] [] + 0: IDENT@926..931 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@931..931 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@931..942 @@ -1409,7 +1409,7 @@ HtmlRoot { 0: HTML_LITERAL@997..1000 "li" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@1000..1045 0: VUE_DIRECTIVE@1000..1033 - 0: VUE_IDENT@1000..1005 "v-for" [] [] + 0: IDENT@1000..1005 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@1005..1005 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@1005..1033 @@ -1435,7 +1435,7 @@ HtmlRoot { 0: HTML_LITERAL@1051..1056 "span" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@1056..1091 0: VUE_DIRECTIVE@1056..1083 - 0: VUE_IDENT@1056..1061 "v-for" [] [] + 0: IDENT@1056..1061 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@1061..1061 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@1061..1083 @@ -1484,7 +1484,7 @@ HtmlRoot { 0: HTML_LITERAL@1169..1173 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@1173..1220 0: VUE_DIRECTIVE@1173..1189 - 0: VUE_IDENT@1173..1178 "v-for" [] [] + 0: IDENT@1173..1178 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@1178..1178 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@1178..1189 @@ -1492,7 +1492,7 @@ HtmlRoot { 1: HTML_STRING@1179..1189 0: HTML_STRING_LITERAL@1179..1189 "\"x in xs\"" [] [Whitespace(" ")] 1: VUE_DIRECTIVE@1189..1206 - 0: VUE_IDENT@1189..1193 "v-if" [] [] + 0: IDENT@1189..1193 "v-if" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@1193..1193 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@1193..1206 @@ -1529,7 +1529,7 @@ HtmlRoot { 0: HTML_LITERAL@1243..1247 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@1247..1253 0: VUE_DIRECTIVE@1247..1253 - 0: VUE_IDENT@1247..1253 "v-else" [] [] + 0: IDENT@1247..1253 "v-else" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@1253..1253 3: (empty) @@ -1550,7 +1550,7 @@ HtmlRoot { 0: HTML_LITERAL@1325..1329 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@1329..1370 0: VUE_DIRECTIVE@1329..1356 - 0: VUE_IDENT@1329..1334 "v-for" [] [] + 0: IDENT@1329..1334 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@1334..1334 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@1334..1356 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-html-text.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-html-text.vue.snap index 425ae1b5c116..a44c2afec895 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-html-text.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-html-text.vue.snap @@ -55,7 +55,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@17..23 "v-html" [] [], + name_token: IDENT@17..23 "v-html" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -86,7 +86,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@48..54 "v-text" [] [], + name_token: IDENT@48..54 "v-text" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -117,7 +117,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@77..83 "v-text" [] [], + name_token: IDENT@77..83 "v-text" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -148,7 +148,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@111..117 "v-html" [] [], + name_token: IDENT@111..117 "v-html" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -179,7 +179,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@150..156 "v-bind" [] [], + name_token: IDENT@150..156 "v-bind" [] [], arg: VueDirectiveArgument { colon_token: COLON@156..157 ":" [] [], arg: VueStaticArgument { @@ -270,7 +270,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@231..237 "v-bind" [] [], + name_token: IDENT@231..237 "v-bind" [] [], arg: VueDirectiveArgument { colon_token: COLON@237..238 ":" [] [], arg: VueDynamicArgument { @@ -345,7 +345,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@311..315 "v-on" [] [], + name_token: IDENT@311..315 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@315..316 ":" [] [], arg: VueStaticArgument { @@ -404,7 +404,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@376..380 "v-on" [] [], + name_token: IDENT@376..380 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@380..381 ":" [] [], arg: VueDynamicArgument { @@ -487,7 +487,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@465..472 "v-model" [] [], + name_token: IDENT@465..472 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -508,7 +508,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@491..498 "v-model" [] [], + name_token: IDENT@491..498 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [ VueModifier { @@ -539,7 +539,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@528..534 "v-show" [] [], + name_token: IDENT@528..534 "v-show" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -580,7 +580,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@564..569 "v-for" [] [], + name_token: IDENT@564..569 "v-for" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -670,7 +670,7 @@ HtmlRoot { 0: HTML_LITERAL@13..17 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@17..33 0: VUE_DIRECTIVE@17..33 - 0: VUE_IDENT@17..23 "v-html" [] [] + 0: IDENT@17..23 "v-html" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@23..23 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@23..33 @@ -692,7 +692,7 @@ HtmlRoot { 0: HTML_LITERAL@43..48 "span" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@48..64 0: VUE_DIRECTIVE@48..64 - 0: VUE_IDENT@48..54 "v-text" [] [] + 0: IDENT@48..54 "v-text" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@54..54 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@54..64 @@ -714,7 +714,7 @@ HtmlRoot { 0: HTML_LITERAL@75..77 "p" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@77..99 0: VUE_DIRECTIVE@77..99 - 0: VUE_IDENT@77..83 "v-text" [] [] + 0: IDENT@77..83 "v-text" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@83..83 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@83..99 @@ -736,7 +736,7 @@ HtmlRoot { 0: HTML_LITERAL@107..111 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@111..136 0: VUE_DIRECTIVE@111..136 - 0: VUE_IDENT@111..117 "v-html" [] [] + 0: IDENT@111..117 "v-html" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@117..117 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@117..136 @@ -758,7 +758,7 @@ HtmlRoot { 0: HTML_LITERAL@146..150 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@150..170 0: VUE_DIRECTIVE@150..170 - 0: VUE_IDENT@150..156 "v-bind" [] [] + 0: IDENT@150..156 "v-bind" [] [] 1: VUE_DIRECTIVE_ARGUMENT@156..162 0: COLON@156..157 ":" [] [] 1: VUE_STATIC_ARGUMENT@157..162 @@ -821,7 +821,7 @@ HtmlRoot { 0: HTML_LITERAL@227..231 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@231..260 0: VUE_DIRECTIVE@231..260 - 0: VUE_IDENT@231..237 "v-bind" [] [] + 0: IDENT@231..237 "v-bind" [] [] 1: VUE_DIRECTIVE_ARGUMENT@237..248 0: COLON@237..238 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@238..248 @@ -874,7 +874,7 @@ HtmlRoot { 0: HTML_LITERAL@304..311 "button" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@311..331 0: VUE_DIRECTIVE@311..331 - 0: VUE_IDENT@311..315 "v-on" [] [] + 0: IDENT@311..315 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@315..321 0: COLON@315..316 ":" [] [] 1: VUE_STATIC_ARGUMENT@316..321 @@ -915,7 +915,7 @@ HtmlRoot { 0: HTML_LITERAL@372..376 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@376..396 0: VUE_DIRECTIVE@376..396 - 0: VUE_IDENT@376..380 "v-on" [] [] + 0: IDENT@376..380 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@380..386 0: COLON@380..381 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@381..386 @@ -973,7 +973,7 @@ HtmlRoot { 0: HTML_LITERAL@459..465 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@465..480 0: VUE_DIRECTIVE@465..480 - 0: VUE_IDENT@465..472 "v-model" [] [] + 0: IDENT@465..472 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@472..472 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@472..480 @@ -988,7 +988,7 @@ HtmlRoot { 0: HTML_LITERAL@485..491 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@491..519 0: VUE_DIRECTIVE@491..519 - 0: VUE_IDENT@491..498 "v-model" [] [] + 0: IDENT@491..498 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@498..510 0: VUE_MODIFIER@498..503 @@ -1010,7 +1010,7 @@ HtmlRoot { 0: HTML_LITERAL@524..528 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@528..544 0: VUE_DIRECTIVE@528..544 - 0: VUE_IDENT@528..534 "v-show" [] [] + 0: IDENT@528..534 "v-show" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@534..534 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@534..544 @@ -1040,7 +1040,7 @@ HtmlRoot { 0: HTML_LITERAL@561..564 "li" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@564..607 0: VUE_DIRECTIVE@564..595 - 0: VUE_IDENT@564..569 "v-for" [] [] + 0: IDENT@564..569 "v-for" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@569..569 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@569..595 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-if.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-if.vue.snap index 872f05157af3..74dbd5bda52a 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-if.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-if.vue.snap @@ -39,7 +39,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@17..21 "v-if" [] [], + name_token: IDENT@17..21 "v-if" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -74,7 +74,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@53..59 "v-else" [] [], + name_token: IDENT@53..59 "v-else" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: missing (optional), @@ -134,7 +134,7 @@ HtmlRoot { 0: HTML_LITERAL@13..17 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@17..35 0: VUE_DIRECTIVE@17..35 - 0: VUE_IDENT@17..21 "v-if" [] [] + 0: IDENT@17..21 "v-if" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@21..21 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@21..35 @@ -158,7 +158,7 @@ HtmlRoot { 0: HTML_LITERAL@49..53 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@53..59 0: VUE_DIRECTIVE@53..59 - 0: VUE_IDENT@53..59 "v-else" [] [] + 0: IDENT@53..59 "v-else" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@59..59 3: (empty) diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-mixed-complex.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-mixed-complex.vue.snap index 5d0848bdddc0..c02c6ff0877e 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-mixed-complex.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-mixed-complex.vue.snap @@ -61,7 +61,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@77..84 "v-if" [Newline("\n"), Whitespace("\t\t")] [], + name_token: IDENT@77..84 "v-if" [Newline("\n"), Whitespace("\t\t")] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -72,7 +72,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@114..122 "v-for" [Newline("\n"), Whitespace("\t\t")] [], + name_token: IDENT@114..122 "v-for" [Newline("\n"), Whitespace("\t\t")] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -98,7 +98,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@164..173 "v-show" [Newline("\n"), Whitespace("\t\t")] [], + name_token: IDENT@164..173 "v-show" [Newline("\n"), Whitespace("\t\t")] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -109,7 +109,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@186..196 "v-model" [Newline("\n"), Whitespace("\t\t")] [], + name_token: IDENT@186..196 "v-model" [Newline("\n"), Whitespace("\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@196..197 ":" [] [], arg: VueDynamicArgument { @@ -136,7 +136,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@231..240 "v-bind" [Newline("\n"), Whitespace("\t\t")] [], + name_token: IDENT@231..240 "v-bind" [Newline("\n"), Whitespace("\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@240..241 ":" [] [], arg: VueStaticArgument { @@ -167,7 +167,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@275..284 "v-bind" [Newline("\n"), Whitespace("\t\t")] [], + name_token: IDENT@275..284 "v-bind" [Newline("\n"), Whitespace("\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@284..285 ":" [] [], arg: VueDynamicArgument { @@ -220,7 +220,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@396..403 "v-on" [Newline("\n"), Whitespace("\t\t")] [], + name_token: IDENT@396..403 "v-on" [Newline("\n"), Whitespace("\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@403..404 ":" [] [], arg: VueStaticArgument { @@ -266,7 +266,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@478..485 "v-on" [Newline("\n"), Whitespace("\t\t")] [], + name_token: IDENT@478..485 "v-on" [Newline("\n"), Whitespace("\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@485..486 ":" [] [], arg: VueDynamicArgument { @@ -317,7 +317,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@555..562 "v-on" [Newline("\n"), Whitespace("\t\t")] [], + name_token: IDENT@555..562 "v-on" [Newline("\n"), Whitespace("\t\t")] [], arg: VueDirectiveArgument { colon_token: COLON@562..563 ":" [] [], arg: VueStaticArgument { @@ -516,7 +516,7 @@ HtmlRoot { 0: HTML_LITERAL@63..77 "complex-widget" [] [] 2: HTML_ATTRIBUTE_LIST@77..586 0: VUE_DIRECTIVE@77..114 - 0: VUE_IDENT@77..84 "v-if" [Newline("\n"), Whitespace("\t\t")] [] + 0: IDENT@77..84 "v-if" [Newline("\n"), Whitespace("\t\t")] [] 1: (empty) 2: VUE_MODIFIER_LIST@84..84 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@84..114 @@ -524,7 +524,7 @@ HtmlRoot { 1: HTML_STRING@85..114 0: HTML_STRING_LITERAL@85..114 "\"isReady && items.length > 0\"" [] [] 1: VUE_DIRECTIVE@114..147 - 0: VUE_IDENT@114..122 "v-for" [Newline("\n"), Whitespace("\t\t")] [] + 0: IDENT@114..122 "v-for" [Newline("\n"), Whitespace("\t\t")] [] 1: (empty) 2: VUE_MODIFIER_LIST@122..122 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@122..147 @@ -542,7 +542,7 @@ HtmlRoot { 1: HTML_STRING@155..164 0: HTML_STRING_LITERAL@155..164 "\"item.id\"" [] [] 3: VUE_DIRECTIVE@164..186 - 0: VUE_IDENT@164..173 "v-show" [Newline("\n"), Whitespace("\t\t")] [] + 0: IDENT@164..173 "v-show" [Newline("\n"), Whitespace("\t\t")] [] 1: (empty) 2: VUE_MODIFIER_LIST@173..173 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@173..186 @@ -550,7 +550,7 @@ HtmlRoot { 1: HTML_STRING@174..186 0: HTML_STRING_LITERAL@174..186 "\"showWidget\"" [] [] 4: VUE_DIRECTIVE@186..231 - 0: VUE_IDENT@186..196 "v-model" [Newline("\n"), Whitespace("\t\t")] [] + 0: IDENT@186..196 "v-model" [Newline("\n"), Whitespace("\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@196..209 0: COLON@196..197 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@197..209 @@ -569,7 +569,7 @@ HtmlRoot { 1: HTML_STRING@222..231 0: HTML_STRING_LITERAL@222..231 "\"current\"" [] [] 5: VUE_DIRECTIVE@231..254 - 0: VUE_IDENT@231..240 "v-bind" [Newline("\n"), Whitespace("\t\t")] [] + 0: IDENT@231..240 "v-bind" [Newline("\n"), Whitespace("\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@240..246 0: COLON@240..241 ":" [] [] 1: VUE_STATIC_ARGUMENT@241..246 @@ -590,7 +590,7 @@ HtmlRoot { 1: HTML_STRING@266..275 0: HTML_STRING_LITERAL@266..275 "\"item.id\"" [] [] 7: VUE_DIRECTIVE@275..319 - 0: VUE_IDENT@275..284 "v-bind" [Newline("\n"), Whitespace("\t\t")] [] + 0: IDENT@275..284 "v-bind" [Newline("\n"), Whitespace("\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@284..298 0: COLON@284..285 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@285..298 @@ -626,7 +626,7 @@ HtmlRoot { 1: HTML_STRING@368..396 0: HTML_STRING_LITERAL@368..396 "\"{ color: color, '--x': x }\"" [] [] 10: VUE_DIRECTIVE@396..432 - 0: VUE_IDENT@396..403 "v-on" [Newline("\n"), Whitespace("\t\t")] [] + 0: IDENT@396..403 "v-on" [Newline("\n"), Whitespace("\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@403..409 0: COLON@403..404 ":" [] [] 1: VUE_STATIC_ARGUMENT@404..409 @@ -658,7 +658,7 @@ HtmlRoot { 1: HTML_STRING@469..478 0: HTML_STRING_LITERAL@469..478 "\"onEnter\"" [] [] 12: VUE_DIRECTIVE@478..522 - 0: VUE_IDENT@478..485 "v-on" [Newline("\n"), Whitespace("\t\t")] [] + 0: IDENT@478..485 "v-on" [Newline("\n"), Whitespace("\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@485..497 0: COLON@485..486 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@486..497 @@ -694,7 +694,7 @@ HtmlRoot { 1: HTML_STRING@548..555 0: HTML_STRING_LITERAL@548..555 "\"onRaw\"" [] [] 14: VUE_DIRECTIVE@555..586 - 0: VUE_IDENT@555..562 "v-on" [Newline("\n"), Whitespace("\t\t")] [] + 0: IDENT@555..562 "v-on" [Newline("\n"), Whitespace("\t\t")] [] 1: VUE_DIRECTIVE_ARGUMENT@562..568 0: COLON@562..563 ":" [] [] 1: VUE_STATIC_ARGUMENT@563..568 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-model-mixed.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-model-mixed.vue.snap index a3e8da51fbc8..4812e21cafdf 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-model-mixed.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-model-mixed.vue.snap @@ -74,7 +74,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@62..69 "v-model" [] [], + name_token: IDENT@62..69 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -96,7 +96,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@91..98 "v-model" [] [], + name_token: IDENT@91..98 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -126,7 +126,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@167..174 "v-model" [] [], + name_token: IDENT@167..174 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [ VueModifier { @@ -152,7 +152,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@202..209 "v-model" [] [], + name_token: IDENT@202..209 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [ VueModifier { @@ -178,7 +178,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@234..241 "v-model" [] [], + name_token: IDENT@234..241 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [ VueModifier { @@ -204,7 +204,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@265..272 "v-model" [] [], + name_token: IDENT@265..272 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [ VueModifier { @@ -238,7 +238,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@374..381 "v-model" [] [], + name_token: IDENT@374..381 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -259,7 +259,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@404..411 "v-model" [] [], + name_token: IDENT@404..411 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -293,7 +293,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@492..499 "v-model" [] [], + name_token: IDENT@492..499 "v-model" [] [], arg: VueDirectiveArgument { colon_token: COLON@499..500 ":" [] [], arg: VueStaticArgument { @@ -319,7 +319,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@532..539 "v-model" [] [], + name_token: IDENT@532..539 "v-model" [] [], arg: VueDirectiveArgument { colon_token: COLON@539..540 ":" [] [], arg: VueStaticArgument { @@ -345,7 +345,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@571..578 "v-model" [] [], + name_token: IDENT@571..578 "v-model" [] [], arg: VueDirectiveArgument { colon_token: COLON@578..579 ":" [] [], arg: VueStaticArgument { @@ -371,7 +371,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@610..617 "v-model" [] [], + name_token: IDENT@610..617 "v-model" [] [], arg: VueDirectiveArgument { colon_token: COLON@617..618 ":" [] [], arg: VueStaticArgument { @@ -406,7 +406,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@717..724 "v-model" [] [], + name_token: IDENT@717..724 "v-model" [] [], arg: VueDirectiveArgument { colon_token: COLON@724..725 ":" [] [], arg: VueDynamicArgument { @@ -439,7 +439,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@768..775 "v-model" [] [], + name_token: IDENT@768..775 "v-model" [] [], arg: VueDirectiveArgument { colon_token: COLON@775..776 ":" [] [], arg: VueDynamicArgument { @@ -472,7 +472,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@818..825 "v-model" [] [], + name_token: IDENT@818..825 "v-model" [] [], arg: VueDirectiveArgument { colon_token: COLON@825..826 ":" [] [], arg: VueDynamicArgument { @@ -506,7 +506,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@918..925 "v-model" [] [], + name_token: IDENT@918..925 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -622,7 +622,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@1049..1056 "v-model" [] [], + name_token: IDENT@1049..1056 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -665,7 +665,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@1101..1108 "v-model" [] [], + name_token: IDENT@1101..1108 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -708,7 +708,7 @@ HtmlRoot { }, }, VueDirective { - name_token: VUE_IDENT@1154..1161 "v-model" [] [], + name_token: IDENT@1154..1161 "v-model" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -751,7 +751,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@1275..1282 "v-model" [] [], + name_token: IDENT@1275..1282 "v-model" [] [], arg: VueDirectiveArgument { colon_token: COLON@1282..1283 ":" [] [], arg: VueStaticArgument { @@ -817,7 +817,7 @@ HtmlRoot { 0: HTML_LITERAL@56..62 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@62..77 0: VUE_DIRECTIVE@62..77 - 0: VUE_IDENT@62..69 "v-model" [] [] + 0: IDENT@62..69 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@69..69 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@69..77 @@ -833,7 +833,7 @@ HtmlRoot { 0: HTML_LITERAL@82..91 "textarea" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@91..108 0: VUE_DIRECTIVE@91..108 - 0: VUE_IDENT@91..98 "v-model" [] [] + 0: IDENT@91..98 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@98..98 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@98..108 @@ -854,7 +854,7 @@ HtmlRoot { 0: HTML_LITERAL@161..167 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@167..191 0: VUE_DIRECTIVE@167..191 - 0: VUE_IDENT@167..174 "v-model" [] [] + 0: IDENT@167..174 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@174..179 0: VUE_MODIFIER@174..179 @@ -872,7 +872,7 @@ HtmlRoot { 0: HTML_LITERAL@196..202 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@202..223 0: VUE_DIRECTIVE@202..223 - 0: VUE_IDENT@202..209 "v-model" [] [] + 0: IDENT@202..209 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@209..216 0: VUE_MODIFIER@209..216 @@ -890,7 +890,7 @@ HtmlRoot { 0: HTML_LITERAL@228..234 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@234..254 0: VUE_DIRECTIVE@234..254 - 0: VUE_IDENT@234..241 "v-model" [] [] + 0: IDENT@234..241 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@241..246 0: VUE_MODIFIER@241..246 @@ -908,7 +908,7 @@ HtmlRoot { 0: HTML_LITERAL@259..265 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@265..300 0: VUE_DIRECTIVE@265..300 - 0: VUE_IDENT@265..272 "v-model" [] [] + 0: IDENT@265..272 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@272..289 0: VUE_MODIFIER@272..277 @@ -932,7 +932,7 @@ HtmlRoot { 0: HTML_LITERAL@361..374 "custom-input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@374..390 0: VUE_DIRECTIVE@374..390 - 0: VUE_IDENT@374..381 "v-model" [] [] + 0: IDENT@374..381 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@381..381 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@381..390 @@ -947,7 +947,7 @@ HtmlRoot { 0: HTML_LITERAL@395..404 "my-input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@404..435 0: VUE_DIRECTIVE@404..418 - 0: VUE_IDENT@404..411 "v-model" [] [] + 0: IDENT@404..411 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@411..411 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@411..418 @@ -971,7 +971,7 @@ HtmlRoot { 0: HTML_LITERAL@480..492 "date-picker" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@492..513 0: VUE_DIRECTIVE@492..513 - 0: VUE_IDENT@492..499 "v-model" [] [] + 0: IDENT@492..499 "v-model" [] [] 1: VUE_DIRECTIVE_ARGUMENT@499..505 0: COLON@499..500 ":" [] [] 1: VUE_STATIC_ARGUMENT@500..505 @@ -989,7 +989,7 @@ HtmlRoot { 0: HTML_LITERAL@518..532 "custom-select" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@532..560 0: VUE_DIRECTIVE@532..560 - 0: VUE_IDENT@532..539 "v-model" [] [] + 0: IDENT@532..539 "v-model" [] [] 1: VUE_DIRECTIVE_ARGUMENT@539..548 0: COLON@539..540 ":" [] [] 1: VUE_STATIC_ARGUMENT@540..548 @@ -1007,7 +1007,7 @@ HtmlRoot { 0: HTML_LITERAL@565..571 "child" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@571..599 0: VUE_DIRECTIVE@571..599 - 0: VUE_IDENT@571..578 "v-model" [] [] + 0: IDENT@571..578 "v-model" [] [] 1: VUE_DIRECTIVE_ARGUMENT@578..586 0: COLON@578..579 ":" [] [] 1: VUE_STATIC_ARGUMENT@579..586 @@ -1025,7 +1025,7 @@ HtmlRoot { 0: HTML_LITERAL@604..610 "child" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@610..646 0: VUE_DIRECTIVE@610..646 - 0: VUE_IDENT@610..617 "v-model" [] [] + 0: IDENT@610..617 "v-model" [] [] 1: VUE_DIRECTIVE_ARGUMENT@617..624 0: COLON@617..618 ":" [] [] 1: VUE_STATIC_ARGUMENT@618..624 @@ -1049,7 +1049,7 @@ HtmlRoot { 0: HTML_LITERAL@705..717 "component-a" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@717..751 0: VUE_DIRECTIVE@717..751 - 0: VUE_IDENT@717..724 "v-model" [] [] + 0: IDENT@717..724 "v-model" [] [] 1: VUE_DIRECTIVE_ARGUMENT@724..735 0: COLON@724..725 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@725..735 @@ -1072,7 +1072,7 @@ HtmlRoot { 0: HTML_LITERAL@756..768 "component-b" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@768..801 0: VUE_DIRECTIVE@768..801 - 0: VUE_IDENT@768..775 "v-model" [] [] + 0: IDENT@768..775 "v-model" [] [] 1: VUE_DIRECTIVE_ARGUMENT@775..787 0: COLON@775..776 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@776..787 @@ -1095,7 +1095,7 @@ HtmlRoot { 0: HTML_LITERAL@806..818 "component-c" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@818..855 0: VUE_DIRECTIVE@818..855 - 0: VUE_IDENT@818..825 "v-model" [] [] + 0: IDENT@818..825 "v-model" [] [] 1: VUE_DIRECTIVE_ARGUMENT@825..839 0: COLON@825..826 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@826..839 @@ -1119,7 +1119,7 @@ HtmlRoot { 0: HTML_LITERAL@911..918 "select" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@918..942 0: VUE_DIRECTIVE@918..942 - 0: VUE_IDENT@918..925 "v-model" [] [] + 0: IDENT@918..925 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@925..925 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@925..942 @@ -1196,7 +1196,7 @@ HtmlRoot { 1: HTML_STRING@1038..1049 0: HTML_STRING_LITERAL@1038..1049 "\"checkbox\"" [] [Whitespace(" ")] 1: VUE_DIRECTIVE@1049..1065 - 0: VUE_IDENT@1049..1056 "v-model" [] [] + 0: IDENT@1049..1056 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@1056..1056 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@1056..1065 @@ -1225,7 +1225,7 @@ HtmlRoot { 1: HTML_STRING@1095..1101 0: HTML_STRING_LITERAL@1095..1101 "\"one\"" [] [Whitespace(" ")] 2: VUE_DIRECTIVE@1101..1118 - 0: VUE_IDENT@1101..1108 "v-model" [] [] + 0: IDENT@1101..1108 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@1108..1108 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@1108..1118 @@ -1254,7 +1254,7 @@ HtmlRoot { 1: HTML_STRING@1148..1154 0: HTML_STRING_LITERAL@1148..1154 "\"two\"" [] [Whitespace(" ")] 2: VUE_DIRECTIVE@1154..1171 - 0: VUE_IDENT@1154..1161 "v-model" [] [] + 0: IDENT@1154..1161 "v-model" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@1161..1161 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@1161..1171 @@ -1284,7 +1284,7 @@ HtmlRoot { 0: HTML_LITERAL@1269..1275 "child" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@1275..1297 0: VUE_DIRECTIVE@1275..1297 - 0: VUE_IDENT@1275..1282 "v-model" [] [] + 0: IDENT@1275..1282 "v-model" [] [] 1: VUE_DIRECTIVE_ARGUMENT@1282..1287 0: COLON@1282..1283 ":" [] [] 1: VUE_STATIC_ARGUMENT@1283..1287 diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/v-on-mixed.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/v-on-mixed.vue.snap index 7dbf55b45570..2111a8102676 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/v-on-mixed.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/v-on-mixed.vue.snap @@ -59,7 +59,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@40..44 "v-on" [] [], + name_token: IDENT@40..44 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@44..45 ":" [] [], arg: VueStaticArgument { @@ -99,7 +99,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@83..87 "v-on" [] [], + name_token: IDENT@83..87 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@87..88 ":" [] [], arg: VueStaticArgument { @@ -144,7 +144,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@133..137 "v-on" [] [], + name_token: IDENT@133..137 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@137..138 ":" [] [], arg: VueStaticArgument { @@ -184,7 +184,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@188..192 "v-on" [] [], + name_token: IDENT@188..192 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@192..193 ":" [] [], arg: VueStaticArgument { @@ -342,7 +342,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@448..452 "v-on" [] [], + name_token: IDENT@448..452 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@452..453 ":" [] [], arg: VueDynamicArgument { @@ -415,7 +415,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@570..574 "v-on" [] [], + name_token: IDENT@570..574 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@574..575 ":" [] [], arg: VueDynamicArgument { @@ -534,7 +534,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@742..746 "v-on" [] [], + name_token: IDENT@742..746 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@746..747 ":" [] [], arg: VueStaticArgument { @@ -579,7 +579,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@812..816 "v-on" [] [], + name_token: IDENT@812..816 "v-on" [] [], arg: VueDirectiveArgument { colon_token: COLON@816..817 ":" [] [], arg: VueStaticArgument { @@ -650,7 +650,7 @@ HtmlRoot { 0: HTML_LITERAL@33..40 "button" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@40..60 0: VUE_DIRECTIVE@40..60 - 0: VUE_IDENT@40..44 "v-on" [] [] + 0: IDENT@40..44 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@44..50 0: COLON@44..45 ":" [] [] 1: VUE_STATIC_ARGUMENT@45..50 @@ -677,7 +677,7 @@ HtmlRoot { 0: HTML_LITERAL@78..83 "form" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@83..118 0: VUE_DIRECTIVE@83..118 - 0: VUE_IDENT@83..87 "v-on" [] [] + 0: IDENT@83..87 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@87..94 0: COLON@87..88 ":" [] [] 1: VUE_STATIC_ARGUMENT@88..94 @@ -708,7 +708,7 @@ HtmlRoot { 0: HTML_LITERAL@129..133 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@133..172 0: VUE_DIRECTIVE@133..172 - 0: VUE_IDENT@133..137 "v-on" [] [] + 0: IDENT@133..137 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@137..148 0: COLON@137..138 ":" [] [] 1: VUE_STATIC_ARGUMENT@138..148 @@ -735,7 +735,7 @@ HtmlRoot { 0: HTML_LITERAL@182..188 "input" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@188..221 0: VUE_DIRECTIVE@188..221 - 0: VUE_IDENT@188..192 "v-on" [] [] + 0: IDENT@188..192 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@192..198 0: COLON@192..193 ":" [] [] 1: VUE_STATIC_ARGUMENT@193..198 @@ -842,7 +842,7 @@ HtmlRoot { 0: HTML_LITERAL@444..448 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@448..475 0: VUE_DIRECTIVE@448..475 - 0: VUE_IDENT@448..452 "v-on" [] [] + 0: IDENT@448..452 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@452..458 0: COLON@452..453 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@453..458 @@ -894,7 +894,7 @@ HtmlRoot { 0: HTML_LITERAL@566..570 "div" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@570..609 0: VUE_DIRECTIVE@570..609 - 0: VUE_IDENT@570..574 "v-on" [] [] + 0: IDENT@570..574 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@574..580 0: COLON@574..575 ":" [] [] 1: VUE_DYNAMIC_ARGUMENT@575..580 @@ -977,7 +977,7 @@ HtmlRoot { 0: HTML_LITERAL@735..742 "button" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@742..782 0: VUE_DIRECTIVE@742..782 - 0: VUE_IDENT@742..746 "v-on" [] [] + 0: IDENT@742..746 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@746..758 0: COLON@746..747 ":" [] [] 1: VUE_STATIC_ARGUMENT@747..758 @@ -1007,7 +1007,7 @@ HtmlRoot { 0: HTML_LITERAL@802..812 "component" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@812..847 0: VUE_DIRECTIVE@812..847 - 0: VUE_IDENT@812..816 "v-on" [] [] + 0: IDENT@812..816 "v-on" [] [] 1: VUE_DIRECTIVE_ARGUMENT@816..822 0: COLON@816..817 ":" [] [] 1: VUE_STATIC_ARGUMENT@817..822 diff --git a/crates/biome_html_parser/tests/quick_test.rs b/crates/biome_html_parser/tests/quick_test.rs index 6af6bf6b16fe..1b44866eb7a1 100644 --- a/crates/biome_html_parser/tests/quick_test.rs +++ b/crates/biome_html_parser/tests/quick_test.rs @@ -4,8 +4,8 @@ use biome_test_utils::has_bogus_nodes_or_empty_slots; #[ignore] #[test] pub fn quick_test() { - let code = r#" -
+ let code = r#"{#each items as item} + {/each} "#; let options = HtmlParseOptions::default().with_vue(); diff --git a/crates/biome_html_syntax/src/generated/kind.rs b/crates/biome_html_syntax/src/generated/kind.rs index 8570042e2f89..0182319ba45a 100644 --- a/crates/biome_html_syntax/src/generated/kind.rs +++ b/crates/biome_html_syntax/src/generated/kind.rs @@ -48,15 +48,14 @@ pub enum HtmlSyntaxKind { ATTACH_KW, ELSE_KW, IF_KW, + AS_KW, + EACH_KW, HTML_STRING_LITERAL, HTML_LITERAL, ERROR_TOKEN, NEWLINE, WHITESPACE, IDENT, - HTML_IDENT, - SVELTE_IDENT, - VUE_IDENT, HTML_ROOT, HTML_DIRECTIVE, HTML_SELF_CLOSING_TAG, @@ -96,6 +95,11 @@ pub enum HtmlSyntaxKind { SVELTE_ELSE_CLAUSE, SVELTE_IF_CLOSING_BLOCK, SVELTE_ELSE_IF_CLAUSE, + SVELTE_EACH_BLOCK, + SVELTE_EACH_OPENING_BLOCK, + SVELTE_EACH_ITEM, + SVELTE_EACH_INDEX, + SVELTE_EACH_CLOSING_BLOCK, VUE_DIRECTIVE, VUE_DIRECTIVE_ARGUMENT, VUE_V_BIND_SHORTHAND_DIRECTIVE, @@ -174,6 +178,8 @@ impl HtmlSyntaxKind { "attach" => ATTACH_KW, "else" => ELSE_KW, "if" => IF_KW, + "as" => AS_KW, + "each" => EACH_KW, _ => return None, }; Some(kw) @@ -216,6 +222,8 @@ impl HtmlSyntaxKind { ATTACH_KW => "attach", ELSE_KW => "else", IF_KW => "if", + AS_KW => "as", + EACH_KW => "each", EOF => "EOF", HTML_STRING_LITERAL => "string literal", _ => return None, @@ -225,4 +233,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 } ; [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 } ; [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 } ; [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 } ; [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 45c924262e9f..351c7d63f450 100644 --- a/crates/biome_html_syntax/src/generated/macros.rs +++ b/crates/biome_html_syntax/src/generated/macros.rs @@ -105,6 +105,18 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::SvelteDebugBlock::new_unchecked(node) }; $body } + $crate::HtmlSyntaxKind::SVELTE_EACH_BLOCK => { + let $pattern = unsafe { $crate::SvelteEachBlock::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::SVELTE_EACH_CLOSING_BLOCK => { + let $pattern = unsafe { $crate::SvelteEachClosingBlock::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::SVELTE_EACH_OPENING_BLOCK => { + let $pattern = unsafe { $crate::SvelteEachOpeningBlock::new_unchecked(node) }; + $body + } $crate::HtmlSyntaxKind::SVELTE_ELSE_CLAUSE => { let $pattern = unsafe { $crate::SvelteElseClause::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 0aa106a58e14..f9c5e853f12f 100644 --- a/crates/biome_html_syntax/src/generated/nodes.rs +++ b/crates/biome_html_syntax/src/generated/nodes.rs @@ -1000,6 +1000,156 @@ pub struct SvelteDebugBlockFields { pub r_curly_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteEachBlock { + pub(crate) syntax: SyntaxNode, +} +impl SvelteEachBlock { + #[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) -> SvelteEachBlockFields { + SvelteEachBlockFields { + opening_block: self.opening_block(), + children: self.children(), + closing_block: self.closing_block(), + } + } + pub fn opening_block(&self) -> SyntaxResult { + support::required_node(&self.syntax, 0usize) + } + pub fn children(&self) -> HtmlElementList { + support::list(&self.syntax, 1usize) + } + pub fn closing_block(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } +} +impl Serialize for SvelteEachBlock { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteEachBlockFields { + pub opening_block: SyntaxResult, + pub children: HtmlElementList, + pub closing_block: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteEachClosingBlock { + pub(crate) syntax: SyntaxNode, +} +impl SvelteEachClosingBlock { + #[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) -> SvelteEachClosingBlockFields { + SvelteEachClosingBlockFields { + sv_curly_slash_token: self.sv_curly_slash_token(), + each_token: self.each_token(), + r_curly_token: self.r_curly_token(), + } + } + pub fn sv_curly_slash_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn each_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } + pub fn r_curly_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 2usize) + } +} +impl Serialize for SvelteEachClosingBlock { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteEachClosingBlockFields { + pub sv_curly_slash_token: SyntaxResult, + pub each_token: SyntaxResult, + pub r_curly_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteEachOpeningBlock { + pub(crate) syntax: SyntaxNode, +} +impl SvelteEachOpeningBlock { + #[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) -> SvelteEachOpeningBlockFields { + SvelteEachOpeningBlockFields { + sv_curly_hash_token: self.sv_curly_hash_token(), + each_token: self.each_token(), + list: self.list(), + as_token: self.as_token(), + item: self.item(), + r_curly_token: self.r_curly_token(), + } + } + pub fn sv_curly_hash_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn each_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } + pub fn list(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } + pub fn as_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } + pub fn item(&self) -> SyntaxResult { + support::required_node(&self.syntax, 4usize) + } + pub fn r_curly_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 5usize) + } +} +impl Serialize for SvelteEachOpeningBlock { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteEachOpeningBlockFields { + pub sv_curly_hash_token: SyntaxResult, + pub each_token: SyntaxResult, + pub list: SyntaxResult, + pub as_token: SyntaxResult, + pub item: SyntaxResult, + pub r_curly_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct SvelteElseClause { pub(crate) syntax: SyntaxNode, } @@ -1465,10 +1615,10 @@ impl SvelteName { } pub fn as_fields(&self) -> SvelteNameFields { SvelteNameFields { - svelte_ident_token: self.svelte_ident_token(), + ident_token: self.ident_token(), } } - pub fn svelte_ident_token(&self) -> SyntaxResult { + pub fn ident_token(&self) -> SyntaxResult { support::required_token(&self.syntax, 0usize) } } @@ -1482,7 +1632,7 @@ impl Serialize for SvelteName { } #[derive(Serialize)] pub struct SvelteNameFields { - pub svelte_ident_token: SyntaxResult, + pub ident_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] pub struct SvelteRenderBlock { @@ -2078,6 +2228,7 @@ pub enum AnySvelteBlock { SvelteBogusBlock(SvelteBogusBlock), SvelteConstBlock(SvelteConstBlock), SvelteDebugBlock(SvelteDebugBlock), + SvelteEachBlock(SvelteEachBlock), SvelteHtmlBlock(SvelteHtmlBlock), SvelteIfBlock(SvelteIfBlock), SvelteKeyBlock(SvelteKeyBlock), @@ -2102,6 +2253,12 @@ impl AnySvelteBlock { _ => None, } } + pub fn as_svelte_each_block(&self) -> Option<&SvelteEachBlock> { + match &self { + Self::SvelteEachBlock(item) => Some(item), + _ => None, + } + } pub fn as_svelte_html_block(&self) -> Option<&SvelteHtmlBlock> { match &self { Self::SvelteHtmlBlock(item) => Some(item), @@ -3407,6 +3564,174 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for SvelteEachBlock { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_EACH_BLOCK as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_EACH_BLOCK + } + 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 SvelteEachBlock { + 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("SvelteEachBlock") + .field( + "opening_block", + &support::DebugSyntaxResult(self.opening_block()), + ) + .field("children", &self.children()) + .field( + "closing_block", + &support::DebugSyntaxResult(self.closing_block()), + ) + .finish() + } else { + f.debug_struct("SvelteEachBlock").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteEachBlock) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteEachBlock) -> Self { + n.syntax.into() + } +} +impl AstNode for SvelteEachClosingBlock { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_EACH_CLOSING_BLOCK as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_EACH_CLOSING_BLOCK + } + 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 SvelteEachClosingBlock { + 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("SvelteEachClosingBlock") + .field( + "sv_curly_slash_token", + &support::DebugSyntaxResult(self.sv_curly_slash_token()), + ) + .field("each_token", &support::DebugSyntaxResult(self.each_token())) + .field( + "r_curly_token", + &support::DebugSyntaxResult(self.r_curly_token()), + ) + .finish() + } else { + f.debug_struct("SvelteEachClosingBlock").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteEachClosingBlock) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteEachClosingBlock) -> Self { + n.syntax.into() + } +} +impl AstNode for SvelteEachOpeningBlock { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_EACH_OPENING_BLOCK as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_EACH_OPENING_BLOCK + } + 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 SvelteEachOpeningBlock { + 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("SvelteEachOpeningBlock") + .field( + "sv_curly_hash_token", + &support::DebugSyntaxResult(self.sv_curly_hash_token()), + ) + .field("each_token", &support::DebugSyntaxResult(self.each_token())) + .field("list", &support::DebugSyntaxResult(self.list())) + .field("as_token", &support::DebugSyntaxResult(self.as_token())) + .field("item", &support::DebugSyntaxResult(self.item())) + .field( + "r_curly_token", + &support::DebugSyntaxResult(self.r_curly_token()), + ) + .finish() + } else { + f.debug_struct("SvelteEachOpeningBlock").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteEachOpeningBlock) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteEachOpeningBlock) -> Self { + n.syntax.into() + } +} impl AstNode for SvelteElseClause { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -3943,8 +4268,8 @@ impl std::fmt::Debug for SvelteName { DEPTH.set(current_depth + 1); f.debug_struct("SvelteName") .field( - "svelte_ident_token", - &support::DebugSyntaxResult(self.svelte_ident_token()), + "ident_token", + &support::DebugSyntaxResult(self.ident_token()), ) .finish() } else { @@ -4963,6 +5288,11 @@ impl From for AnySvelteBlock { Self::SvelteDebugBlock(node) } } +impl From for AnySvelteBlock { + fn from(node: SvelteEachBlock) -> Self { + Self::SvelteEachBlock(node) + } +} impl From for AnySvelteBlock { fn from(node: SvelteHtmlBlock) -> Self { Self::SvelteHtmlBlock(node) @@ -4988,6 +5318,7 @@ impl AstNode for AnySvelteBlock { const KIND_SET: SyntaxKindSet = SvelteBogusBlock::KIND_SET .union(SvelteConstBlock::KIND_SET) .union(SvelteDebugBlock::KIND_SET) + .union(SvelteEachBlock::KIND_SET) .union(SvelteHtmlBlock::KIND_SET) .union(SvelteIfBlock::KIND_SET) .union(SvelteKeyBlock::KIND_SET) @@ -4998,6 +5329,7 @@ impl AstNode for AnySvelteBlock { SVELTE_BOGUS_BLOCK | SVELTE_CONST_BLOCK | SVELTE_DEBUG_BLOCK + | SVELTE_EACH_BLOCK | SVELTE_HTML_BLOCK | SVELTE_IF_BLOCK | SVELTE_KEY_BLOCK @@ -5009,6 +5341,7 @@ impl AstNode for AnySvelteBlock { SVELTE_BOGUS_BLOCK => Self::SvelteBogusBlock(SvelteBogusBlock { syntax }), SVELTE_CONST_BLOCK => Self::SvelteConstBlock(SvelteConstBlock { syntax }), SVELTE_DEBUG_BLOCK => Self::SvelteDebugBlock(SvelteDebugBlock { syntax }), + SVELTE_EACH_BLOCK => Self::SvelteEachBlock(SvelteEachBlock { syntax }), SVELTE_HTML_BLOCK => Self::SvelteHtmlBlock(SvelteHtmlBlock { syntax }), SVELTE_IF_BLOCK => Self::SvelteIfBlock(SvelteIfBlock { syntax }), SVELTE_KEY_BLOCK => Self::SvelteKeyBlock(SvelteKeyBlock { syntax }), @@ -5022,6 +5355,7 @@ impl AstNode for AnySvelteBlock { Self::SvelteBogusBlock(it) => it.syntax(), Self::SvelteConstBlock(it) => it.syntax(), Self::SvelteDebugBlock(it) => it.syntax(), + Self::SvelteEachBlock(it) => it.syntax(), Self::SvelteHtmlBlock(it) => it.syntax(), Self::SvelteIfBlock(it) => it.syntax(), Self::SvelteKeyBlock(it) => it.syntax(), @@ -5033,6 +5367,7 @@ impl AstNode for AnySvelteBlock { Self::SvelteBogusBlock(it) => it.into_syntax(), Self::SvelteConstBlock(it) => it.into_syntax(), Self::SvelteDebugBlock(it) => it.into_syntax(), + Self::SvelteEachBlock(it) => it.into_syntax(), Self::SvelteHtmlBlock(it) => it.into_syntax(), Self::SvelteIfBlock(it) => it.into_syntax(), Self::SvelteKeyBlock(it) => it.into_syntax(), @@ -5046,6 +5381,7 @@ impl std::fmt::Debug for AnySvelteBlock { Self::SvelteBogusBlock(it) => std::fmt::Debug::fmt(it, f), Self::SvelteConstBlock(it) => std::fmt::Debug::fmt(it, f), Self::SvelteDebugBlock(it) => std::fmt::Debug::fmt(it, f), + Self::SvelteEachBlock(it) => std::fmt::Debug::fmt(it, f), Self::SvelteHtmlBlock(it) => std::fmt::Debug::fmt(it, f), Self::SvelteIfBlock(it) => std::fmt::Debug::fmt(it, f), Self::SvelteKeyBlock(it) => std::fmt::Debug::fmt(it, f), @@ -5059,6 +5395,7 @@ impl From for SyntaxNode { AnySvelteBlock::SvelteBogusBlock(it) => it.into_syntax(), AnySvelteBlock::SvelteConstBlock(it) => it.into_syntax(), AnySvelteBlock::SvelteDebugBlock(it) => it.into_syntax(), + AnySvelteBlock::SvelteEachBlock(it) => it.into_syntax(), AnySvelteBlock::SvelteHtmlBlock(it) => it.into_syntax(), AnySvelteBlock::SvelteIfBlock(it) => it.into_syntax(), AnySvelteBlock::SvelteKeyBlock(it) => it.into_syntax(), @@ -5409,6 +5746,21 @@ impl std::fmt::Display for SvelteDebugBlock { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for SvelteEachBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for SvelteEachClosingBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for SvelteEachOpeningBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for SvelteElseClause { 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 ba84a5728eae..c1be99d70fcd 100644 --- a/crates/biome_html_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_html_syntax/src/generated/nodes_mut.rs @@ -431,6 +431,84 @@ impl SvelteDebugBlock { ) } } +impl SvelteEachBlock { + pub fn with_opening_block(self, element: SvelteEachOpeningBlock) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_children(self, element: HtmlElementList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_closing_block(self, element: SvelteEachClosingBlock) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } +} +impl SvelteEachClosingBlock { + pub fn with_sv_curly_slash_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_each_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_r_curly_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into()))), + ) + } +} +impl SvelteEachOpeningBlock { + pub fn with_sv_curly_hash_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_each_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_list(self, element: HtmlTextExpression) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_as_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } + pub fn with_item(self, element: HtmlTextExpression) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(4usize..=4usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_curly_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(5usize..=5usize, once(Some(element.into()))), + ) + } +} impl SvelteElseClause { pub fn with_sv_curly_colon_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( @@ -666,7 +744,7 @@ impl SvelteKeyOpeningBlock { } } impl SvelteName { - pub fn with_svelte_ident_token(self, element: SyntaxToken) -> Self { + pub fn with_ident_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into()))), diff --git a/xtask/codegen/html.ungram b/xtask/codegen/html.ungram index 6a217e9e7247..577631233f3e 100644 --- a/xtask/codegen/html.ungram +++ b/xtask/codegen/html.ungram @@ -200,6 +200,7 @@ AnySvelteBlock = | SvelteHtmlBlock | SvelteConstBlock | SvelteIfBlock + | SvelteEachBlock // {@debug} // ^^^^^^^^ @@ -234,7 +235,6 @@ SvelteKeyClosingBlock = SvelteBindingList = (SvelteName (',' SvelteName)*) -SvelteName = 'svelte_ident' // {@render ...} // ^^^^^^^^^^^^^ @@ -313,6 +313,35 @@ SvelteIfClosingBlock = 'if' '}' + +// {#each ... as ...} ... {/each} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +SvelteEachBlock = + opening_block: SvelteEachOpeningBlock + children: HtmlElementList + closing_block: SvelteEachClosingBlock + +// {#each ... as item, i} ... {/each} +// ^^^^^^^^^^^^^^^^^^^^^^ +SvelteEachOpeningBlock = + '{#' + 'each' + list: HtmlTextExpression + 'as' + item: HtmlTextExpression + '}' + + +// {#each ... as item, i} ... {/each} +// ^^^^^^^ +SvelteEachClosingBlock = + '{/' + 'each' + '}' + + +// Keep it different just for svelte +SvelteName = 'ident' HtmlString = value: 'html_string_literal' HtmlTagName = value: 'html_literal' HtmlAttributeName = value: 'html_literal' @@ -335,7 +364,7 @@ AnyVueDirective = //
// ^^^^^^^^^^^^^^^^^ VueDirective = - name: 'vue_ident' + name: 'ident' arg: VueDirectiveArgument? modifiers: VueModifierList initializer: HtmlAttributeInitializerClause? diff --git a/xtask/codegen/src/html_kinds_src.rs b/xtask/codegen/src/html_kinds_src.rs index c5dc7cb21489..30c8661841cb 100644 --- a/xtask/codegen/src/html_kinds_src.rs +++ b/xtask/codegen/src/html_kinds_src.rs @@ -28,19 +28,11 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { ("#", "HASH"), ], keywords: &[ - "null", "true", "false", "doctype", "html", "debug", "key", "render", "const", "attach", - "else", "if", + "null", "true", "false", "doctype", "html", // Svelte keywords + "debug", "key", "render", "const", "attach", "else", "if", "as", "each", ], literals: &["HTML_STRING_LITERAL", "HTML_LITERAL"], - tokens: &[ - "ERROR_TOKEN", - "NEWLINE", - "WHITESPACE", - "IDENT", - "HTML_IDENT", - "SVELTE_IDENT", - "VUE_IDENT", - ], + tokens: &["ERROR_TOKEN", "NEWLINE", "WHITESPACE", "IDENT"], nodes: &[ "HTML_ROOT", "HTML_DIRECTIVE", @@ -83,6 +75,11 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { "SVELTE_ELSE_CLAUSE", "SVELTE_IF_CLOSING_BLOCK", "SVELTE_ELSE_IF_CLAUSE", + "SVELTE_EACH_BLOCK", + "SVELTE_EACH_OPENING_BLOCK", + "SVELTE_EACH_ITEM", + "SVELTE_EACH_INDEX", + "SVELTE_EACH_CLOSING_BLOCK", // Vue nodes "VUE_DIRECTIVE", "VUE_DIRECTIVE_ARGUMENT", From 51f2cb2a158ab93b31900e447fae4b6f855e6a69 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Wed, 17 Dec 2025 17:21:49 +0000 Subject: [PATCH 2/6] finally finish each syntax --- .../src/generated/node_factory.rs | 163 +++++- .../src/generated/syntax_factory.rs | 116 ++++- crates/biome_html_formatter/src/generated.rs | 177 +++++++ .../src/html/auxiliary/text_expression.rs | 19 +- .../src/svelte/any/block_item.rs | 15 + .../src/svelte/any/mod.rs | 1 + .../src/svelte/auxiliary/attach_attribute.rs | 1 + .../src/svelte/auxiliary/const_block.rs | 1 + .../svelte/auxiliary/each_as_keyed_item.rs | 27 + .../src/svelte/auxiliary/each_block.rs | 19 +- .../svelte/auxiliary/each_closing_block.rs | 19 +- .../src/svelte/auxiliary/each_index.rs | 12 + .../src/svelte/auxiliary/each_key.rs | 12 + .../src/svelte/auxiliary/each_keyed_item.rs | 16 + .../svelte/auxiliary/each_opening_block.rs | 24 +- .../src/svelte/auxiliary/else_if_clause.rs | 1 + .../src/svelte/auxiliary/html_block.rs | 1 + .../src/svelte/auxiliary/if_opening_block.rs | 1 + .../src/svelte/auxiliary/key_opening_block.rs | 1 + .../src/svelte/auxiliary/mod.rs | 4 + .../src/svelte/auxiliary/render_block.rs | 1 + .../tests/specs/html/svelte/each_basic.svelte | 3 + .../specs/html/svelte/each_basic.svelte.snap | 37 ++ .../svelte/each_complex_combinations.svelte | 13 + .../each_complex_combinations.svelte.snap | 57 ++ .../svelte/each_complex_expression.svelte | 11 + .../each_complex_expression.svelte.snap | 53 ++ .../specs/html/svelte/each_nested.svelte | 14 + .../specs/html/svelte/each_nested.svelte.snap | 59 +++ .../svelte/each_with_destructuring.svelte | 15 + .../each_with_destructuring.svelte.snap | 61 +++ .../specs/html/svelte/each_with_else.svelte | 5 + .../html/svelte/each_with_else.svelte.snap | 41 ++ .../specs/html/svelte/each_with_index.svelte | 3 + .../html/svelte/each_with_index.svelte.snap | 37 ++ .../svelte/each_with_index_and_key.svelte | 3 + .../each_with_index_and_key.svelte.snap | 37 ++ .../specs/html/svelte/each_with_key.svelte | 3 + .../html/svelte/each_with_key.svelte.snap | 37 ++ .../html/svelte/each_with_whitespace.svelte | 11 + .../svelte/each_with_whitespace.svelte.snap | 53 ++ crates/biome_html_parser/src/lexer/mod.rs | 93 +++- crates/biome_html_parser/src/syntax/svelte.rs | 165 +++++- crates/biome_html_parser/src/token_source.rs | 13 +- .../svelte/debug-trailing-comma.svelte.snap | 8 +- .../error/svelte/each_missing_as.svelte | 3 + .../error/svelte/each_missing_as.svelte.snap | 131 +++++ .../error/svelte/each_missing_binding.svelte | 3 + .../svelte/each_missing_binding.svelte.snap | 142 +++++ .../svelte/each_missing_expression.svelte | 3 + .../each_missing_expression.svelte.snap | 142 +++++ .../error/svelte/each_unclosed.svelte | 3 + .../error/svelte/each_unclosed.svelte.snap | 96 ++++ .../html_specs/ok/svelte/debug.svelte.snap | 20 +- .../ok/svelte/each_as_in_identifier.svelte | 7 + .../svelte/each_as_in_identifier.svelte.snap | 237 +++++++++ .../html_specs/ok/svelte/each_basic.svelte | 3 + .../ok/svelte/each_basic.svelte.snap | 129 +++++ .../svelte/each_complex_combinations.svelte | 13 + .../each_complex_combinations.svelte.snap | 399 ++++++++++++++ .../ok/svelte/each_complex_expression.svelte | 11 + .../each_complex_expression.svelte.snap | 319 ++++++++++++ .../ok/svelte/each_index_only.svelte | 3 + .../ok/svelte/each_index_only.svelte.snap | 133 +++++ .../ok/svelte/each_with_else.svelte | 5 + .../ok/svelte/each_with_else.svelte.snap | 180 +++++++ .../ok/svelte/each_with_index.svelte | 3 + .../ok/svelte/each_with_index.svelte.snap | 154 ++++++ .../ok/svelte/each_with_index_and_key.svelte | 3 + .../each_with_index_and_key.svelte.snap | 154 ++++++ .../html_specs/ok/svelte/each_with_key.svelte | 3 + .../ok/svelte/each_with_key.svelte.snap | 129 +++++ .../ok/svelte/each_with_whitespace.svelte | 3 + .../svelte/each_with_whitespace.svelte.snap | 129 +++++ .../html_specs/ok/vue/issue-8174.vue.snap | 8 +- crates/biome_html_parser/tests/quick_test.rs | 6 +- .../biome_html_syntax/src/generated/kind.rs | 4 +- .../biome_html_syntax/src/generated/macros.rs | 16 + .../biome_html_syntax/src/generated/nodes.rs | 488 +++++++++++++++++- .../src/generated/nodes_mut.rs | 80 ++- xtask/codegen/html.ungram | 38 +- xtask/codegen/src/html_kinds_src.rs | 4 +- 82 files changed, 4566 insertions(+), 101 deletions(-) create mode 100644 crates/biome_html_formatter/src/svelte/any/block_item.rs create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/each_as_keyed_item.rs create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/each_index.rs create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/each_key.rs create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/each_keyed_item.rs create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_as.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_as.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_binding.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_binding.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_expression.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_expression.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/each_unclosed.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/each_unclosed.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_as_in_identifier.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_as_in_identifier.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_basic.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_basic.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_expression.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_expression.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_index_only.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_index_only.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_else.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_else.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte.snap diff --git a/crates/biome_html_factory/src/generated/node_factory.rs b/crates/biome_html_factory/src/generated/node_factory.rs index 8b03e2afe11e..5e0b4745d170 100644 --- a/crates/biome_html_factory/src/generated/node_factory.rs +++ b/crates/biome_html_factory/src/generated/node_factory.rs @@ -395,19 +395,81 @@ pub fn svelte_debug_block( ], )) } +pub fn svelte_each_as_keyed_item( + as_token: SyntaxToken, + name: HtmlTextExpression, +) -> SvelteEachAsKeyedItemBuilder { + SvelteEachAsKeyedItemBuilder { + as_token, + name, + index: None, + key: None, + } +} +pub struct SvelteEachAsKeyedItemBuilder { + as_token: SyntaxToken, + name: HtmlTextExpression, + index: Option, + key: Option, +} +impl SvelteEachAsKeyedItemBuilder { + pub fn with_index(mut self, index: SvelteEachIndex) -> Self { + self.index = Some(index); + self + } + pub fn with_key(mut self, key: SvelteEachKey) -> Self { + self.key = Some(key); + self + } + pub fn build(self) -> SvelteEachAsKeyedItem { + SvelteEachAsKeyedItem::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_EACH_AS_KEYED_ITEM, + [ + Some(SyntaxElement::Token(self.as_token)), + Some(SyntaxElement::Node(self.name.into_syntax())), + self.index + .map(|token| SyntaxElement::Node(token.into_syntax())), + self.key + .map(|token| SyntaxElement::Node(token.into_syntax())), + ], + )) + } +} pub fn svelte_each_block( opening_block: SvelteEachOpeningBlock, children: HtmlElementList, closing_block: SvelteEachClosingBlock, -) -> SvelteEachBlock { - SvelteEachBlock::unwrap_cast(SyntaxNode::new_detached( - HtmlSyntaxKind::SVELTE_EACH_BLOCK, - [ - Some(SyntaxElement::Node(opening_block.into_syntax())), - Some(SyntaxElement::Node(children.into_syntax())), - Some(SyntaxElement::Node(closing_block.into_syntax())), - ], - )) +) -> SvelteEachBlockBuilder { + SvelteEachBlockBuilder { + opening_block, + children, + closing_block, + else_clause: None, + } +} +pub struct SvelteEachBlockBuilder { + opening_block: SvelteEachOpeningBlock, + children: HtmlElementList, + closing_block: SvelteEachClosingBlock, + else_clause: Option, +} +impl SvelteEachBlockBuilder { + pub fn with_else_clause(mut self, else_clause: SvelteElseClause) -> Self { + self.else_clause = Some(else_clause); + self + } + pub fn build(self) -> SvelteEachBlock { + SvelteEachBlock::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_EACH_BLOCK, + [ + Some(SyntaxElement::Node(self.opening_block.into_syntax())), + Some(SyntaxElement::Node(self.children.into_syntax())), + self.else_clause + .map(|token| SyntaxElement::Node(token.into_syntax())), + Some(SyntaxElement::Node(self.closing_block.into_syntax())), + ], + )) + } } pub fn svelte_each_closing_block( sv_curly_slash_token: SyntaxToken, @@ -423,25 +485,80 @@ pub fn svelte_each_closing_block( ], )) } +pub fn svelte_each_index(comma_token: SyntaxToken, value: HtmlTextExpression) -> SvelteEachIndex { + SvelteEachIndex::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_EACH_INDEX, + [ + Some(SyntaxElement::Token(comma_token)), + Some(SyntaxElement::Node(value.into_syntax())), + ], + )) +} +pub fn svelte_each_key(expression: HtmlTextExpression) -> SvelteEachKey { + SvelteEachKey::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_EACH_KEY, + [Some(SyntaxElement::Node(expression.into_syntax()))], + )) +} +pub fn svelte_each_keyed_item() -> SvelteEachKeyedItemBuilder { + SvelteEachKeyedItemBuilder { index: None } +} +pub struct SvelteEachKeyedItemBuilder { + index: Option, +} +impl SvelteEachKeyedItemBuilder { + pub fn with_index(mut self, index: SvelteEachIndex) -> Self { + self.index = Some(index); + self + } + pub fn build(self) -> SvelteEachKeyedItem { + SvelteEachKeyedItem::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_EACH_KEYED_ITEM, + [self + .index + .map(|token| SyntaxElement::Node(token.into_syntax()))], + )) + } +} pub fn svelte_each_opening_block( sv_curly_hash_token: SyntaxToken, each_token: SyntaxToken, list: HtmlTextExpression, - as_token: SyntaxToken, - item: HtmlTextExpression, r_curly_token: SyntaxToken, -) -> SvelteEachOpeningBlock { - SvelteEachOpeningBlock::unwrap_cast(SyntaxNode::new_detached( - HtmlSyntaxKind::SVELTE_EACH_OPENING_BLOCK, - [ - Some(SyntaxElement::Token(sv_curly_hash_token)), - Some(SyntaxElement::Token(each_token)), - Some(SyntaxElement::Node(list.into_syntax())), - Some(SyntaxElement::Token(as_token)), - Some(SyntaxElement::Node(item.into_syntax())), - Some(SyntaxElement::Token(r_curly_token)), - ], - )) +) -> SvelteEachOpeningBlockBuilder { + SvelteEachOpeningBlockBuilder { + sv_curly_hash_token, + each_token, + list, + r_curly_token, + item: None, + } +} +pub struct SvelteEachOpeningBlockBuilder { + sv_curly_hash_token: SyntaxToken, + each_token: SyntaxToken, + list: HtmlTextExpression, + r_curly_token: SyntaxToken, + item: Option, +} +impl SvelteEachOpeningBlockBuilder { + pub fn with_item(mut self, item: AnySvelteBlockItem) -> Self { + self.item = Some(item); + self + } + pub fn build(self) -> SvelteEachOpeningBlock { + SvelteEachOpeningBlock::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_EACH_OPENING_BLOCK, + [ + Some(SyntaxElement::Token(self.sv_curly_hash_token)), + Some(SyntaxElement::Token(self.each_token)), + Some(SyntaxElement::Node(self.list.into_syntax())), + self.item + .map(|token| SyntaxElement::Node(token.into_syntax())), + Some(SyntaxElement::Token(self.r_curly_token)), + ], + )) + } } pub fn svelte_else_clause( sv_curly_colon_token: SyntaxToken, diff --git a/crates/biome_html_factory/src/generated/syntax_factory.rs b/crates/biome_html_factory/src/generated/syntax_factory.rs index 75a2845695e0..b4f9ac246237 100644 --- a/crates/biome_html_factory/src/generated/syntax_factory.rs +++ b/crates/biome_html_factory/src/generated/syntax_factory.rs @@ -736,9 +736,49 @@ impl SyntaxFactory for HtmlSyntaxFactory { } slots.into_node(SVELTE_DEBUG_BLOCK, children) } + SVELTE_EACH_AS_KEYED_ITEM => { + 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![as] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && HtmlTextExpression::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && SvelteEachIndex::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && SvelteEachKey::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + SVELTE_EACH_AS_KEYED_ITEM.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_EACH_AS_KEYED_ITEM, children) + } SVELTE_EACH_BLOCK => { let mut elements = (&children).into_iter(); - let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); let mut current_element = elements.next(); if let Some(element) = ¤t_element && SvelteEachOpeningBlock::can_cast(element.kind()) @@ -754,6 +794,13 @@ impl SyntaxFactory for HtmlSyntaxFactory { current_element = elements.next(); } slots.next_slot(); + if let Some(element) = ¤t_element + && SvelteElseClause::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); if let Some(element) = ¤t_element && SvelteEachClosingBlock::can_cast(element.kind()) { @@ -802,24 +849,36 @@ impl SyntaxFactory for HtmlSyntaxFactory { } slots.into_node(SVELTE_EACH_CLOSING_BLOCK, children) } - SVELTE_EACH_OPENING_BLOCK => { + SVELTE_EACH_INDEX => { let mut elements = (&children).into_iter(); - let mut slots: RawNodeSlots<6usize> = RawNodeSlots::default(); + let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); let mut current_element = elements.next(); if let Some(element) = ¤t_element - && element.kind() == T!["{#"] + && element.kind() == T ! [,] { slots.mark_present(); current_element = elements.next(); } slots.next_slot(); if let Some(element) = ¤t_element - && element.kind() == T![each] + && HtmlTextExpression::can_cast(element.kind()) { slots.mark_present(); current_element = elements.next(); } slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + SVELTE_EACH_INDEX.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_EACH_INDEX, children) + } + SVELTE_EACH_KEY => { + 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 && HtmlTextExpression::can_cast(element.kind()) { @@ -827,8 +886,46 @@ impl SyntaxFactory for HtmlSyntaxFactory { current_element = elements.next(); } slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + SVELTE_EACH_KEY.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_EACH_KEY, children) + } + SVELTE_EACH_KEYED_ITEM => { + 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() == T![as] + && SvelteEachIndex::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + SVELTE_EACH_KEYED_ITEM.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_EACH_KEYED_ITEM, children) + } + SVELTE_EACH_OPENING_BLOCK => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<5usize> = 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 + && element.kind() == T![each] { slots.mark_present(); current_element = elements.next(); @@ -841,6 +938,13 @@ impl SyntaxFactory for HtmlSyntaxFactory { current_element = elements.next(); } slots.next_slot(); + if let Some(element) = ¤t_element + && AnySvelteBlockItem::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); if let Some(element) = ¤t_element && element.kind() == T!['}'] { diff --git a/crates/biome_html_formatter/src/generated.rs b/crates/biome_html_formatter/src/generated.rs index 81c4fb9d6cf5..887911f3a9f0 100644 --- a/crates/biome_html_formatter/src/generated.rs +++ b/crates/biome_html_formatter/src/generated.rs @@ -830,6 +830,44 @@ impl IntoFormat for biome_html_syntax::SvelteDebugBlock { ) } } +impl FormatRule + for crate::svelte::auxiliary::each_as_keyed_item::FormatSvelteEachAsKeyedItem +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteEachAsKeyedItem, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteEachAsKeyedItem { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteEachAsKeyedItem, + crate::svelte::auxiliary::each_as_keyed_item::FormatSvelteEachAsKeyedItem, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::each_as_keyed_item::FormatSvelteEachAsKeyedItem::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteEachAsKeyedItem { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteEachAsKeyedItem, + crate::svelte::auxiliary::each_as_keyed_item::FormatSvelteEachAsKeyedItem, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::each_as_keyed_item::FormatSvelteEachAsKeyedItem::default(), + ) + } +} impl FormatRule for crate::svelte::auxiliary::each_block::FormatSvelteEachBlock { @@ -906,6 +944,120 @@ impl IntoFormat for biome_html_syntax::SvelteEachClosingBlock ) } } +impl FormatRule + for crate::svelte::auxiliary::each_index::FormatSvelteEachIndex +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteEachIndex, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteEachIndex { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteEachIndex, + crate::svelte::auxiliary::each_index::FormatSvelteEachIndex, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::each_index::FormatSvelteEachIndex::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteEachIndex { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteEachIndex, + crate::svelte::auxiliary::each_index::FormatSvelteEachIndex, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::each_index::FormatSvelteEachIndex::default(), + ) + } +} +impl FormatRule + for crate::svelte::auxiliary::each_key::FormatSvelteEachKey +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteEachKey, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteEachKey { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteEachKey, + crate::svelte::auxiliary::each_key::FormatSvelteEachKey, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::each_key::FormatSvelteEachKey::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteEachKey { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteEachKey, + crate::svelte::auxiliary::each_key::FormatSvelteEachKey, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::each_key::FormatSvelteEachKey::default(), + ) + } +} +impl FormatRule + for crate::svelte::auxiliary::each_keyed_item::FormatSvelteEachKeyedItem +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteEachKeyedItem, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteEachKeyedItem { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteEachKeyedItem, + crate::svelte::auxiliary::each_keyed_item::FormatSvelteEachKeyedItem, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::each_keyed_item::FormatSvelteEachKeyedItem::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteEachKeyedItem { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteEachKeyedItem, + crate::svelte::auxiliary::each_keyed_item::FormatSvelteEachKeyedItem, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::each_keyed_item::FormatSvelteEachKeyedItem::default(), + ) + } +} impl FormatRule for crate::svelte::auxiliary::each_opening_block::FormatSvelteEachOpeningBlock { @@ -2236,6 +2388,31 @@ impl IntoFormat for biome_html_syntax::AnySvelteBlock { ) } } +impl AsFormat for biome_html_syntax::AnySvelteBlockItem { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::AnySvelteBlockItem, + crate::svelte::any::block_item::FormatAnySvelteBlockItem, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::any::block_item::FormatAnySvelteBlockItem::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::AnySvelteBlockItem { + type Format = FormatOwnedWithRule< + biome_html_syntax::AnySvelteBlockItem, + crate::svelte::any::block_item::FormatAnySvelteBlockItem, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::any::block_item::FormatAnySvelteBlockItem::default(), + ) + } +} impl AsFormat for biome_html_syntax::AnyVueDirective { type Format<'a> = FormatRefWithRule< 'a, diff --git a/crates/biome_html_formatter/src/html/auxiliary/text_expression.rs b/crates/biome_html_formatter/src/html/auxiliary/text_expression.rs index 7a28f88c1c65..f472c503408b 100644 --- a/crates/biome_html_formatter/src/html/auxiliary/text_expression.rs +++ b/crates/biome_html_formatter/src/html/auxiliary/text_expression.rs @@ -1,9 +1,26 @@ use crate::prelude::*; +use biome_formatter::CstFormatContext; +use biome_formatter::write; use biome_html_syntax::HtmlTextExpression; + #[derive(Debug, Clone, Default)] pub(crate) struct FormatHtmlTextExpression; impl FormatNodeRule for FormatHtmlTextExpression { fn fmt_fields(&self, node: &HtmlTextExpression, f: &mut HtmlFormatter) -> FormatResult<()> { - format_verbatim_skipped(node.syntax()).fmt(f) + if f.context().comments().is_suppressed(node.syntax()) { + return write!(f, [format_suppressed_node(node.syntax())]); + } + + let token = node.html_literal_token()?; + let token_text = token.text(); + let trimmed_text = token_text.trim_start().trim_end(); + + write!( + f, + [format_replaced( + &token, + &text(trimmed_text, token.text_range().start()) + )] + ) } } diff --git a/crates/biome_html_formatter/src/svelte/any/block_item.rs b/crates/biome_html_formatter/src/svelte/any/block_item.rs new file mode 100644 index 000000000000..91860f7d304b --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/any/block_item.rs @@ -0,0 +1,15 @@ +//! 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::AnySvelteBlockItem; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnySvelteBlockItem; +impl FormatRule for FormatAnySvelteBlockItem { + type Context = HtmlFormatContext; + fn fmt(&self, node: &AnySvelteBlockItem, f: &mut HtmlFormatter) -> FormatResult<()> { + match node { + AnySvelteBlockItem::SvelteEachAsKeyedItem(node) => node.format().fmt(f), + AnySvelteBlockItem::SvelteEachKeyedItem(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_html_formatter/src/svelte/any/mod.rs b/crates/biome_html_formatter/src/svelte/any/mod.rs index 658acab55c20..7c9c4f36c31a 100644 --- a/crates/biome_html_formatter/src/svelte/any/mod.rs +++ b/crates/biome_html_formatter/src/svelte/any/mod.rs @@ -1,3 +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 block; +pub(crate) mod block_item; diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/attach_attribute.rs b/crates/biome_html_formatter/src/svelte/auxiliary/attach_attribute.rs index 2a04f1cb6182..b5017166ddd4 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/attach_attribute.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/attach_attribute.rs @@ -17,6 +17,7 @@ impl FormatNodeRule for FormatSvelteAttachAttribute { [ sv_curly_at_token.format(), attach_token.format(), + space(), expression.format(), r_curly_token.format() ] diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/const_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/const_block.rs index df4032c80ac3..ecd89477aa65 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/const_block.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/const_block.rs @@ -17,6 +17,7 @@ impl FormatNodeRule for FormatSvelteConstBlock { [ sv_curly_at_token.format(), const_token.format(), + space(), expression.format(), r_curly_token.format() ] diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_as_keyed_item.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_as_keyed_item.rs new file mode 100644 index 000000000000..82230cc8deaa --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_as_keyed_item.rs @@ -0,0 +1,27 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{SvelteEachAsKeyedItem, SvelteEachAsKeyedItemFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteEachAsKeyedItem; +impl FormatNodeRule for FormatSvelteEachAsKeyedItem { + fn fmt_fields(&self, node: &SvelteEachAsKeyedItem, f: &mut HtmlFormatter) -> FormatResult<()> { + let SvelteEachAsKeyedItemFields { + as_token, + name, + index, + key, + } = node.as_fields(); + + write!(f, [space(), as_token.format(), space(), name.format(),])?; + + if let Some(index) = index { + write!(f, [index.format()])?; + } + + if let Some(key) = key { + write!(f, [key.format()])?; + } + + Ok(()) + } +} diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_block.rs index 9d6ea9bb431c..5a52ea4795ba 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/each_block.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_block.rs @@ -1,10 +1,23 @@ use crate::prelude::*; -use biome_html_syntax::SvelteEachBlock; -use biome_rowan::AstNode; +use biome_formatter::write; +use biome_html_syntax::{SvelteEachBlock, SvelteEachBlockFields}; #[derive(Debug, Clone, Default)] pub(crate) struct FormatSvelteEachBlock; impl FormatNodeRule for FormatSvelteEachBlock { fn fmt_fields(&self, node: &SvelteEachBlock, f: &mut HtmlFormatter) -> FormatResult<()> { - format_html_verbatim_node(node.syntax()).fmt(f) + let SvelteEachBlockFields { + opening_block, + children, + else_clause, + closing_block, + } = node.as_fields(); + + write!(f, [opening_block.format(), children.format(),])?; + + if let Some(else_clause) = else_clause { + write!(f, [else_clause.format()])?; + } + + write!(f, [closing_block.format()]) } } diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_closing_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_closing_block.rs index 41f49437b6fb..624714635b73 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/each_closing_block.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_closing_block.rs @@ -1,10 +1,23 @@ use crate::prelude::*; -use biome_html_syntax::SvelteEachClosingBlock; -use biome_rowan::AstNode; +use biome_formatter::write; +use biome_html_syntax::{SvelteEachClosingBlock, SvelteEachClosingBlockFields}; #[derive(Debug, Clone, Default)] pub(crate) struct FormatSvelteEachClosingBlock; impl FormatNodeRule for FormatSvelteEachClosingBlock { fn fmt_fields(&self, node: &SvelteEachClosingBlock, f: &mut HtmlFormatter) -> FormatResult<()> { - format_html_verbatim_node(node.syntax()).fmt(f) + let SvelteEachClosingBlockFields { + sv_curly_slash_token, + each_token, + r_curly_token, + } = node.as_fields(); + + write!( + f, + [ + sv_curly_slash_token.format(), + each_token.format(), + r_curly_token.format() + ] + ) } } diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_index.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_index.rs new file mode 100644 index 000000000000..fcf72c66a3ed --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_index.rs @@ -0,0 +1,12 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{SvelteEachIndex, SvelteEachIndexFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteEachIndex; +impl FormatNodeRule for FormatSvelteEachIndex { + fn fmt_fields(&self, node: &SvelteEachIndex, f: &mut HtmlFormatter) -> FormatResult<()> { + let SvelteEachIndexFields { comma_token, value } = node.as_fields(); + + write!(f, [comma_token.format(), space(), value.format()]) + } +} diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_key.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_key.rs new file mode 100644 index 000000000000..b7c19d95c2b5 --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_key.rs @@ -0,0 +1,12 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{SvelteEachKey, SvelteEachKeyFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteEachKey; +impl FormatNodeRule for FormatSvelteEachKey { + fn fmt_fields(&self, node: &SvelteEachKey, f: &mut HtmlFormatter) -> FormatResult<()> { + let SvelteEachKeyFields { expression } = node.as_fields(); + + write!(f, [expression.format()]) + } +} diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_keyed_item.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_keyed_item.rs new file mode 100644 index 000000000000..92412a1f3561 --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_keyed_item.rs @@ -0,0 +1,16 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{SvelteEachKeyedItem, SvelteEachKeyedItemFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteEachKeyedItem; +impl FormatNodeRule for FormatSvelteEachKeyedItem { + fn fmt_fields(&self, node: &SvelteEachKeyedItem, f: &mut HtmlFormatter) -> FormatResult<()> { + let SvelteEachKeyedItemFields { index } = node.as_fields(); + + if let Some(index) = index { + write!(f, [index.format()]) + } else { + Ok(()) + } + } +} diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_opening_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_opening_block.rs index 1f8faba28b8d..0b9abfd11192 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/each_opening_block.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_opening_block.rs @@ -1,10 +1,28 @@ use crate::prelude::*; -use biome_html_syntax::SvelteEachOpeningBlock; -use biome_rowan::AstNode; +use biome_formatter::write; +use biome_html_syntax::{SvelteEachOpeningBlock, SvelteEachOpeningBlockFields}; #[derive(Debug, Clone, Default)] pub(crate) struct FormatSvelteEachOpeningBlock; impl FormatNodeRule for FormatSvelteEachOpeningBlock { fn fmt_fields(&self, node: &SvelteEachOpeningBlock, f: &mut HtmlFormatter) -> FormatResult<()> { - format_html_verbatim_node(node.syntax()).fmt(f) + let SvelteEachOpeningBlockFields { + sv_curly_hash_token, + each_token, + list, + item, + r_curly_token, + } = node.as_fields(); + + write!( + f, + [ + sv_curly_hash_token.format(), + each_token.format(), + space(), + list.format(), + item.format(), + r_curly_token.format(), + ] + ) } } diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/else_if_clause.rs b/crates/biome_html_formatter/src/svelte/auxiliary/else_if_clause.rs index c435c6a38fac..3a9cbe9ccf89 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/else_if_clause.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/else_if_clause.rs @@ -23,6 +23,7 @@ impl FormatNodeRule for FormatSvelteElseIfClause { else_token.format(), space(), if_token.format(), + space(), expression.format(), r_curly_token.format() ] diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/html_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/html_block.rs index 8936e9ac05d9..40ae13e44e62 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/html_block.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/html_block.rs @@ -17,6 +17,7 @@ impl FormatNodeRule for FormatSvelteHtmlBlock { [ sv_curly_at_token.format(), html_token.format(), + space(), expression.format(), r_curly_token.format() ] diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/if_opening_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/if_opening_block.rs index 1fc9593f223e..2e51916a4a31 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/if_opening_block.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/if_opening_block.rs @@ -20,6 +20,7 @@ impl FormatNodeRule for FormatSvelteIfOpeningBlock { [ sv_curly_hash_token.format(), if_token.format(), + space(), expression.format(), r_curly_token.format(), ] diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/key_opening_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/key_opening_block.rs index 6ab4174912a4..217fb89d8f7b 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/key_opening_block.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/key_opening_block.rs @@ -18,6 +18,7 @@ impl FormatNodeRule for FormatSvelteKeyOpeningBlock { [ sv_curly_hash_token.format(), key_token.format(), + space(), expression.format(), r_curly_token.format(), ] diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/mod.rs b/crates/biome_html_formatter/src/svelte/auxiliary/mod.rs index 8676b43b4764..70f5348a158f 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/mod.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/mod.rs @@ -3,8 +3,12 @@ pub(crate) mod attach_attribute; pub(crate) mod const_block; pub(crate) mod debug_block; +pub(crate) mod each_as_keyed_item; pub(crate) mod each_block; pub(crate) mod each_closing_block; +pub(crate) mod each_index; +pub(crate) mod each_key; +pub(crate) mod each_keyed_item; pub(crate) mod each_opening_block; pub(crate) mod else_clause; pub(crate) mod else_if_clause; diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/render_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/render_block.rs index fe9d0c7beaa3..33929433e31e 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/render_block.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/render_block.rs @@ -17,6 +17,7 @@ impl FormatNodeRule for FormatSvelteRenderBlock { [ sv_curly_at_token.format(), render_token.format(), + space(), expression.format(), r_curly_token.format() ] diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte new file mode 100644 index 000000000000..310226be8da5 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte @@ -0,0 +1,3 @@ +{#each items as item} +
{item}
+{/each} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte.snap new file mode 100644 index 000000000000..618b68817d29 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte.snap @@ -0,0 +1,37 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/each_basic.svelte +--- +# Input + +```svelte +{#each items as item} +
{item}
+{/each} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```svelte +{#each items as item} +
{item}
+{/each} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte new file mode 100644 index 000000000000..44dde5f2f208 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte @@ -0,0 +1,13 @@ +{#each items as item, i (item.id)} +
{i}: {item.name}
+{:else} +
Empty list
+{/each} + +{#each users, index} +
User #{index}
+{/each} + +{#each products as product (product.sku)} +
{product.title}
+{/each} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte.snap new file mode 100644 index 000000000000..7b368862e09a --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte.snap @@ -0,0 +1,57 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/each_complex_combinations.svelte +--- +# Input + +```svelte +{#each items as item, i (item.id)} +
{i}: {item.name}
+{:else} +
Empty list
+{/each} + +{#each users, index} +
User #{index}
+{/each} + +{#each products as product (product.sku)} +
{product.title}
+{/each} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```svelte +{#each items as item, i (item.id)} +
{i}: {item.name}
+{:else} +
Empty list
+{/each} + +{#each users, index} +
User #{index}
+{/each} + +{#each products as product (product.sku)} +
{product.title}
+{/each} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte new file mode 100644 index 000000000000..3d676d1c97fb --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte @@ -0,0 +1,11 @@ +{#each obj.items as item} +
{item}
+{/each} + +{#each getItems() as item} +
{item}
+{/each} + +{#each items.filter(x => x.active) as item} +
{item}
+{/each} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte.snap new file mode 100644 index 000000000000..ca3bbc8f812f --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte.snap @@ -0,0 +1,53 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/each_complex_expression.svelte +--- +# Input + +```svelte +{#each obj.items as item} +
{item}
+{/each} + +{#each getItems() as item} +
{item}
+{/each} + +{#each items.filter(x => x.active) as item} +
{item}
+{/each} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```svelte +{#each obj.items as item} +
{item}
+{/each} + +{#each getItems() as item} +
{item}
+{/each} + +{#each items.filter(x => x.active) as item} +
{item}
+{/each} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte new file mode 100644 index 000000000000..19dfcb2cef74 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte @@ -0,0 +1,14 @@ +{#each categories as category} +
+

{category.name}

+ {#each category.items as item, i} +
{i}: {item.title}
+ {/each} +
+{/each} + +{#each matrix as row} + {#each row as cell} + {cell} + {/each} +{/each} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte.snap new file mode 100644 index 000000000000..0a6b4b326826 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte.snap @@ -0,0 +1,59 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/each_nested.svelte +--- +# Input + +```svelte +{#each categories as category} +
+

{category.name}

+ {#each category.items as item, i} +
{i}: {item.title}
+ {/each} +
+{/each} + +{#each matrix as row} + {#each row as cell} + {cell} + {/each} +{/each} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```svelte +{#each categories as category} +
+

{category.name}

+ {#each category.items as item, i} +
{i}: {item.title}
+ {/each} +
+{/each} + +{#each matrix as row} + {#each row as cell} + {cell} + {/each} +{/each} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte new file mode 100644 index 000000000000..821ca50d6d29 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte @@ -0,0 +1,15 @@ +{#each items as { id, name }} +
{id}: {name}
+{/each} + +{#each users as { name, email }, i} +
{i}: {name} ({email})
+{/each} + +{#each products as [id, title]} +
{id}: {title}
+{/each} + +{#each items as { id, ...rest }} +
{id}
+{/each} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte.snap new file mode 100644 index 000000000000..6449115b6fe5 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte.snap @@ -0,0 +1,61 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/each_with_destructuring.svelte +--- +# Input + +```svelte +{#each items as { id, name }} +
{id}: {name}
+{/each} + +{#each users as { name, email }, i} +
{i}: {name} ({email})
+{/each} + +{#each products as [id, title]} +
{id}: {title}
+{/each} + +{#each items as { id, ...rest }} +
{id}
+{/each} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```svelte +{#each items as { id, name }} +
{id}: {name}
+{/each} + +{#each users as { name, email }, i} +
{i}: {name}({email})
+{/each} + +{#each products as [id, title]} +
{id}: {title}
+{/each} + +{#each items as { id, ...rest }} +
{id}
+{/each} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte new file mode 100644 index 000000000000..bc1bed801ddc --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte @@ -0,0 +1,5 @@ +{#each items as item} +
{item}
+{:else} +
No items found
+{/each} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte.snap new file mode 100644 index 000000000000..3be2c48ee4d7 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte.snap @@ -0,0 +1,41 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/each_with_else.svelte +--- +# Input + +```svelte +{#each items as item} +
{item}
+{:else} +
No items found
+{/each} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```svelte +{#each items as item} +
{item}
+{:else} +
No items found
+{/each} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte new file mode 100644 index 000000000000..4e9ecaa3dda2 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte @@ -0,0 +1,3 @@ +{#each items as item, i} +
{i}: {item}
+{/each} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte.snap new file mode 100644 index 000000000000..0f79f7799183 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte.snap @@ -0,0 +1,37 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/each_with_index.svelte +--- +# Input + +```svelte +{#each items as item, i} +
{i}: {item}
+{/each} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```svelte +{#each items as item, i} +
{i}: {item}
+{/each} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte new file mode 100644 index 000000000000..5c15eaf3d75d --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte @@ -0,0 +1,3 @@ +{#each items as item, i (item.id)} +
{i}: {item.name}
+{/each} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte.snap new file mode 100644 index 000000000000..2ffcf0650203 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte.snap @@ -0,0 +1,37 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/each_with_index_and_key.svelte +--- +# Input + +```svelte +{#each items as item, i (item.id)} +
{i}: {item.name}
+{/each} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```svelte +{#each items as item, i (item.id)} +
{i}: {item.name}
+{/each} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte new file mode 100644 index 000000000000..54c2dd60ed86 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte @@ -0,0 +1,3 @@ +{#each items as item (item.id)} +
{item.name}
+{/each} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte.snap new file mode 100644 index 000000000000..3f879eef8f38 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte.snap @@ -0,0 +1,37 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/each_with_key.svelte +--- +# Input + +```svelte +{#each items as item (item.id)} +
{item.name}
+{/each} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```svelte +{#each items as item (item.id)} +
{item.name}
+{/each} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte new file mode 100644 index 000000000000..7d244b88df62 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte @@ -0,0 +1,11 @@ +{#each items as item } +
{item}
+{/each} + +{#each items as item,i} +
{item}
+{/each} + +{#each items as item ( item.id ) } +
{item}
+{/each} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap new file mode 100644 index 000000000000..1a97fdc1622e --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap @@ -0,0 +1,53 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/each_with_whitespace.svelte +--- +# Input + +```svelte +{#each items as item } +
{item}
+{/each} + +{#each items as item,i} +
{item}
+{/each} + +{#each items as item ( item.id ) } +
{item}
+{/each} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```svelte +{#each items as item} +
{item}
+{/each} + +{#each items as item, i} +
{item}
+{/each} + +{#each items as item ( item.id )} +
{item}
+{/each} +``` diff --git a/crates/biome_html_parser/src/lexer/mod.rs b/crates/biome_html_parser/src/lexer/mod.rs index 48757a62fd87..aafa19f28e72 100644 --- a/crates/biome_html_parser/src/lexer/mod.rs +++ b/crates/biome_html_parser/src/lexer/mod.rs @@ -1,7 +1,8 @@ mod tests; use crate::token_source::{ - HtmlEmbeddedLanguage, HtmlLexContext, HtmlReLexContext, TextExpressionKind, + HtmlEmbeddedLanguage, HtmlLexContext, HtmlReLexContext, RestrictedExpressionKind, + TextExpressionKind, }; use biome_html_syntax::HtmlSyntaxKind::{ AS_KW, ATTACH_KW, COMMENT, CONST_KW, DEBUG_KW, DOCTYPE_KW, EACH_KW, ELSE_KW, EOF, ERROR_TOKEN, @@ -335,6 +336,74 @@ impl<'src> HtmlLexer<'src> { HTML_LITERAL } + /// Consumes a restricted single text expression that stops at specific keywords + /// (e.g., 'as' in Svelte #each blocks). Tracks nested brackets and stops when + /// encountering a keyword at the top level. + fn consume_restricted_single_text_expression( + &mut self, + kind: RestrictedExpressionKind, + ) -> HtmlSyntaxKind { + let start_pos = self.position; + let mut brackets_stack = 0; + + while let Some(current) = self.current_byte() { + match current { + b'}' => { + if brackets_stack == 0 { + // Reached the closing brace + break; + } else { + brackets_stack -= 1; + self.advance(1); + } + } + b'{' => { + brackets_stack += 1; + self.advance(1); + } + b',' if brackets_stack == 0 => { + // Stop at comma (for index-only each syntax) + let should_stop = match kind { + RestrictedExpressionKind::StopAtAsOrComma => true, + }; + if should_stop { + break; + } + self.advance(1); + } + _ if brackets_stack == 0 && is_at_start_identifier(current) => { + // Check if we're at a stop keyword + let checkpoint_pos = self.position; + if let Some(keyword_kind) = self.consume_language_identifier(current) { + // Check if this keyword is in our stop list + let should_stop = match kind { + RestrictedExpressionKind::StopAtAsOrComma => keyword_kind == AS_KW, + }; + + if should_stop { + // Rewind - don't consume the keyword + self.position = checkpoint_pos; + break; + } + // Not a stop keyword, continue (position already advanced by consume_language_identifier) + } else { + // Not a keyword, advance one byte (position was reset by consume_language_identifier) + self.advance_byte_or_char(current); + } + } + _ => { + self.advance(1); + } + } + } + + if self.position > start_pos { + HTML_LITERAL + } else { + EOF + } + } + /// Consumes an HTML comment starting with '' is found. /// Returns COMMENT token type. fn consume_comment(&mut self) -> HtmlSyntaxKind { @@ -401,10 +470,26 @@ impl<'src> HtmlLexer<'src> { b'{' => self.consume_byte(T!['{']), _ if is_at_start_identifier(current) => self .consume_language_identifier(current) - .unwrap_or_else(|| self.consume_single_text_expression()), + .unwrap_or_else(|| self.consume_svelte_identifier(current)), _ => self.consume_single_text_expression(), } } + + /// Consumes a Svelte identifier (alphanumeric + underscore only) + fn consume_svelte_identifier(&mut self, first: u8) -> HtmlSyntaxKind { + self.assert_current_char_boundary(); + self.advance_byte_or_char(first); + + while let Some(byte) = self.current_byte() { + if is_at_continue_identifier(byte) { + self.advance(1); + } else { + break; + } + } + + IDENT + } /// Bumps the current byte and creates a lexed token of the passed in kind. #[inline] fn consume_byte(&mut self, tok: HtmlSyntaxKind) -> HtmlSyntaxKind { @@ -991,6 +1076,9 @@ impl<'src> Lexer<'src> for HtmlLexer<'src> { TextExpressionKind::Double => self.consume_double_text_expression(current), TextExpressionKind::Single => self.consume_single_text_expression(), }, + HtmlLexContext::RestrictedSingleExpression(kind) => { + self.consume_restricted_single_text_expression(kind) + } HtmlLexContext::CdataSection => self.consume_inside_cdata(current), HtmlLexContext::AstroFencedCodeBlock => { self.consume_astro_frontmatter(current, context) @@ -1071,7 +1159,6 @@ impl<'src> ReLexer<'src> for HtmlLexer<'src> { let re_lexed_kind = match self.current_byte() { Some(current) => match context { HtmlReLexContext::Svelte => self.consume_svelte(current), - HtmlReLexContext::SingleTextExpression => self.consume_single_text_expression(), HtmlReLexContext::HtmlText => self.consume_html_text(current), }, None => EOF, diff --git a/crates/biome_html_parser/src/syntax/svelte.rs b/crates/biome_html_parser/src/syntax/svelte.rs index a13ec0fe85ae..9c1836e451db 100644 --- a/crates/biome_html_parser/src/syntax/svelte.rs +++ b/crates/biome_html_parser/src/syntax/svelte.rs @@ -3,11 +3,12 @@ use crate::syntax::parse_error::{ expected_child_or_block, expected_svelte_closing_block, expected_text_expression, }; use crate::syntax::{parse_html_element, parse_single_text_expression_content}; -use crate::token_source::{HtmlLexContext, HtmlReLexContext}; +use crate::token_source::{HtmlLexContext, HtmlReLexContext, RestrictedExpressionKind}; use biome_html_syntax::HtmlSyntaxKind::{ - EOF, HTML_BOGUS_ELEMENT, HTML_ELEMENT_LIST, IDENT, SVELTE_ATTACH_ATTRIBUTE, + EOF, HTML_BOGUS_ELEMENT, HTML_ELEMENT_LIST, HTML_LITERAL, IDENT, SVELTE_ATTACH_ATTRIBUTE, SVELTE_BINDING_LIST, SVELTE_BOGUS_BLOCK, SVELTE_CONST_BLOCK, SVELTE_DEBUG_BLOCK, - SVELTE_EACH_BLOCK, SVELTE_EACH_CLOSING_BLOCK, SVELTE_EACH_OPENING_BLOCK, SVELTE_ELSE_CLAUSE, + SVELTE_EACH_AS_KEYED_ITEM, SVELTE_EACH_BLOCK, SVELTE_EACH_CLOSING_BLOCK, SVELTE_EACH_INDEX, + SVELTE_EACH_KEY, SVELTE_EACH_KEYED_ITEM, SVELTE_EACH_OPENING_BLOCK, SVELTE_ELSE_CLAUSE, SVELTE_ELSE_IF_CLAUSE, SVELTE_ELSE_IF_CLAUSE_LIST, SVELTE_HTML_BLOCK, SVELTE_IF_BLOCK, SVELTE_IF_CLOSING_BLOCK, SVELTE_IF_OPENING_BLOCK, SVELTE_KEY_BLOCK, SVELTE_KEY_CLOSING_BLOCK, SVELTE_KEY_OPENING_BLOCK, SVELTE_NAME, SVELTE_RENDER_BLOCK, @@ -156,36 +157,168 @@ fn parse_each_block(p: &mut HtmlParser, parent_marker: Marker) -> ParsedSyntax { return Absent; } - let result = parse_each_opening_block(p, parent_marker); + let (result, has_errors) = parse_each_opening_block(p, parent_marker); + let m = result.precede(p); - SvelteElementList::new().parse_list(p); + SvelteElementList::new() + .with_stop_at_curly_colon() + .parse_list(p); + + // Parse optional {:else} clause + if at_else_opening_block(p) { + parse_else_clause(p).ok(); + } parse_closing_block(p, T![each], SVELTE_EACH_CLOSING_BLOCK).or_add_diagnostic(p, |p, range| { expected_svelte_closing_block(p, range) .with_detail(range.sub(m.start()), "This is where the block started.") }); - Present(m.complete(p, SVELTE_EACH_BLOCK)) + if has_errors { + Present(m.complete(p, SVELTE_BOGUS_BLOCK)) + } else { + Present(m.complete(p, SVELTE_EACH_BLOCK)) + } +} + +/// Parses the "as item, index (key)" part of an each block +/// This includes the 'as' keyword, the item binding name, optional index, and optional key +fn parse_each_as_keyed_item(p: &mut HtmlParser) -> ParsedSyntax { + if !p.at(T![as]) { + return Absent; + } + + let m = p.start(); + + // Consume 'as' and switch context for name (stop at comma for optional index) + p.bump_with_context( + T![as], + HtmlLexContext::restricted_expression(RestrictedExpressionKind::StopAtAsOrComma), + ); + + // Parse name (required) + parse_single_text_expression_content(p).or_add_diagnostic(p, |p, range| { + p.err_builder("Expected a binding pattern after 'as'", range) + }); + + // Re-lex to Svelte context to recognize ',' and other tokens + p.re_lex(HtmlReLexContext::Svelte); + + // Parse optional index: , index + if p.at(T![,]) { + parse_each_index(p).ok(); + // Re-lex again to recognize a key expression opening paren + p.re_lex(HtmlReLexContext::Svelte); + } + + // Parse optional key: (key_expression) + // The key expression includes parentheses as part of the literal + if p.at(HTML_LITERAL) && p.cur_text().trim_start().starts_with('(') { + let key_m = p.start(); + let key_text = p.cur_text().to_string(); + parse_single_text_expression_content(p).or_add_diagnostic(p, |p, range| { + p.err_builder("Expected a key expression in parentheses", range) + }); + + // Validate that the key expression has matching parentheses + let trimmed = key_text.trim(); + if !trimmed.ends_with(')') { + p.error(p.err_builder("Expected closing ')' for key expression", p.cur_range())); + } + + key_m.complete(p, SVELTE_EACH_KEY); + } + + Present(m.complete(p, SVELTE_EACH_AS_KEYED_ITEM)) } -fn parse_each_opening_block(p: &mut HtmlParser, parent_marker: Marker) -> ParsedSyntax { +/// Parses the ", index" part for index-only syntax (without 'as') +/// Example: {#each items, i} +fn parse_each_keyed_item(p: &mut HtmlParser) -> ParsedSyntax { + if !p.at(T![,]) { + return Absent; + } + + let m = p.start(); + + // Parse the index + parse_each_index(p).ok(); + + Present(m.complete(p, SVELTE_EACH_KEYED_ITEM)) +} + +/// Parses the ", index" part for index-only syntax (without 'as') +/// Example: {#each items, i} +fn parse_each_index(p: &mut HtmlParser) -> ParsedSyntax { + if !p.at(T![,]) { + return Absent; + } + // Parse the index + let m = p.start(); + p.bump_with_context(T![,], HtmlLexContext::single_expression()); + parse_single_text_expression_content(p).or_add_diagnostic(p, |p, range| { + p.err_builder("Expected an index binding after ','", range) + }); + Present(m.complete(p, SVELTE_EACH_INDEX)) +} + +/// Determines which variant of AnySvelteBlockItem to parse based on lookahead +fn parse_svelte_block_item(p: &mut HtmlParser) -> ParsedSyntax { + if p.at(T![as]) { + parse_each_as_keyed_item(p) + } else if p.at(T![,]) { + parse_each_keyed_item(p) + } else { + // Error: missing 'as' or ',' + p.error(p.err_builder( + "Expected 'as' keyword for item binding or ',' for index-only syntax", + p.cur_range(), + )); + Absent + } +} + +fn parse_each_opening_block(p: &mut HtmlParser, parent_marker: Marker) -> (ParsedSyntax, bool) { if !p.at(T![each]) { parent_marker.abandon(p); - return Absent; + return (Absent, false); } - p.bump_with_context(T![each], HtmlLexContext::single_expression()); + p.bump_with_context( + T![each], + HtmlLexContext::restricted_expression(RestrictedExpressionKind::StopAtAsOrComma), + ); + // Flags used to track possible errors so that the final block can be emitted as a bogus node + let mut has_errors = false; + + // Parse the collection expression (stops at 'as' or ',') + let result = parse_single_text_expression_content(p).or_add_diagnostic(p, |p, range| { + p.err_builder( + "Expected an expression after 'each'", + range.sub_start(parent_marker.start()), + ) + }); - // It should stop at `as` - parse_single_text_expression_content(p).ok(); + // In case there's nothing parsed, it's possible we have whitespaces or noice. + // We consume any possible token, so we can recover and resume normal parsing. + if result.is_none() { + has_errors |= true; + p.bump_any(); + } - p.expect_with_context(T![as], HtmlLexContext::single_expression()); + // After parsing the expression, switch back to the Svelte context so we can properly + // tokenize 'as', ',', and other tokens + p.re_lex(HtmlReLexContext::Svelte); - parse_single_text_expression_content(p).ok(); + // Parse the optional item binding (either 'as item...' or ', index') + parse_svelte_block_item(p).ok(); p.expect(T!['}']); - Present(parent_marker.complete(p, SVELTE_EACH_OPENING_BLOCK)) + ( + Present(parent_marker.complete(p, SVELTE_EACH_OPENING_BLOCK)), + has_errors, + ) } /// Parses a `{# expression }` block. @@ -474,3 +607,7 @@ pub(crate) fn is_at_svelte_keyword(p: &HtmlParser) -> bool { | T![as] ) } + +fn at_else_opening_block(p: &mut HtmlParser) -> bool { + p.at(T!["{:"]) && p.nth_at(1, T![else]) +} diff --git a/crates/biome_html_parser/src/token_source.rs b/crates/biome_html_parser/src/token_source.rs index b21055ed1233..e8c7c7a9c309 100644 --- a/crates/biome_html_parser/src/token_source.rs +++ b/crates/biome_html_parser/src/token_source.rs @@ -44,6 +44,8 @@ pub(crate) enum HtmlLexContext { /// - `{{ foo }}` /// - `attr={ foo }` TextExpression(TextExpressionKind), + /// Single expression that stops at certain keywords (e.g., 'as' in Svelte each blocks) + RestrictedSingleExpression(RestrictedExpressionKind), /// Enables the `html` keyword token. /// /// When the parser has encounters the sequence `` token is encountered. @@ -65,6 +67,10 @@ impl HtmlLexContext { pub fn double_expression() -> Self { Self::TextExpression(TextExpressionKind::Double) } + + pub fn restricted_expression(kind: RestrictedExpressionKind) -> Self { + Self::RestrictedSingleExpression(kind) + } } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] @@ -76,6 +82,12 @@ pub(crate) enum TextExpressionKind { Single, } +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) enum RestrictedExpressionKind { + /// Stops at 'as' keyword or ',' (for Svelte #each blocks) + StopAtAsOrComma, +} + #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] pub(crate) enum HtmlEmbeddedLanguage { Script, @@ -102,7 +114,6 @@ impl LexContext for HtmlLexContext { #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub(crate) enum HtmlReLexContext { Svelte, - SingleTextExpression, /// Relex tokens using `HtmlLexer::consume_html_text` HtmlText, } diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/debug-trailing-comma.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/debug-trailing-comma.svelte.snap index d8ae22d2301d..f1bb7bdae8f8 100644 --- a/crates/biome_html_parser/tests/html_specs/error/svelte/debug-trailing-comma.svelte.snap +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/debug-trailing-comma.svelte.snap @@ -23,11 +23,11 @@ HtmlRoot { debug_token: DEBUG_KW@2..8 "debug" [] [Whitespace(" ")], bindings: SvelteBindingList [ SvelteName { - svelte_ident_token: SVELTE_IDENT@8..17 "something" [] [], + ident_token: IDENT@8..17 "something" [] [], }, COMMA@17..18 "," [] [], SvelteName { - svelte_ident_token: SVELTE_IDENT@18..19 "}" [] [], + ident_token: IDENT@18..19 "}" [] [], }, ], r_curly_token: missing (required), @@ -50,10 +50,10 @@ HtmlRoot { 1: DEBUG_KW@2..8 "debug" [] [Whitespace(" ")] 2: SVELTE_BINDING_LIST@8..19 0: SVELTE_NAME@8..17 - 0: SVELTE_IDENT@8..17 "something" [] [] + 0: IDENT@8..17 "something" [] [] 1: COMMA@17..18 "," [] [] 2: SVELTE_NAME@18..19 - 0: SVELTE_IDENT@18..19 "}" [] [] + 0: IDENT@18..19 "}" [] [] 3: (empty) 4: EOF@19..20 "" [Newline("\n")] [] diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_as.svelte b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_as.svelte new file mode 100644 index 000000000000..8ae95ef9e30c --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_as.svelte @@ -0,0 +1,3 @@ +{#each items item} +
{item}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_as.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_as.svelte.snap new file mode 100644 index 000000000000..8ade3aa0a5fd --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_as.svelte.snap @@ -0,0 +1,131 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items item} +
{item}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..17 " items item" [] [], + }, + item: missing (optional), + r_curly_token: R_CURLY@17..18 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@18..22 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@22..25 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@25..26 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@26..27 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@27..31 "item" [] [], + }, + r_curly_token: R_CURLY@31..32 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@32..33 "<" [] [], + slash_token: SLASH@33..34 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@34..37 "div" [] [], + }, + r_angle_token: R_ANGLE@37..38 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@38..41 "{/" [Newline("\n")] [], + each_token: EACH_KW@41..45 "each" [] [], + r_curly_token: R_CURLY@45..46 "}" [] [], + }, + }, + ], + eof_token: EOF@46..47 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..47 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..46 + 0: SVELTE_EACH_BLOCK@0..46 + 0: SVELTE_EACH_OPENING_BLOCK@0..18 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..17 + 0: HTML_LITERAL@6..17 " items item" [] [] + 3: (empty) + 4: R_CURLY@17..18 "}" [] [] + 1: HTML_ELEMENT_LIST@18..38 + 0: HTML_ELEMENT@18..38 + 0: HTML_OPENING_ELEMENT@18..26 + 0: L_ANGLE@18..22 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@22..25 + 0: HTML_LITERAL@22..25 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@25..25 + 3: R_ANGLE@25..26 ">" [] [] + 1: HTML_ELEMENT_LIST@26..32 + 0: HTML_SINGLE_TEXT_EXPRESSION@26..32 + 0: L_CURLY@26..27 "{" [] [] + 1: HTML_TEXT_EXPRESSION@27..31 + 0: HTML_LITERAL@27..31 "item" [] [] + 2: R_CURLY@31..32 "}" [] [] + 2: HTML_CLOSING_ELEMENT@32..38 + 0: L_ANGLE@32..33 "<" [] [] + 1: SLASH@33..34 "/" [] [] + 2: HTML_TAG_NAME@34..37 + 0: HTML_LITERAL@34..37 "div" [] [] + 3: R_ANGLE@37..38 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@38..46 + 0: SV_CURLY_SLASH@38..41 "{/" [Newline("\n")] [] + 1: EACH_KW@41..45 "each" [] [] + 2: R_CURLY@45..46 "}" [] [] + 4: EOF@46..47 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +each_missing_as.svelte:1:18 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected 'as' keyword for item binding or ',' for index-only syntax + + > 1 │ {#each items item} + │ ^ + 2 │
{item}
+ 3 │ {/each} + +``` diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_binding.svelte b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_binding.svelte new file mode 100644 index 000000000000..10be041a53b1 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_binding.svelte @@ -0,0 +1,3 @@ +{#each items as } +
content
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_binding.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_binding.svelte.snap new file mode 100644 index 000000000000..91c436e9b128 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_binding.svelte.snap @@ -0,0 +1,142 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items as } +
content
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..13 " items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@13..15 "as" [] [], + name: missing (required), + index: missing (optional), + key: missing (optional), + }, + r_curly_token: missing (required), + }, + children: HtmlElementList [ + HtmlBogusElement { + items: [ + WHITESPACE@15..16 " " [] [], + R_CURLY@16..17 "}" [] [], + ], + }, + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@17..21 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@21..24 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@24..25 ">" [] [], + }, + children: HtmlElementList [ + HtmlContent { + value_token: HTML_LITERAL@25..32 "content" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@32..33 "<" [] [], + slash_token: SLASH@33..34 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@34..37 "div" [] [], + }, + r_angle_token: R_ANGLE@37..38 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@38..41 "{/" [Newline("\n")] [], + each_token: EACH_KW@41..45 "each" [] [], + r_curly_token: R_CURLY@45..46 "}" [] [], + }, + }, + ], + eof_token: EOF@46..47 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..47 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..46 + 0: SVELTE_EACH_BLOCK@0..46 + 0: SVELTE_EACH_OPENING_BLOCK@0..15 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..13 + 0: HTML_LITERAL@6..13 " items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@13..15 + 0: AS_KW@13..15 "as" [] [] + 1: (empty) + 2: (empty) + 3: (empty) + 4: (empty) + 1: HTML_ELEMENT_LIST@15..38 + 0: HTML_BOGUS_ELEMENT@15..17 + 0: WHITESPACE@15..16 " " [] [] + 1: R_CURLY@16..17 "}" [] [] + 1: HTML_ELEMENT@17..38 + 0: HTML_OPENING_ELEMENT@17..25 + 0: L_ANGLE@17..21 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@21..24 + 0: HTML_LITERAL@21..24 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@24..24 + 3: R_ANGLE@24..25 ">" [] [] + 1: HTML_ELEMENT_LIST@25..32 + 0: HTML_CONTENT@25..32 + 0: HTML_LITERAL@25..32 "content" [] [] + 2: HTML_CLOSING_ELEMENT@32..38 + 0: L_ANGLE@32..33 "<" [] [] + 1: SLASH@33..34 "/" [] [] + 2: HTML_TAG_NAME@34..37 + 0: HTML_LITERAL@34..37 "div" [] [] + 3: R_ANGLE@37..38 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@38..46 + 0: SV_CURLY_SLASH@38..41 "{/" [Newline("\n")] [] + 1: EACH_KW@41..45 "each" [] [] + 2: R_CURLY@45..46 "}" [] [] + 4: EOF@46..47 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +each_missing_binding.svelte:1:16 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a binding pattern after 'as' + + > 1 │ {#each items as } + │ ^ + 2 │
content
+ 3 │ {/each} + +``` diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_expression.svelte b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_expression.svelte new file mode 100644 index 000000000000..2a89af5e25d2 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_expression.svelte @@ -0,0 +1,3 @@ +{#each as item} +
{item}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_expression.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_expression.svelte.snap new file mode 100644 index 000000000000..355a6ecdff31 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_expression.svelte.snap @@ -0,0 +1,142 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each as item} +
{item}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteBogusBlock { + items: [ + HtmlBogus { + items: [ + SV_CURLY_HASH@0..2 "{#" [] [], + EACH_KW@2..6 "each" [] [], + HTML_LITERAL@6..7 " " [] [], + SvelteEachAsKeyedItem { + as_token: AS_KW@7..9 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@9..14 " item" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + R_CURLY@14..15 "}" [] [], + ], + }, + HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@15..19 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@19..22 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@22..23 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@23..24 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@24..28 "item" [] [], + }, + r_curly_token: R_CURLY@28..29 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@29..30 "<" [] [], + slash_token: SLASH@30..31 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@31..34 "div" [] [], + }, + r_angle_token: R_ANGLE@34..35 ">" [] [], + }, + }, + ], + SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@35..38 "{/" [Newline("\n")] [], + each_token: EACH_KW@38..42 "each" [] [], + r_curly_token: R_CURLY@42..43 "}" [] [], + }, + ], + }, + ], + eof_token: EOF@43..44 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..44 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..43 + 0: SVELTE_BOGUS_BLOCK@0..43 + 0: HTML_BOGUS@0..15 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_LITERAL@6..7 " " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@7..14 + 0: AS_KW@7..9 "as" [] [] + 1: HTML_TEXT_EXPRESSION@9..14 + 0: HTML_LITERAL@9..14 " item" [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@14..15 "}" [] [] + 1: HTML_ELEMENT_LIST@15..35 + 0: HTML_ELEMENT@15..35 + 0: HTML_OPENING_ELEMENT@15..23 + 0: L_ANGLE@15..19 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@19..22 + 0: HTML_LITERAL@19..22 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@22..22 + 3: R_ANGLE@22..23 ">" [] [] + 1: HTML_ELEMENT_LIST@23..29 + 0: HTML_SINGLE_TEXT_EXPRESSION@23..29 + 0: L_CURLY@23..24 "{" [] [] + 1: HTML_TEXT_EXPRESSION@24..28 + 0: HTML_LITERAL@24..28 "item" [] [] + 2: R_CURLY@28..29 "}" [] [] + 2: HTML_CLOSING_ELEMENT@29..35 + 0: L_ANGLE@29..30 "<" [] [] + 1: SLASH@30..31 "/" [] [] + 2: HTML_TAG_NAME@31..34 + 0: HTML_LITERAL@31..34 "div" [] [] + 3: R_ANGLE@34..35 ">" [] [] + 2: SVELTE_EACH_CLOSING_BLOCK@35..43 + 0: SV_CURLY_SLASH@35..38 "{/" [Newline("\n")] [] + 1: EACH_KW@38..42 "each" [] [] + 2: R_CURLY@42..43 "}" [] [] + 4: EOF@43..44 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +each_missing_expression.svelte:1:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression after 'each' + + > 1 │ {#each as item} + │ ^ + 2 │
{item}
+ 3 │ {/each} + +``` diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/each_unclosed.svelte b/crates/biome_html_parser/tests/html_specs/error/svelte/each_unclosed.svelte new file mode 100644 index 000000000000..3d3980ac48b0 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/each_unclosed.svelte @@ -0,0 +1,3 @@ +{#each items as item +
{item}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/each_unclosed.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/each_unclosed.svelte.snap new file mode 100644 index 000000000000..2fe113529cc0 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/each_unclosed.svelte.snap @@ -0,0 +1,96 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items as item +
{item}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..13 " items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@13..15 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@15..49 " item\n
{item}
\n{/each}\n" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: missing (required), + }, + children: HtmlElementList [], + else_clause: missing (optional), + closing_block: missing (required), + }, + ], + eof_token: EOF@49..49 "" [] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..49 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..49 + 0: SVELTE_EACH_BLOCK@0..49 + 0: SVELTE_EACH_OPENING_BLOCK@0..49 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..13 + 0: HTML_LITERAL@6..13 " items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@13..49 + 0: AS_KW@13..15 "as" [] [] + 1: HTML_TEXT_EXPRESSION@15..49 + 0: HTML_LITERAL@15..49 " item\n
{item}
\n{/each}\n" [] [] + 2: (empty) + 3: (empty) + 4: (empty) + 1: HTML_ELEMENT_LIST@49..49 + 2: (empty) + 3: (empty) + 4: EOF@49..49 "" [] [] + +``` + +## Diagnostics + +``` +each_unclosed.svelte:4:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `}` but instead the file ends + + 2 │
{item}
+ 3 │ {/each} + > 4 │ + │ + + i the file ends here + + 2 │
{item}
+ 3 │ {/each} + > 4 │ + │ + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/debug.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/debug.svelte.snap index 63d26d253685..421e701409d6 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/svelte/debug.svelte.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/debug.svelte.snap @@ -52,7 +52,15 @@ HtmlRoot { debug_token: DEBUG_KW@45..51 "debug" [] [Whitespace(" ")], bindings: SvelteBindingList [ SvelteName { - ident_token: IDENT@51..82 "something, something, something" [] [], + ident_token: IDENT@51..60 "something" [] [], + }, + COMMA@60..62 "," [] [Whitespace(" ")], + SvelteName { + ident_token: IDENT@62..71 "something" [] [], + }, + COMMA@71..73 "," [] [Whitespace(" ")], + SvelteName { + ident_token: IDENT@73..82 "something" [] [], }, ], r_curly_token: R_CURLY@82..83 "}" [] [], @@ -93,8 +101,14 @@ HtmlRoot { 0: SV_CURLY_AT@42..45 "{@" [Newline("\n")] [] 1: DEBUG_KW@45..51 "debug" [] [Whitespace(" ")] 2: SVELTE_BINDING_LIST@51..82 - 0: SVELTE_NAME@51..82 - 0: IDENT@51..82 "something, something, something" [] [] + 0: SVELTE_NAME@51..60 + 0: IDENT@51..60 "something" [] [] + 1: COMMA@60..62 "," [] [Whitespace(" ")] + 2: SVELTE_NAME@62..71 + 0: IDENT@62..71 "something" [] [] + 3: COMMA@71..73 "," [] [Whitespace(" ")] + 4: SVELTE_NAME@73..82 + 0: IDENT@73..82 "something" [] [] 3: R_CURLY@82..83 "}" [] [] 4: EOF@83..84 "" [Newline("\n")] [] diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_as_in_identifier.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_as_in_identifier.svelte new file mode 100644 index 000000000000..0a6a2fcf5bd2 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_as_in_identifier.svelte @@ -0,0 +1,7 @@ +{#each classes as class} +
+{/each} + +{#each async_tasks as task} +
{task}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_as_in_identifier.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_as_in_identifier.svelte.snap new file mode 100644 index 000000000000..f8bce0247c12 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_as_in_identifier.svelte.snap @@ -0,0 +1,237 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each classes as class} +
+{/each} + +{#each async_tasks as task} +
{task}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..15 " classes " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@15..17 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@17..23 " class" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: R_CURLY@23..24 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@24..28 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@28..32 "div" [] [Whitespace(" ")], + }, + attributes: HtmlAttributeList [ + HtmlAttribute { + name: HtmlAttributeName { + value_token: HTML_LITERAL@32..37 "class" [] [], + }, + initializer: HtmlAttributeInitializerClause { + eq_token: EQ@37..38 "=" [] [], + value: HtmlSingleTextExpression { + l_curly_token: L_CURLY@38..39 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@39..44 "class" [] [], + }, + r_curly_token: R_CURLY@44..45 "}" [] [], + }, + }, + }, + ], + r_angle_token: R_ANGLE@45..46 ">" [] [], + }, + children: HtmlElementList [], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@46..47 "<" [] [], + slash_token: SLASH@47..48 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@48..51 "div" [] [], + }, + r_angle_token: R_ANGLE@51..52 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@52..55 "{/" [Newline("\n")] [], + each_token: EACH_KW@55..59 "each" [] [], + r_curly_token: R_CURLY@59..60 "}" [] [], + }, + }, + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@60..64 "{#" [Newline("\n"), Newline("\n")] [], + each_token: EACH_KW@64..68 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@68..81 " async_tasks " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@81..83 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@83..88 " task" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: R_CURLY@88..89 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@89..93 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@93..96 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@96..97 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@97..98 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@98..102 "task" [] [], + }, + r_curly_token: R_CURLY@102..103 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@103..104 "<" [] [], + slash_token: SLASH@104..105 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@105..108 "div" [] [], + }, + r_angle_token: R_ANGLE@108..109 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@109..112 "{/" [Newline("\n")] [], + each_token: EACH_KW@112..116 "each" [] [], + r_curly_token: R_CURLY@116..117 "}" [] [], + }, + }, + ], + eof_token: EOF@117..118 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..118 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..117 + 0: SVELTE_EACH_BLOCK@0..60 + 0: SVELTE_EACH_OPENING_BLOCK@0..24 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..15 + 0: HTML_LITERAL@6..15 " classes " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@15..23 + 0: AS_KW@15..17 "as" [] [] + 1: HTML_TEXT_EXPRESSION@17..23 + 0: HTML_LITERAL@17..23 " class" [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@23..24 "}" [] [] + 1: HTML_ELEMENT_LIST@24..52 + 0: HTML_ELEMENT@24..52 + 0: HTML_OPENING_ELEMENT@24..46 + 0: L_ANGLE@24..28 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@28..32 + 0: HTML_LITERAL@28..32 "div" [] [Whitespace(" ")] + 2: HTML_ATTRIBUTE_LIST@32..45 + 0: HTML_ATTRIBUTE@32..45 + 0: HTML_ATTRIBUTE_NAME@32..37 + 0: HTML_LITERAL@32..37 "class" [] [] + 1: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@37..45 + 0: EQ@37..38 "=" [] [] + 1: HTML_SINGLE_TEXT_EXPRESSION@38..45 + 0: L_CURLY@38..39 "{" [] [] + 1: HTML_TEXT_EXPRESSION@39..44 + 0: HTML_LITERAL@39..44 "class" [] [] + 2: R_CURLY@44..45 "}" [] [] + 3: R_ANGLE@45..46 ">" [] [] + 1: HTML_ELEMENT_LIST@46..46 + 2: HTML_CLOSING_ELEMENT@46..52 + 0: L_ANGLE@46..47 "<" [] [] + 1: SLASH@47..48 "/" [] [] + 2: HTML_TAG_NAME@48..51 + 0: HTML_LITERAL@48..51 "div" [] [] + 3: R_ANGLE@51..52 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@52..60 + 0: SV_CURLY_SLASH@52..55 "{/" [Newline("\n")] [] + 1: EACH_KW@55..59 "each" [] [] + 2: R_CURLY@59..60 "}" [] [] + 1: SVELTE_EACH_BLOCK@60..117 + 0: SVELTE_EACH_OPENING_BLOCK@60..89 + 0: SV_CURLY_HASH@60..64 "{#" [Newline("\n"), Newline("\n")] [] + 1: EACH_KW@64..68 "each" [] [] + 2: HTML_TEXT_EXPRESSION@68..81 + 0: HTML_LITERAL@68..81 " async_tasks " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@81..88 + 0: AS_KW@81..83 "as" [] [] + 1: HTML_TEXT_EXPRESSION@83..88 + 0: HTML_LITERAL@83..88 " task" [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@88..89 "}" [] [] + 1: HTML_ELEMENT_LIST@89..109 + 0: HTML_ELEMENT@89..109 + 0: HTML_OPENING_ELEMENT@89..97 + 0: L_ANGLE@89..93 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@93..96 + 0: HTML_LITERAL@93..96 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@96..96 + 3: R_ANGLE@96..97 ">" [] [] + 1: HTML_ELEMENT_LIST@97..103 + 0: HTML_SINGLE_TEXT_EXPRESSION@97..103 + 0: L_CURLY@97..98 "{" [] [] + 1: HTML_TEXT_EXPRESSION@98..102 + 0: HTML_LITERAL@98..102 "task" [] [] + 2: R_CURLY@102..103 "}" [] [] + 2: HTML_CLOSING_ELEMENT@103..109 + 0: L_ANGLE@103..104 "<" [] [] + 1: SLASH@104..105 "/" [] [] + 2: HTML_TAG_NAME@105..108 + 0: HTML_LITERAL@105..108 "div" [] [] + 3: R_ANGLE@108..109 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@109..117 + 0: SV_CURLY_SLASH@109..112 "{/" [Newline("\n")] [] + 1: EACH_KW@112..116 "each" [] [] + 2: R_CURLY@116..117 "}" [] [] + 4: EOF@117..118 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_basic.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_basic.svelte new file mode 100644 index 000000000000..310226be8da5 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_basic.svelte @@ -0,0 +1,3 @@ +{#each items as item} +
{item}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_basic.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_basic.svelte.snap new file mode 100644 index 000000000000..0c7044397d44 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_basic.svelte.snap @@ -0,0 +1,129 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items as item} +
{item}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..13 " items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@13..15 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@15..20 " item" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: R_CURLY@20..21 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@21..25 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@25..28 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@28..29 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@29..30 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@30..34 "item" [] [], + }, + r_curly_token: R_CURLY@34..35 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@35..36 "<" [] [], + slash_token: SLASH@36..37 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@37..40 "div" [] [], + }, + r_angle_token: R_ANGLE@40..41 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@41..44 "{/" [Newline("\n")] [], + each_token: EACH_KW@44..48 "each" [] [], + r_curly_token: R_CURLY@48..49 "}" [] [], + }, + }, + ], + eof_token: EOF@49..50 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..50 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..49 + 0: SVELTE_EACH_BLOCK@0..49 + 0: SVELTE_EACH_OPENING_BLOCK@0..21 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..13 + 0: HTML_LITERAL@6..13 " items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@13..20 + 0: AS_KW@13..15 "as" [] [] + 1: HTML_TEXT_EXPRESSION@15..20 + 0: HTML_LITERAL@15..20 " item" [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@20..21 "}" [] [] + 1: HTML_ELEMENT_LIST@21..41 + 0: HTML_ELEMENT@21..41 + 0: HTML_OPENING_ELEMENT@21..29 + 0: L_ANGLE@21..25 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@25..28 + 0: HTML_LITERAL@25..28 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@28..28 + 3: R_ANGLE@28..29 ">" [] [] + 1: HTML_ELEMENT_LIST@29..35 + 0: HTML_SINGLE_TEXT_EXPRESSION@29..35 + 0: L_CURLY@29..30 "{" [] [] + 1: HTML_TEXT_EXPRESSION@30..34 + 0: HTML_LITERAL@30..34 "item" [] [] + 2: R_CURLY@34..35 "}" [] [] + 2: HTML_CLOSING_ELEMENT@35..41 + 0: L_ANGLE@35..36 "<" [] [] + 1: SLASH@36..37 "/" [] [] + 2: HTML_TAG_NAME@37..40 + 0: HTML_LITERAL@37..40 "div" [] [] + 3: R_ANGLE@40..41 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@41..49 + 0: SV_CURLY_SLASH@41..44 "{/" [Newline("\n")] [] + 1: EACH_KW@44..48 "each" [] [] + 2: R_CURLY@48..49 "}" [] [] + 4: EOF@49..50 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte new file mode 100644 index 000000000000..44dde5f2f208 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte @@ -0,0 +1,13 @@ +{#each items as item, i (item.id)} +
{i}: {item.name}
+{:else} +
Empty list
+{/each} + +{#each users, index} +
User #{index}
+{/each} + +{#each products as product (product.sku)} +
{product.title}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte.snap new file mode 100644 index 000000000000..56125f35fc8a --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte.snap @@ -0,0 +1,399 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items as item, i (item.id)} +
{i}: {item.name}
+{:else} +
Empty list
+{/each} + +{#each users, index} +
User #{index}
+{/each} + +{#each products as product (product.sku)} +
{product.title}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..13 " items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@13..15 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@15..20 " item" [] [], + }, + index: SvelteEachIndex { + comma_token: COMMA@20..21 "," [] [], + value: HtmlTextExpression { + html_literal_token: HTML_LITERAL@21..33 " i (item.id)" [] [], + }, + }, + key: missing (optional), + }, + r_curly_token: R_CURLY@33..34 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@34..38 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@38..41 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@41..42 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@42..43 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@43..44 "i" [] [], + }, + r_curly_token: R_CURLY@44..45 "}" [] [], + }, + HtmlContent { + value_token: HTML_LITERAL@45..47 ":" [] [Whitespace(" ")], + }, + HtmlSingleTextExpression { + l_curly_token: L_CURLY@47..48 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@48..57 "item.name" [] [], + }, + r_curly_token: R_CURLY@57..58 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@58..59 "<" [] [], + slash_token: SLASH@59..60 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@60..63 "div" [] [], + }, + r_angle_token: R_ANGLE@63..64 ">" [] [], + }, + }, + ], + else_clause: SvelteElseClause { + sv_curly_colon_token: SV_CURLY_COLON@64..67 "{:" [Newline("\n")] [], + else_token: ELSE_KW@67..71 "else" [] [], + r_curly_token: R_CURLY@71..72 "}" [] [], + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@72..76 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@76..79 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@79..80 ">" [] [], + }, + children: HtmlElementList [ + HtmlContent { + value_token: HTML_LITERAL@80..90 "Empty list" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@90..91 "<" [] [], + slash_token: SLASH@91..92 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@92..95 "div" [] [], + }, + r_angle_token: R_ANGLE@95..96 ">" [] [], + }, + }, + ], + }, + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@96..99 "{/" [Newline("\n")] [], + each_token: EACH_KW@99..103 "each" [] [], + r_curly_token: R_CURLY@103..104 "}" [] [], + }, + }, + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@104..108 "{#" [Newline("\n"), Newline("\n")] [], + each_token: EACH_KW@108..112 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@112..118 " users" [] [], + }, + item: SvelteEachKeyedItem { + index: SvelteEachIndex { + comma_token: COMMA@118..119 "," [] [], + value: HtmlTextExpression { + html_literal_token: HTML_LITERAL@119..125 " index" [] [], + }, + }, + }, + r_curly_token: R_CURLY@125..126 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@126..130 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@130..133 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@133..134 ">" [] [], + }, + children: HtmlElementList [ + HtmlContent { + value_token: HTML_LITERAL@134..140 "User #" [] [], + }, + HtmlSingleTextExpression { + l_curly_token: L_CURLY@140..141 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@141..146 "index" [] [], + }, + r_curly_token: R_CURLY@146..147 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@147..148 "<" [] [], + slash_token: SLASH@148..149 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@149..152 "div" [] [], + }, + r_angle_token: R_ANGLE@152..153 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@153..156 "{/" [Newline("\n")] [], + each_token: EACH_KW@156..160 "each" [] [], + r_curly_token: R_CURLY@160..161 "}" [] [], + }, + }, + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@161..165 "{#" [Newline("\n"), Newline("\n")] [], + each_token: EACH_KW@165..169 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@169..179 " products " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@179..181 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@181..203 " product (product.sku)" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: R_CURLY@203..204 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@204..208 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@208..211 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@211..212 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@212..213 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@213..226 "product.title" [] [], + }, + r_curly_token: R_CURLY@226..227 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@227..228 "<" [] [], + slash_token: SLASH@228..229 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@229..232 "div" [] [], + }, + r_angle_token: R_ANGLE@232..233 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@233..236 "{/" [Newline("\n")] [], + each_token: EACH_KW@236..240 "each" [] [], + r_curly_token: R_CURLY@240..241 "}" [] [], + }, + }, + ], + eof_token: EOF@241..242 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..242 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..241 + 0: SVELTE_EACH_BLOCK@0..104 + 0: SVELTE_EACH_OPENING_BLOCK@0..34 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..13 + 0: HTML_LITERAL@6..13 " items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@13..33 + 0: AS_KW@13..15 "as" [] [] + 1: HTML_TEXT_EXPRESSION@15..20 + 0: HTML_LITERAL@15..20 " item" [] [] + 2: SVELTE_EACH_INDEX@20..33 + 0: COMMA@20..21 "," [] [] + 1: HTML_TEXT_EXPRESSION@21..33 + 0: HTML_LITERAL@21..33 " i (item.id)" [] [] + 3: (empty) + 4: R_CURLY@33..34 "}" [] [] + 1: HTML_ELEMENT_LIST@34..64 + 0: HTML_ELEMENT@34..64 + 0: HTML_OPENING_ELEMENT@34..42 + 0: L_ANGLE@34..38 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@38..41 + 0: HTML_LITERAL@38..41 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@41..41 + 3: R_ANGLE@41..42 ">" [] [] + 1: HTML_ELEMENT_LIST@42..58 + 0: HTML_SINGLE_TEXT_EXPRESSION@42..45 + 0: L_CURLY@42..43 "{" [] [] + 1: HTML_TEXT_EXPRESSION@43..44 + 0: HTML_LITERAL@43..44 "i" [] [] + 2: R_CURLY@44..45 "}" [] [] + 1: HTML_CONTENT@45..47 + 0: HTML_LITERAL@45..47 ":" [] [Whitespace(" ")] + 2: HTML_SINGLE_TEXT_EXPRESSION@47..58 + 0: L_CURLY@47..48 "{" [] [] + 1: HTML_TEXT_EXPRESSION@48..57 + 0: HTML_LITERAL@48..57 "item.name" [] [] + 2: R_CURLY@57..58 "}" [] [] + 2: HTML_CLOSING_ELEMENT@58..64 + 0: L_ANGLE@58..59 "<" [] [] + 1: SLASH@59..60 "/" [] [] + 2: HTML_TAG_NAME@60..63 + 0: HTML_LITERAL@60..63 "div" [] [] + 3: R_ANGLE@63..64 ">" [] [] + 2: SVELTE_ELSE_CLAUSE@64..96 + 0: SV_CURLY_COLON@64..67 "{:" [Newline("\n")] [] + 1: ELSE_KW@67..71 "else" [] [] + 2: R_CURLY@71..72 "}" [] [] + 3: HTML_ELEMENT_LIST@72..96 + 0: HTML_ELEMENT@72..96 + 0: HTML_OPENING_ELEMENT@72..80 + 0: L_ANGLE@72..76 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@76..79 + 0: HTML_LITERAL@76..79 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@79..79 + 3: R_ANGLE@79..80 ">" [] [] + 1: HTML_ELEMENT_LIST@80..90 + 0: HTML_CONTENT@80..90 + 0: HTML_LITERAL@80..90 "Empty list" [] [] + 2: HTML_CLOSING_ELEMENT@90..96 + 0: L_ANGLE@90..91 "<" [] [] + 1: SLASH@91..92 "/" [] [] + 2: HTML_TAG_NAME@92..95 + 0: HTML_LITERAL@92..95 "div" [] [] + 3: R_ANGLE@95..96 ">" [] [] + 3: SVELTE_EACH_CLOSING_BLOCK@96..104 + 0: SV_CURLY_SLASH@96..99 "{/" [Newline("\n")] [] + 1: EACH_KW@99..103 "each" [] [] + 2: R_CURLY@103..104 "}" [] [] + 1: SVELTE_EACH_BLOCK@104..161 + 0: SVELTE_EACH_OPENING_BLOCK@104..126 + 0: SV_CURLY_HASH@104..108 "{#" [Newline("\n"), Newline("\n")] [] + 1: EACH_KW@108..112 "each" [] [] + 2: HTML_TEXT_EXPRESSION@112..118 + 0: HTML_LITERAL@112..118 " users" [] [] + 3: SVELTE_EACH_KEYED_ITEM@118..125 + 0: SVELTE_EACH_INDEX@118..125 + 0: COMMA@118..119 "," [] [] + 1: HTML_TEXT_EXPRESSION@119..125 + 0: HTML_LITERAL@119..125 " index" [] [] + 4: R_CURLY@125..126 "}" [] [] + 1: HTML_ELEMENT_LIST@126..153 + 0: HTML_ELEMENT@126..153 + 0: HTML_OPENING_ELEMENT@126..134 + 0: L_ANGLE@126..130 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@130..133 + 0: HTML_LITERAL@130..133 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@133..133 + 3: R_ANGLE@133..134 ">" [] [] + 1: HTML_ELEMENT_LIST@134..147 + 0: HTML_CONTENT@134..140 + 0: HTML_LITERAL@134..140 "User #" [] [] + 1: HTML_SINGLE_TEXT_EXPRESSION@140..147 + 0: L_CURLY@140..141 "{" [] [] + 1: HTML_TEXT_EXPRESSION@141..146 + 0: HTML_LITERAL@141..146 "index" [] [] + 2: R_CURLY@146..147 "}" [] [] + 2: HTML_CLOSING_ELEMENT@147..153 + 0: L_ANGLE@147..148 "<" [] [] + 1: SLASH@148..149 "/" [] [] + 2: HTML_TAG_NAME@149..152 + 0: HTML_LITERAL@149..152 "div" [] [] + 3: R_ANGLE@152..153 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@153..161 + 0: SV_CURLY_SLASH@153..156 "{/" [Newline("\n")] [] + 1: EACH_KW@156..160 "each" [] [] + 2: R_CURLY@160..161 "}" [] [] + 2: SVELTE_EACH_BLOCK@161..241 + 0: SVELTE_EACH_OPENING_BLOCK@161..204 + 0: SV_CURLY_HASH@161..165 "{#" [Newline("\n"), Newline("\n")] [] + 1: EACH_KW@165..169 "each" [] [] + 2: HTML_TEXT_EXPRESSION@169..179 + 0: HTML_LITERAL@169..179 " products " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@179..203 + 0: AS_KW@179..181 "as" [] [] + 1: HTML_TEXT_EXPRESSION@181..203 + 0: HTML_LITERAL@181..203 " product (product.sku)" [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@203..204 "}" [] [] + 1: HTML_ELEMENT_LIST@204..233 + 0: HTML_ELEMENT@204..233 + 0: HTML_OPENING_ELEMENT@204..212 + 0: L_ANGLE@204..208 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@208..211 + 0: HTML_LITERAL@208..211 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@211..211 + 3: R_ANGLE@211..212 ">" [] [] + 1: HTML_ELEMENT_LIST@212..227 + 0: HTML_SINGLE_TEXT_EXPRESSION@212..227 + 0: L_CURLY@212..213 "{" [] [] + 1: HTML_TEXT_EXPRESSION@213..226 + 0: HTML_LITERAL@213..226 "product.title" [] [] + 2: R_CURLY@226..227 "}" [] [] + 2: HTML_CLOSING_ELEMENT@227..233 + 0: L_ANGLE@227..228 "<" [] [] + 1: SLASH@228..229 "/" [] [] + 2: HTML_TAG_NAME@229..232 + 0: HTML_LITERAL@229..232 "div" [] [] + 3: R_ANGLE@232..233 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@233..241 + 0: SV_CURLY_SLASH@233..236 "{/" [Newline("\n")] [] + 1: EACH_KW@236..240 "each" [] [] + 2: R_CURLY@240..241 "}" [] [] + 4: EOF@241..242 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_expression.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_expression.svelte new file mode 100644 index 000000000000..3d676d1c97fb --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_expression.svelte @@ -0,0 +1,11 @@ +{#each obj.items as item} +
{item}
+{/each} + +{#each getItems() as item} +
{item}
+{/each} + +{#each items.filter(x => x.active) as item} +
{item}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_expression.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_expression.svelte.snap new file mode 100644 index 000000000000..4b0f65295fb3 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_expression.svelte.snap @@ -0,0 +1,319 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each obj.items as item} +
{item}
+{/each} + +{#each getItems() as item} +
{item}
+{/each} + +{#each items.filter(x => x.active) as item} +
{item}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..17 " obj.items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@17..19 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@19..24 " item" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: R_CURLY@24..25 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@25..29 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@29..32 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@32..33 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@33..34 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@34..38 "item" [] [], + }, + r_curly_token: R_CURLY@38..39 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@39..40 "<" [] [], + slash_token: SLASH@40..41 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@41..44 "div" [] [], + }, + r_angle_token: R_ANGLE@44..45 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@45..48 "{/" [Newline("\n")] [], + each_token: EACH_KW@48..52 "each" [] [], + r_curly_token: R_CURLY@52..53 "}" [] [], + }, + }, + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@53..57 "{#" [Newline("\n"), Newline("\n")] [], + each_token: EACH_KW@57..61 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@61..73 " getItems() " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@73..75 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@75..80 " item" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: R_CURLY@80..81 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@81..85 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@85..88 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@88..89 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@89..90 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@90..94 "item" [] [], + }, + r_curly_token: R_CURLY@94..95 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@95..96 "<" [] [], + slash_token: SLASH@96..97 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@97..100 "div" [] [], + }, + r_angle_token: R_ANGLE@100..101 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@101..104 "{/" [Newline("\n")] [], + each_token: EACH_KW@104..108 "each" [] [], + r_curly_token: R_CURLY@108..109 "}" [] [], + }, + }, + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@109..113 "{#" [Newline("\n"), Newline("\n")] [], + each_token: EACH_KW@113..117 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@117..146 " items.filter(x => x.active) " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@146..148 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@148..153 " item" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: R_CURLY@153..154 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@154..158 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@158..161 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@161..162 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@162..163 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@163..167 "item" [] [], + }, + r_curly_token: R_CURLY@167..168 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@168..169 "<" [] [], + slash_token: SLASH@169..170 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@170..173 "div" [] [], + }, + r_angle_token: R_ANGLE@173..174 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@174..177 "{/" [Newline("\n")] [], + each_token: EACH_KW@177..181 "each" [] [], + r_curly_token: R_CURLY@181..182 "}" [] [], + }, + }, + ], + eof_token: EOF@182..183 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..183 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..182 + 0: SVELTE_EACH_BLOCK@0..53 + 0: SVELTE_EACH_OPENING_BLOCK@0..25 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..17 + 0: HTML_LITERAL@6..17 " obj.items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@17..24 + 0: AS_KW@17..19 "as" [] [] + 1: HTML_TEXT_EXPRESSION@19..24 + 0: HTML_LITERAL@19..24 " item" [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@24..25 "}" [] [] + 1: HTML_ELEMENT_LIST@25..45 + 0: HTML_ELEMENT@25..45 + 0: HTML_OPENING_ELEMENT@25..33 + 0: L_ANGLE@25..29 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@29..32 + 0: HTML_LITERAL@29..32 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@32..32 + 3: R_ANGLE@32..33 ">" [] [] + 1: HTML_ELEMENT_LIST@33..39 + 0: HTML_SINGLE_TEXT_EXPRESSION@33..39 + 0: L_CURLY@33..34 "{" [] [] + 1: HTML_TEXT_EXPRESSION@34..38 + 0: HTML_LITERAL@34..38 "item" [] [] + 2: R_CURLY@38..39 "}" [] [] + 2: HTML_CLOSING_ELEMENT@39..45 + 0: L_ANGLE@39..40 "<" [] [] + 1: SLASH@40..41 "/" [] [] + 2: HTML_TAG_NAME@41..44 + 0: HTML_LITERAL@41..44 "div" [] [] + 3: R_ANGLE@44..45 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@45..53 + 0: SV_CURLY_SLASH@45..48 "{/" [Newline("\n")] [] + 1: EACH_KW@48..52 "each" [] [] + 2: R_CURLY@52..53 "}" [] [] + 1: SVELTE_EACH_BLOCK@53..109 + 0: SVELTE_EACH_OPENING_BLOCK@53..81 + 0: SV_CURLY_HASH@53..57 "{#" [Newline("\n"), Newline("\n")] [] + 1: EACH_KW@57..61 "each" [] [] + 2: HTML_TEXT_EXPRESSION@61..73 + 0: HTML_LITERAL@61..73 " getItems() " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@73..80 + 0: AS_KW@73..75 "as" [] [] + 1: HTML_TEXT_EXPRESSION@75..80 + 0: HTML_LITERAL@75..80 " item" [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@80..81 "}" [] [] + 1: HTML_ELEMENT_LIST@81..101 + 0: HTML_ELEMENT@81..101 + 0: HTML_OPENING_ELEMENT@81..89 + 0: L_ANGLE@81..85 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@85..88 + 0: HTML_LITERAL@85..88 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@88..88 + 3: R_ANGLE@88..89 ">" [] [] + 1: HTML_ELEMENT_LIST@89..95 + 0: HTML_SINGLE_TEXT_EXPRESSION@89..95 + 0: L_CURLY@89..90 "{" [] [] + 1: HTML_TEXT_EXPRESSION@90..94 + 0: HTML_LITERAL@90..94 "item" [] [] + 2: R_CURLY@94..95 "}" [] [] + 2: HTML_CLOSING_ELEMENT@95..101 + 0: L_ANGLE@95..96 "<" [] [] + 1: SLASH@96..97 "/" [] [] + 2: HTML_TAG_NAME@97..100 + 0: HTML_LITERAL@97..100 "div" [] [] + 3: R_ANGLE@100..101 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@101..109 + 0: SV_CURLY_SLASH@101..104 "{/" [Newline("\n")] [] + 1: EACH_KW@104..108 "each" [] [] + 2: R_CURLY@108..109 "}" [] [] + 2: SVELTE_EACH_BLOCK@109..182 + 0: SVELTE_EACH_OPENING_BLOCK@109..154 + 0: SV_CURLY_HASH@109..113 "{#" [Newline("\n"), Newline("\n")] [] + 1: EACH_KW@113..117 "each" [] [] + 2: HTML_TEXT_EXPRESSION@117..146 + 0: HTML_LITERAL@117..146 " items.filter(x => x.active) " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@146..153 + 0: AS_KW@146..148 "as" [] [] + 1: HTML_TEXT_EXPRESSION@148..153 + 0: HTML_LITERAL@148..153 " item" [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@153..154 "}" [] [] + 1: HTML_ELEMENT_LIST@154..174 + 0: HTML_ELEMENT@154..174 + 0: HTML_OPENING_ELEMENT@154..162 + 0: L_ANGLE@154..158 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@158..161 + 0: HTML_LITERAL@158..161 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@161..161 + 3: R_ANGLE@161..162 ">" [] [] + 1: HTML_ELEMENT_LIST@162..168 + 0: HTML_SINGLE_TEXT_EXPRESSION@162..168 + 0: L_CURLY@162..163 "{" [] [] + 1: HTML_TEXT_EXPRESSION@163..167 + 0: HTML_LITERAL@163..167 "item" [] [] + 2: R_CURLY@167..168 "}" [] [] + 2: HTML_CLOSING_ELEMENT@168..174 + 0: L_ANGLE@168..169 "<" [] [] + 1: SLASH@169..170 "/" [] [] + 2: HTML_TAG_NAME@170..173 + 0: HTML_LITERAL@170..173 "div" [] [] + 3: R_ANGLE@173..174 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@174..182 + 0: SV_CURLY_SLASH@174..177 "{/" [Newline("\n")] [] + 1: EACH_KW@177..181 "each" [] [] + 2: R_CURLY@181..182 "}" [] [] + 4: EOF@182..183 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_index_only.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_index_only.svelte new file mode 100644 index 000000000000..2268560e608b --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_index_only.svelte @@ -0,0 +1,3 @@ +{#each items, i} +
Item {i}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_index_only.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_index_only.svelte.snap new file mode 100644 index 000000000000..dc782879b762 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_index_only.svelte.snap @@ -0,0 +1,133 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items, i} +
Item {i}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..12 " items" [] [], + }, + item: SvelteEachKeyedItem { + index: SvelteEachIndex { + comma_token: COMMA@12..13 "," [] [], + value: HtmlTextExpression { + html_literal_token: HTML_LITERAL@13..15 " i" [] [], + }, + }, + }, + r_curly_token: R_CURLY@15..16 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@16..20 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@20..23 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@23..24 ">" [] [], + }, + children: HtmlElementList [ + HtmlContent { + value_token: HTML_LITERAL@24..29 "Item" [] [Whitespace(" ")], + }, + HtmlSingleTextExpression { + l_curly_token: L_CURLY@29..30 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@30..31 "i" [] [], + }, + r_curly_token: R_CURLY@31..32 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@32..33 "<" [] [], + slash_token: SLASH@33..34 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@34..37 "div" [] [], + }, + r_angle_token: R_ANGLE@37..38 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@38..41 "{/" [Newline("\n")] [], + each_token: EACH_KW@41..45 "each" [] [], + r_curly_token: R_CURLY@45..46 "}" [] [], + }, + }, + ], + eof_token: EOF@46..47 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..47 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..46 + 0: SVELTE_EACH_BLOCK@0..46 + 0: SVELTE_EACH_OPENING_BLOCK@0..16 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..12 + 0: HTML_LITERAL@6..12 " items" [] [] + 3: SVELTE_EACH_KEYED_ITEM@12..15 + 0: SVELTE_EACH_INDEX@12..15 + 0: COMMA@12..13 "," [] [] + 1: HTML_TEXT_EXPRESSION@13..15 + 0: HTML_LITERAL@13..15 " i" [] [] + 4: R_CURLY@15..16 "}" [] [] + 1: HTML_ELEMENT_LIST@16..38 + 0: HTML_ELEMENT@16..38 + 0: HTML_OPENING_ELEMENT@16..24 + 0: L_ANGLE@16..20 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@20..23 + 0: HTML_LITERAL@20..23 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@23..23 + 3: R_ANGLE@23..24 ">" [] [] + 1: HTML_ELEMENT_LIST@24..32 + 0: HTML_CONTENT@24..29 + 0: HTML_LITERAL@24..29 "Item" [] [Whitespace(" ")] + 1: HTML_SINGLE_TEXT_EXPRESSION@29..32 + 0: L_CURLY@29..30 "{" [] [] + 1: HTML_TEXT_EXPRESSION@30..31 + 0: HTML_LITERAL@30..31 "i" [] [] + 2: R_CURLY@31..32 "}" [] [] + 2: HTML_CLOSING_ELEMENT@32..38 + 0: L_ANGLE@32..33 "<" [] [] + 1: SLASH@33..34 "/" [] [] + 2: HTML_TAG_NAME@34..37 + 0: HTML_LITERAL@34..37 "div" [] [] + 3: R_ANGLE@37..38 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@38..46 + 0: SV_CURLY_SLASH@38..41 "{/" [Newline("\n")] [] + 1: EACH_KW@41..45 "each" [] [] + 2: R_CURLY@45..46 "}" [] [] + 4: EOF@46..47 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_else.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_else.svelte new file mode 100644 index 000000000000..bc1bed801ddc --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_else.svelte @@ -0,0 +1,5 @@ +{#each items as item} +
{item}
+{:else} +
No items found
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_else.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_else.svelte.snap new file mode 100644 index 000000000000..78d57103e468 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_else.svelte.snap @@ -0,0 +1,180 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items as item} +
{item}
+{:else} +
No items found
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..13 " items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@13..15 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@15..20 " item" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: R_CURLY@20..21 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@21..25 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@25..28 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@28..29 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@29..30 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@30..34 "item" [] [], + }, + r_curly_token: R_CURLY@34..35 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@35..36 "<" [] [], + slash_token: SLASH@36..37 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@37..40 "div" [] [], + }, + r_angle_token: R_ANGLE@40..41 ">" [] [], + }, + }, + ], + else_clause: SvelteElseClause { + sv_curly_colon_token: SV_CURLY_COLON@41..44 "{:" [Newline("\n")] [], + else_token: ELSE_KW@44..48 "else" [] [], + r_curly_token: R_CURLY@48..49 "}" [] [], + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@49..53 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@53..56 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@56..57 ">" [] [], + }, + children: HtmlElementList [ + HtmlContent { + value_token: HTML_LITERAL@57..71 "No items found" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@71..72 "<" [] [], + slash_token: SLASH@72..73 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@73..76 "div" [] [], + }, + r_angle_token: R_ANGLE@76..77 ">" [] [], + }, + }, + ], + }, + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@77..80 "{/" [Newline("\n")] [], + each_token: EACH_KW@80..84 "each" [] [], + r_curly_token: R_CURLY@84..85 "}" [] [], + }, + }, + ], + eof_token: EOF@85..86 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..86 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..85 + 0: SVELTE_EACH_BLOCK@0..85 + 0: SVELTE_EACH_OPENING_BLOCK@0..21 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..13 + 0: HTML_LITERAL@6..13 " items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@13..20 + 0: AS_KW@13..15 "as" [] [] + 1: HTML_TEXT_EXPRESSION@15..20 + 0: HTML_LITERAL@15..20 " item" [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@20..21 "}" [] [] + 1: HTML_ELEMENT_LIST@21..41 + 0: HTML_ELEMENT@21..41 + 0: HTML_OPENING_ELEMENT@21..29 + 0: L_ANGLE@21..25 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@25..28 + 0: HTML_LITERAL@25..28 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@28..28 + 3: R_ANGLE@28..29 ">" [] [] + 1: HTML_ELEMENT_LIST@29..35 + 0: HTML_SINGLE_TEXT_EXPRESSION@29..35 + 0: L_CURLY@29..30 "{" [] [] + 1: HTML_TEXT_EXPRESSION@30..34 + 0: HTML_LITERAL@30..34 "item" [] [] + 2: R_CURLY@34..35 "}" [] [] + 2: HTML_CLOSING_ELEMENT@35..41 + 0: L_ANGLE@35..36 "<" [] [] + 1: SLASH@36..37 "/" [] [] + 2: HTML_TAG_NAME@37..40 + 0: HTML_LITERAL@37..40 "div" [] [] + 3: R_ANGLE@40..41 ">" [] [] + 2: SVELTE_ELSE_CLAUSE@41..77 + 0: SV_CURLY_COLON@41..44 "{:" [Newline("\n")] [] + 1: ELSE_KW@44..48 "else" [] [] + 2: R_CURLY@48..49 "}" [] [] + 3: HTML_ELEMENT_LIST@49..77 + 0: HTML_ELEMENT@49..77 + 0: HTML_OPENING_ELEMENT@49..57 + 0: L_ANGLE@49..53 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@53..56 + 0: HTML_LITERAL@53..56 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@56..56 + 3: R_ANGLE@56..57 ">" [] [] + 1: HTML_ELEMENT_LIST@57..71 + 0: HTML_CONTENT@57..71 + 0: HTML_LITERAL@57..71 "No items found" [] [] + 2: HTML_CLOSING_ELEMENT@71..77 + 0: L_ANGLE@71..72 "<" [] [] + 1: SLASH@72..73 "/" [] [] + 2: HTML_TAG_NAME@73..76 + 0: HTML_LITERAL@73..76 "div" [] [] + 3: R_ANGLE@76..77 ">" [] [] + 3: SVELTE_EACH_CLOSING_BLOCK@77..85 + 0: SV_CURLY_SLASH@77..80 "{/" [Newline("\n")] [] + 1: EACH_KW@80..84 "each" [] [] + 2: R_CURLY@84..85 "}" [] [] + 4: EOF@85..86 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index.svelte new file mode 100644 index 000000000000..4e9ecaa3dda2 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index.svelte @@ -0,0 +1,3 @@ +{#each items as item, i} +
{i}: {item}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index.svelte.snap new file mode 100644 index 000000000000..3fa9785d14ca --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index.svelte.snap @@ -0,0 +1,154 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items as item, i} +
{i}: {item}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..13 " items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@13..15 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@15..20 " item" [] [], + }, + index: SvelteEachIndex { + comma_token: COMMA@20..21 "," [] [], + value: HtmlTextExpression { + html_literal_token: HTML_LITERAL@21..23 " i" [] [], + }, + }, + key: missing (optional), + }, + r_curly_token: R_CURLY@23..24 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@24..28 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@28..31 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@31..32 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@32..33 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@33..34 "i" [] [], + }, + r_curly_token: R_CURLY@34..35 "}" [] [], + }, + HtmlContent { + value_token: HTML_LITERAL@35..37 ":" [] [Whitespace(" ")], + }, + HtmlSingleTextExpression { + l_curly_token: L_CURLY@37..38 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@38..42 "item" [] [], + }, + r_curly_token: R_CURLY@42..43 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@43..44 "<" [] [], + slash_token: SLASH@44..45 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@45..48 "div" [] [], + }, + r_angle_token: R_ANGLE@48..49 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@49..52 "{/" [Newline("\n")] [], + each_token: EACH_KW@52..56 "each" [] [], + r_curly_token: R_CURLY@56..57 "}" [] [], + }, + }, + ], + eof_token: EOF@57..58 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..58 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..57 + 0: SVELTE_EACH_BLOCK@0..57 + 0: SVELTE_EACH_OPENING_BLOCK@0..24 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..13 + 0: HTML_LITERAL@6..13 " items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@13..23 + 0: AS_KW@13..15 "as" [] [] + 1: HTML_TEXT_EXPRESSION@15..20 + 0: HTML_LITERAL@15..20 " item" [] [] + 2: SVELTE_EACH_INDEX@20..23 + 0: COMMA@20..21 "," [] [] + 1: HTML_TEXT_EXPRESSION@21..23 + 0: HTML_LITERAL@21..23 " i" [] [] + 3: (empty) + 4: R_CURLY@23..24 "}" [] [] + 1: HTML_ELEMENT_LIST@24..49 + 0: HTML_ELEMENT@24..49 + 0: HTML_OPENING_ELEMENT@24..32 + 0: L_ANGLE@24..28 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@28..31 + 0: HTML_LITERAL@28..31 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@31..31 + 3: R_ANGLE@31..32 ">" [] [] + 1: HTML_ELEMENT_LIST@32..43 + 0: HTML_SINGLE_TEXT_EXPRESSION@32..35 + 0: L_CURLY@32..33 "{" [] [] + 1: HTML_TEXT_EXPRESSION@33..34 + 0: HTML_LITERAL@33..34 "i" [] [] + 2: R_CURLY@34..35 "}" [] [] + 1: HTML_CONTENT@35..37 + 0: HTML_LITERAL@35..37 ":" [] [Whitespace(" ")] + 2: HTML_SINGLE_TEXT_EXPRESSION@37..43 + 0: L_CURLY@37..38 "{" [] [] + 1: HTML_TEXT_EXPRESSION@38..42 + 0: HTML_LITERAL@38..42 "item" [] [] + 2: R_CURLY@42..43 "}" [] [] + 2: HTML_CLOSING_ELEMENT@43..49 + 0: L_ANGLE@43..44 "<" [] [] + 1: SLASH@44..45 "/" [] [] + 2: HTML_TAG_NAME@45..48 + 0: HTML_LITERAL@45..48 "div" [] [] + 3: R_ANGLE@48..49 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@49..57 + 0: SV_CURLY_SLASH@49..52 "{/" [Newline("\n")] [] + 1: EACH_KW@52..56 "each" [] [] + 2: R_CURLY@56..57 "}" [] [] + 4: EOF@57..58 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte new file mode 100644 index 000000000000..5c15eaf3d75d --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte @@ -0,0 +1,3 @@ +{#each items as item, i (item.id)} +
{i}: {item.name}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte.snap new file mode 100644 index 000000000000..4a84da795a94 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte.snap @@ -0,0 +1,154 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items as item, i (item.id)} +
{i}: {item.name}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..13 " items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@13..15 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@15..20 " item" [] [], + }, + index: SvelteEachIndex { + comma_token: COMMA@20..21 "," [] [], + value: HtmlTextExpression { + html_literal_token: HTML_LITERAL@21..33 " i (item.id)" [] [], + }, + }, + key: missing (optional), + }, + r_curly_token: R_CURLY@33..34 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@34..38 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@38..41 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@41..42 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@42..43 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@43..44 "i" [] [], + }, + r_curly_token: R_CURLY@44..45 "}" [] [], + }, + HtmlContent { + value_token: HTML_LITERAL@45..47 ":" [] [Whitespace(" ")], + }, + HtmlSingleTextExpression { + l_curly_token: L_CURLY@47..48 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@48..57 "item.name" [] [], + }, + r_curly_token: R_CURLY@57..58 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@58..59 "<" [] [], + slash_token: SLASH@59..60 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@60..63 "div" [] [], + }, + r_angle_token: R_ANGLE@63..64 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@64..67 "{/" [Newline("\n")] [], + each_token: EACH_KW@67..71 "each" [] [], + r_curly_token: R_CURLY@71..72 "}" [] [], + }, + }, + ], + eof_token: EOF@72..73 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..73 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..72 + 0: SVELTE_EACH_BLOCK@0..72 + 0: SVELTE_EACH_OPENING_BLOCK@0..34 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..13 + 0: HTML_LITERAL@6..13 " items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@13..33 + 0: AS_KW@13..15 "as" [] [] + 1: HTML_TEXT_EXPRESSION@15..20 + 0: HTML_LITERAL@15..20 " item" [] [] + 2: SVELTE_EACH_INDEX@20..33 + 0: COMMA@20..21 "," [] [] + 1: HTML_TEXT_EXPRESSION@21..33 + 0: HTML_LITERAL@21..33 " i (item.id)" [] [] + 3: (empty) + 4: R_CURLY@33..34 "}" [] [] + 1: HTML_ELEMENT_LIST@34..64 + 0: HTML_ELEMENT@34..64 + 0: HTML_OPENING_ELEMENT@34..42 + 0: L_ANGLE@34..38 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@38..41 + 0: HTML_LITERAL@38..41 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@41..41 + 3: R_ANGLE@41..42 ">" [] [] + 1: HTML_ELEMENT_LIST@42..58 + 0: HTML_SINGLE_TEXT_EXPRESSION@42..45 + 0: L_CURLY@42..43 "{" [] [] + 1: HTML_TEXT_EXPRESSION@43..44 + 0: HTML_LITERAL@43..44 "i" [] [] + 2: R_CURLY@44..45 "}" [] [] + 1: HTML_CONTENT@45..47 + 0: HTML_LITERAL@45..47 ":" [] [Whitespace(" ")] + 2: HTML_SINGLE_TEXT_EXPRESSION@47..58 + 0: L_CURLY@47..48 "{" [] [] + 1: HTML_TEXT_EXPRESSION@48..57 + 0: HTML_LITERAL@48..57 "item.name" [] [] + 2: R_CURLY@57..58 "}" [] [] + 2: HTML_CLOSING_ELEMENT@58..64 + 0: L_ANGLE@58..59 "<" [] [] + 1: SLASH@59..60 "/" [] [] + 2: HTML_TAG_NAME@60..63 + 0: HTML_LITERAL@60..63 "div" [] [] + 3: R_ANGLE@63..64 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@64..72 + 0: SV_CURLY_SLASH@64..67 "{/" [Newline("\n")] [] + 1: EACH_KW@67..71 "each" [] [] + 2: R_CURLY@71..72 "}" [] [] + 4: EOF@72..73 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte new file mode 100644 index 000000000000..54c2dd60ed86 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte @@ -0,0 +1,3 @@ +{#each items as item (item.id)} +
{item.name}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte.snap new file mode 100644 index 000000000000..20dca9551026 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte.snap @@ -0,0 +1,129 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items as item (item.id)} +
{item.name}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..13 " items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@13..15 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@15..30 " item (item.id)" [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: R_CURLY@30..31 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@31..35 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@35..38 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@38..39 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@39..40 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@40..49 "item.name" [] [], + }, + r_curly_token: R_CURLY@49..50 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@50..51 "<" [] [], + slash_token: SLASH@51..52 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@52..55 "div" [] [], + }, + r_angle_token: R_ANGLE@55..56 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@56..59 "{/" [Newline("\n")] [], + each_token: EACH_KW@59..63 "each" [] [], + r_curly_token: R_CURLY@63..64 "}" [] [], + }, + }, + ], + eof_token: EOF@64..65 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..65 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..64 + 0: SVELTE_EACH_BLOCK@0..64 + 0: SVELTE_EACH_OPENING_BLOCK@0..31 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..13 + 0: HTML_LITERAL@6..13 " items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@13..30 + 0: AS_KW@13..15 "as" [] [] + 1: HTML_TEXT_EXPRESSION@15..30 + 0: HTML_LITERAL@15..30 " item (item.id)" [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@30..31 "}" [] [] + 1: HTML_ELEMENT_LIST@31..56 + 0: HTML_ELEMENT@31..56 + 0: HTML_OPENING_ELEMENT@31..39 + 0: L_ANGLE@31..35 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@35..38 + 0: HTML_LITERAL@35..38 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@38..38 + 3: R_ANGLE@38..39 ">" [] [] + 1: HTML_ELEMENT_LIST@39..50 + 0: HTML_SINGLE_TEXT_EXPRESSION@39..50 + 0: L_CURLY@39..40 "{" [] [] + 1: HTML_TEXT_EXPRESSION@40..49 + 0: HTML_LITERAL@40..49 "item.name" [] [] + 2: R_CURLY@49..50 "}" [] [] + 2: HTML_CLOSING_ELEMENT@50..56 + 0: L_ANGLE@50..51 "<" [] [] + 1: SLASH@51..52 "/" [] [] + 2: HTML_TAG_NAME@52..55 + 0: HTML_LITERAL@52..55 "div" [] [] + 3: R_ANGLE@55..56 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@56..64 + 0: SV_CURLY_SLASH@56..59 "{/" [Newline("\n")] [] + 1: EACH_KW@59..63 "each" [] [] + 2: R_CURLY@63..64 "}" [] [] + 4: EOF@64..65 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte new file mode 100644 index 000000000000..5b5604a33761 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte @@ -0,0 +1,3 @@ +{#each items as item } +
{item}
+{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte.snap new file mode 100644 index 000000000000..e7aa8261d403 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte.snap @@ -0,0 +1,129 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items as item } +
{item}
+{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..15 " items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@15..17 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@17..25 " item " [] [], + }, + index: missing (optional), + key: missing (optional), + }, + r_curly_token: R_CURLY@25..26 "}" [] [], + }, + children: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@26..30 "<" [Newline("\n"), Whitespace(" ")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@30..33 "div" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@33..34 ">" [] [], + }, + children: HtmlElementList [ + HtmlSingleTextExpression { + l_curly_token: L_CURLY@34..35 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@35..39 "item" [] [], + }, + r_curly_token: R_CURLY@39..40 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@40..41 "<" [] [], + slash_token: SLASH@41..42 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@42..45 "div" [] [], + }, + r_angle_token: R_ANGLE@45..46 ">" [] [], + }, + }, + ], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@46..49 "{/" [Newline("\n")] [], + each_token: EACH_KW@49..53 "each" [] [], + r_curly_token: R_CURLY@53..54 "}" [] [], + }, + }, + ], + eof_token: EOF@54..55 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..55 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..54 + 0: SVELTE_EACH_BLOCK@0..54 + 0: SVELTE_EACH_OPENING_BLOCK@0..26 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..15 + 0: HTML_LITERAL@6..15 " items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@15..25 + 0: AS_KW@15..17 "as" [] [] + 1: HTML_TEXT_EXPRESSION@17..25 + 0: HTML_LITERAL@17..25 " item " [] [] + 2: (empty) + 3: (empty) + 4: R_CURLY@25..26 "}" [] [] + 1: HTML_ELEMENT_LIST@26..46 + 0: HTML_ELEMENT@26..46 + 0: HTML_OPENING_ELEMENT@26..34 + 0: L_ANGLE@26..30 "<" [Newline("\n"), Whitespace(" ")] [] + 1: HTML_TAG_NAME@30..33 + 0: HTML_LITERAL@30..33 "div" [] [] + 2: HTML_ATTRIBUTE_LIST@33..33 + 3: R_ANGLE@33..34 ">" [] [] + 1: HTML_ELEMENT_LIST@34..40 + 0: HTML_SINGLE_TEXT_EXPRESSION@34..40 + 0: L_CURLY@34..35 "{" [] [] + 1: HTML_TEXT_EXPRESSION@35..39 + 0: HTML_LITERAL@35..39 "item" [] [] + 2: R_CURLY@39..40 "}" [] [] + 2: HTML_CLOSING_ELEMENT@40..46 + 0: L_ANGLE@40..41 "<" [] [] + 1: SLASH@41..42 "/" [] [] + 2: HTML_TAG_NAME@42..45 + 0: HTML_LITERAL@42..45 "div" [] [] + 3: R_ANGLE@45..46 ">" [] [] + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@46..54 + 0: SV_CURLY_SLASH@46..49 "{/" [Newline("\n")] [] + 1: EACH_KW@49..53 "each" [] [] + 2: R_CURLY@53..54 "}" [] [] + 4: EOF@54..55 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/issue-8174.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/issue-8174.vue.snap index 51172c0f9d11..e52b43981eb5 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/vue/issue-8174.vue.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/issue-8174.vue.snap @@ -26,7 +26,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@11..15 "v-if" [] [], + name_token: IDENT@11..15 "v-if" [] [], arg: missing (optional), modifiers: VueModifierList [], initializer: HtmlAttributeInitializerClause { @@ -62,7 +62,7 @@ HtmlRoot { }, attributes: HtmlAttributeList [ VueDirective { - name_token: VUE_IDENT@58..65 "v-else" [] [Whitespace(" ")], + name_token: IDENT@58..65 "v-else" [] [Whitespace(" ")], arg: missing (optional), modifiers: VueModifierList [], initializer: missing (optional), @@ -105,7 +105,7 @@ HtmlRoot { 0: HTML_LITERAL@1..11 "Component" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@11..44 0: VUE_DIRECTIVE@11..28 - 0: VUE_IDENT@11..15 "v-if" [] [] + 0: IDENT@11..15 "v-if" [] [] 1: (empty) 2: VUE_MODIFIER_LIST@15..15 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@15..28 @@ -130,7 +130,7 @@ HtmlRoot { 0: HTML_LITERAL@48..58 "Component" [] [Whitespace(" ")] 2: HTML_ATTRIBUTE_LIST@58..81 0: VUE_DIRECTIVE@58..65 - 0: VUE_IDENT@58..65 "v-else" [] [Whitespace(" ")] + 0: IDENT@58..65 "v-else" [] [Whitespace(" ")] 1: (empty) 2: VUE_MODIFIER_LIST@65..65 3: (empty) diff --git a/crates/biome_html_parser/tests/quick_test.rs b/crates/biome_html_parser/tests/quick_test.rs index 1b44866eb7a1..fa17576135da 100644 --- a/crates/biome_html_parser/tests/quick_test.rs +++ b/crates/biome_html_parser/tests/quick_test.rs @@ -4,8 +4,10 @@ use biome_test_utils::has_bogus_nodes_or_empty_slots; #[ignore] #[test] pub fn quick_test() { - let code = r#"{#each items as item} - {/each} + let code = r#"{#each users , i} +
{item}
+{/each} + "#; let options = HtmlParseOptions::default().with_vue(); diff --git a/crates/biome_html_syntax/src/generated/kind.rs b/crates/biome_html_syntax/src/generated/kind.rs index 0182319ba45a..1a099b58af39 100644 --- a/crates/biome_html_syntax/src/generated/kind.rs +++ b/crates/biome_html_syntax/src/generated/kind.rs @@ -97,8 +97,10 @@ pub enum HtmlSyntaxKind { SVELTE_ELSE_IF_CLAUSE, SVELTE_EACH_BLOCK, SVELTE_EACH_OPENING_BLOCK, - SVELTE_EACH_ITEM, + SVELTE_EACH_AS_KEYED_ITEM, + SVELTE_EACH_KEYED_ITEM, SVELTE_EACH_INDEX, + SVELTE_EACH_KEY, SVELTE_EACH_CLOSING_BLOCK, VUE_DIRECTIVE, VUE_DIRECTIVE_ARGUMENT, diff --git a/crates/biome_html_syntax/src/generated/macros.rs b/crates/biome_html_syntax/src/generated/macros.rs index 351c7d63f450..040048a04820 100644 --- a/crates/biome_html_syntax/src/generated/macros.rs +++ b/crates/biome_html_syntax/src/generated/macros.rs @@ -105,6 +105,10 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::SvelteDebugBlock::new_unchecked(node) }; $body } + $crate::HtmlSyntaxKind::SVELTE_EACH_AS_KEYED_ITEM => { + let $pattern = unsafe { $crate::SvelteEachAsKeyedItem::new_unchecked(node) }; + $body + } $crate::HtmlSyntaxKind::SVELTE_EACH_BLOCK => { let $pattern = unsafe { $crate::SvelteEachBlock::new_unchecked(node) }; $body @@ -113,6 +117,18 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::SvelteEachClosingBlock::new_unchecked(node) }; $body } + $crate::HtmlSyntaxKind::SVELTE_EACH_INDEX => { + let $pattern = unsafe { $crate::SvelteEachIndex::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::SVELTE_EACH_KEY => { + let $pattern = unsafe { $crate::SvelteEachKey::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::SVELTE_EACH_KEYED_ITEM => { + let $pattern = unsafe { $crate::SvelteEachKeyedItem::new_unchecked(node) }; + $body + } $crate::HtmlSyntaxKind::SVELTE_EACH_OPENING_BLOCK => { let $pattern = unsafe { $crate::SvelteEachOpeningBlock::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 f9c5e853f12f..974aac89493c 100644 --- a/crates/biome_html_syntax/src/generated/nodes.rs +++ b/crates/biome_html_syntax/src/generated/nodes.rs @@ -1000,6 +1000,56 @@ pub struct SvelteDebugBlockFields { pub r_curly_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteEachAsKeyedItem { + pub(crate) syntax: SyntaxNode, +} +impl SvelteEachAsKeyedItem { + #[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) -> SvelteEachAsKeyedItemFields { + SvelteEachAsKeyedItemFields { + as_token: self.as_token(), + name: self.name(), + index: self.index(), + key: self.key(), + } + } + pub fn as_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } + pub fn index(&self) -> Option { + support::node(&self.syntax, 2usize) + } + pub fn key(&self) -> Option { + support::node(&self.syntax, 3usize) + } +} +impl Serialize for SvelteEachAsKeyedItem { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteEachAsKeyedItemFields { + pub as_token: SyntaxResult, + pub name: SyntaxResult, + pub index: Option, + pub key: Option, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct SvelteEachBlock { pub(crate) syntax: SyntaxNode, } @@ -1017,6 +1067,7 @@ impl SvelteEachBlock { SvelteEachBlockFields { opening_block: self.opening_block(), children: self.children(), + else_clause: self.else_clause(), closing_block: self.closing_block(), } } @@ -1026,8 +1077,11 @@ impl SvelteEachBlock { pub fn children(&self) -> HtmlElementList { support::list(&self.syntax, 1usize) } + pub fn else_clause(&self) -> Option { + support::node(&self.syntax, 2usize) + } pub fn closing_block(&self) -> SyntaxResult { - support::required_node(&self.syntax, 2usize) + support::required_node(&self.syntax, 3usize) } } impl Serialize for SvelteEachBlock { @@ -1042,6 +1096,7 @@ impl Serialize for SvelteEachBlock { pub struct SvelteEachBlockFields { pub opening_block: SyntaxResult, pub children: HtmlElementList, + pub else_clause: Option, pub closing_block: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] @@ -1090,6 +1145,116 @@ pub struct SvelteEachClosingBlockFields { pub r_curly_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteEachIndex { + pub(crate) syntax: SyntaxNode, +} +impl SvelteEachIndex { + #[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) -> SvelteEachIndexFields { + SvelteEachIndexFields { + comma_token: self.comma_token(), + value: self.value(), + } + } + pub fn comma_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn value(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } +} +impl Serialize for SvelteEachIndex { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteEachIndexFields { + pub comma_token: SyntaxResult, + pub value: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteEachKey { + pub(crate) syntax: SyntaxNode, +} +impl SvelteEachKey { + #[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) -> SvelteEachKeyFields { + SvelteEachKeyFields { + expression: self.expression(), + } + } + pub fn expression(&self) -> SyntaxResult { + support::required_node(&self.syntax, 0usize) + } +} +impl Serialize for SvelteEachKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteEachKeyFields { + pub expression: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteEachKeyedItem { + pub(crate) syntax: SyntaxNode, +} +impl SvelteEachKeyedItem { + #[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) -> SvelteEachKeyedItemFields { + SvelteEachKeyedItemFields { + index: self.index(), + } + } + pub fn index(&self) -> Option { + support::node(&self.syntax, 0usize) + } +} +impl Serialize for SvelteEachKeyedItem { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteEachKeyedItemFields { + pub index: Option, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct SvelteEachOpeningBlock { pub(crate) syntax: SyntaxNode, } @@ -1108,7 +1273,6 @@ impl SvelteEachOpeningBlock { sv_curly_hash_token: self.sv_curly_hash_token(), each_token: self.each_token(), list: self.list(), - as_token: self.as_token(), item: self.item(), r_curly_token: self.r_curly_token(), } @@ -1122,14 +1286,11 @@ impl SvelteEachOpeningBlock { pub fn list(&self) -> SyntaxResult { support::required_node(&self.syntax, 2usize) } - pub fn as_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 3usize) - } - pub fn item(&self) -> SyntaxResult { - support::required_node(&self.syntax, 4usize) + pub fn item(&self) -> Option { + support::node(&self.syntax, 3usize) } pub fn r_curly_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 5usize) + support::required_token(&self.syntax, 4usize) } } impl Serialize for SvelteEachOpeningBlock { @@ -1145,8 +1306,7 @@ pub struct SvelteEachOpeningBlockFields { pub sv_curly_hash_token: SyntaxResult, pub each_token: SyntaxResult, pub list: SyntaxResult, - pub as_token: SyntaxResult, - pub item: SyntaxResult, + pub item: Option, pub r_curly_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] @@ -2285,6 +2445,25 @@ impl AnySvelteBlock { } } #[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub enum AnySvelteBlockItem { + SvelteEachAsKeyedItem(SvelteEachAsKeyedItem), + SvelteEachKeyedItem(SvelteEachKeyedItem), +} +impl AnySvelteBlockItem { + pub fn as_svelte_each_as_keyed_item(&self) -> Option<&SvelteEachAsKeyedItem> { + match &self { + Self::SvelteEachAsKeyedItem(item) => Some(item), + _ => None, + } + } + pub fn as_svelte_each_keyed_item(&self) -> Option<&SvelteEachKeyedItem> { + match &self { + Self::SvelteEachKeyedItem(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] pub enum AnyVueDirective { VueBogusDirective(VueBogusDirective), VueDirective(VueDirective), @@ -3564,6 +3743,56 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for SvelteEachAsKeyedItem { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_EACH_AS_KEYED_ITEM as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_EACH_AS_KEYED_ITEM + } + 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 SvelteEachAsKeyedItem { + 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("SvelteEachAsKeyedItem") + .field("as_token", &support::DebugSyntaxResult(self.as_token())) + .field("name", &support::DebugSyntaxResult(self.name())) + .field("index", &support::DebugOptionalElement(self.index())) + .field("key", &support::DebugOptionalElement(self.key())) + .finish() + } else { + f.debug_struct("SvelteEachAsKeyedItem").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteEachAsKeyedItem) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteEachAsKeyedItem) -> Self { + n.syntax.into() + } +} impl AstNode for SvelteEachBlock { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -3597,6 +3826,10 @@ impl std::fmt::Debug for SvelteEachBlock { &support::DebugSyntaxResult(self.opening_block()), ) .field("children", &self.children()) + .field( + "else_clause", + &support::DebugOptionalElement(self.else_clause()), + ) .field( "closing_block", &support::DebugSyntaxResult(self.closing_block()), @@ -3674,6 +3907,151 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for SvelteEachIndex { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_EACH_INDEX as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_EACH_INDEX + } + 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 SvelteEachIndex { + 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("SvelteEachIndex") + .field( + "comma_token", + &support::DebugSyntaxResult(self.comma_token()), + ) + .field("value", &support::DebugSyntaxResult(self.value())) + .finish() + } else { + f.debug_struct("SvelteEachIndex").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteEachIndex) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteEachIndex) -> Self { + n.syntax.into() + } +} +impl AstNode for SvelteEachKey { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_EACH_KEY as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_EACH_KEY + } + 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 SvelteEachKey { + 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("SvelteEachKey") + .field("expression", &support::DebugSyntaxResult(self.expression())) + .finish() + } else { + f.debug_struct("SvelteEachKey").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteEachKey) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteEachKey) -> Self { + n.syntax.into() + } +} +impl AstNode for SvelteEachKeyedItem { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_EACH_KEYED_ITEM as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_EACH_KEYED_ITEM + } + 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 SvelteEachKeyedItem { + 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("SvelteEachKeyedItem") + .field("index", &support::DebugOptionalElement(self.index())) + .finish() + } else { + f.debug_struct("SvelteEachKeyedItem").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteEachKeyedItem) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteEachKeyedItem) -> Self { + n.syntax.into() + } +} impl AstNode for SvelteEachOpeningBlock { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -3708,8 +4086,7 @@ impl std::fmt::Debug for SvelteEachOpeningBlock { ) .field("each_token", &support::DebugSyntaxResult(self.each_token())) .field("list", &support::DebugSyntaxResult(self.list())) - .field("as_token", &support::DebugSyntaxResult(self.as_token())) - .field("item", &support::DebugSyntaxResult(self.item())) + .field("item", &support::DebugOptionalElement(self.item())) .field( "r_curly_token", &support::DebugSyntaxResult(self.r_curly_token()), @@ -5409,6 +5786,68 @@ impl From for SyntaxElement { node.into() } } +impl From for AnySvelteBlockItem { + fn from(node: SvelteEachAsKeyedItem) -> Self { + Self::SvelteEachAsKeyedItem(node) + } +} +impl From for AnySvelteBlockItem { + fn from(node: SvelteEachKeyedItem) -> Self { + Self::SvelteEachKeyedItem(node) + } +} +impl AstNode for AnySvelteBlockItem { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SvelteEachAsKeyedItem::KIND_SET.union(SvelteEachKeyedItem::KIND_SET); + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, SVELTE_EACH_AS_KEYED_ITEM | SVELTE_EACH_KEYED_ITEM) + } + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + SVELTE_EACH_AS_KEYED_ITEM => { + Self::SvelteEachAsKeyedItem(SvelteEachAsKeyedItem { syntax }) + } + SVELTE_EACH_KEYED_ITEM => Self::SvelteEachKeyedItem(SvelteEachKeyedItem { syntax }), + _ => return None, + }; + Some(res) + } + fn syntax(&self) -> &SyntaxNode { + match self { + Self::SvelteEachAsKeyedItem(it) => it.syntax(), + Self::SvelteEachKeyedItem(it) => it.syntax(), + } + } + fn into_syntax(self) -> SyntaxNode { + match self { + Self::SvelteEachAsKeyedItem(it) => it.into_syntax(), + Self::SvelteEachKeyedItem(it) => it.into_syntax(), + } + } +} +impl std::fmt::Debug for AnySvelteBlockItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SvelteEachAsKeyedItem(it) => std::fmt::Debug::fmt(it, f), + Self::SvelteEachKeyedItem(it) => std::fmt::Debug::fmt(it, f), + } + } +} +impl From for SyntaxNode { + fn from(n: AnySvelteBlockItem) -> Self { + match n { + AnySvelteBlockItem::SvelteEachAsKeyedItem(it) => it.into_syntax(), + AnySvelteBlockItem::SvelteEachKeyedItem(it) => it.into_syntax(), + } + } +} +impl From for SyntaxElement { + fn from(n: AnySvelteBlockItem) -> Self { + let node: SyntaxNode = n.into(); + node.into() + } +} impl From for AnyVueDirective { fn from(node: VueBogusDirective) -> Self { Self::VueBogusDirective(node) @@ -5626,6 +6065,11 @@ impl std::fmt::Display for AnySvelteBlock { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for AnySvelteBlockItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for AnyVueDirective { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -5746,6 +6190,11 @@ impl std::fmt::Display for SvelteDebugBlock { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for SvelteEachAsKeyedItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for SvelteEachBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -5756,6 +6205,21 @@ impl std::fmt::Display for SvelteEachClosingBlock { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for SvelteEachIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for SvelteEachKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for SvelteEachKeyedItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for SvelteEachOpeningBlock { 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 c1be99d70fcd..28bffc62ec7e 100644 --- a/crates/biome_html_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_html_syntax/src/generated/nodes_mut.rs @@ -431,6 +431,32 @@ impl SvelteDebugBlock { ) } } +impl SvelteEachAsKeyedItem { + pub fn with_as_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: HtmlTextExpression) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_index(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 2usize..=2usize, + once(element.map(|element| element.into_syntax().into())), + )) + } + pub fn with_key(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 3usize..=3usize, + once(element.map(|element| element.into_syntax().into())), + )) + } +} impl SvelteEachBlock { pub fn with_opening_block(self, element: SvelteEachOpeningBlock) -> Self { Self::unwrap_cast( @@ -444,10 +470,16 @@ impl SvelteEachBlock { .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), ) } + pub fn with_else_clause(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 2usize..=2usize, + once(element.map(|element| element.into_syntax().into())), + )) + } pub fn with_closing_block(self, element: SvelteEachClosingBlock) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + .splice_slots(3usize..=3usize, once(Some(element.into_syntax().into()))), ) } } @@ -471,41 +503,65 @@ impl SvelteEachClosingBlock { ) } } -impl SvelteEachOpeningBlock { - pub fn with_sv_curly_hash_token(self, element: SyntaxToken) -> Self { +impl SvelteEachIndex { + pub fn with_comma_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into()))), ) } - pub fn with_each_token(self, element: SyntaxToken) -> Self { + pub fn with_value(self, element: HtmlTextExpression) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(1usize..=1usize, once(Some(element.into()))), + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_list(self, element: HtmlTextExpression) -> Self { +} +impl SvelteEachKey { + pub fn with_expression(self, element: HtmlTextExpression) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_as_token(self, element: SyntaxToken) -> Self { +} +impl SvelteEachKeyedItem { + pub fn with_index(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 0usize..=0usize, + once(element.map(|element| element.into_syntax().into())), + )) + } +} +impl SvelteEachOpeningBlock { + pub fn with_sv_curly_hash_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(3usize..=3usize, once(Some(element.into()))), + .splice_slots(0usize..=0usize, once(Some(element.into()))), ) } - pub fn with_item(self, element: HtmlTextExpression) -> Self { + pub fn with_each_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(4usize..=4usize, once(Some(element.into_syntax().into()))), + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_list(self, element: HtmlTextExpression) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), ) } + pub fn with_item(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 3usize..=3usize, + once(element.map(|element| element.into_syntax().into())), + )) + } pub fn with_r_curly_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(5usize..=5usize, once(Some(element.into()))), + .splice_slots(4usize..=4usize, once(Some(element.into()))), ) } } diff --git a/xtask/codegen/html.ungram b/xtask/codegen/html.ungram index 577631233f3e..41c2e0b62c9b 100644 --- a/xtask/codegen/html.ungram +++ b/xtask/codegen/html.ungram @@ -195,12 +195,12 @@ AnyHtmlAttributeInitializer = AnySvelteBlock = SvelteDebugBlock | SvelteKeyBlock - | SvelteBogusBlock | SvelteRenderBlock | SvelteHtmlBlock | SvelteConstBlock | SvelteIfBlock | SvelteEachBlock + | SvelteBogusBlock // {@debug} // ^^^^^^^^ @@ -319,18 +319,46 @@ SvelteIfClosingBlock = SvelteEachBlock = opening_block: SvelteEachOpeningBlock children: HtmlElementList + else_clause: SvelteElseClause? closing_block: SvelteEachClosingBlock -// {#each ... as item, i} ... {/each} -// ^^^^^^^^^^^^^^^^^^^^^^ +// {#each items as item, index (key)} ... {/each} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SvelteEachOpeningBlock = '{#' 'each' list: HtmlTextExpression - 'as' - item: HtmlTextExpression + item: AnySvelteBlockItem? '}' +AnySvelteBlockItem = + SvelteEachAsKeyedItem + | SvelteEachKeyedItem + + +// {#each items as item, index (key)} ... {/each} +// ^^^^^^^^^^^^^^^^^^^^ +SvelteEachAsKeyedItem = + 'as' + name: HtmlTextExpression + index: SvelteEachIndex? + key: SvelteEachKey? + +// {#each items, index} ... {/each} +// ^^^^^^^^ +SvelteEachKeyedItem = + index: SvelteEachIndex? + +// {#each items as item, index} ... {/each} +// ^^^^^^^^ +SvelteEachIndex = + ',' + value: HtmlTextExpression + +// {#each items as item (key)} ... {/each} +// ^^^^^ +SvelteEachKey = + expression: HtmlTextExpression // {#each ... as item, i} ... {/each} // ^^^^^^^ diff --git a/xtask/codegen/src/html_kinds_src.rs b/xtask/codegen/src/html_kinds_src.rs index 30c8661841cb..d20b234cc8f8 100644 --- a/xtask/codegen/src/html_kinds_src.rs +++ b/xtask/codegen/src/html_kinds_src.rs @@ -77,8 +77,10 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { "SVELTE_ELSE_IF_CLAUSE", "SVELTE_EACH_BLOCK", "SVELTE_EACH_OPENING_BLOCK", - "SVELTE_EACH_ITEM", + "SVELTE_EACH_AS_KEYED_ITEM", + "SVELTE_EACH_KEYED_ITEM", "SVELTE_EACH_INDEX", + "SVELTE_EACH_KEY", "SVELTE_EACH_CLOSING_BLOCK", // Vue nodes "VUE_DIRECTIVE", From 621ebe6b66ffae85f511708147297587daabfde6 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Wed, 17 Dec 2025 17:24:24 +0000 Subject: [PATCH 3/6] add changeset --- .changeset/green-clubs-search.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changeset/green-clubs-search.md diff --git a/.changeset/green-clubs-search.md b/.changeset/green-clubs-search.md new file mode 100644 index 000000000000..ec2074978a82 --- /dev/null +++ b/.changeset/green-clubs-search.md @@ -0,0 +1,12 @@ +--- +"@biomejs/biome": patch +--- + +Added support for parsing and formatting the Svelte `{#each}` syntax, when `html.experimentalFullSupportEnabled` is set to `true`. + +```diff +- {#each items in item } ++ {#each items in item} + +{/each} +``` From a18255abc74ececf4babafe8a4b18c850a0b92b3 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 18 Dec 2025 11:25:52 +0000 Subject: [PATCH 4/6] fix: better parsing and better formatting --- .changeset/green-clubs-search.md | 4 +- .../src/generated/node_factory.rs | 12 +- .../src/generated/syntax_factory.rs | 16 ++- .../html/auxiliary/double_text_expression.rs | 2 + .../svelte/auxiliary/each_as_keyed_item.rs | 2 +- .../src/svelte/auxiliary/each_key.rs | 15 +- .../html/interpolation/interpolation.html | 3 + .../interpolation/interpolation.html.snap | 57 ++++++++ .../specs/html/interpolation/options.json | 11 ++ .../svelte/each_with_whitespace.svelte.snap | 2 +- crates/biome_html_parser/src/lexer/mod.rs | 13 +- crates/biome_html_parser/src/syntax/svelte.rs | 47 ++++--- crates/biome_html_parser/src/token_source.rs | 12 +- .../svelte/each_missing_closing_paren.svelte | 2 + .../each_missing_closing_paren.svelte.snap | 115 ++++++++++++++++ .../each_complex_combinations.svelte.snap | 42 ++++-- .../each_with_index_and_key.svelte.snap | 22 ++- .../ok/svelte/each_with_key.svelte.snap | 20 ++- .../ok/svelte/each_with_whitespace.svelte | 3 - .../svelte/each_with_whitespace.svelte.snap | 129 ------------------ crates/biome_html_parser/tests/quick_test.rs | 4 +- .../biome_html_syntax/src/generated/kind.rs | 8 +- .../biome_html_syntax/src/generated/nodes.rs | 20 ++- .../src/generated/nodes_mut.rs | 14 +- xtask/codegen/html.ungram | 8 +- xtask/codegen/src/html_kinds_src.rs | 2 + 26 files changed, 390 insertions(+), 195 deletions(-) create mode 100644 crates/biome_html_formatter/tests/specs/html/interpolation/interpolation.html create mode 100644 crates/biome_html_formatter/tests/specs/html/interpolation/interpolation.html.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/interpolation/options.json create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_closing_paren.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_closing_paren.svelte.snap delete mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte delete mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte.snap diff --git a/.changeset/green-clubs-search.md b/.changeset/green-clubs-search.md index ec2074978a82..c86a9c6e3070 100644 --- a/.changeset/green-clubs-search.md +++ b/.changeset/green-clubs-search.md @@ -5,8 +5,8 @@ Added support for parsing and formatting the Svelte `{#each}` syntax, when `html.experimentalFullSupportEnabled` is set to `true`. ```diff -- {#each items in item } -+ {#each items in item} +- {#each items as item } ++ {#each items as item} {/each} ``` diff --git a/crates/biome_html_factory/src/generated/node_factory.rs b/crates/biome_html_factory/src/generated/node_factory.rs index 5e0b4745d170..4a780da313fa 100644 --- a/crates/biome_html_factory/src/generated/node_factory.rs +++ b/crates/biome_html_factory/src/generated/node_factory.rs @@ -494,10 +494,18 @@ pub fn svelte_each_index(comma_token: SyntaxToken, value: HtmlTextExpression) -> ], )) } -pub fn svelte_each_key(expression: HtmlTextExpression) -> SvelteEachKey { +pub fn svelte_each_key( + l_paren_token: SyntaxToken, + expression: HtmlTextExpression, + r_paren_token: SyntaxToken, +) -> SvelteEachKey { SvelteEachKey::unwrap_cast(SyntaxNode::new_detached( HtmlSyntaxKind::SVELTE_EACH_KEY, - [Some(SyntaxElement::Node(expression.into_syntax()))], + [ + Some(SyntaxElement::Token(l_paren_token)), + Some(SyntaxElement::Node(expression.into_syntax())), + Some(SyntaxElement::Token(r_paren_token)), + ], )) } pub fn svelte_each_keyed_item() -> SvelteEachKeyedItemBuilder { diff --git a/crates/biome_html_factory/src/generated/syntax_factory.rs b/crates/biome_html_factory/src/generated/syntax_factory.rs index b4f9ac246237..b66e8256316f 100644 --- a/crates/biome_html_factory/src/generated/syntax_factory.rs +++ b/crates/biome_html_factory/src/generated/syntax_factory.rs @@ -877,8 +877,15 @@ impl SyntaxFactory for HtmlSyntaxFactory { } SVELTE_EACH_KEY => { let mut elements = (&children).into_iter(); - let mut slots: RawNodeSlots<1usize> = RawNodeSlots::default(); + 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 && HtmlTextExpression::can_cast(element.kind()) { @@ -886,6 +893,13 @@ impl SyntaxFactory for HtmlSyntaxFactory { 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 current_element.is_some() { return RawSyntaxNode::new( SVELTE_EACH_KEY.to_bogus(), diff --git a/crates/biome_html_formatter/src/html/auxiliary/double_text_expression.rs b/crates/biome_html_formatter/src/html/auxiliary/double_text_expression.rs index bf08ad978c21..fdf192bd8160 100644 --- a/crates/biome_html_formatter/src/html/auxiliary/double_text_expression.rs +++ b/crates/biome_html_formatter/src/html/auxiliary/double_text_expression.rs @@ -20,7 +20,9 @@ impl FormatNodeRule for FormatHtmlDoubleTextExpression f, [ l_double_curly_token.format(), + space(), expression.format(), + space(), r_double_curly_token.format(), ] ) diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_as_keyed_item.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_as_keyed_item.rs index 82230cc8deaa..71449b8b6950 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/each_as_keyed_item.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_as_keyed_item.rs @@ -19,7 +19,7 @@ impl FormatNodeRule for FormatSvelteEachAsKeyedItem { } if let Some(key) = key { - write!(f, [key.format()])?; + write!(f, [space(), key.format()])?; } Ok(()) diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/each_key.rs b/crates/biome_html_formatter/src/svelte/auxiliary/each_key.rs index b7c19d95c2b5..d169d26641fd 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/each_key.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/each_key.rs @@ -5,8 +5,19 @@ use biome_html_syntax::{SvelteEachKey, SvelteEachKeyFields}; pub(crate) struct FormatSvelteEachKey; impl FormatNodeRule for FormatSvelteEachKey { fn fmt_fields(&self, node: &SvelteEachKey, f: &mut HtmlFormatter) -> FormatResult<()> { - let SvelteEachKeyFields { expression } = node.as_fields(); + let SvelteEachKeyFields { + expression, + l_paren_token, + r_paren_token, + } = node.as_fields(); - write!(f, [expression.format()]) + write!( + f, + [ + l_paren_token.format(), + expression.format(), + r_paren_token.format() + ] + ) } } diff --git a/crates/biome_html_formatter/tests/specs/html/interpolation/interpolation.html b/crates/biome_html_formatter/tests/specs/html/interpolation/interpolation.html new file mode 100644 index 000000000000..1c40fe032bfb --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/interpolation/interpolation.html @@ -0,0 +1,3 @@ +
+ {{ $interpolation }} +
diff --git a/crates/biome_html_formatter/tests/specs/html/interpolation/interpolation.html.snap b/crates/biome_html_formatter/tests/specs/html/interpolation/interpolation.html.snap new file mode 100644 index 000000000000..4fdc257476b4 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/interpolation/interpolation.html.snap @@ -0,0 +1,57 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: interpolation/interpolation.html +--- +# Input + +```html +
+ {{ $interpolation }} +
+ +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```html +
+ {{ $interpolation }} +
+``` + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```html +
+ {{ $interpolation }} +
+``` diff --git a/crates/biome_html_formatter/tests/specs/html/interpolation/options.json b/crates/biome_html_formatter/tests/specs/html/interpolation/options.json new file mode 100644 index 000000000000..904a2fba547f --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/interpolation/options.json @@ -0,0 +1,11 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "html": { + "parser": { + "interpolation": true + }, + "formatter": { + "enabled": true + } + } +} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap index 1a97fdc1622e..84345a135f07 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap @@ -47,7 +47,7 @@ Self close void elements: never
{item}
{/each} -{#each items as item ( item.id )} +{#each items as item (item.id)}
{item}
{/each} ``` diff --git a/crates/biome_html_parser/src/lexer/mod.rs b/crates/biome_html_parser/src/lexer/mod.rs index aafa19f28e72..f913e2e8afdf 100644 --- a/crates/biome_html_parser/src/lexer/mod.rs +++ b/crates/biome_html_parser/src/lexer/mod.rs @@ -361,10 +361,13 @@ impl<'src> HtmlLexer<'src> { brackets_stack += 1; self.advance(1); } - b',' if brackets_stack == 0 => { - // Stop at comma (for index-only each syntax) + _ if brackets_stack == 0 && !is_at_start_identifier(current) => { let should_stop = match kind { - RestrictedExpressionKind::StopAtAsOrComma => true, + RestrictedExpressionKind::StopAtAsOrComma => current == b',', + RestrictedExpressionKind::StopAtOpeningParenOrComma => { + current == b'(' || current == b',' + } + RestrictedExpressionKind::StopAtClosingParen => current == b')', }; if should_stop { break; @@ -378,6 +381,8 @@ impl<'src> HtmlLexer<'src> { // Check if this keyword is in our stop list let should_stop = match kind { RestrictedExpressionKind::StopAtAsOrComma => keyword_kind == AS_KW, + RestrictedExpressionKind::StopAtOpeningParenOrComma => false, + RestrictedExpressionKind::StopAtClosingParen => false, }; if should_stop { @@ -466,6 +471,8 @@ impl<'src> HtmlLexer<'src> { b'\n' | b'\r' | b'\t' | b' ' => self.consume_newline_or_whitespaces(), b'}' => self.consume_byte(T!['}']), b',' => self.consume_byte(T![,]), + b'(' => self.consume_byte(T!['(']), + b')' => self.consume_byte(T![')']), b'{' if self.at_svelte_opening_block() => self.consume_svelte_opening_block(), b'{' => self.consume_byte(T!['{']), _ if is_at_start_identifier(current) => self diff --git a/crates/biome_html_parser/src/syntax/svelte.rs b/crates/biome_html_parser/src/syntax/svelte.rs index 9c1836e451db..cd9ec479b555 100644 --- a/crates/biome_html_parser/src/syntax/svelte.rs +++ b/crates/biome_html_parser/src/syntax/svelte.rs @@ -5,7 +5,7 @@ use crate::syntax::parse_error::{ use crate::syntax::{parse_html_element, parse_single_text_expression_content}; use crate::token_source::{HtmlLexContext, HtmlReLexContext, RestrictedExpressionKind}; use biome_html_syntax::HtmlSyntaxKind::{ - EOF, HTML_BOGUS_ELEMENT, HTML_ELEMENT_LIST, HTML_LITERAL, IDENT, SVELTE_ATTACH_ATTRIBUTE, + EOF, HTML_BOGUS_ELEMENT, HTML_ELEMENT_LIST, IDENT, SVELTE_ATTACH_ATTRIBUTE, SVELTE_BINDING_LIST, SVELTE_BOGUS_BLOCK, SVELTE_CONST_BLOCK, SVELTE_DEBUG_BLOCK, SVELTE_EACH_AS_KEYED_ITEM, SVELTE_EACH_BLOCK, SVELTE_EACH_CLOSING_BLOCK, SVELTE_EACH_INDEX, SVELTE_EACH_KEY, SVELTE_EACH_KEYED_ITEM, SVELTE_EACH_OPENING_BLOCK, SVELTE_ELSE_CLAUSE, @@ -193,7 +193,7 @@ fn parse_each_as_keyed_item(p: &mut HtmlParser) -> ParsedSyntax { // Consume 'as' and switch context for name (stop at comma for optional index) p.bump_with_context( T![as], - HtmlLexContext::restricted_expression(RestrictedExpressionKind::StopAtAsOrComma), + HtmlLexContext::restricted_expression(RestrictedExpressionKind::StopAtOpeningParenOrComma), ); // Parse name (required) @@ -213,23 +213,33 @@ fn parse_each_as_keyed_item(p: &mut HtmlParser) -> ParsedSyntax { // Parse optional key: (key_expression) // The key expression includes parentheses as part of the literal - if p.at(HTML_LITERAL) && p.cur_text().trim_start().starts_with('(') { - let key_m = p.start(); - let key_text = p.cur_text().to_string(); - parse_single_text_expression_content(p).or_add_diagnostic(p, |p, range| { - p.err_builder("Expected a key expression in parentheses", range) - }); - - // Validate that the key expression has matching parentheses - let trimmed = key_text.trim(); - if !trimmed.ends_with(')') { - p.error(p.err_builder("Expected closing ')' for key expression", p.cur_range())); - } + parse_each_key(p).ok(); + + Present(m.complete(p, SVELTE_EACH_AS_KEYED_ITEM)) +} - key_m.complete(p, SVELTE_EACH_KEY); +/// Parse the `( key )` inside the `#each` block +fn parse_each_key(p: &mut HtmlParser) -> ParsedSyntax { + if !p.at(T!['(']) { + return Absent; } - Present(m.complete(p, SVELTE_EACH_AS_KEYED_ITEM)) + let m = p.start(); + p.bump_with_context( + T!['('], + HtmlLexContext::restricted_expression(RestrictedExpressionKind::StopAtClosingParen), + ); + + parse_single_text_expression_content(p).or_add_diagnostic(p, |p, range| { + p.err_builder("Expected a key expression in parentheses", range) + }); + + // Re-lex to Svelte context to recognize ',) and other tokens + p.re_lex(HtmlReLexContext::Svelte); + + p.expect(T![')']); + + Present(m.complete(p, SVELTE_EACH_KEY)) } /// Parses the ", index" part for index-only syntax (without 'as') @@ -255,7 +265,10 @@ fn parse_each_index(p: &mut HtmlParser) -> ParsedSyntax { } // Parse the index let m = p.start(); - p.bump_with_context(T![,], HtmlLexContext::single_expression()); + p.bump_with_context( + T![,], + HtmlLexContext::restricted_expression(RestrictedExpressionKind::StopAtOpeningParenOrComma), + ); parse_single_text_expression_content(p).or_add_diagnostic(p, |p, range| { p.err_builder("Expected an index binding after ','", range) }); diff --git a/crates/biome_html_parser/src/token_source.rs b/crates/biome_html_parser/src/token_source.rs index e8c7c7a9c309..1b1c0287c230 100644 --- a/crates/biome_html_parser/src/token_source.rs +++ b/crates/biome_html_parser/src/token_source.rs @@ -16,7 +16,7 @@ pub(crate) struct HtmlTokenSource<'source> { pub(super) trivia_list: Vec, } -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum HtmlLexContext { /// The default state. This state is used for lexing outside of tags. /// @@ -73,7 +73,7 @@ impl HtmlLexContext { } } -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub(crate) enum TextExpressionKind { // {{ expr }} #[default] @@ -82,13 +82,17 @@ pub(crate) enum TextExpressionKind { Single, } -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum RestrictedExpressionKind { /// Stops at 'as' keyword or ',' (for Svelte #each blocks) StopAtAsOrComma, + /// Stops at `(` + StopAtOpeningParenOrComma, + /// Stops at `)` + StopAtClosingParen, } -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum HtmlEmbeddedLanguage { Script, Style, diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_closing_paren.svelte b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_closing_paren.svelte new file mode 100644 index 000000000000..0a40623fc5ab --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_closing_paren.svelte @@ -0,0 +1,2 @@ +{#each items as item, index (key} +{/each} diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_closing_paren.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_closing_paren.svelte.snap new file mode 100644 index 000000000000..7b38f656ffe4 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/each_missing_closing_paren.svelte.snap @@ -0,0 +1,115 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{#each items as item, index (key} +{/each} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteEachBlock { + opening_block: SvelteEachOpeningBlock { + sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], + each_token: EACH_KW@2..6 "each" [] [], + list: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..13 " items " [] [], + }, + item: SvelteEachAsKeyedItem { + as_token: AS_KW@13..15 "as" [] [], + name: HtmlTextExpression { + html_literal_token: HTML_LITERAL@15..20 " item" [] [], + }, + index: SvelteEachIndex { + comma_token: COMMA@20..21 "," [] [], + value: HtmlTextExpression { + html_literal_token: HTML_LITERAL@21..28 " index " [] [], + }, + }, + key: SvelteEachKey { + l_paren_token: L_PAREN@28..29 "(" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@29..32 "key" [] [], + }, + r_paren_token: missing (required), + }, + }, + r_curly_token: R_CURLY@32..33 "}" [] [], + }, + children: HtmlElementList [], + else_clause: missing (optional), + closing_block: SvelteEachClosingBlock { + sv_curly_slash_token: SV_CURLY_SLASH@33..36 "{/" [Newline("\n")] [], + each_token: EACH_KW@36..40 "each" [] [], + r_curly_token: R_CURLY@40..41 "}" [] [], + }, + }, + ], + eof_token: EOF@41..42 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..42 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..41 + 0: SVELTE_EACH_BLOCK@0..41 + 0: SVELTE_EACH_OPENING_BLOCK@0..33 + 0: SV_CURLY_HASH@0..2 "{#" [] [] + 1: EACH_KW@2..6 "each" [] [] + 2: HTML_TEXT_EXPRESSION@6..13 + 0: HTML_LITERAL@6..13 " items " [] [] + 3: SVELTE_EACH_AS_KEYED_ITEM@13..32 + 0: AS_KW@13..15 "as" [] [] + 1: HTML_TEXT_EXPRESSION@15..20 + 0: HTML_LITERAL@15..20 " item" [] [] + 2: SVELTE_EACH_INDEX@20..28 + 0: COMMA@20..21 "," [] [] + 1: HTML_TEXT_EXPRESSION@21..28 + 0: HTML_LITERAL@21..28 " index " [] [] + 3: SVELTE_EACH_KEY@28..32 + 0: L_PAREN@28..29 "(" [] [] + 1: HTML_TEXT_EXPRESSION@29..32 + 0: HTML_LITERAL@29..32 "key" [] [] + 2: (empty) + 4: R_CURLY@32..33 "}" [] [] + 1: HTML_ELEMENT_LIST@33..33 + 2: (empty) + 3: SVELTE_EACH_CLOSING_BLOCK@33..41 + 0: SV_CURLY_SLASH@33..36 "{/" [Newline("\n")] [] + 1: EACH_KW@36..40 "each" [] [] + 2: R_CURLY@40..41 "}" [] [] + 4: EOF@41..42 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +each_missing_closing_paren.svelte:1:33 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead found `}` + + > 1 │ {#each items as item, index (key} + │ ^ + 2 │ {/each} + 3 │ + + i Remove } + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte.snap index 56125f35fc8a..45aaa7ef2863 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_complex_combinations.svelte.snap @@ -45,10 +45,16 @@ HtmlRoot { index: SvelteEachIndex { comma_token: COMMA@20..21 "," [] [], value: HtmlTextExpression { - html_literal_token: HTML_LITERAL@21..33 " i (item.id)" [] [], + html_literal_token: HTML_LITERAL@21..24 " i " [] [], }, }, - key: missing (optional), + key: SvelteEachKey { + l_paren_token: L_PAREN@24..25 "(" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@25..32 "item.id" [] [], + }, + r_paren_token: R_PAREN@32..33 ")" [] [], + }, }, r_curly_token: R_CURLY@33..34 "}" [] [], }, @@ -193,10 +199,16 @@ HtmlRoot { item: SvelteEachAsKeyedItem { as_token: AS_KW@179..181 "as" [] [], name: HtmlTextExpression { - html_literal_token: HTML_LITERAL@181..203 " product (product.sku)" [] [], + html_literal_token: HTML_LITERAL@181..190 " product " [] [], }, index: missing (optional), - key: missing (optional), + key: SvelteEachKey { + l_paren_token: L_PAREN@190..191 "(" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@191..202 "product.sku" [] [], + }, + r_paren_token: R_PAREN@202..203 ")" [] [], + }, }, r_curly_token: R_CURLY@203..204 "}" [] [], }, @@ -259,11 +271,15 @@ HtmlRoot { 0: AS_KW@13..15 "as" [] [] 1: HTML_TEXT_EXPRESSION@15..20 0: HTML_LITERAL@15..20 " item" [] [] - 2: SVELTE_EACH_INDEX@20..33 + 2: SVELTE_EACH_INDEX@20..24 0: COMMA@20..21 "," [] [] - 1: HTML_TEXT_EXPRESSION@21..33 - 0: HTML_LITERAL@21..33 " i (item.id)" [] [] - 3: (empty) + 1: HTML_TEXT_EXPRESSION@21..24 + 0: HTML_LITERAL@21..24 " i " [] [] + 3: SVELTE_EACH_KEY@24..33 + 0: L_PAREN@24..25 "(" [] [] + 1: HTML_TEXT_EXPRESSION@25..32 + 0: HTML_LITERAL@25..32 "item.id" [] [] + 2: R_PAREN@32..33 ")" [] [] 4: R_CURLY@33..34 "}" [] [] 1: HTML_ELEMENT_LIST@34..64 0: HTML_ELEMENT@34..64 @@ -364,10 +380,14 @@ HtmlRoot { 0: HTML_LITERAL@169..179 " products " [] [] 3: SVELTE_EACH_AS_KEYED_ITEM@179..203 0: AS_KW@179..181 "as" [] [] - 1: HTML_TEXT_EXPRESSION@181..203 - 0: HTML_LITERAL@181..203 " product (product.sku)" [] [] + 1: HTML_TEXT_EXPRESSION@181..190 + 0: HTML_LITERAL@181..190 " product " [] [] 2: (empty) - 3: (empty) + 3: SVELTE_EACH_KEY@190..203 + 0: L_PAREN@190..191 "(" [] [] + 1: HTML_TEXT_EXPRESSION@191..202 + 0: HTML_LITERAL@191..202 "product.sku" [] [] + 2: R_PAREN@202..203 ")" [] [] 4: R_CURLY@203..204 "}" [] [] 1: HTML_ELEMENT_LIST@204..233 0: HTML_ELEMENT@204..233 diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte.snap index 4a84da795a94..2b6c3c811ed5 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_index_and_key.svelte.snap @@ -35,10 +35,16 @@ HtmlRoot { index: SvelteEachIndex { comma_token: COMMA@20..21 "," [] [], value: HtmlTextExpression { - html_literal_token: HTML_LITERAL@21..33 " i (item.id)" [] [], + html_literal_token: HTML_LITERAL@21..24 " i " [] [], }, }, - key: missing (optional), + key: SvelteEachKey { + l_paren_token: L_PAREN@24..25 "(" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@25..32 "item.id" [] [], + }, + r_paren_token: R_PAREN@32..33 ")" [] [], + }, }, r_curly_token: R_CURLY@33..34 "}" [] [], }, @@ -111,11 +117,15 @@ HtmlRoot { 0: AS_KW@13..15 "as" [] [] 1: HTML_TEXT_EXPRESSION@15..20 0: HTML_LITERAL@15..20 " item" [] [] - 2: SVELTE_EACH_INDEX@20..33 + 2: SVELTE_EACH_INDEX@20..24 0: COMMA@20..21 "," [] [] - 1: HTML_TEXT_EXPRESSION@21..33 - 0: HTML_LITERAL@21..33 " i (item.id)" [] [] - 3: (empty) + 1: HTML_TEXT_EXPRESSION@21..24 + 0: HTML_LITERAL@21..24 " i " [] [] + 3: SVELTE_EACH_KEY@24..33 + 0: L_PAREN@24..25 "(" [] [] + 1: HTML_TEXT_EXPRESSION@25..32 + 0: HTML_LITERAL@25..32 "item.id" [] [] + 2: R_PAREN@32..33 ")" [] [] 4: R_CURLY@33..34 "}" [] [] 1: HTML_ELEMENT_LIST@34..64 0: HTML_ELEMENT@34..64 diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte.snap index 20dca9551026..445b3be19794 100644 --- a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte.snap +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_key.svelte.snap @@ -30,10 +30,16 @@ HtmlRoot { item: SvelteEachAsKeyedItem { as_token: AS_KW@13..15 "as" [] [], name: HtmlTextExpression { - html_literal_token: HTML_LITERAL@15..30 " item (item.id)" [] [], + html_literal_token: HTML_LITERAL@15..21 " item " [] [], }, index: missing (optional), - key: missing (optional), + key: SvelteEachKey { + l_paren_token: L_PAREN@21..22 "(" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@22..29 "item.id" [] [], + }, + r_paren_token: R_PAREN@29..30 ")" [] [], + }, }, r_curly_token: R_CURLY@30..31 "}" [] [], }, @@ -94,10 +100,14 @@ HtmlRoot { 0: HTML_LITERAL@6..13 " items " [] [] 3: SVELTE_EACH_AS_KEYED_ITEM@13..30 0: AS_KW@13..15 "as" [] [] - 1: HTML_TEXT_EXPRESSION@15..30 - 0: HTML_LITERAL@15..30 " item (item.id)" [] [] + 1: HTML_TEXT_EXPRESSION@15..21 + 0: HTML_LITERAL@15..21 " item " [] [] 2: (empty) - 3: (empty) + 3: SVELTE_EACH_KEY@21..30 + 0: L_PAREN@21..22 "(" [] [] + 1: HTML_TEXT_EXPRESSION@22..29 + 0: HTML_LITERAL@22..29 "item.id" [] [] + 2: R_PAREN@29..30 ")" [] [] 4: R_CURLY@30..31 "}" [] [] 1: HTML_ELEMENT_LIST@31..56 0: HTML_ELEMENT@31..56 diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte deleted file mode 100644 index 5b5604a33761..000000000000 --- a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte +++ /dev/null @@ -1,3 +0,0 @@ -{#each items as item } -
{item}
-{/each} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte.snap deleted file mode 100644 index e7aa8261d403..000000000000 --- a/crates/biome_html_parser/tests/html_specs/ok/svelte/each_with_whitespace.svelte.snap +++ /dev/null @@ -1,129 +0,0 @@ ---- -source: crates/biome_html_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```svelte -{#each items as item } -
{item}
-{/each} - -``` - - -## AST - -``` -HtmlRoot { - bom_token: missing (optional), - frontmatter: missing (optional), - directive: missing (optional), - html: HtmlElementList [ - SvelteEachBlock { - opening_block: SvelteEachOpeningBlock { - sv_curly_hash_token: SV_CURLY_HASH@0..2 "{#" [] [], - each_token: EACH_KW@2..6 "each" [] [], - list: HtmlTextExpression { - html_literal_token: HTML_LITERAL@6..15 " items " [] [], - }, - item: SvelteEachAsKeyedItem { - as_token: AS_KW@15..17 "as" [] [], - name: HtmlTextExpression { - html_literal_token: HTML_LITERAL@17..25 " item " [] [], - }, - index: missing (optional), - key: missing (optional), - }, - r_curly_token: R_CURLY@25..26 "}" [] [], - }, - children: HtmlElementList [ - HtmlElement { - opening_element: HtmlOpeningElement { - l_angle_token: L_ANGLE@26..30 "<" [Newline("\n"), Whitespace(" ")] [], - name: HtmlTagName { - value_token: HTML_LITERAL@30..33 "div" [] [], - }, - attributes: HtmlAttributeList [], - r_angle_token: R_ANGLE@33..34 ">" [] [], - }, - children: HtmlElementList [ - HtmlSingleTextExpression { - l_curly_token: L_CURLY@34..35 "{" [] [], - expression: HtmlTextExpression { - html_literal_token: HTML_LITERAL@35..39 "item" [] [], - }, - r_curly_token: R_CURLY@39..40 "}" [] [], - }, - ], - closing_element: HtmlClosingElement { - l_angle_token: L_ANGLE@40..41 "<" [] [], - slash_token: SLASH@41..42 "/" [] [], - name: HtmlTagName { - value_token: HTML_LITERAL@42..45 "div" [] [], - }, - r_angle_token: R_ANGLE@45..46 ">" [] [], - }, - }, - ], - else_clause: missing (optional), - closing_block: SvelteEachClosingBlock { - sv_curly_slash_token: SV_CURLY_SLASH@46..49 "{/" [Newline("\n")] [], - each_token: EACH_KW@49..53 "each" [] [], - r_curly_token: R_CURLY@53..54 "}" [] [], - }, - }, - ], - eof_token: EOF@54..55 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: HTML_ROOT@0..55 - 0: (empty) - 1: (empty) - 2: (empty) - 3: HTML_ELEMENT_LIST@0..54 - 0: SVELTE_EACH_BLOCK@0..54 - 0: SVELTE_EACH_OPENING_BLOCK@0..26 - 0: SV_CURLY_HASH@0..2 "{#" [] [] - 1: EACH_KW@2..6 "each" [] [] - 2: HTML_TEXT_EXPRESSION@6..15 - 0: HTML_LITERAL@6..15 " items " [] [] - 3: SVELTE_EACH_AS_KEYED_ITEM@15..25 - 0: AS_KW@15..17 "as" [] [] - 1: HTML_TEXT_EXPRESSION@17..25 - 0: HTML_LITERAL@17..25 " item " [] [] - 2: (empty) - 3: (empty) - 4: R_CURLY@25..26 "}" [] [] - 1: HTML_ELEMENT_LIST@26..46 - 0: HTML_ELEMENT@26..46 - 0: HTML_OPENING_ELEMENT@26..34 - 0: L_ANGLE@26..30 "<" [Newline("\n"), Whitespace(" ")] [] - 1: HTML_TAG_NAME@30..33 - 0: HTML_LITERAL@30..33 "div" [] [] - 2: HTML_ATTRIBUTE_LIST@33..33 - 3: R_ANGLE@33..34 ">" [] [] - 1: HTML_ELEMENT_LIST@34..40 - 0: HTML_SINGLE_TEXT_EXPRESSION@34..40 - 0: L_CURLY@34..35 "{" [] [] - 1: HTML_TEXT_EXPRESSION@35..39 - 0: HTML_LITERAL@35..39 "item" [] [] - 2: R_CURLY@39..40 "}" [] [] - 2: HTML_CLOSING_ELEMENT@40..46 - 0: L_ANGLE@40..41 "<" [] [] - 1: SLASH@41..42 "/" [] [] - 2: HTML_TAG_NAME@42..45 - 0: HTML_LITERAL@42..45 "div" [] [] - 3: R_ANGLE@45..46 ">" [] [] - 2: (empty) - 3: SVELTE_EACH_CLOSING_BLOCK@46..54 - 0: SV_CURLY_SLASH@46..49 "{/" [Newline("\n")] [] - 1: EACH_KW@49..53 "each" [] [] - 2: R_CURLY@53..54 "}" [] [] - 4: EOF@54..55 "" [Newline("\n")] [] - -``` diff --git a/crates/biome_html_parser/tests/quick_test.rs b/crates/biome_html_parser/tests/quick_test.rs index fa17576135da..d5682336b5af 100644 --- a/crates/biome_html_parser/tests/quick_test.rs +++ b/crates/biome_html_parser/tests/quick_test.rs @@ -4,8 +4,8 @@ use biome_test_utils::has_bogus_nodes_or_empty_slots; #[ignore] #[test] pub fn quick_test() { - let code = r#"{#each users , i} -
{item}
+ let code = r#"{#each items as item, i (item.id)} +
{i}: {item.name}
{/each} "#; diff --git a/crates/biome_html_syntax/src/generated/kind.rs b/crates/biome_html_syntax/src/generated/kind.rs index 1a099b58af39..23ca5d4bf8b3 100644 --- a/crates/biome_html_syntax/src/generated/kind.rs +++ b/crates/biome_html_syntax/src/generated/kind.rs @@ -36,6 +36,8 @@ pub enum HtmlSyntaxKind { L_BRACKET, R_BRACKET, HASH, + L_PAREN, + R_PAREN, NULL_KW, TRUE_KW, FALSE_KW, @@ -151,6 +153,8 @@ impl HtmlSyntaxKind { | L_BRACKET | R_BRACKET | HASH + | L_PAREN + | R_PAREN ) } pub const fn is_literal(self) -> bool { @@ -212,6 +216,8 @@ impl HtmlSyntaxKind { L_BRACKET => "[", R_BRACKET => "]", HASH => "#", + L_PAREN => "(", + R_PAREN => ")", NULL_KW => "null", TRUE_KW => "true", FALSE_KW => "false", @@ -235,4 +241,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 } ; [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 } ; [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 } ; [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 } ; [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/nodes.rs b/crates/biome_html_syntax/src/generated/nodes.rs index 974aac89493c..5ea275ebb2f1 100644 --- a/crates/biome_html_syntax/src/generated/nodes.rs +++ b/crates/biome_html_syntax/src/generated/nodes.rs @@ -1200,11 +1200,19 @@ impl SvelteEachKey { } pub fn as_fields(&self) -> SvelteEachKeyFields { SvelteEachKeyFields { + l_paren_token: self.l_paren_token(), expression: self.expression(), + r_paren_token: self.r_paren_token(), } } + pub fn l_paren_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } pub fn expression(&self) -> SyntaxResult { - support::required_node(&self.syntax, 0usize) + support::required_node(&self.syntax, 1usize) + } + pub fn r_paren_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 2usize) } } impl Serialize for SvelteEachKey { @@ -1217,7 +1225,9 @@ impl Serialize for SvelteEachKey { } #[derive(Serialize)] pub struct SvelteEachKeyFields { + pub l_paren_token: SyntaxResult, pub expression: SyntaxResult, + pub r_paren_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] pub struct SvelteEachKeyedItem { @@ -3986,7 +3996,15 @@ impl std::fmt::Debug for SvelteEachKey { let result = if current_depth < 16 { DEPTH.set(current_depth + 1); f.debug_struct("SvelteEachKey") + .field( + "l_paren_token", + &support::DebugSyntaxResult(self.l_paren_token()), + ) .field("expression", &support::DebugSyntaxResult(self.expression())) + .field( + "r_paren_token", + &support::DebugSyntaxResult(self.r_paren_token()), + ) .finish() } else { f.debug_struct("SvelteEachKey").finish() diff --git a/crates/biome_html_syntax/src/generated/nodes_mut.rs b/crates/biome_html_syntax/src/generated/nodes_mut.rs index 28bffc62ec7e..4a6259e6c9c0 100644 --- a/crates/biome_html_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_html_syntax/src/generated/nodes_mut.rs @@ -518,10 +518,22 @@ impl SvelteEachIndex { } } impl SvelteEachKey { + 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_expression(self, element: HtmlTextExpression) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + .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()))), ) } } diff --git a/xtask/codegen/html.ungram b/xtask/codegen/html.ungram index 41c2e0b62c9b..6cfe7b5b265f 100644 --- a/xtask/codegen/html.ungram +++ b/xtask/codegen/html.ungram @@ -350,15 +350,17 @@ SvelteEachKeyedItem = index: SvelteEachIndex? // {#each items as item, index} ... {/each} -// ^^^^^^^^ +// ^^^^^^^ SvelteEachIndex = ',' value: HtmlTextExpression -// {#each items as item (key)} ... {/each} -// ^^^^^ +// {#each items as item, item (key)} ... {/each} +// ^^^^^ SvelteEachKey = + '(' expression: HtmlTextExpression + ')' // {#each ... as item, i} ... {/each} // ^^^^^^^ diff --git a/xtask/codegen/src/html_kinds_src.rs b/xtask/codegen/src/html_kinds_src.rs index d20b234cc8f8..177cd5420163 100644 --- a/xtask/codegen/src/html_kinds_src.rs +++ b/xtask/codegen/src/html_kinds_src.rs @@ -26,6 +26,8 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { ("[", "L_BRACKET"), ("]", "R_BRACKET"), ("#", "HASH"), + ("(", "L_PAREN"), + (")", "R_PAREN"), ], keywords: &[ "null", "true", "false", "doctype", "html", // Svelte keywords From 6827ce64d78a2dd3dfe159ba3e9d3fbc758ddd28 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 18 Dec 2025 11:34:23 +0000 Subject: [PATCH 5/6] clippy --- crates/biome_html_parser/src/lexer/mod.rs | 16 ++++++++-------- crates/biome_html_parser/src/syntax/svelte.rs | 10 +++++----- crates/biome_html_parser/src/token_source.rs | 12 ++++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/biome_html_parser/src/lexer/mod.rs b/crates/biome_html_parser/src/lexer/mod.rs index f913e2e8afdf..48e072237266 100644 --- a/crates/biome_html_parser/src/lexer/mod.rs +++ b/crates/biome_html_parser/src/lexer/mod.rs @@ -1,7 +1,7 @@ mod tests; use crate::token_source::{ - HtmlEmbeddedLanguage, HtmlLexContext, HtmlReLexContext, RestrictedExpressionKind, + HtmlEmbeddedLanguage, HtmlLexContext, HtmlReLexContext, RestrictedExpressionStopAt, TextExpressionKind, }; use biome_html_syntax::HtmlSyntaxKind::{ @@ -341,7 +341,7 @@ impl<'src> HtmlLexer<'src> { /// encountering a keyword at the top level. fn consume_restricted_single_text_expression( &mut self, - kind: RestrictedExpressionKind, + kind: RestrictedExpressionStopAt, ) -> HtmlSyntaxKind { let start_pos = self.position; let mut brackets_stack = 0; @@ -363,11 +363,11 @@ impl<'src> HtmlLexer<'src> { } _ if brackets_stack == 0 && !is_at_start_identifier(current) => { let should_stop = match kind { - RestrictedExpressionKind::StopAtAsOrComma => current == b',', - RestrictedExpressionKind::StopAtOpeningParenOrComma => { + RestrictedExpressionStopAt::AsOrComma => current == b',', + RestrictedExpressionStopAt::OpeningParenOrComma => { current == b'(' || current == b',' } - RestrictedExpressionKind::StopAtClosingParen => current == b')', + RestrictedExpressionStopAt::ClosingParen => current == b')', }; if should_stop { break; @@ -380,9 +380,9 @@ impl<'src> HtmlLexer<'src> { if let Some(keyword_kind) = self.consume_language_identifier(current) { // Check if this keyword is in our stop list let should_stop = match kind { - RestrictedExpressionKind::StopAtAsOrComma => keyword_kind == AS_KW, - RestrictedExpressionKind::StopAtOpeningParenOrComma => false, - RestrictedExpressionKind::StopAtClosingParen => false, + RestrictedExpressionStopAt::AsOrComma => keyword_kind == AS_KW, + RestrictedExpressionStopAt::OpeningParenOrComma => false, + RestrictedExpressionStopAt::ClosingParen => false, }; if should_stop { diff --git a/crates/biome_html_parser/src/syntax/svelte.rs b/crates/biome_html_parser/src/syntax/svelte.rs index cd9ec479b555..e08fe5ad9298 100644 --- a/crates/biome_html_parser/src/syntax/svelte.rs +++ b/crates/biome_html_parser/src/syntax/svelte.rs @@ -3,7 +3,7 @@ use crate::syntax::parse_error::{ expected_child_or_block, expected_svelte_closing_block, expected_text_expression, }; use crate::syntax::{parse_html_element, parse_single_text_expression_content}; -use crate::token_source::{HtmlLexContext, HtmlReLexContext, RestrictedExpressionKind}; +use crate::token_source::{HtmlLexContext, HtmlReLexContext, RestrictedExpressionStopAt}; use biome_html_syntax::HtmlSyntaxKind::{ EOF, HTML_BOGUS_ELEMENT, HTML_ELEMENT_LIST, IDENT, SVELTE_ATTACH_ATTRIBUTE, SVELTE_BINDING_LIST, SVELTE_BOGUS_BLOCK, SVELTE_CONST_BLOCK, SVELTE_DEBUG_BLOCK, @@ -193,7 +193,7 @@ fn parse_each_as_keyed_item(p: &mut HtmlParser) -> ParsedSyntax { // Consume 'as' and switch context for name (stop at comma for optional index) p.bump_with_context( T![as], - HtmlLexContext::restricted_expression(RestrictedExpressionKind::StopAtOpeningParenOrComma), + HtmlLexContext::restricted_expression(RestrictedExpressionStopAt::OpeningParenOrComma), ); // Parse name (required) @@ -227,7 +227,7 @@ fn parse_each_key(p: &mut HtmlParser) -> ParsedSyntax { let m = p.start(); p.bump_with_context( T!['('], - HtmlLexContext::restricted_expression(RestrictedExpressionKind::StopAtClosingParen), + HtmlLexContext::restricted_expression(RestrictedExpressionStopAt::ClosingParen), ); parse_single_text_expression_content(p).or_add_diagnostic(p, |p, range| { @@ -267,7 +267,7 @@ fn parse_each_index(p: &mut HtmlParser) -> ParsedSyntax { let m = p.start(); p.bump_with_context( T![,], - HtmlLexContext::restricted_expression(RestrictedExpressionKind::StopAtOpeningParenOrComma), + HtmlLexContext::restricted_expression(RestrictedExpressionStopAt::OpeningParenOrComma), ); parse_single_text_expression_content(p).or_add_diagnostic(p, |p, range| { p.err_builder("Expected an index binding after ','", range) @@ -299,7 +299,7 @@ fn parse_each_opening_block(p: &mut HtmlParser, parent_marker: Marker) -> (Parse p.bump_with_context( T![each], - HtmlLexContext::restricted_expression(RestrictedExpressionKind::StopAtAsOrComma), + HtmlLexContext::restricted_expression(RestrictedExpressionStopAt::AsOrComma), ); // Flags used to track possible errors so that the final block can be emitted as a bogus node let mut has_errors = false; diff --git a/crates/biome_html_parser/src/token_source.rs b/crates/biome_html_parser/src/token_source.rs index 1b1c0287c230..ac3bb8c5ae99 100644 --- a/crates/biome_html_parser/src/token_source.rs +++ b/crates/biome_html_parser/src/token_source.rs @@ -45,7 +45,7 @@ pub(crate) enum HtmlLexContext { /// - `attr={ foo }` TextExpression(TextExpressionKind), /// Single expression that stops at certain keywords (e.g., 'as' in Svelte each blocks) - RestrictedSingleExpression(RestrictedExpressionKind), + RestrictedSingleExpression(RestrictedExpressionStopAt), /// Enables the `html` keyword token. /// /// When the parser has encounters the sequence `` token is encountered. @@ -68,7 +68,7 @@ impl HtmlLexContext { Self::TextExpression(TextExpressionKind::Double) } - pub fn restricted_expression(kind: RestrictedExpressionKind) -> Self { + pub fn restricted_expression(kind: RestrictedExpressionStopAt) -> Self { Self::RestrictedSingleExpression(kind) } } @@ -83,13 +83,13 @@ pub(crate) enum TextExpressionKind { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum RestrictedExpressionKind { +pub(crate) enum RestrictedExpressionStopAt { /// Stops at 'as' keyword or ',' (for Svelte #each blocks) - StopAtAsOrComma, + AsOrComma, /// Stops at `(` - StopAtOpeningParenOrComma, + OpeningParenOrComma, /// Stops at `)` - StopAtClosingParen, + ClosingParen, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] From b22f665db8517a4366f00728eaee6cee61c61faf Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 18 Dec 2025 11:35:02 +0000 Subject: [PATCH 6/6] suggestion --- crates/biome_html_parser/src/lexer/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_html_parser/src/lexer/mod.rs b/crates/biome_html_parser/src/lexer/mod.rs index 48e072237266..f05401c65b66 100644 --- a/crates/biome_html_parser/src/lexer/mod.rs +++ b/crates/biome_html_parser/src/lexer/mod.rs @@ -405,7 +405,7 @@ impl<'src> HtmlLexer<'src> { if self.position > start_pos { HTML_LITERAL } else { - EOF + ERROR_TOKEN } }