Skip to content

Commit fa51a85

Browse files
committed
feat(html): implement formatter
1 parent 147c4df commit fa51a85

File tree

9 files changed

+221
-6
lines changed

9 files changed

+221
-6
lines changed
File renamed without changes.

crates/biome_html_formatter/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod cst;
2020
mod generated;
2121
mod html;
2222
pub(crate) mod prelude;
23+
pub(crate) mod separated;
2324
mod svelte;
2425
mod trivia;
2526
pub mod utils;
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use crate::FormatHtmlSyntaxToken;
2+
use crate::prelude::*;
3+
use biome_formatter::separated::{
4+
FormatSeparatedElementRule, FormatSeparatedIter, TrailingSeparator,
5+
};
6+
use biome_formatter::{FormatRefWithRule, FormatRuleWithOptions};
7+
use biome_html_syntax::{HtmlLanguage, HtmlSyntaxToken, SvelteName};
8+
use biome_rowan::{AstNode, AstSeparatedList, AstSeparatedListElementsIterator};
9+
use std::marker::PhantomData;
10+
11+
#[derive(Clone)]
12+
pub(crate) struct HtmlFormatSeparatedElementRule<N> {
13+
node: PhantomData<N>,
14+
}
15+
16+
impl<N> FormatSeparatedElementRule<N> for HtmlFormatSeparatedElementRule<N>
17+
where
18+
N: AstNode<Language = HtmlLanguage> + AsFormat<HtmlFormatContext> + 'static,
19+
{
20+
type Context = HtmlFormatContext;
21+
type FormatNode<'a> = N::Format<'a>;
22+
type FormatSeparator<'a> = FormatRefWithRule<'a, HtmlSyntaxToken, FormatHtmlSyntaxToken>;
23+
24+
fn format_node<'a>(&self, node: &'a N) -> Self::FormatNode<'a> {
25+
node.format()
26+
}
27+
28+
fn format_separator<'a>(&self, separator: &'a HtmlSyntaxToken) -> Self::FormatSeparator<'a> {
29+
separator.format()
30+
}
31+
}
32+
33+
type HtmlFormatSeparatedIter<Node, C> = FormatSeparatedIter<
34+
AstSeparatedListElementsIterator<HtmlLanguage, Node>,
35+
Node,
36+
HtmlFormatSeparatedElementRule<Node>,
37+
C,
38+
>;
39+
40+
/// AST Separated list formatting extension methods
41+
pub(crate) trait FormatAstSeparatedListExtension:
42+
AstSeparatedList<Language = HtmlLanguage>
43+
{
44+
/// Prints a separated list of nodes
45+
///
46+
/// Trailing separators will be reused from the original list or created by
47+
/// calling the `separator_factory` function. The last trailing separator
48+
/// will not be printed by default. Use `with_trailing_separator` to add it
49+
/// in where necessary.
50+
fn format_separated(
51+
&self,
52+
separator: &'static str,
53+
) -> HtmlFormatSeparatedIter<Self::Node, HtmlFormatContext> {
54+
HtmlFormatSeparatedIter::new(
55+
self.elements(),
56+
separator,
57+
HtmlFormatSeparatedElementRule { node: PhantomData },
58+
on_skipped,
59+
on_removed,
60+
)
61+
.with_trailing_separator(TrailingSeparator::Disallowed)
62+
}
63+
}
64+
65+
impl<T> FormatAstSeparatedListExtension for T where T: AstSeparatedList<Language = HtmlLanguage> {}
66+
67+
#[derive(Default, Debug, Clone, Copy)]
68+
pub(crate) struct HtmlFormatSeparatedElementRuleWithOptions<N, O> {
69+
node: PhantomData<N>,
70+
options: O,
71+
}
72+
73+
impl<N, O> HtmlFormatSeparatedElementRuleWithOptions<N, O> {
74+
pub(crate) fn new(options: O) -> Self {
75+
Self {
76+
node: PhantomData,
77+
options,
78+
}
79+
}
80+
}
81+
82+
impl<N, O, R> FormatSeparatedElementRule<N> for HtmlFormatSeparatedElementRuleWithOptions<N, O>
83+
where
84+
O: Clone + Copy,
85+
R: FormatNodeRule<N> + FormatRuleWithOptions<N, Context = HtmlFormatContext, Options = O>,
86+
N: AstNode<Language = HtmlLanguage>
87+
+ for<'a> AsFormat<HtmlFormatContext, Format<'a> = FormatRefWithRule<'a, N, R>>
88+
+ 'static,
89+
{
90+
type Context = HtmlFormatContext;
91+
type FormatNode<'a> = FormatRefWithRule<'a, N, R>;
92+
type FormatSeparator<'a> = FormatRefWithRule<'a, HtmlSyntaxToken, FormatHtmlSyntaxToken>;
93+
94+
fn format_node<'a>(&self, node: &'a N) -> Self::FormatNode<'a> {
95+
node.format().with_options(self.options)
96+
}
97+
98+
fn format_separator<'a>(&self, separator: &'a HtmlSyntaxToken) -> Self::FormatSeparator<'a> {
99+
separator.format()
100+
}
101+
}
102+
103+
type HtmlFormatSeparatedIterWithOptions<Node, Options, C> = FormatSeparatedIter<
104+
AstSeparatedListElementsIterator<HtmlLanguage, Node>,
105+
Node,
106+
HtmlFormatSeparatedElementRuleWithOptions<Node, Options>,
107+
C,
108+
>;
109+
110+
/// AST Separated list formatting extension methods with options
111+
#[expect(dead_code)]
112+
pub(crate) trait FormatAstSeparatedListWithOptionsExtension<O>:
113+
AstSeparatedList<Language = HtmlLanguage>
114+
{
115+
/// Prints a separated list of nodes with options
116+
///
117+
/// Trailing separators will be reused from the original list or created by
118+
/// calling the `separator_factory` function. The last trailing separator
119+
/// will not be printed by default. Use `with_trailing_separator` to add it
120+
/// in where necessary.
121+
fn format_separated_with_options(
122+
&self,
123+
separator: &'static str,
124+
options: O,
125+
) -> HtmlFormatSeparatedIterWithOptions<Self::Node, O, HtmlFormatContext> {
126+
FormatSeparatedIter::new(
127+
self.elements(),
128+
separator,
129+
HtmlFormatSeparatedElementRuleWithOptions::new(options),
130+
on_skipped,
131+
on_removed,
132+
)
133+
.with_trailing_separator(TrailingSeparator::Disallowed)
134+
}
135+
}
136+
137+
impl<T, O> FormatAstSeparatedListWithOptionsExtension<O> for T where
138+
// TODO: probably it requires an update because it's tight to svelte grammar
139+
T: AstSeparatedList<Language = HtmlLanguage, Node = SvelteName>
140+
{
141+
}

