Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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/fix-css-bom-formatting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@biomejs/biome": patch
---

Fixed CSS formatter incorrectly collapsing selectors when a BOM (Byte Order Mark) character is present at the start of the file. The formatter now correctly preserves line breaks between comments and selectors in BOM-prefixed CSS files, matching Prettier's behavior.

This file was deleted.

32 changes: 20 additions & 12 deletions crates/biome_css_parser/src/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,24 @@ impl<'src> CssLexer<'src> {
LSS => self.consume_lss(),

DOL => self.consume_dol(),

// Check for BOM first at position 0, before checking if UNI is an identifier start.
// The BOM character (U+FEFF) is in the valid CSS non-ASCII identifier range,
// so without this check it would be incorrectly consumed as part of an identifier.
UNI if self.position == 0 => {
if let Some((bom, bom_size)) = self.consume_potential_bom(UNICODE_BOM) {
self.unicode_bom_length = bom_size;
return bom;
}
// Not a BOM, check other UNI cases below
if self.options.is_metavariable_enabled() && self.is_metavariable_start() {
self.consume_metavariable(GRIT_METAVARIABLE)
} else if self.is_ident_start() {
self.consume_identifier()
} else {
self.consume_unexpected_character()
}
}
UNI if self.options.is_metavariable_enabled() && self.is_metavariable_start() => {
self.consume_metavariable(GRIT_METAVARIABLE)
}
Expand All @@ -380,7 +398,7 @@ impl<'src> CssLexer<'src> {
PNC => self.consume_byte(T![')']),
BEO => self.consume_byte(T!['{']),
BEC => self.consume_byte(T!['}']),
BTO => self.consume_byte(T!('[')),
BTO => self.consume_byte(T!['[']),
BTC => self.consume_byte(T![']']),
COM => self.consume_byte(T![,]),
MOR => self.consume_mor(),
Expand All @@ -391,17 +409,7 @@ impl<'src> CssLexer<'src> {
PRC => self.consume_byte(T![%]),
Dispatch::AMP => self.consume_byte(T![&]),

UNI => {
// A BOM can only appear at the start of a file, so if we haven't advanced at all yet,
// perform the check. At any other position, the BOM is just considered plain whitespace.
if self.position == 0
&& let Some((bom, bom_size)) = self.consume_potential_bom(UNICODE_BOM)
{
self.unicode_bom_length = bom_size;
return bom;
}
self.consume_unexpected_character()
}
UNI => self.consume_unexpected_character(),

_ => self.consume_unexpected_character(),
}
Expand Down