From eda5e007038545e2643ab41d54bb1056e2b2c03f Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 6 Nov 2025 11:21:42 +0000 Subject: [PATCH 1/2] feat(html/svelte): parsing of new blocks --- .changeset/all-tips-hang.md | 12 + .changeset/brown-poets-sit.md | 12 + .changeset/icy-planes-leave.md | 11 + .changeset/light-toys-check.md | 1 + .changeset/mean-carrots-peel.md | 12 + .../src/generated/node_factory.rs | 64 +++ .../src/generated/syntax_factory.rs | 160 ++++++ crates/biome_html_formatter/src/generated.rs | 152 +++++ .../src/html/any/attribute.rs | 1 + .../src/html/lists/attribute_list.rs | 3 + .../src/svelte/any/block.rs | 3 + .../src/svelte/auxiliary/attach_attribute.rs | 25 + .../src/svelte/auxiliary/const_block.rs | 25 + .../src/svelte/auxiliary/html_block.rs | 25 + .../src/svelte/auxiliary/mod.rs | 4 + .../src/svelte/auxiliary/render_block.rs | 25 + .../tests/specs/html/svelte/attach.svelte | 16 + .../specs/html/svelte/attach.svelte.snap | 61 ++ .../tests/specs/html/svelte/const.svelte | 1 + .../tests/specs/html/svelte/const.svelte.snap | 33 ++ .../tests/specs/html/svelte/html.svelte | 5 + .../tests/specs/html/svelte/html.svelte.snap | 39 ++ .../tests/specs/html/svelte/render.svelte | 1 + .../specs/html/svelte/render.svelte.snap | 33 ++ crates/biome_html_parser/src/lexer/mod.rs | 29 +- crates/biome_html_parser/src/lexer/tests.rs | 23 +- crates/biome_html_parser/src/syntax/mod.rs | 64 ++- .../src/syntax/parse_error.rs | 6 +- crates/biome_html_parser/src/syntax/svelte.rs | 82 ++- .../html_specs/error/svelte/attach.svelte | 1 + .../error/svelte/attach.svelte.snap | 91 +++ .../html_specs/error/svelte/const.svelte | 1 + .../html_specs/error/svelte/const.svelte.snap | 69 +++ .../tests/html_specs/error/svelte/html.svelte | 1 + .../html_specs/error/svelte/html.svelte.snap | 69 +++ .../html_specs/error/svelte/render.svelte | 1 + .../error/svelte/render.svelte.snap | 69 +++ .../tests/html_specs/ok/svelte/attach.svelte | 16 + .../html_specs/ok/svelte/attach.svelte.snap | 257 +++++++++ .../tests/html_specs/ok/svelte/const.svelte | 1 + .../html_specs/ok/svelte/const.svelte.snap | 50 ++ .../tests/html_specs/ok/svelte/html.svelte | 5 + .../html_specs/ok/svelte/html.svelte.snap | 121 ++++ .../tests/html_specs/ok/svelte/render.svelte | 1 + .../html_specs/ok/svelte/render.svelte.snap | 50 ++ crates/biome_html_parser/tests/spec_test.rs | 5 +- .../biome_html_syntax/src/generated/kind.rs | 15 +- .../biome_html_syntax/src/generated/macros.rs | 16 + .../biome_html_syntax/src/generated/nodes.rs | 539 +++++++++++++++++- .../src/generated/nodes_mut.rs | 104 ++++ xtask/codegen/html.ungram | 36 ++ xtask/codegen/src/html_kinds_src.rs | 8 +- 52 files changed, 2400 insertions(+), 54 deletions(-) create mode 100644 .changeset/all-tips-hang.md create mode 100644 .changeset/brown-poets-sit.md create mode 100644 .changeset/icy-planes-leave.md create mode 100644 .changeset/mean-carrots-peel.md create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/attach_attribute.rs create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/const_block.rs create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/html_block.rs create mode 100644 crates/biome_html_formatter/src/svelte/auxiliary/render_block.rs create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/const.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/const.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/html.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/html.svelte.snap create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/render.svelte create mode 100644 crates/biome_html_formatter/tests/specs/html/svelte/render.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/attach.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/attach.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/const.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/const.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/html.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/html.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/render.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/error/svelte/render.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/attach.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/attach.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/const.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/const.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/html.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/html.svelte.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/render.svelte create mode 100644 crates/biome_html_parser/tests/html_specs/ok/svelte/render.svelte.snap diff --git a/.changeset/all-tips-hang.md b/.changeset/all-tips-hang.md new file mode 100644 index 000000000000..d2b4ec07d284 --- /dev/null +++ b/.changeset/all-tips-hang.md @@ -0,0 +1,12 @@ +--- +"@biomejs/biome": patch +--- + + +Added support Svelte syntax `{@html}`. Biome now is able to parse and format the Svelte syntax [`{@html}`](https://svelte.dev/docs/svelte/@html): + +```diff +-{@html 'div'} ++{@html 'div'} +``` +The contents of the expressions inside the `{@html }` aren't formatted yet. diff --git a/.changeset/brown-poets-sit.md b/.changeset/brown-poets-sit.md new file mode 100644 index 000000000000..eb8b34f37cfd --- /dev/null +++ b/.changeset/brown-poets-sit.md @@ -0,0 +1,12 @@ +--- +"@biomejs/biome": patch +--- + +Added support Svelte syntax `{@render}`. Biome now is able to parse and format the Svelte syntax [`{@render}`](https://svelte.dev/docs/svelte/@render): + +```diff +-{@render sum(1, 2) } ++{@render sum(1, 2)} +``` + +The contents of the expressions inside the `{@render }` aren't formatted yet. diff --git a/.changeset/icy-planes-leave.md b/.changeset/icy-planes-leave.md new file mode 100644 index 000000000000..f47eda584af9 --- /dev/null +++ b/.changeset/icy-planes-leave.md @@ -0,0 +1,11 @@ +--- +"@biomejs/biome": patch +--- + +Added support Svelte syntax `{@attach}`. Biome now is able to parse and format the Svelte syntax [`{@attach}`](https://svelte.dev/docs/svelte/@attach): + +```diff +-
...
++
...
+``` +The contents of the expressions inside the `{@attach }` aren't formatted yet. diff --git a/.changeset/light-toys-check.md b/.changeset/light-toys-check.md index ffda5b3523e9..b6c54c9ee44f 100644 --- a/.changeset/light-toys-check.md +++ b/.changeset/light-toys-check.md @@ -10,3 +10,4 @@ Added support Svelte syntax `{#key}`. Biome now is able to parse and format the +
+{/key} ``` +The contents of the expressions inside the `{@key }` aren't formatted yet. diff --git a/.changeset/mean-carrots-peel.md b/.changeset/mean-carrots-peel.md new file mode 100644 index 000000000000..ccc005ce77ad --- /dev/null +++ b/.changeset/mean-carrots-peel.md @@ -0,0 +1,12 @@ +--- +"@biomejs/biome": patch +--- + +Added support Svelte syntax `{@const}`. Biome now is able to parse and format the Svelte syntax [`{@const}`](https://svelte.dev/docs/svelte/@const): + +```diff +-{@const name = value} ++{@const name = value} +``` + +The contents of the expressions inside the `{@const }` aren't formatted yet. diff --git a/crates/biome_html_factory/src/generated/node_factory.rs b/crates/biome_html_factory/src/generated/node_factory.rs index be4731c12db6..380ca76961c0 100644 --- a/crates/biome_html_factory/src/generated/node_factory.rs +++ b/crates/biome_html_factory/src/generated/node_factory.rs @@ -347,6 +347,38 @@ pub fn html_text_expression(html_literal_token: SyntaxToken) -> HtmlTextExpressi [Some(SyntaxElement::Token(html_literal_token))], )) } +pub fn svelte_attach_attribute( + sv_curly_at_token: SyntaxToken, + attach_token: SyntaxToken, + expression: HtmlTextExpression, + r_curly_token: SyntaxToken, +) -> SvelteAttachAttribute { + SvelteAttachAttribute::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_ATTACH_ATTRIBUTE, + [ + Some(SyntaxElement::Token(sv_curly_at_token)), + Some(SyntaxElement::Token(attach_token)), + Some(SyntaxElement::Node(expression.into_syntax())), + Some(SyntaxElement::Token(r_curly_token)), + ], + )) +} +pub fn svelte_const_block( + sv_curly_at_token: SyntaxToken, + const_token: SyntaxToken, + expression: HtmlTextExpression, + r_curly_token: SyntaxToken, +) -> SvelteConstBlock { + SvelteConstBlock::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_CONST_BLOCK, + [ + Some(SyntaxElement::Token(sv_curly_at_token)), + Some(SyntaxElement::Token(const_token)), + Some(SyntaxElement::Node(expression.into_syntax())), + Some(SyntaxElement::Token(r_curly_token)), + ], + )) +} pub fn svelte_debug_block( sv_curly_at_token: SyntaxToken, debug_token: SyntaxToken, @@ -363,6 +395,22 @@ pub fn svelte_debug_block( ], )) } +pub fn svelte_html_block( + sv_curly_at_token: SyntaxToken, + html_token: SyntaxToken, + expression: HtmlTextExpression, + r_curly_token: SyntaxToken, +) -> SvelteHtmlBlock { + SvelteHtmlBlock::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_HTML_BLOCK, + [ + Some(SyntaxElement::Token(sv_curly_at_token)), + Some(SyntaxElement::Token(html_token)), + Some(SyntaxElement::Node(expression.into_syntax())), + Some(SyntaxElement::Token(r_curly_token)), + ], + )) +} pub fn svelte_key_block( opening_block: SvelteKeyOpeningBlock, children: HtmlElementList, @@ -413,6 +461,22 @@ pub fn svelte_name(svelte_ident_token: SyntaxToken) -> SvelteName { [Some(SyntaxElement::Token(svelte_ident_token))], )) } +pub fn svelte_render_block( + sv_curly_at_token: SyntaxToken, + render_token: SyntaxToken, + expression: HtmlTextExpression, + r_curly_token: SyntaxToken, +) -> SvelteRenderBlock { + SvelteRenderBlock::unwrap_cast(SyntaxNode::new_detached( + HtmlSyntaxKind::SVELTE_RENDER_BLOCK, + [ + Some(SyntaxElement::Token(sv_curly_at_token)), + Some(SyntaxElement::Token(render_token)), + Some(SyntaxElement::Node(expression.into_syntax())), + Some(SyntaxElement::Token(r_curly_token)), + ], + )) +} pub fn html_attribute_list(items: I) -> HtmlAttributeList where I: IntoIterator, diff --git a/crates/biome_html_factory/src/generated/syntax_factory.rs b/crates/biome_html_factory/src/generated/syntax_factory.rs index de78d2e04a7f..93632b9d45ef 100644 --- a/crates/biome_html_factory/src/generated/syntax_factory.rs +++ b/crates/biome_html_factory/src/generated/syntax_factory.rs @@ -612,6 +612,86 @@ impl SyntaxFactory for HtmlSyntaxFactory { } slots.into_node(HTML_TEXT_EXPRESSION, children) } + SVELTE_ATTACH_ATTRIBUTE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T!["{@"] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T![attach] + { + 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_ATTACH_ATTRIBUTE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_ATTACH_ATTRIBUTE, children) + } + SVELTE_CONST_BLOCK => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T!["{@"] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T![const] + { + 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_CONST_BLOCK.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_CONST_BLOCK, children) + } SVELTE_DEBUG_BLOCK => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); @@ -652,6 +732,46 @@ impl SyntaxFactory for HtmlSyntaxFactory { } slots.into_node(SVELTE_DEBUG_BLOCK, children) } + SVELTE_HTML_BLOCK => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T!["{@"] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T![html] + { + 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_HTML_BLOCK.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_HTML_BLOCK, children) + } SVELTE_KEY_BLOCK => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default(); @@ -777,6 +897,46 @@ impl SyntaxFactory for HtmlSyntaxFactory { } slots.into_node(SVELTE_NAME, children) } + SVELTE_RENDER_BLOCK => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T!["{@"] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T![render] + { + 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_RENDER_BLOCK.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SVELTE_RENDER_BLOCK, children) + } HTML_ATTRIBUTE_LIST => { Self::make_node_list_syntax(kind, children, AnyHtmlAttribute::can_cast) } diff --git a/crates/biome_html_formatter/src/generated.rs b/crates/biome_html_formatter/src/generated.rs index 120639cdc377..e41b9b02bd68 100644 --- a/crates/biome_html_formatter/src/generated.rs +++ b/crates/biome_html_formatter/src/generated.rs @@ -716,6 +716,82 @@ impl IntoFormat for biome_html_syntax::HtmlTextExpression { ) } } +impl FormatRule + for crate::svelte::auxiliary::attach_attribute::FormatSvelteAttachAttribute +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteAttachAttribute, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteAttachAttribute { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteAttachAttribute, + crate::svelte::auxiliary::attach_attribute::FormatSvelteAttachAttribute, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::attach_attribute::FormatSvelteAttachAttribute::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteAttachAttribute { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteAttachAttribute, + crate::svelte::auxiliary::attach_attribute::FormatSvelteAttachAttribute, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::attach_attribute::FormatSvelteAttachAttribute::default(), + ) + } +} +impl FormatRule + for crate::svelte::auxiliary::const_block::FormatSvelteConstBlock +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteConstBlock, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteConstBlock { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteConstBlock, + crate::svelte::auxiliary::const_block::FormatSvelteConstBlock, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::const_block::FormatSvelteConstBlock::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteConstBlock { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteConstBlock, + crate::svelte::auxiliary::const_block::FormatSvelteConstBlock, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::const_block::FormatSvelteConstBlock::default(), + ) + } +} impl FormatRule for crate::svelte::auxiliary::debug_block::FormatSvelteDebugBlock { @@ -754,6 +830,44 @@ impl IntoFormat for biome_html_syntax::SvelteDebugBlock { ) } } +impl FormatRule + for crate::svelte::auxiliary::html_block::FormatSvelteHtmlBlock +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteHtmlBlock, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteHtmlBlock { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteHtmlBlock, + crate::svelte::auxiliary::html_block::FormatSvelteHtmlBlock, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::html_block::FormatSvelteHtmlBlock::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteHtmlBlock { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteHtmlBlock, + crate::svelte::auxiliary::html_block::FormatSvelteHtmlBlock, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::html_block::FormatSvelteHtmlBlock::default(), + ) + } +} impl FormatRule for crate::svelte::auxiliary::key_block::FormatSvelteKeyBlock { @@ -902,6 +1016,44 @@ impl IntoFormat for biome_html_syntax::SvelteName { ) } } +impl FormatRule + for crate::svelte::auxiliary::render_block::FormatSvelteRenderBlock +{ + type Context = HtmlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_html_syntax::SvelteRenderBlock, + f: &mut HtmlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_html_syntax::SvelteRenderBlock { + type Format<'a> = FormatRefWithRule< + 'a, + biome_html_syntax::SvelteRenderBlock, + crate::svelte::auxiliary::render_block::FormatSvelteRenderBlock, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::svelte::auxiliary::render_block::FormatSvelteRenderBlock::default(), + ) + } +} +impl IntoFormat for biome_html_syntax::SvelteRenderBlock { + type Format = FormatOwnedWithRule< + biome_html_syntax::SvelteRenderBlock, + crate::svelte::auxiliary::render_block::FormatSvelteRenderBlock, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::svelte::auxiliary::render_block::FormatSvelteRenderBlock::default(), + ) + } +} impl AsFormat for biome_html_syntax::HtmlAttributeList { type Format<'a> = FormatRefWithRule< 'a, diff --git a/crates/biome_html_formatter/src/html/any/attribute.rs b/crates/biome_html_formatter/src/html/any/attribute.rs index b1c6704f75bf..a5db620cf323 100644 --- a/crates/biome_html_formatter/src/html/any/attribute.rs +++ b/crates/biome_html_formatter/src/html/any/attribute.rs @@ -12,6 +12,7 @@ impl FormatRule for FormatAnyHtmlAttribute { AnyHtmlAttribute::HtmlBogusAttribute(node) => node.format().fmt(f), AnyHtmlAttribute::HtmlDoubleTextExpression(node) => node.format().fmt(f), AnyHtmlAttribute::HtmlSingleTextExpression(node) => node.format().fmt(f), + AnyHtmlAttribute::SvelteAttachAttribute(node) => node.format().fmt(f), } } } diff --git a/crates/biome_html_formatter/src/html/lists/attribute_list.rs b/crates/biome_html_formatter/src/html/lists/attribute_list.rs index 26252d23f692..c8f5cd031db3 100644 --- a/crates/biome_html_formatter/src/html/lists/attribute_list.rs +++ b/crates/biome_html_formatter/src/html/lists/attribute_list.rs @@ -69,6 +69,9 @@ impl FormatRule for FormatHtmlAttributeList { AnyHtmlAttribute::HtmlBogusAttribute(attr) => { attr.format().fmt(f) } + AnyHtmlAttribute::SvelteAttachAttribute(attr) => { + attr.format().fmt(f) + } }) })) .finish()?; diff --git a/crates/biome_html_formatter/src/svelte/any/block.rs b/crates/biome_html_formatter/src/svelte/any/block.rs index 79139b39cad7..719cdf4bf5e4 100644 --- a/crates/biome_html_formatter/src/svelte/any/block.rs +++ b/crates/biome_html_formatter/src/svelte/any/block.rs @@ -9,8 +9,11 @@ impl FormatRule for FormatAnySvelteBlock { fn fmt(&self, node: &AnySvelteBlock, f: &mut HtmlFormatter) -> FormatResult<()> { match node { AnySvelteBlock::SvelteBogusBlock(node) => node.format().fmt(f), + AnySvelteBlock::SvelteConstBlock(node) => node.format().fmt(f), AnySvelteBlock::SvelteDebugBlock(node) => node.format().fmt(f), + AnySvelteBlock::SvelteHtmlBlock(node) => node.format().fmt(f), AnySvelteBlock::SvelteKeyBlock(node) => node.format().fmt(f), + AnySvelteBlock::SvelteRenderBlock(node) => node.format().fmt(f), } } } diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/attach_attribute.rs b/crates/biome_html_formatter/src/svelte/auxiliary/attach_attribute.rs new file mode 100644 index 000000000000..2a04f1cb6182 --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/attach_attribute.rs @@ -0,0 +1,25 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{SvelteAttachAttribute, SvelteAttachAttributeFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteAttachAttribute; +impl FormatNodeRule for FormatSvelteAttachAttribute { + fn fmt_fields(&self, node: &SvelteAttachAttribute, f: &mut HtmlFormatter) -> FormatResult<()> { + let SvelteAttachAttributeFields { + sv_curly_at_token, + expression, + r_curly_token, + attach_token, + } = node.as_fields(); + + write!( + f, + [ + sv_curly_at_token.format(), + attach_token.format(), + 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 new file mode 100644 index 000000000000..df4032c80ac3 --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/const_block.rs @@ -0,0 +1,25 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{SvelteConstBlock, SvelteConstBlockFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteConstBlock; +impl FormatNodeRule for FormatSvelteConstBlock { + fn fmt_fields(&self, node: &SvelteConstBlock, f: &mut HtmlFormatter) -> FormatResult<()> { + let SvelteConstBlockFields { + r_curly_token, + expression, + sv_curly_at_token, + const_token, + } = node.as_fields(); + + write!( + f, + [ + sv_curly_at_token.format(), + const_token.format(), + 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 new file mode 100644 index 000000000000..8936e9ac05d9 --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/html_block.rs @@ -0,0 +1,25 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{SvelteHtmlBlock, SvelteHtmlBlockFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteHtmlBlock; +impl FormatNodeRule for FormatSvelteHtmlBlock { + fn fmt_fields(&self, node: &SvelteHtmlBlock, f: &mut HtmlFormatter) -> FormatResult<()> { + let SvelteHtmlBlockFields { + r_curly_token, + expression, + sv_curly_at_token, + html_token, + } = node.as_fields(); + + write!( + f, + [ + sv_curly_at_token.format(), + html_token.format(), + 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 dca13324601c..4578b146e1c0 100644 --- a/crates/biome_html_formatter/src/svelte/auxiliary/mod.rs +++ b/crates/biome_html_formatter/src/svelte/auxiliary/mod.rs @@ -1,7 +1,11 @@ //! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. +pub(crate) mod attach_attribute; +pub(crate) mod const_block; pub(crate) mod debug_block; +pub(crate) mod html_block; pub(crate) mod key_block; pub(crate) mod key_closing_block; pub(crate) mod key_opening_block; pub(crate) mod name; +pub(crate) mod render_block; diff --git a/crates/biome_html_formatter/src/svelte/auxiliary/render_block.rs b/crates/biome_html_formatter/src/svelte/auxiliary/render_block.rs new file mode 100644 index 000000000000..fe9d0c7beaa3 --- /dev/null +++ b/crates/biome_html_formatter/src/svelte/auxiliary/render_block.rs @@ -0,0 +1,25 @@ +use crate::prelude::*; +use biome_formatter::write; +use biome_html_syntax::{SvelteRenderBlock, SvelteRenderBlockFields}; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatSvelteRenderBlock; +impl FormatNodeRule for FormatSvelteRenderBlock { + fn fmt_fields(&self, node: &SvelteRenderBlock, f: &mut HtmlFormatter) -> FormatResult<()> { + let SvelteRenderBlockFields { + expression, + r_curly_token, + sv_curly_at_token, + render_token, + } = node.as_fields(); + + write!( + f, + [ + sv_curly_at_token.format(), + render_token.format(), + expression.format(), + r_curly_token.format() + ] + ) + } +} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte new file mode 100644 index 000000000000..84ad99c0b09c --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte @@ -0,0 +1,16 @@ +
...
+ + { + const context = canvas.getContext('2d'); + + $effect(() => { + context.fillStyle = color; + context.fillRect(0, 0, canvas.width, canvas.height); + }); + }} +> diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte.snap new file mode 100644 index 000000000000..05711b97d927 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte.snap @@ -0,0 +1,61 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/attach.svelte +--- +# Input + +```svelte +
...
+ + { + const context = canvas.getContext('2d'); + + $effect(() => { + context.fillStyle = color; + context.fillRect(0, 0, canvas.width, canvas.height); + }); + }} +> + +``` + + +============================= + +# 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 +
...
+ + { + const context = canvas.getContext('2d'); + + $effect(() => { + context.fillStyle = color; + context.fillRect(0, 0, canvas.width, canvas.height); + }); + }} +> +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/const.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/const.svelte new file mode 100644 index 000000000000..ff9d53e56fc3 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/const.svelte @@ -0,0 +1 @@ +{@const area = box.width * box.height} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/const.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/const.svelte.snap new file mode 100644 index 000000000000..4771a7a1bcdc --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/const.svelte.snap @@ -0,0 +1,33 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/const.svelte +--- +# Input + +```svelte +{@const area = box.width * box.height} + +``` + + +============================= + +# 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 +{@const area = box.width * box.height} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/html.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/html.svelte new file mode 100644 index 000000000000..ec5da57493c1 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/html.svelte @@ -0,0 +1,5 @@ +
+ {@html content} +
+ +{@html '
'}content{@html '
'} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/html.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/html.svelte.snap new file mode 100644 index 000000000000..9f8450071298 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/html.svelte.snap @@ -0,0 +1,39 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/html.svelte +--- +# Input + +```svelte +
+ {@html content} +
+ +{@html '
'}content{@html '
'} + +``` + + +============================= + +# 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 +
{@html content}
+ +{@html '
'}content{@html '
'} +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/render.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/render.svelte new file mode 100644 index 000000000000..84f5774c7796 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/render.svelte @@ -0,0 +1 @@ +{@render sum(1, 2)} diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/render.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/render.svelte.snap new file mode 100644 index 000000000000..571e953fd65c --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/render.svelte.snap @@ -0,0 +1,33 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/render.svelte +--- +# Input + +```svelte +{@render sum(1, 2)} + +``` + + +============================= + +# 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 +{@render sum(1, 2)} +``` diff --git a/crates/biome_html_parser/src/lexer/mod.rs b/crates/biome_html_parser/src/lexer/mod.rs index 2e3d2eaf523e..926acfb65ea8 100644 --- a/crates/biome_html_parser/src/lexer/mod.rs +++ b/crates/biome_html_parser/src/lexer/mod.rs @@ -2,8 +2,9 @@ mod tests; use crate::token_source::{HtmlEmbeddedLanguage, HtmlLexContext, TextExpressionKind}; use biome_html_syntax::HtmlSyntaxKind::{ - COMMENT, DEBUG_KW, DOCTYPE_KW, EOF, ERROR_TOKEN, HTML_KW, HTML_LITERAL, HTML_STRING_LITERAL, - KEY_KW, NEWLINE, SVELTE_IDENT, TOMBSTONE, UNICODE_BOM, WHITESPACE, + ATTACH_KW, COMMENT, CONST_KW, DEBUG_KW, DOCTYPE_KW, EOF, ERROR_TOKEN, HTML_KW, HTML_LITERAL, + HTML_STRING_LITERAL, KEY_KW, NEWLINE, RENDER_KW, SVELTE_IDENT, TOMBSTONE, UNICODE_BOM, + WHITESPACE, }; use biome_html_syntax::{HtmlSyntaxKind, T, TextLen, TextSize}; use biome_parser::diagnostic::ParseDiagnostic; @@ -422,11 +423,25 @@ impl<'src> HtmlLexer<'src> { match &buffer[..len] { b"doctype" | b"DOCTYPE" if !context.is_svelte() => DOCTYPE_KW, b"html" | b"HTML" if context.is_doctype() => HTML_KW, - buffer if context.is_svelte() => match buffer { - b"debug" if self.current_kind == T!["{@"] => DEBUG_KW, - b"key" if self.current_kind == T!["{#"] || self.current_kind == T!["{/"] => KEY_KW, - _ => SVELTE_IDENT, - }, + buffer if context.is_svelte() => { + if self.current_kind == T!["{@"] { + match buffer { + b"debug" => DEBUG_KW, + b"attach" => ATTACH_KW, + b"const" => CONST_KW, + b"render" => RENDER_KW, + b"html" => HTML_KW, + _ => SVELTE_IDENT, + } + } else if self.current_kind == T!["{#"] || self.current_kind == T!["{/"] { + match buffer { + b"key" => KEY_KW, + _ => SVELTE_IDENT, + } + } else { + SVELTE_IDENT + } + } _ => HTML_LITERAL, } } diff --git a/crates/biome_html_parser/src/lexer/tests.rs b/crates/biome_html_parser/src/lexer/tests.rs index fe9beb1eee3b..ffae14853385 100644 --- a/crates/biome_html_parser/src/lexer/tests.rs +++ b/crates/biome_html_parser/src/lexer/tests.rs @@ -335,34 +335,49 @@ fn cdata_full() { #[test] fn svelte_openings() { assert_lex! { - HtmlLexContext::InsideTag, + HtmlLexContext::Regular, "{@debug}", SV_CURLY_AT: 2, HTML_LITERAL: 6, } assert_lex! { - HtmlLexContext::InsideTag, + HtmlLexContext::Regular, "{/debug}", SV_CURLY_SLASH: 2, HTML_LITERAL: 6, } assert_lex! { - HtmlLexContext::InsideTag, + HtmlLexContext::Regular, "{:debug}", SV_CURLY_COLON: 2, HTML_LITERAL: 6, } assert_lex! { - HtmlLexContext::InsideTag, + HtmlLexContext::Regular, "{#debug}", SV_CURLY_HASH: 2, HTML_LITERAL: 6, } } +#[test] +fn single_text_expression() { + assert_lex!( + HtmlLexContext::single_expression(), + "expression", + HTML_LITERAL: 10, + ); + + assert_lex!( + HtmlLexContext::single_expression(), + "'expression'", + HTML_LITERAL: 12, + ); +} + #[test] fn svelte_keywords() { assert_lex!( diff --git a/crates/biome_html_parser/src/syntax/mod.rs b/crates/biome_html_parser/src/syntax/mod.rs index 4dc7df5bb8e6..31b082af0a06 100644 --- a/crates/biome_html_parser/src/syntax/mod.rs +++ b/crates/biome_html_parser/src/syntax/mod.rs @@ -3,9 +3,12 @@ mod parse_error; mod svelte; use crate::parser::HtmlParser; +use crate::syntax::HtmlSyntaxFeatures::{DoubleTextExpressions, SingleTextExpressions}; use crate::syntax::astro::parse_astro_fence; use crate::syntax::parse_error::*; -use crate::syntax::svelte::{parse_svelte_at_block, parse_svelte_hash_block}; +use crate::syntax::svelte::{ + parse_attach_attribute, parse_svelte_at_block, parse_svelte_hash_block, +}; use crate::token_source::{HtmlEmbeddedLanguage, HtmlLexContext, TextExpressionKind}; use biome_html_syntax::HtmlSyntaxKind::*; use biome_html_syntax::{HtmlSyntaxKind, T}; @@ -309,37 +312,46 @@ fn parse_attribute(p: &mut HtmlParser) -> ParsedSyntax { return Absent; } - let m = p.start(); - if p.at(T!["{{"]) { - HtmlSyntaxFeatures::DoubleTextExpressions - .parse_exclusive_syntax( - p, - |p| parse_double_text_expression(p, HtmlLexContext::InsideTag), - |p, marker| disabled_interpolation(p, marker.range(p)), - ) - .ok(); + match p.cur() { + T!["{{"] => { + let m = p.start(); + DoubleTextExpressions + .parse_exclusive_syntax( + p, + |p| parse_double_text_expression(p, HtmlLexContext::InsideTag), + |p, marker| disabled_interpolation(p, marker.range(p)), + ) + .ok(); - Present(m.complete(p, HTML_ATTRIBUTE)) - } else if p.at(T!['{']) { - m.abandon(p); - HtmlSyntaxFeatures::SingleTextExpressions.parse_exclusive_syntax( + Present(m.complete(p, HTML_ATTRIBUTE)) + } + T!['{'] => SingleTextExpressions.parse_exclusive_syntax( p, |p| parse_single_text_expression(p, HtmlLexContext::InsideTag), |p: &HtmlParser<'_>, m: &CompletedMarker| disabled_svelte_prop(p, m.range(p)), - ) - } else { - parse_literal(p, HTML_ATTRIBUTE_NAME).or_add_diagnostic(p, expected_attribute); - if p.at(T![=]) { - parse_attribute_initializer(p).ok(); - Present(m.complete(p, HTML_ATTRIBUTE)) - } else { - Present(m.complete(p, HTML_ATTRIBUTE)) + ), + T!["{@"] => SingleTextExpressions.parse_exclusive_syntax( + p, + |p| parse_attach_attribute(p), + |p: &HtmlParser<'_>, m: &CompletedMarker| disabled_svelte_prop(p, m.range(p)), + ), + _ => { + let m = p.start(); + + parse_literal(p, HTML_ATTRIBUTE_NAME).or_add_diagnostic(p, expected_attribute); + if p.at(T![=]) { + parse_attribute_initializer(p).ok(); + Present(m.complete(p, HTML_ATTRIBUTE)) + } else { + Present(m.complete(p, HTML_ATTRIBUTE)) + } } } } fn is_at_attribute_start(p: &mut HtmlParser) -> bool { p.at_ts(token_set![HTML_LITERAL, T!["{{"], T!['{']]) + || (SingleTextExpressions.is_supported(p) && p.at(T!["{@"])) } fn parse_literal(p: &mut HtmlParser, kind: HtmlSyntaxKind) -> ParsedSyntax { @@ -473,7 +485,7 @@ fn parse_double_text_expression(p: &mut HtmlParser, context: HtmlLexContext) -> p.expect_with_context(T!["}}"], context); Present(m.complete(p, HTML_DOUBLE_TEXT_EXPRESSION)) } else if p.at(T![<]) { - let diagnostic = expected_text_expression(p, p.cur_range(), opening_range); + let diagnostic = expected_closing_text_expression(p, p.cur_range(), opening_range); p.error(diagnostic); Present(m.complete(p, HTML_BOGUS_TEXT_EXPRESSION)) } else { @@ -483,7 +495,7 @@ fn parse_double_text_expression(p: &mut HtmlParser, context: HtmlLexContext) -> let recovery = ParseRecoveryTokenSet::new(HTML_BOGUS_TEXT_EXPRESSION, RECOVER_TEXT_EXPRESSION_LIST); if let Ok(m) = recovery.enable_recovery_on_line_break().recover(p) { - let diagnostic = expected_text_expression(p, m.range(p), opening_range); + let diagnostic = expected_closing_text_expression(p, m.range(p), opening_range); p.error(diagnostic); Present(m) } else { @@ -520,7 +532,7 @@ pub(crate) fn parse_single_text_expression( p.bump_remap_with_context(T!['}'], context); Present(m.complete(p, HTML_SINGLE_TEXT_EXPRESSION)) } else if p.at(T![<]) { - let diagnostic = expected_text_expression(p, p.cur_range(), opening_range); + let diagnostic = expected_closing_text_expression(p, p.cur_range(), opening_range); p.error(diagnostic); Present(m.complete(p, HTML_BOGUS_TEXT_EXPRESSION)) } else { @@ -529,7 +541,7 @@ pub(crate) fn parse_single_text_expression( let recovery = ParseRecoveryTokenSet::new(HTML_BOGUS_TEXT_EXPRESSION, RECOVER_TEXT_EXPRESSION_LIST); if let Ok(m) = recovery.enable_recovery_on_line_break().recover(p) { - let diagnostic = expected_text_expression(p, m.range(p), opening_range); + let diagnostic = expected_closing_text_expression(p, m.range(p), opening_range); p.error(diagnostic); Present(m) } else { diff --git a/crates/biome_html_parser/src/syntax/parse_error.rs b/crates/biome_html_parser/src/syntax/parse_error.rs index 340d325b9997..4119b5b449f3 100644 --- a/crates/biome_html_parser/src/syntax/parse_error.rs +++ b/crates/biome_html_parser/src/syntax/parse_error.rs @@ -17,7 +17,7 @@ pub(crate) fn disabled_svelte_prop(p: &HtmlParser, range: TextRange) -> ParseDia p.err_builder("This looks like Svelte syntax, but this is not a Svelte file.", range).with_hint(markup!("Remove it or rename this file to have the "".svelte"" file extension.")) } -pub(crate) fn expected_text_expression( +pub(crate) fn expected_closing_text_expression( p: &HtmlParser, curr_range: TextRange, opening_range: TextRange, @@ -32,6 +32,10 @@ pub(crate) fn expected_text_expression( ) } +pub(crate) fn expected_text_expression(p: &HtmlParser, range: TextRange) -> ParseDiagnostic { + expected_node("expression", range, p).into_diagnostic(p) +} + pub(crate) fn expected_child(p: &HtmlParser, range: TextRange) -> ParseDiagnostic { expect_one_of(&["element", "text", "closing block"], range).into_diagnostic(p) } diff --git a/crates/biome_html_parser/src/syntax/svelte.rs b/crates/biome_html_parser/src/syntax/svelte.rs index 62a49fc08e0b..7f86d645000c 100644 --- a/crates/biome_html_parser/src/syntax/svelte.rs +++ b/crates/biome_html_parser/src/syntax/svelte.rs @@ -1,11 +1,14 @@ use crate::parser::HtmlParser; -use crate::syntax::parse_error::{expected_child, expected_svelte_closing_block}; +use crate::syntax::parse_error::{ + expected_child, expected_svelte_closing_block, expected_text_expression, +}; use crate::syntax::{TextExpression, parse_html_element}; use crate::token_source::HtmlLexContext; use biome_html_syntax::HtmlSyntaxKind::{ - EOF, HTML_BOGUS_ELEMENT, HTML_ELEMENT_LIST, SVELTE_BINDING_LIST, SVELTE_BOGUS_BLOCK, - SVELTE_DEBUG_BLOCK, SVELTE_IDENT, SVELTE_KEY_BLOCK, SVELTE_KEY_CLOSING_BLOCK, - SVELTE_KEY_OPENING_BLOCK, SVELTE_NAME, + EOF, HTML_BOGUS_ELEMENT, HTML_ELEMENT_LIST, SVELTE_ATTACH_ATTRIBUTE, SVELTE_BINDING_LIST, + SVELTE_BOGUS_BLOCK, SVELTE_CONST_BLOCK, SVELTE_DEBUG_BLOCK, SVELTE_HTML_BLOCK, SVELTE_IDENT, + 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}; @@ -76,7 +79,7 @@ pub(crate) fn parse_opening_block( ) }); - p.expect_with_context(T!['}'], HtmlLexContext::InsideTag); + p.expect(T!['}']); Present(m.complete(p, node)) } @@ -97,7 +100,7 @@ pub(crate) fn parse_closing_block( p.expect_with_context(keyword, HtmlLexContext::Svelte); - p.expect_with_context(T!['}'], HtmlLexContext::InsideTag); + p.expect(T!['}']); Present(m.complete(p, node)) } @@ -111,6 +114,9 @@ pub(crate) fn parse_svelte_at_block(p: &mut HtmlParser) -> ParsedSyntax { match p.cur() { T![debug] => parse_debug_block(p, m), + T![html] => parse_html_block(p, m), + T![render] => parse_render_block(p, m), + T![const] => parse_const_block(p, m), _ => { m.abandon(p); Absent @@ -125,11 +131,73 @@ pub(crate) fn parse_debug_block(p: &mut HtmlParser, marker: Marker) -> ParsedSyn BindingList.parse_list(p); - p.expect_with_context(T!['}'], HtmlLexContext::InsideTag); + p.expect(T!['}']); Present(marker.complete(p, SVELTE_DEBUG_BLOCK)) } +pub(crate) fn parse_html_block(p: &mut HtmlParser, marker: Marker) -> ParsedSyntax { + if !p.at(T![html]) { + return Absent; + } + p.bump_with_context(T![html], HtmlLexContext::single_expression()); + + TextExpression::new_single() + .parse_element(p) + .or_add_diagnostic(p, expected_text_expression); + + p.expect(T!['}']); + + Present(marker.complete(p, SVELTE_HTML_BLOCK)) +} + +pub(crate) fn parse_render_block(p: &mut HtmlParser, marker: Marker) -> ParsedSyntax { + if !p.at(T![render]) { + return Absent; + } + p.bump_with_context(T![render], HtmlLexContext::single_expression()); + + TextExpression::new_single() + .parse_element(p) + .or_add_diagnostic(p, expected_text_expression); + + p.expect(T!['}']); + + Present(marker.complete(p, SVELTE_RENDER_BLOCK)) +} + +pub(crate) fn parse_attach_attribute(p: &mut HtmlParser) -> ParsedSyntax { + if !p.at(T!["{@"]) { + return Absent; + } + let m = p.start(); + p.bump_with_context(T!["{@"], HtmlLexContext::Svelte); + p.expect_with_context(T![attach], HtmlLexContext::single_expression()); + + TextExpression::new_single() + .parse_element(p) + .or_add_diagnostic(p, expected_text_expression); + + p.expect_with_context(T!['}'], HtmlLexContext::InsideTag); + + Present(m.complete(p, SVELTE_ATTACH_ATTRIBUTE)) +} + +pub(crate) fn parse_const_block(p: &mut HtmlParser, marker: Marker) -> ParsedSyntax { + if !p.at(T![const]) { + return Absent; + } + p.bump_with_context(T![const], HtmlLexContext::single_expression()); + + TextExpression::new_single() + .parse_element(p) + .or_add_diagnostic(p, expected_text_expression); + + p.expect(T!['}']); + + Present(marker.complete(p, SVELTE_CONST_BLOCK)) +} + const BLOCK_RECOVER: TokenSet = token_set!( T!['{'], T![<], diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/attach.svelte b/crates/biome_html_parser/tests/html_specs/error/svelte/attach.svelte new file mode 100644 index 000000000000..6b60c291e4ab --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/attach.svelte @@ -0,0 +1 @@ +
...
diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/attach.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/attach.svelte.snap new file mode 100644 index 000000000000..45357509a0b3 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/attach.svelte.snap @@ -0,0 +1,91 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +
...
+ +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@0..1 "<" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@1..5 "div" [] [Whitespace(" ")], + }, + attributes: HtmlAttributeList [ + SvelteAttachAttribute { + sv_curly_at_token: SV_CURLY_AT@5..7 "{@" [] [], + attach_token: ATTACH_KW@7..13 "attach" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@13..25 " >...\n" [] [], + }, + r_curly_token: missing (required), + }, + ], + r_angle_token: missing (required), + }, + children: HtmlElementList [], + closing_element: missing (required), + }, + ], + eof_token: EOF@25..25 "" [] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..25 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..25 + 0: HTML_ELEMENT@0..25 + 0: HTML_OPENING_ELEMENT@0..25 + 0: L_ANGLE@0..1 "<" [] [] + 1: HTML_TAG_NAME@1..5 + 0: HTML_LITERAL@1..5 "div" [] [Whitespace(" ")] + 2: HTML_ATTRIBUTE_LIST@5..25 + 0: SVELTE_ATTACH_ATTRIBUTE@5..25 + 0: SV_CURLY_AT@5..7 "{@" [] [] + 1: ATTACH_KW@7..13 "attach" [] [] + 2: HTML_TEXT_EXPRESSION@13..25 + 0: HTML_LITERAL@13..25 " >...\n" [] [] + 3: (empty) + 3: (empty) + 1: HTML_ELEMENT_LIST@25..25 + 2: (empty) + 4: EOF@25..25 "" [] [] + +``` + +## Diagnostics + +``` +attach.svelte:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `}` but instead the file ends + + 1 │
...
+ > 2 │ + │ + + i the file ends here + + 1 │
...
+ > 2 │ + │ + +``` diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/const.svelte b/crates/biome_html_parser/tests/html_specs/error/svelte/const.svelte new file mode 100644 index 000000000000..45fbd1fcac30 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/const.svelte @@ -0,0 +1 @@ +{@const area = box.width * box.height diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/const.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/const.svelte.snap new file mode 100644 index 000000000000..ff8576eca6e7 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/const.svelte.snap @@ -0,0 +1,69 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{@const area = box.width * box.height + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteConstBlock { + sv_curly_at_token: SV_CURLY_AT@0..2 "{@" [] [], + const_token: CONST_KW@2..7 "const" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@7..38 " area = box.width * box.height\n" [] [], + }, + r_curly_token: missing (required), + }, + ], + eof_token: EOF@38..38 "" [] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..38 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..38 + 0: SVELTE_CONST_BLOCK@0..38 + 0: SV_CURLY_AT@0..2 "{@" [] [] + 1: CONST_KW@2..7 "const" [] [] + 2: HTML_TEXT_EXPRESSION@7..38 + 0: HTML_LITERAL@7..38 " area = box.width * box.height\n" [] [] + 3: (empty) + 4: EOF@38..38 "" [] [] + +``` + +## Diagnostics + +``` +const.svelte:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `}` but instead the file ends + + 1 │ {@const area = box.width * box.height + > 2 │ + │ + + i the file ends here + + 1 │ {@const area = box.width * box.height + > 2 │ + │ + +``` diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/html.svelte b/crates/biome_html_parser/tests/html_specs/error/svelte/html.svelte new file mode 100644 index 000000000000..37bc42242feb --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/html.svelte @@ -0,0 +1 @@ +{@html '
' diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/html.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/html.svelte.snap new file mode 100644 index 000000000000..0e8d474461a8 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/html.svelte.snap @@ -0,0 +1,69 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{@html '
' + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteHtmlBlock { + sv_curly_at_token: SV_CURLY_AT@0..2 "{@" [] [], + html_token: HTML_KW@2..6 "html" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@6..15 " '
'\n" [] [], + }, + r_curly_token: missing (required), + }, + ], + eof_token: EOF@15..15 "" [] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..15 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..15 + 0: SVELTE_HTML_BLOCK@0..15 + 0: SV_CURLY_AT@0..2 "{@" [] [] + 1: HTML_KW@2..6 "html" [] [] + 2: HTML_TEXT_EXPRESSION@6..15 + 0: HTML_LITERAL@6..15 " '
'\n" [] [] + 3: (empty) + 4: EOF@15..15 "" [] [] + +``` + +## Diagnostics + +``` +html.svelte:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `}` but instead the file ends + + 1 │ {@html '
' + > 2 │ + │ + + i the file ends here + + 1 │ {@html '
' + > 2 │ + │ + +``` diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/render.svelte b/crates/biome_html_parser/tests/html_specs/error/svelte/render.svelte new file mode 100644 index 000000000000..a9cc1f063d22 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/render.svelte @@ -0,0 +1 @@ +{@render sum(1, 2) diff --git a/crates/biome_html_parser/tests/html_specs/error/svelte/render.svelte.snap b/crates/biome_html_parser/tests/html_specs/error/svelte/render.svelte.snap new file mode 100644 index 000000000000..b06a68e0d408 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/error/svelte/render.svelte.snap @@ -0,0 +1,69 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{@render sum(1, 2) + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteRenderBlock { + sv_curly_at_token: SV_CURLY_AT@0..2 "{@" [] [], + render_token: RENDER_KW@2..8 "render" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@8..19 " sum(1, 2)\n" [] [], + }, + r_curly_token: missing (required), + }, + ], + eof_token: EOF@19..19 "" [] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..19 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..19 + 0: SVELTE_RENDER_BLOCK@0..19 + 0: SV_CURLY_AT@0..2 "{@" [] [] + 1: RENDER_KW@2..8 "render" [] [] + 2: HTML_TEXT_EXPRESSION@8..19 + 0: HTML_LITERAL@8..19 " sum(1, 2)\n" [] [] + 3: (empty) + 4: EOF@19..19 "" [] [] + +``` + +## Diagnostics + +``` +render.svelte:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `}` but instead the file ends + + 1 │ {@render sum(1, 2) + > 2 │ + │ + + i the file ends here + + 1 │ {@render sum(1, 2) + > 2 │ + │ + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/attach.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/attach.svelte new file mode 100644 index 000000000000..84ad99c0b09c --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/attach.svelte @@ -0,0 +1,16 @@ +
...
+ + { + const context = canvas.getContext('2d'); + + $effect(() => { + context.fillStyle = color; + context.fillRect(0, 0, canvas.width, canvas.height); + }); + }} +> diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/attach.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/attach.svelte.snap new file mode 100644 index 000000000000..a2f583a36ca7 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/attach.svelte.snap @@ -0,0 +1,257 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +
...
+ + { + const context = canvas.getContext('2d'); + + $effect(() => { + context.fillStyle = color; + context.fillRect(0, 0, canvas.width, canvas.height); + }); + }} +> + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@0..1 "<" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@1..5 "div" [] [Whitespace(" ")], + }, + attributes: HtmlAttributeList [ + SvelteAttachAttribute { + sv_curly_at_token: SV_CURLY_AT@5..7 "{@" [] [], + attach_token: ATTACH_KW@7..13 "attach" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@13..26 " myAttachment" [] [], + }, + r_curly_token: R_CURLY@26..27 "}" [] [], + }, + ], + r_angle_token: R_ANGLE@27..28 ">" [] [], + }, + children: HtmlElementList [ + HtmlContent { + value_token: HTML_LITERAL@28..31 "..." [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@31..32 "<" [] [], + slash_token: SLASH@32..33 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@33..36 "div" [] [], + }, + r_angle_token: R_ANGLE@36..37 ">" [] [], + }, + }, + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@37..39 "<" [Newline("\n")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@39..46 "button" [] [Whitespace(" ")], + }, + attributes: HtmlAttributeList [ + SvelteAttachAttribute { + sv_curly_at_token: SV_CURLY_AT@46..48 "{@" [] [], + attach_token: ATTACH_KW@48..54 "attach" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@54..71 " tooltip(content)" [] [], + }, + r_curly_token: R_CURLY@71..72 "}" [] [], + }, + ], + r_angle_token: R_ANGLE@72..73 ">" [] [], + }, + children: HtmlElementList [ + HtmlContent { + value_token: HTML_LITERAL@73..83 "Hover me" [Newline("\n"), Whitespace("\t")] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@83..85 "<" [Newline("\n")] [], + slash_token: SLASH@85..86 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@86..92 "button" [] [], + }, + r_angle_token: R_ANGLE@92..93 ">" [] [], + }, + }, + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@93..95 "<" [Newline("\n")] [], + name: HtmlTagName { + value_token: HTML_LITERAL@95..101 "canvas" [] [], + }, + attributes: HtmlAttributeList [ + HtmlAttribute { + name: HtmlAttributeName { + value_token: HTML_LITERAL@101..108 "width" [Newline("\n"), Whitespace("\t")] [], + }, + initializer: HtmlAttributeInitializerClause { + eq_token: EQ@108..109 "=" [] [], + value: HtmlSingleTextExpression { + l_curly_token: L_CURLY@109..110 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@110..112 "32" [] [], + }, + r_curly_token: R_CURLY@112..113 "}" [] [], + }, + }, + }, + HtmlAttribute { + name: HtmlAttributeName { + value_token: HTML_LITERAL@113..121 "height" [Newline("\n"), Whitespace("\t")] [], + }, + initializer: HtmlAttributeInitializerClause { + eq_token: EQ@121..122 "=" [] [], + value: HtmlSingleTextExpression { + l_curly_token: L_CURLY@122..123 "{" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@123..125 "32" [] [], + }, + r_curly_token: R_CURLY@125..126 "}" [] [], + }, + }, + }, + SvelteAttachAttribute { + sv_curly_at_token: SV_CURLY_AT@126..130 "{@" [Newline("\n"), Whitespace("\t")] [], + attach_token: ATTACH_KW@130..136 "attach" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@136..307 " (canvas) => {\n\t\tconst context = canvas.getContext('2d');\n\n\t\t$effect(() => {\n\t\t\tcontext.fillStyle = color;\n\t\t\tcontext.fillRect(0, 0, canvas.width, canvas.height);\n\t\t});\n\t}" [] [], + }, + r_curly_token: R_CURLY@307..308 "}" [] [], + }, + ], + r_angle_token: R_ANGLE@308..310 ">" [Newline("\n")] [], + }, + children: HtmlElementList [], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@310..311 "<" [] [], + slash_token: SLASH@311..312 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@312..318 "canvas" [] [], + }, + r_angle_token: R_ANGLE@318..319 ">" [] [], + }, + }, + ], + eof_token: EOF@319..320 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..320 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..319 + 0: HTML_ELEMENT@0..37 + 0: HTML_OPENING_ELEMENT@0..28 + 0: L_ANGLE@0..1 "<" [] [] + 1: HTML_TAG_NAME@1..5 + 0: HTML_LITERAL@1..5 "div" [] [Whitespace(" ")] + 2: HTML_ATTRIBUTE_LIST@5..27 + 0: SVELTE_ATTACH_ATTRIBUTE@5..27 + 0: SV_CURLY_AT@5..7 "{@" [] [] + 1: ATTACH_KW@7..13 "attach" [] [] + 2: HTML_TEXT_EXPRESSION@13..26 + 0: HTML_LITERAL@13..26 " myAttachment" [] [] + 3: R_CURLY@26..27 "}" [] [] + 3: R_ANGLE@27..28 ">" [] [] + 1: HTML_ELEMENT_LIST@28..31 + 0: HTML_CONTENT@28..31 + 0: HTML_LITERAL@28..31 "..." [] [] + 2: HTML_CLOSING_ELEMENT@31..37 + 0: L_ANGLE@31..32 "<" [] [] + 1: SLASH@32..33 "/" [] [] + 2: HTML_TAG_NAME@33..36 + 0: HTML_LITERAL@33..36 "div" [] [] + 3: R_ANGLE@36..37 ">" [] [] + 1: HTML_ELEMENT@37..93 + 0: HTML_OPENING_ELEMENT@37..73 + 0: L_ANGLE@37..39 "<" [Newline("\n")] [] + 1: HTML_TAG_NAME@39..46 + 0: HTML_LITERAL@39..46 "button" [] [Whitespace(" ")] + 2: HTML_ATTRIBUTE_LIST@46..72 + 0: SVELTE_ATTACH_ATTRIBUTE@46..72 + 0: SV_CURLY_AT@46..48 "{@" [] [] + 1: ATTACH_KW@48..54 "attach" [] [] + 2: HTML_TEXT_EXPRESSION@54..71 + 0: HTML_LITERAL@54..71 " tooltip(content)" [] [] + 3: R_CURLY@71..72 "}" [] [] + 3: R_ANGLE@72..73 ">" [] [] + 1: HTML_ELEMENT_LIST@73..83 + 0: HTML_CONTENT@73..83 + 0: HTML_LITERAL@73..83 "Hover me" [Newline("\n"), Whitespace("\t")] [] + 2: HTML_CLOSING_ELEMENT@83..93 + 0: L_ANGLE@83..85 "<" [Newline("\n")] [] + 1: SLASH@85..86 "/" [] [] + 2: HTML_TAG_NAME@86..92 + 0: HTML_LITERAL@86..92 "button" [] [] + 3: R_ANGLE@92..93 ">" [] [] + 2: HTML_ELEMENT@93..319 + 0: HTML_OPENING_ELEMENT@93..310 + 0: L_ANGLE@93..95 "<" [Newline("\n")] [] + 1: HTML_TAG_NAME@95..101 + 0: HTML_LITERAL@95..101 "canvas" [] [] + 2: HTML_ATTRIBUTE_LIST@101..308 + 0: HTML_ATTRIBUTE@101..113 + 0: HTML_ATTRIBUTE_NAME@101..108 + 0: HTML_LITERAL@101..108 "width" [Newline("\n"), Whitespace("\t")] [] + 1: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@108..113 + 0: EQ@108..109 "=" [] [] + 1: HTML_SINGLE_TEXT_EXPRESSION@109..113 + 0: L_CURLY@109..110 "{" [] [] + 1: HTML_TEXT_EXPRESSION@110..112 + 0: HTML_LITERAL@110..112 "32" [] [] + 2: R_CURLY@112..113 "}" [] [] + 1: HTML_ATTRIBUTE@113..126 + 0: HTML_ATTRIBUTE_NAME@113..121 + 0: HTML_LITERAL@113..121 "height" [Newline("\n"), Whitespace("\t")] [] + 1: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@121..126 + 0: EQ@121..122 "=" [] [] + 1: HTML_SINGLE_TEXT_EXPRESSION@122..126 + 0: L_CURLY@122..123 "{" [] [] + 1: HTML_TEXT_EXPRESSION@123..125 + 0: HTML_LITERAL@123..125 "32" [] [] + 2: R_CURLY@125..126 "}" [] [] + 2: SVELTE_ATTACH_ATTRIBUTE@126..308 + 0: SV_CURLY_AT@126..130 "{@" [Newline("\n"), Whitespace("\t")] [] + 1: ATTACH_KW@130..136 "attach" [] [] + 2: HTML_TEXT_EXPRESSION@136..307 + 0: HTML_LITERAL@136..307 " (canvas) => {\n\t\tconst context = canvas.getContext('2d');\n\n\t\t$effect(() => {\n\t\t\tcontext.fillStyle = color;\n\t\t\tcontext.fillRect(0, 0, canvas.width, canvas.height);\n\t\t});\n\t}" [] [] + 3: R_CURLY@307..308 "}" [] [] + 3: R_ANGLE@308..310 ">" [Newline("\n")] [] + 1: HTML_ELEMENT_LIST@310..310 + 2: HTML_CLOSING_ELEMENT@310..319 + 0: L_ANGLE@310..311 "<" [] [] + 1: SLASH@311..312 "/" [] [] + 2: HTML_TAG_NAME@312..318 + 0: HTML_LITERAL@312..318 "canvas" [] [] + 3: R_ANGLE@318..319 ">" [] [] + 4: EOF@319..320 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/const.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/const.svelte new file mode 100644 index 000000000000..ff9d53e56fc3 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/const.svelte @@ -0,0 +1 @@ +{@const area = box.width * box.height} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/const.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/const.svelte.snap new file mode 100644 index 000000000000..33edf4e876aa --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/const.svelte.snap @@ -0,0 +1,50 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{@const area = box.width * box.height} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteConstBlock { + sv_curly_at_token: SV_CURLY_AT@0..2 "{@" [] [], + const_token: CONST_KW@2..7 "const" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@7..37 " area = box.width * box.height" [] [], + }, + r_curly_token: R_CURLY@37..38 "}" [] [], + }, + ], + eof_token: EOF@38..39 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..39 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..38 + 0: SVELTE_CONST_BLOCK@0..38 + 0: SV_CURLY_AT@0..2 "{@" [] [] + 1: CONST_KW@2..7 "const" [] [] + 2: HTML_TEXT_EXPRESSION@7..37 + 0: HTML_LITERAL@7..37 " area = box.width * box.height" [] [] + 3: R_CURLY@37..38 "}" [] [] + 4: EOF@38..39 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/html.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/html.svelte new file mode 100644 index 000000000000..ec5da57493c1 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/html.svelte @@ -0,0 +1,5 @@ +
+ {@html content} +
+ +{@html '
'}content{@html '
'} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/html.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/html.svelte.snap new file mode 100644 index 000000000000..25f1fa9fc323 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/html.svelte.snap @@ -0,0 +1,121 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +
+ {@html content} +
+ +{@html '
'}content{@html '
'} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + HtmlElement { + opening_element: HtmlOpeningElement { + l_angle_token: L_ANGLE@0..1 "<" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@1..8 "article" [] [], + }, + attributes: HtmlAttributeList [], + r_angle_token: R_ANGLE@8..9 ">" [] [], + }, + children: HtmlElementList [ + SvelteHtmlBlock { + sv_curly_at_token: SV_CURLY_AT@9..13 "{@" [Newline("\n"), Whitespace("\t")] [], + html_token: HTML_KW@13..17 "html" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@17..25 " content" [] [], + }, + r_curly_token: R_CURLY@25..26 "}" [] [], + }, + ], + closing_element: HtmlClosingElement { + l_angle_token: L_ANGLE@26..28 "<" [Newline("\n")] [], + slash_token: SLASH@28..29 "/" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@29..36 "article" [] [], + }, + r_angle_token: R_ANGLE@36..37 ">" [] [], + }, + }, + SvelteHtmlBlock { + sv_curly_at_token: SV_CURLY_AT@37..41 "{@" [Newline("\n"), Newline("\n")] [], + html_token: HTML_KW@41..45 "html" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@45..53 " '
'" [] [], + }, + r_curly_token: R_CURLY@53..54 "}" [] [], + }, + HtmlContent { + value_token: HTML_LITERAL@54..61 "content" [] [], + }, + SvelteHtmlBlock { + sv_curly_at_token: SV_CURLY_AT@61..63 "{@" [] [], + html_token: HTML_KW@63..67 "html" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@67..76 " '
'" [] [], + }, + r_curly_token: R_CURLY@76..77 "}" [] [], + }, + ], + eof_token: EOF@77..78 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..78 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..77 + 0: HTML_ELEMENT@0..37 + 0: HTML_OPENING_ELEMENT@0..9 + 0: L_ANGLE@0..1 "<" [] [] + 1: HTML_TAG_NAME@1..8 + 0: HTML_LITERAL@1..8 "article" [] [] + 2: HTML_ATTRIBUTE_LIST@8..8 + 3: R_ANGLE@8..9 ">" [] [] + 1: HTML_ELEMENT_LIST@9..26 + 0: SVELTE_HTML_BLOCK@9..26 + 0: SV_CURLY_AT@9..13 "{@" [Newline("\n"), Whitespace("\t")] [] + 1: HTML_KW@13..17 "html" [] [] + 2: HTML_TEXT_EXPRESSION@17..25 + 0: HTML_LITERAL@17..25 " content" [] [] + 3: R_CURLY@25..26 "}" [] [] + 2: HTML_CLOSING_ELEMENT@26..37 + 0: L_ANGLE@26..28 "<" [Newline("\n")] [] + 1: SLASH@28..29 "/" [] [] + 2: HTML_TAG_NAME@29..36 + 0: HTML_LITERAL@29..36 "article" [] [] + 3: R_ANGLE@36..37 ">" [] [] + 1: SVELTE_HTML_BLOCK@37..54 + 0: SV_CURLY_AT@37..41 "{@" [Newline("\n"), Newline("\n")] [] + 1: HTML_KW@41..45 "html" [] [] + 2: HTML_TEXT_EXPRESSION@45..53 + 0: HTML_LITERAL@45..53 " '
'" [] [] + 3: R_CURLY@53..54 "}" [] [] + 2: HTML_CONTENT@54..61 + 0: HTML_LITERAL@54..61 "content" [] [] + 3: SVELTE_HTML_BLOCK@61..77 + 0: SV_CURLY_AT@61..63 "{@" [] [] + 1: HTML_KW@63..67 "html" [] [] + 2: HTML_TEXT_EXPRESSION@67..76 + 0: HTML_LITERAL@67..76 " '
'" [] [] + 3: R_CURLY@76..77 "}" [] [] + 4: EOF@77..78 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/render.svelte b/crates/biome_html_parser/tests/html_specs/ok/svelte/render.svelte new file mode 100644 index 000000000000..84f5774c7796 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/render.svelte @@ -0,0 +1 @@ +{@render sum(1, 2)} diff --git a/crates/biome_html_parser/tests/html_specs/ok/svelte/render.svelte.snap b/crates/biome_html_parser/tests/html_specs/ok/svelte/render.svelte.snap new file mode 100644 index 000000000000..8bb6eb7e4969 --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/svelte/render.svelte.snap @@ -0,0 +1,50 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```svelte +{@render sum(1, 2)} + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + SvelteRenderBlock { + sv_curly_at_token: SV_CURLY_AT@0..2 "{@" [] [], + render_token: RENDER_KW@2..8 "render" [] [], + expression: HtmlTextExpression { + html_literal_token: HTML_LITERAL@8..18 " sum(1, 2)" [] [], + }, + r_curly_token: R_CURLY@18..19 "}" [] [], + }, + ], + eof_token: EOF@19..20 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..20 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..19 + 0: SVELTE_RENDER_BLOCK@0..19 + 0: SV_CURLY_AT@0..2 "{@" [] [] + 1: RENDER_KW@2..8 "render" [] [] + 2: HTML_TEXT_EXPRESSION@8..18 + 0: HTML_LITERAL@8..18 " sum(1, 2)" [] [] + 3: R_CURLY@18..19 "}" [] [] + 4: EOF@19..20 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_html_parser/tests/spec_test.rs b/crates/biome_html_parser/tests/spec_test.rs index 25f34bcb6f06..9e6fa4394c06 100644 --- a/crates/biome_html_parser/tests/spec_test.rs +++ b/crates/biome_html_parser/tests/spec_test.rs @@ -142,10 +142,7 @@ pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_ #[ignore] #[test] pub fn quick_test() { - let code = r#" -{#key expression} -
-{/key} + let code = r#"{@html '
'}content{@html '
'} "#; diff --git a/crates/biome_html_syntax/src/generated/kind.rs b/crates/biome_html_syntax/src/generated/kind.rs index efd18f796df1..141061e9328d 100644 --- a/crates/biome_html_syntax/src/generated/kind.rs +++ b/crates/biome_html_syntax/src/generated/kind.rs @@ -37,6 +37,9 @@ pub enum HtmlSyntaxKind { HTML_KW, DEBUG_KW, KEY_KW, + RENDER_KW, + CONST_KW, + ATTACH_KW, HTML_STRING_LITERAL, HTML_LITERAL, ERROR_TOKEN, @@ -74,6 +77,10 @@ pub enum HtmlSyntaxKind { SVELTE_KEY_CLOSING_BLOCK, SVELTE_BINDING_LIST, SVELTE_NAME, + SVELTE_RENDER_BLOCK, + SVELTE_ATTACH_ATTRIBUTE, + SVELTE_HTML_BLOCK, + SVELTE_CONST_BLOCK, HTML_BOGUS, HTML_BOGUS_ELEMENT, HTML_BOGUS_ATTRIBUTE, @@ -126,6 +133,9 @@ impl HtmlSyntaxKind { "html" => HTML_KW, "debug" => DEBUG_KW, "key" => KEY_KW, + "render" => RENDER_KW, + "const" => CONST_KW, + "attach" => ATTACH_KW, _ => return None, }; Some(kw) @@ -157,6 +167,9 @@ impl HtmlSyntaxKind { HTML_KW => "html", DEBUG_KW => "debug", KEY_KW => "key", + RENDER_KW => "render", + CONST_KW => "const", + ATTACH_KW => "attach", EOF => "EOF", HTML_STRING_LITERAL => "string literal", _ => return None, @@ -166,4 +179,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 } ; [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 } ; [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 } ; [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 } ; [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 910a70e3a2bf..7bae2a0020b3 100644 --- a/crates/biome_html_syntax/src/generated/macros.rs +++ b/crates/biome_html_syntax/src/generated/macros.rs @@ -93,10 +93,22 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::HtmlTextExpression::new_unchecked(node) }; $body } + $crate::HtmlSyntaxKind::SVELTE_ATTACH_ATTRIBUTE => { + let $pattern = unsafe { $crate::SvelteAttachAttribute::new_unchecked(node) }; + $body + } + $crate::HtmlSyntaxKind::SVELTE_CONST_BLOCK => { + let $pattern = unsafe { $crate::SvelteConstBlock::new_unchecked(node) }; + $body + } $crate::HtmlSyntaxKind::SVELTE_DEBUG_BLOCK => { let $pattern = unsafe { $crate::SvelteDebugBlock::new_unchecked(node) }; $body } + $crate::HtmlSyntaxKind::SVELTE_HTML_BLOCK => { + let $pattern = unsafe { $crate::SvelteHtmlBlock::new_unchecked(node) }; + $body + } $crate::HtmlSyntaxKind::SVELTE_KEY_BLOCK => { let $pattern = unsafe { $crate::SvelteKeyBlock::new_unchecked(node) }; $body @@ -113,6 +125,10 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::SvelteName::new_unchecked(node) }; $body } + $crate::HtmlSyntaxKind::SVELTE_RENDER_BLOCK => { + let $pattern = unsafe { $crate::SvelteRenderBlock::new_unchecked(node) }; + $body + } $crate::HtmlSyntaxKind::ASTRO_BOGUS_FRONTMATTER => { let $pattern = unsafe { $crate::AstroBogusFrontmatter::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 2a31f24a4e39..665eb0f8c2d5 100644 --- a/crates/biome_html_syntax/src/generated/nodes.rs +++ b/crates/biome_html_syntax/src/generated/nodes.rs @@ -850,6 +850,106 @@ pub struct HtmlTextExpressionFields { pub html_literal_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteAttachAttribute { + pub(crate) syntax: SyntaxNode, +} +impl SvelteAttachAttribute { + #[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) -> SvelteAttachAttributeFields { + SvelteAttachAttributeFields { + sv_curly_at_token: self.sv_curly_at_token(), + attach_token: self.attach_token(), + expression: self.expression(), + r_curly_token: self.r_curly_token(), + } + } + pub fn sv_curly_at_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn attach_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } + pub fn expression(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } + pub fn r_curly_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } +} +impl Serialize for SvelteAttachAttribute { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteAttachAttributeFields { + pub sv_curly_at_token: SyntaxResult, + pub attach_token: SyntaxResult, + pub expression: SyntaxResult, + pub r_curly_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteConstBlock { + pub(crate) syntax: SyntaxNode, +} +impl SvelteConstBlock { + #[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) -> SvelteConstBlockFields { + SvelteConstBlockFields { + sv_curly_at_token: self.sv_curly_at_token(), + const_token: self.const_token(), + expression: self.expression(), + r_curly_token: self.r_curly_token(), + } + } + pub fn sv_curly_at_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn const_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } + pub fn expression(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } + pub fn r_curly_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } +} +impl Serialize for SvelteConstBlock { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteConstBlockFields { + pub sv_curly_at_token: SyntaxResult, + pub const_token: SyntaxResult, + pub expression: SyntaxResult, + pub r_curly_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct SvelteDebugBlock { pub(crate) syntax: SyntaxNode, } @@ -900,6 +1000,56 @@ pub struct SvelteDebugBlockFields { pub r_curly_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteHtmlBlock { + pub(crate) syntax: SyntaxNode, +} +impl SvelteHtmlBlock { + #[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) -> SvelteHtmlBlockFields { + SvelteHtmlBlockFields { + sv_curly_at_token: self.sv_curly_at_token(), + html_token: self.html_token(), + expression: self.expression(), + r_curly_token: self.r_curly_token(), + } + } + pub fn sv_curly_at_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn html_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } + pub fn expression(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } + pub fn r_curly_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } +} +impl Serialize for SvelteHtmlBlock { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteHtmlBlockFields { + pub sv_curly_at_token: SyntaxResult, + pub html_token: SyntaxResult, + pub expression: SyntaxResult, + pub r_curly_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct SvelteKeyBlock { pub(crate) syntax: SyntaxNode, } @@ -1074,6 +1224,56 @@ impl Serialize for SvelteName { pub struct SvelteNameFields { pub svelte_ident_token: SyntaxResult, } +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct SvelteRenderBlock { + pub(crate) syntax: SyntaxNode, +} +impl SvelteRenderBlock { + #[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) -> SvelteRenderBlockFields { + SvelteRenderBlockFields { + sv_curly_at_token: self.sv_curly_at_token(), + render_token: self.render_token(), + expression: self.expression(), + r_curly_token: self.r_curly_token(), + } + } + pub fn sv_curly_at_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn render_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } + pub fn expression(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } + pub fn r_curly_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } +} +impl Serialize for SvelteRenderBlock { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct SvelteRenderBlockFields { + pub sv_curly_at_token: SyntaxResult, + pub render_token: SyntaxResult, + pub expression: SyntaxResult, + pub r_curly_token: SyntaxResult, +} #[derive(Clone, PartialEq, Eq, Hash, Serialize)] pub enum AnyAstroFrontmatterElement { AstroBogusFrontmatter(AstroBogusFrontmatter), @@ -1099,6 +1299,7 @@ pub enum AnyHtmlAttribute { HtmlBogusAttribute(HtmlBogusAttribute), HtmlDoubleTextExpression(HtmlDoubleTextExpression), HtmlSingleTextExpression(HtmlSingleTextExpression), + SvelteAttachAttribute(SvelteAttachAttribute), } impl AnyHtmlAttribute { pub fn as_html_attribute(&self) -> Option<&HtmlAttribute> { @@ -1125,6 +1326,12 @@ impl AnyHtmlAttribute { _ => None, } } + pub fn as_svelte_attach_attribute(&self) -> Option<&SvelteAttachAttribute> { + match &self { + Self::SvelteAttachAttribute(item) => Some(item), + _ => None, + } + } } #[derive(Clone, PartialEq, Eq, Hash, Serialize)] pub enum AnyHtmlAttributeInitializer { @@ -1247,8 +1454,11 @@ impl AnyHtmlTextExpression { #[derive(Clone, PartialEq, Eq, Hash, Serialize)] pub enum AnySvelteBlock { SvelteBogusBlock(SvelteBogusBlock), + SvelteConstBlock(SvelteConstBlock), SvelteDebugBlock(SvelteDebugBlock), + SvelteHtmlBlock(SvelteHtmlBlock), SvelteKeyBlock(SvelteKeyBlock), + SvelteRenderBlock(SvelteRenderBlock), } impl AnySvelteBlock { pub fn as_svelte_bogus_block(&self) -> Option<&SvelteBogusBlock> { @@ -1257,18 +1467,36 @@ impl AnySvelteBlock { _ => None, } } + pub fn as_svelte_const_block(&self) -> Option<&SvelteConstBlock> { + match &self { + Self::SvelteConstBlock(item) => Some(item), + _ => None, + } + } pub fn as_svelte_debug_block(&self) -> Option<&SvelteDebugBlock> { match &self { Self::SvelteDebugBlock(item) => Some(item), _ => None, } } + pub fn as_svelte_html_block(&self) -> Option<&SvelteHtmlBlock> { + match &self { + Self::SvelteHtmlBlock(item) => Some(item), + _ => None, + } + } pub fn as_svelte_key_block(&self) -> Option<&SvelteKeyBlock> { match &self { Self::SvelteKeyBlock(item) => Some(item), _ => None, } } + pub fn as_svelte_render_block(&self) -> Option<&SvelteRenderBlock> { + match &self { + Self::SvelteRenderBlock(item) => Some(item), + _ => None, + } + } } impl AstNode for AstroEmbeddedContent { type Language = Language; @@ -2307,6 +2535,124 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for SvelteAttachAttribute { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_ATTACH_ATTRIBUTE as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_ATTACH_ATTRIBUTE + } + 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 SvelteAttachAttribute { + 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("SvelteAttachAttribute") + .field( + "sv_curly_at_token", + &support::DebugSyntaxResult(self.sv_curly_at_token()), + ) + .field( + "attach_token", + &support::DebugSyntaxResult(self.attach_token()), + ) + .field("expression", &support::DebugSyntaxResult(self.expression())) + .field( + "r_curly_token", + &support::DebugSyntaxResult(self.r_curly_token()), + ) + .finish() + } else { + f.debug_struct("SvelteAttachAttribute").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteAttachAttribute) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteAttachAttribute) -> Self { + n.syntax.into() + } +} +impl AstNode for SvelteConstBlock { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_CONST_BLOCK as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_CONST_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 SvelteConstBlock { + 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("SvelteConstBlock") + .field( + "sv_curly_at_token", + &support::DebugSyntaxResult(self.sv_curly_at_token()), + ) + .field( + "const_token", + &support::DebugSyntaxResult(self.const_token()), + ) + .field("expression", &support::DebugSyntaxResult(self.expression())) + .field( + "r_curly_token", + &support::DebugSyntaxResult(self.r_curly_token()), + ) + .finish() + } else { + f.debug_struct("SvelteConstBlock").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteConstBlock) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteConstBlock) -> Self { + n.syntax.into() + } +} impl AstNode for SvelteDebugBlock { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -2366,6 +2712,62 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for SvelteHtmlBlock { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_HTML_BLOCK as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_HTML_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 SvelteHtmlBlock { + 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("SvelteHtmlBlock") + .field( + "sv_curly_at_token", + &support::DebugSyntaxResult(self.sv_curly_at_token()), + ) + .field("html_token", &support::DebugSyntaxResult(self.html_token())) + .field("expression", &support::DebugSyntaxResult(self.expression())) + .field( + "r_curly_token", + &support::DebugSyntaxResult(self.r_curly_token()), + ) + .finish() + } else { + f.debug_struct("SvelteHtmlBlock").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteHtmlBlock) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteHtmlBlock) -> Self { + n.syntax.into() + } +} impl AstNode for SvelteKeyBlock { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -2582,6 +2984,65 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for SvelteRenderBlock { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SVELTE_RENDER_BLOCK as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SVELTE_RENDER_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 SvelteRenderBlock { + 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("SvelteRenderBlock") + .field( + "sv_curly_at_token", + &support::DebugSyntaxResult(self.sv_curly_at_token()), + ) + .field( + "render_token", + &support::DebugSyntaxResult(self.render_token()), + ) + .field("expression", &support::DebugSyntaxResult(self.expression())) + .field( + "r_curly_token", + &support::DebugSyntaxResult(self.r_curly_token()), + ) + .finish() + } else { + f.debug_struct("SvelteRenderBlock").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: SvelteRenderBlock) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: SvelteRenderBlock) -> Self { + n.syntax.into() + } +} impl From for AnyAstroFrontmatterElement { fn from(node: AstroBogusFrontmatter) -> Self { Self::AstroBogusFrontmatter(node) @@ -2666,12 +3127,18 @@ impl From for AnyHtmlAttribute { Self::HtmlSingleTextExpression(node) } } +impl From for AnyHtmlAttribute { + fn from(node: SvelteAttachAttribute) -> Self { + Self::SvelteAttachAttribute(node) + } +} impl AstNode for AnyHtmlAttribute { type Language = Language; const KIND_SET: SyntaxKindSet = HtmlAttribute::KIND_SET .union(HtmlBogusAttribute::KIND_SET) .union(HtmlDoubleTextExpression::KIND_SET) - .union(HtmlSingleTextExpression::KIND_SET); + .union(HtmlSingleTextExpression::KIND_SET) + .union(SvelteAttachAttribute::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { matches!( kind, @@ -2679,6 +3146,7 @@ impl AstNode for AnyHtmlAttribute { | HTML_BOGUS_ATTRIBUTE | HTML_DOUBLE_TEXT_EXPRESSION | HTML_SINGLE_TEXT_EXPRESSION + | SVELTE_ATTACH_ATTRIBUTE ) } fn cast(syntax: SyntaxNode) -> Option { @@ -2691,6 +3159,9 @@ impl AstNode for AnyHtmlAttribute { HTML_SINGLE_TEXT_EXPRESSION => { Self::HtmlSingleTextExpression(HtmlSingleTextExpression { syntax }) } + SVELTE_ATTACH_ATTRIBUTE => { + Self::SvelteAttachAttribute(SvelteAttachAttribute { syntax }) + } _ => return None, }; Some(res) @@ -2701,6 +3172,7 @@ impl AstNode for AnyHtmlAttribute { Self::HtmlBogusAttribute(it) => &it.syntax, Self::HtmlDoubleTextExpression(it) => &it.syntax, Self::HtmlSingleTextExpression(it) => &it.syntax, + Self::SvelteAttachAttribute(it) => &it.syntax, } } fn into_syntax(self) -> SyntaxNode { @@ -2709,6 +3181,7 @@ impl AstNode for AnyHtmlAttribute { Self::HtmlBogusAttribute(it) => it.syntax, Self::HtmlDoubleTextExpression(it) => it.syntax, Self::HtmlSingleTextExpression(it) => it.syntax, + Self::SvelteAttachAttribute(it) => it.syntax, } } } @@ -2719,6 +3192,7 @@ impl std::fmt::Debug for AnyHtmlAttribute { Self::HtmlBogusAttribute(it) => std::fmt::Debug::fmt(it, f), Self::HtmlDoubleTextExpression(it) => std::fmt::Debug::fmt(it, f), Self::HtmlSingleTextExpression(it) => std::fmt::Debug::fmt(it, f), + Self::SvelteAttachAttribute(it) => std::fmt::Debug::fmt(it, f), } } } @@ -2729,6 +3203,7 @@ impl From for SyntaxNode { AnyHtmlAttribute::HtmlBogusAttribute(it) => it.into(), AnyHtmlAttribute::HtmlDoubleTextExpression(it) => it.into(), AnyHtmlAttribute::HtmlSingleTextExpression(it) => it.into(), + AnyHtmlAttribute::SvelteAttachAttribute(it) => it.into(), } } } @@ -3072,32 +3547,58 @@ impl From for AnySvelteBlock { Self::SvelteBogusBlock(node) } } +impl From for AnySvelteBlock { + fn from(node: SvelteConstBlock) -> Self { + Self::SvelteConstBlock(node) + } +} impl From for AnySvelteBlock { fn from(node: SvelteDebugBlock) -> Self { Self::SvelteDebugBlock(node) } } +impl From for AnySvelteBlock { + fn from(node: SvelteHtmlBlock) -> Self { + Self::SvelteHtmlBlock(node) + } +} impl From for AnySvelteBlock { fn from(node: SvelteKeyBlock) -> Self { Self::SvelteKeyBlock(node) } } +impl From for AnySvelteBlock { + fn from(node: SvelteRenderBlock) -> Self { + Self::SvelteRenderBlock(node) + } +} impl AstNode for AnySvelteBlock { type Language = Language; const KIND_SET: SyntaxKindSet = SvelteBogusBlock::KIND_SET + .union(SvelteConstBlock::KIND_SET) .union(SvelteDebugBlock::KIND_SET) - .union(SvelteKeyBlock::KIND_SET); + .union(SvelteHtmlBlock::KIND_SET) + .union(SvelteKeyBlock::KIND_SET) + .union(SvelteRenderBlock::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { matches!( kind, - SVELTE_BOGUS_BLOCK | SVELTE_DEBUG_BLOCK | SVELTE_KEY_BLOCK + SVELTE_BOGUS_BLOCK + | SVELTE_CONST_BLOCK + | SVELTE_DEBUG_BLOCK + | SVELTE_HTML_BLOCK + | SVELTE_KEY_BLOCK + | SVELTE_RENDER_BLOCK ) } fn cast(syntax: SyntaxNode) -> Option { let res = match syntax.kind() { SVELTE_BOGUS_BLOCK => Self::SvelteBogusBlock(SvelteBogusBlock { syntax }), + SVELTE_CONST_BLOCK => Self::SvelteConstBlock(SvelteConstBlock { syntax }), SVELTE_DEBUG_BLOCK => Self::SvelteDebugBlock(SvelteDebugBlock { syntax }), + SVELTE_HTML_BLOCK => Self::SvelteHtmlBlock(SvelteHtmlBlock { syntax }), SVELTE_KEY_BLOCK => Self::SvelteKeyBlock(SvelteKeyBlock { syntax }), + SVELTE_RENDER_BLOCK => Self::SvelteRenderBlock(SvelteRenderBlock { syntax }), _ => return None, }; Some(res) @@ -3105,15 +3606,21 @@ impl AstNode for AnySvelteBlock { fn syntax(&self) -> &SyntaxNode { match self { Self::SvelteBogusBlock(it) => &it.syntax, + Self::SvelteConstBlock(it) => &it.syntax, Self::SvelteDebugBlock(it) => &it.syntax, + Self::SvelteHtmlBlock(it) => &it.syntax, Self::SvelteKeyBlock(it) => &it.syntax, + Self::SvelteRenderBlock(it) => &it.syntax, } } fn into_syntax(self) -> SyntaxNode { match self { Self::SvelteBogusBlock(it) => it.syntax, + Self::SvelteConstBlock(it) => it.syntax, Self::SvelteDebugBlock(it) => it.syntax, + Self::SvelteHtmlBlock(it) => it.syntax, Self::SvelteKeyBlock(it) => it.syntax, + Self::SvelteRenderBlock(it) => it.syntax, } } } @@ -3121,8 +3628,11 @@ impl std::fmt::Debug for AnySvelteBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { 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::SvelteHtmlBlock(it) => std::fmt::Debug::fmt(it, f), Self::SvelteKeyBlock(it) => std::fmt::Debug::fmt(it, f), + Self::SvelteRenderBlock(it) => std::fmt::Debug::fmt(it, f), } } } @@ -3130,8 +3640,11 @@ impl From for SyntaxNode { fn from(n: AnySvelteBlock) -> Self { match n { AnySvelteBlock::SvelteBogusBlock(it) => it.into(), + AnySvelteBlock::SvelteConstBlock(it) => it.into(), AnySvelteBlock::SvelteDebugBlock(it) => it.into(), + AnySvelteBlock::SvelteHtmlBlock(it) => it.into(), AnySvelteBlock::SvelteKeyBlock(it) => it.into(), + AnySvelteBlock::SvelteRenderBlock(it) => it.into(), } } } @@ -3271,11 +3784,26 @@ impl std::fmt::Display for HtmlTextExpression { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for SvelteAttachAttribute { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for SvelteConstBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for SvelteDebugBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for SvelteHtmlBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for SvelteKeyBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -3296,6 +3824,11 @@ impl std::fmt::Display for SvelteName { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for SvelteRenderBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} #[derive(Clone, PartialEq, Eq, Hash, Serialize)] pub struct AstroBogusFrontmatter { syntax: SyntaxNode, diff --git a/crates/biome_html_syntax/src/generated/nodes_mut.rs b/crates/biome_html_syntax/src/generated/nodes_mut.rs index 5836c7ab5280..3dd06f6d137f 100644 --- a/crates/biome_html_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_html_syntax/src/generated/nodes_mut.rs @@ -353,6 +353,58 @@ impl HtmlTextExpression { ) } } +impl SvelteAttachAttribute { + pub fn with_sv_curly_at_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_attach_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_expression(self, element: HtmlTextExpression) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_curly_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } +} +impl SvelteConstBlock { + pub fn with_sv_curly_at_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_const_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_expression(self, element: HtmlTextExpression) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_curly_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } +} impl SvelteDebugBlock { pub fn with_sv_curly_at_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( @@ -379,6 +431,32 @@ impl SvelteDebugBlock { ) } } +impl SvelteHtmlBlock { + pub fn with_sv_curly_at_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_html_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_expression(self, element: HtmlTextExpression) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_curly_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } +} impl SvelteKeyBlock { pub fn with_opening_block(self, element: SvelteKeyOpeningBlock) -> Self { Self::unwrap_cast( @@ -453,3 +531,29 @@ impl SvelteName { ) } } +impl SvelteRenderBlock { + pub fn with_sv_curly_at_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_render_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_expression(self, element: HtmlTextExpression) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_curly_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } +} diff --git a/xtask/codegen/html.ungram b/xtask/codegen/html.ungram index fe5c7964f999..7b194f0c9331 100644 --- a/xtask/codegen/html.ungram +++ b/xtask/codegen/html.ungram @@ -162,6 +162,7 @@ AnyHtmlAttribute = HtmlAttribute | HtmlDoubleTextExpression | HtmlSingleTextExpression + | SvelteAttachAttribute | HtmlBogusAttribute // @@ -192,6 +193,9 @@ AnySvelteBlock = SvelteDebugBlock | SvelteKeyBlock | SvelteBogusBlock + | SvelteRenderBlock + | SvelteHtmlBlock + | SvelteConstBlock // {@debug} // ^^^^^^^^ @@ -228,6 +232,38 @@ SvelteBindingList = (SvelteName (',' SvelteName)*) SvelteName = 'svelte_ident' +// {@render ...} +// ^^^^^^^^^^^^^ +SvelteRenderBlock = + '{@' + 'render' + expression: HtmlTextExpression + '}' + + // {@attach ...} + // ^^^^^^^^^^^^^ +SvelteAttachAttribute = + '{@' + 'attach' + expression: HtmlTextExpression + '}' + +// {@html ...} +// ^^^^^^^^^^^ +SvelteHtmlBlock = + '{@' + 'html' + expression: HtmlTextExpression + '}' + +// {@const ...} +// ^^^^^^^^^^^ +SvelteConstBlock = + '{@' + 'const' + expression: HtmlTextExpression + '}' + HtmlString = value: 'html_string_literal' HtmlTagName = value: 'html_literal' HtmlAttributeName = value: 'html_literal' diff --git a/xtask/codegen/src/html_kinds_src.rs b/xtask/codegen/src/html_kinds_src.rs index f8938602608f..d3825a5e5d79 100644 --- a/xtask/codegen/src/html_kinds_src.rs +++ b/xtask/codegen/src/html_kinds_src.rs @@ -21,7 +21,9 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { ("{:", "SV_CURLY_COLON"), (",", "COMMA"), ], - keywords: &["null", "true", "false", "doctype", "html", "debug", "key"], + keywords: &[ + "null", "true", "false", "doctype", "html", "debug", "key", "render", "const", "attach", + ], literals: &["HTML_STRING_LITERAL", "HTML_LITERAL"], tokens: &[ "ERROR_TOKEN", @@ -63,6 +65,10 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { "SVELTE_KEY_CLOSING_BLOCK", "SVELTE_BINDING_LIST", "SVELTE_NAME", + "SVELTE_RENDER_BLOCK", + "SVELTE_ATTACH_ATTRIBUTE", + "SVELTE_HTML_BLOCK", + "SVELTE_CONST_BLOCK", // Bogus nodes "HTML_BOGUS", "HTML_BOGUS_ELEMENT", From 0e554fc8a967280f45a19dda6b113f6a72e390d9 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 6 Nov 2025 11:30:45 +0000 Subject: [PATCH 2/2] Update .changeset/light-toys-check.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .changeset/light-toys-check.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/light-toys-check.md b/.changeset/light-toys-check.md index b6c54c9ee44f..769a18ac42bc 100644 --- a/.changeset/light-toys-check.md +++ b/.changeset/light-toys-check.md @@ -10,4 +10,4 @@ Added support Svelte syntax `{#key}`. Biome now is able to parse and format the +
+{/key} ``` -The contents of the expressions inside the `{@key }` aren't formatted yet. +The contents of the expressions inside the `{#key }` aren't formatted yet.