crates/biome_html_formatter/src/svelte/auxiliary/debug_block.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::prelude::*;
22
use biome_formatter::write;
33
use biome_html_syntax::{SvelteDebugBlock, SvelteDebugBlockFields};
4+
use biome_rowan::AstSeparatedList;
45

56
#[derive(Debug, Clone, Default)]
67
pub(crate) struct FormatSvelteDebugBlock;
@@ -15,7 +16,11 @@ impl FormatNodeRule<SvelteDebugBlock> for FormatSvelteDebugBlock {
1516

1617
write!(f, [sv_curly_at_token.format(), debug_token.format(),])?;
1718

18-
write!(f, [space(), bindings.format()])?;
19+
if !bindings.is_empty() {
20+
write!(f, [space()])?;
21+
}
22+
23+
write!(f, [bindings.format()])?;
1924

2025
write!(f, [r_curly_token.format()])
2126
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use crate::prelude::*;
2-
use biome_html_syntax::SvelteName;
3-
use biome_rowan::AstNode;
2+
use biome_formatter::write;
3+
use biome_html_syntax::{SvelteName, SvelteNameFields};
4+
45
#[derive(Debug, Clone, Default)]
56
pub(crate) struct FormatSvelteName;
67
impl FormatNodeRule<SvelteName> for FormatSvelteName {
78
fn fmt_fields(&self, node: &SvelteName, f: &mut HtmlFormatter) -> FormatResult<()> {
8-
format_html_verbatim_node(node.syntax()).fmt(f)
9+
let SvelteNameFields { svelte_ident_token } = node.as_fields();
10+
write!(f, [svelte_ident_token.format()])
911
}
1012
}
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
use crate::prelude::*;
2+
use crate::separated::FormatAstSeparatedListExtension;
23
use biome_html_syntax::SvelteBindingList;
4+
35
#[derive(Debug, Clone, Default)]
46
pub(crate) struct FormatSvelteBindingList;
57
impl FormatRule<SvelteBindingList> for FormatSvelteBindingList {
68
type Context = HtmlFormatContext;
79
fn fmt(&self, node: &SvelteBindingList, f: &mut HtmlFormatter) -> FormatResult<()> {
8-
format_html_verbatim_node(node.syntax()).fmt(f)
10+
let separator = space();
11+
let mut joiner = f.join_with(&separator);
12+
13+
for formatted in node.format_separated(",") {
14+
joiner.entry(&formatted);
15+
}
16+
17+
joiner.finish()
918
}
1019
}

crates/biome_html_formatter/src/trivia.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::FormatHtmlSyntaxToken;
21
use crate::prelude::HtmlFormatContext;
2+
use crate::{FormatHtmlSyntaxToken, HtmlFormatter};
33
use biome_formatter::formatter::Formatter;
44
use biome_formatter::trivia::FormatToken;
55
use biome_formatter::{Argument, Format, FormatResult};
@@ -39,3 +39,11 @@ impl<'a> Format<HtmlFormatContext> for FormatReplaced<'a> {
3939
FormatHtmlSyntaxToken.format_replaced(self.token, &self.content, f)
4040
}
4141
}
42+
43+
pub(crate) fn on_skipped(token: &HtmlSyntaxToken, f: &mut HtmlFormatter) -> FormatResult<()> {
44+
FormatHtmlSyntaxToken.format_skipped_token_trivia(token, f)
45+
}
46+
47+
pub(crate) fn on_removed(token: &HtmlSyntaxToken, f: &mut HtmlFormatter) -> FormatResult<()> {
48+
FormatHtmlSyntaxToken.format_removed(token, f)
49+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{@debug}
2+
3+
{@debug foo}
4+
5+
6+
{@debug foo, bar}{@debug foo, bar, baz}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
source: crates/biome_formatter_test/src/snapshot_builder.rs
3+
info: astro/debug.svelte
4+
---
5+
# Input
6+
7+
```svelte
8+
{@debug}
9+
10+
{@debug foo}
11+
12+
13+
{@debug foo, bar}{@debug foo, bar, baz}
14+
15+
```
16+
17+
18+
=============================
19+
20+
# Outputs
21+
22+
## Output 1
23+
24+
-----
25+
Indent style: Tab
26+
Indent width: 2
27+
Line ending: LF
28+
Line width: 80
29+
Attribute Position: Auto
30+
Bracket same line: false
31+
Whitespace sensitivity: css
32+
Indent script and style: false
33+
Self close void elements: never
34+
-----
35+
36+
```svelte
37+
{@debug}
38+
39+
{@debug foo}
40+
41+
{@debug foo, bar}
42+
{@debug foo, bar, baz}
43+
```

0 commit comments

Comments
 (0)