Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/wet-dingos-spend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@biomejs/biome": patch
---

Fixed Svelte/Astro lowercase component member expressions being formatted with extra space
Comment thread
sepagian marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
let form;
let foo;
let Data;
---
<form.Field></form.Field>
<form.Field attr="value" />
<Data.Client></Data.Client>
<foo.Bar.Baz />
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
source: crates/biome_formatter_test/src/snapshot_builder.rs
info: astro/lowercase-member.astro
---

# Input

```astro
---
let form;
let foo;
let Data;
---
<form.Field></form.Field>
<form.Field attr="value" />
<Data.Client></Data.Client>
<foo.Bar.Baz />

```


=============================

# 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
Trailing newline: true
-----

```astro
---
let form;
let foo;
let Data;
---

<form.Field></form.Field>
<form.Field attr="value" />
<Data.Client></Data.Client>
<foo.Bar.Baz />

```



## Unimplemented nodes/tokens

"let form;\nlet foo;\nlet Data;\n-" => 4..34
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<form.Field></form.Field>
<form.Field attr="value" />
<Data.Client></Data.Client>
<foo.Bar.Baz />
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
source: crates/biome_formatter_test/src/snapshot_builder.rs
info: svelte/lowercase-member.svelte
---

# Input

```svelte
<form.Field></form.Field>
Comment thread
sepagian marked this conversation as resolved.
<form.Field attr="value" />
<Data.Client></Data.Client>
<foo.Bar.Baz />

```


=============================

# 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
Trailing newline: true
-----

```svelte
<form.Field></form.Field>
<form.Field attr="value" />
<Data.Client></Data.Client>
<foo.Bar.Baz />

```
4 changes: 4 additions & 0 deletions crates/biome_html_parser/src/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ impl<'src> HtmlLexer<'src> {
EXL => self.consume_byte(T![!]),
// Handle colons as separate tokens for Astro directives
COL => self.consume_byte(T![:]),
PRD => self.consume_byte(T![.]),
Comment thread
sepagian marked this conversation as resolved.
BEO if self.at_svelte_opening_block() => self.consume_svelte_opening_block(),
BEO => {
if self.at_opening_double_text_expression() {
Expand Down Expand Up @@ -283,6 +284,9 @@ impl<'src> HtmlLexer<'src> {
_ => {}
}
}
if dispatched == PRD {
Comment thread
sepagian marked this conversation as resolved.
Outdated
return self.consume_byte(T![.]);
}
self.consume_token_inside_tag(current)
}

Expand Down
27 changes: 17 additions & 10 deletions crates/biome_html_parser/src/syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,14 @@ fn parse_any_tag_name(p: &mut HtmlParser) -> ParsedSyntax {
if !is_at_start_literal(p) {
return Absent;
}

let tag_text = p.cur_text();

// Step 1: Parse base name (either component or regular tag)
let name = if is_possible_component(p, tag_text) {
// Check if this could be a component or has member expression
let is_component = is_possible_component(p, tag_text);
let has_member_expression = !p.options().is_html() && p.nth_at(1, T![.]);

// Step 1: Parse base name
let name = if is_component || has_member_expression {
// Parse as component name - use component_name_context to allow `.` for member expressions
let m = p.start();
p.bump_with_context(HTML_LITERAL, component_name_context(p));
Comment thread
sepagian marked this conversation as resolved.
Expand All @@ -203,14 +206,18 @@ fn parse_any_tag_name(p: &mut HtmlParser) -> ParsedSyntax {
// Parse as regular HTML tag
parse_literal(p, HTML_TAG_NAME)
};

// Step 2: Extend with member access if present (using .map() pattern from JSX parser)
// Step 2: Extend with member access if present
Comment thread
sepagian marked this conversation as resolved.
Outdated
name.map(|mut name| {
while p.at(T![.]) {
let m = name.precede(p); // Create marker BEFORE already-parsed name
p.bump_with_context(T![.], component_name_context(p)); // Use component context for `.`
// Check kind BEFORE moving name with precede()
let is_lowercase_tag = name.kind(p) == HTML_TAG_NAME;

// Parse member name - must use component_name_context to maintain `.` lexing
while p.at(T![.]) {
// Convert BEFORE precede takes ownership of name
if is_lowercase_tag {
name.change_kind(p, HTML_COMPONENT_NAME);
}
let m = name.precede(p);
p.bump_with_context(T![.], component_name_context(p));
if is_at_start_literal(p) {
let member_m = p.start();
p.bump_with_context(HTML_LITERAL, component_name_context(p));
Expand All @@ -219,7 +226,7 @@ fn parse_any_tag_name(p: &mut HtmlParser) -> ParsedSyntax {
p.error(expected_element_name(p, p.cur_range()));
}

name = m.complete(p, HTML_MEMBER_NAME); // Wrap previous name
name = m.complete(p, HTML_MEMBER_NAME);
}
name
})
Expand Down