Skip to content

Commit 96939c0

Browse files
authored
fix(css/parser): bom parsing (#9259)
1 parent e922005 commit 96939c0

File tree

3 files changed

+25
-49
lines changed

3 files changed

+25
-49
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
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.

crates/biome_css_formatter/tests/specs/prettier/css/bom/bom.css.snap

Lines changed: 0 additions & 37 deletions
This file was deleted.

crates/biome_css_parser/src/lexer/mod.rs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,24 @@ impl<'src> CssLexer<'src> {
365365
LSS => self.consume_lss(),
366366

367367
DOL => self.consume_dol(),
368+
369+
// Check for BOM first at position 0, before checking if UNI is an identifier start.
370+
// The BOM character (U+FEFF) is in the valid CSS non-ASCII identifier range,
371+
// so without this check it would be incorrectly consumed as part of an identifier.
372+
UNI if self.position == 0 => {
373+
if let Some((bom, bom_size)) = self.consume_potential_bom(UNICODE_BOM) {
374+
self.unicode_bom_length = bom_size;
375+
return bom;
376+
}
377+
// Not a BOM, check other UNI cases below
378+
if self.options.is_metavariable_enabled() && self.is_metavariable_start() {
379+
self.consume_metavariable(GRIT_METAVARIABLE)
380+
} else if self.is_ident_start() {
381+
self.consume_identifier()
382+
} else {
383+
self.consume_unexpected_character()
384+
}
385+
}
368386
UNI if self.options.is_metavariable_enabled() && self.is_metavariable_start() => {
369387
self.consume_metavariable(GRIT_METAVARIABLE)
370388
}
@@ -380,7 +398,7 @@ impl<'src> CssLexer<'src> {
380398
PNC => self.consume_byte(T![')']),
381399
BEO => self.consume_byte(T!['{']),
382400
BEC => self.consume_byte(T!['}']),
383-
BTO => self.consume_byte(T!('[')),
401+
BTO => self.consume_byte(T!['[']),
384402
BTC => self.consume_byte(T![']']),
385403
COM => self.consume_byte(T![,]),
386404
MOR => self.consume_mor(),
@@ -391,17 +409,7 @@ impl<'src> CssLexer<'src> {
391409
PRC => self.consume_byte(T![%]),
392410
Dispatch::AMP => self.consume_byte(T![&]),
393411

394-
UNI => {
395-
// A BOM can only appear at the start of a file, so if we haven't advanced at all yet,
396-
// perform the check. At any other position, the BOM is just considered plain whitespace.
397-
if self.position == 0
398-
&& let Some((bom, bom_size)) = self.consume_potential_bom(UNICODE_BOM)
399-
{
400-
self.unicode_bom_length = bom_size;
401-
return bom;
402-
}
403-
self.consume_unexpected_character()
404-
}
412+
UNI => self.consume_unexpected_character(),
405413

406414
_ => self.consume_unexpected_character(),
407415
}

0 commit comments

Comments
 (0)