diff --git a/Cargo.lock b/Cargo.lock index 076edcba17..75c7e8fa13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2109,6 +2109,7 @@ dependencies = [ "oxc_span", "phf", "pico-args", + "smallvec", ] [[package]] diff --git a/crates/oxc_formatter_css/Cargo.toml b/crates/oxc_formatter_css/Cargo.toml index 4fcb0be122..053a73aa26 100644 --- a/crates/oxc_formatter_css/Cargo.toml +++ b/crates/oxc_formatter_css/Cargo.toml @@ -23,6 +23,7 @@ oxc_diagnostics = { workspace = true } oxc_formatter_core = { workspace = true } oxc_span = { workspace = true } phf = { workspace = true, features = ["macros"] } +smallvec = { workspace = true } oxc-css-parser = { workspace = true } [dev-dependencies] diff --git a/crates/oxc_formatter_css/src/print/mod.rs b/crates/oxc_formatter_css/src/print/mod.rs index a4a7eb05ce..d66fdece41 100644 --- a/crates/oxc_formatter_css/src/print/mod.rs +++ b/crates/oxc_formatter_css/src/print/mod.rs @@ -82,13 +82,24 @@ pub fn normalize_whitespace(raw: &str) -> String { /// Mirrors Prettier's `maybeToLowerCase`. /// Lowercase unless the identifier contains variable/interpolation markers. pub fn write_maybe_lowercase<'a>(value: &'a str, f: &mut CssFormatter<'_, 'a>) { - if value.contains('$') - || value.contains('@') - || value.contains('#') + // One pass over the (short) identifier, rather than a search per marker + let (mut open, mut close, mut upper) = (false, false, false); + for b in value.bytes() { + match b { + b'$' | b'@' | b'#' => { + write!(f, text(value)); + return; + } + b'(' => open = true, + b')' => close = true, + _ => upper |= b.is_ascii_uppercase(), + } + } + if !upper + || (open && close) || value.starts_with('%') || value.starts_with("--") || value.starts_with(":--") - || (value.contains('(') && value.contains(')')) { write!(f, text(value)); return; diff --git a/crates/oxc_formatter_css/src/print/value.rs b/crates/oxc_formatter_css/src/print/value.rs index 0e3426674e..18296c60b0 100644 --- a/crates/oxc_formatter_css/src/print/value.rs +++ b/crates/oxc_formatter_css/src/print/value.rs @@ -12,6 +12,7 @@ use std::borrow::Cow; use cow_utils::CowUtils; +use smallvec::SmallVec; use oxc_allocator::Allocator; use oxc_css_parser::{ @@ -513,10 +514,11 @@ impl ValueContext<'_> { /// Each group is paired with the start offset of the comma that follows it (`None` for the last group): /// comments between a group and its comma stay BEFORE the comma (`a /* c */, b`), /// so group printers need the boundary. +/// Inline up to 4 groups: most values have one. pub(super) fn split_comma_groups<'b, 'a>( values: &'b [ComponentValue<'a>], -) -> Vec<(&'b [ComponentValue<'a>], Option)> { - let mut groups = vec![]; +) -> SmallVec<[(&'b [ComponentValue<'a>], Option); 4]> { + let mut groups = SmallVec::new(); let mut start = 0; let mut depth = 0i32; for (i, v) in values.iter().enumerate() { @@ -534,10 +536,9 @@ pub(super) fn split_comma_groups<'b, 'a>( start = i + 1; } } - groups.push((&values[start..], None)); - // A trailing comma produces an empty last group; Prettier drops it - if groups.len() > 1 && groups.last().is_some_and(|(g, _)| g.is_empty()) { - groups.pop(); + // A trailing comma would leave an empty last group; Prettier drops it + if groups.is_empty() || start < values.len() { + groups.push((&values[start..], None)); } groups } @@ -2286,12 +2287,11 @@ pub(super) fn write_function<'a>( let name_span = to_span(func.name.span()); write!(f, text(source.text_for(&name_span))); - let groups = split_comma_groups(&func.args); - if func.args.is_empty() { write!(f, ["(", ")"]); return; } + let groups = split_comma_groups(&func.args); // `url(...)` as a function (e.g. `url(if(...))`): parens hug the content, // no break opportunities of their own (Prettier's `isURLFunctionNode`).