From 633ee960da1c08e9ea325739516231f4e47ba189 Mon Sep 17 00:00:00 2001 From: leaysgur <6259812+leaysgur@users.noreply.github.com> Date: Mon, 2 Nov 2026 08:26:18 +0000 Subject: [PATCH] perf(formatter_css): use `u32` spans (#28708) - bump oxc-css-parser 0.0.19 which changes `Span` from `usize` to `u32` - update conversion codes Peak memory is reduced 5% ~ 20% in large CSS file. --- Cargo.lock | 4 +- Cargo.toml | 2 +- crates/oxc_formatter_css/src/format.rs | 11 +- crates/oxc_formatter_css/src/print/at_rule.rs | 112 ++++++++---------- crates/oxc_formatter_css/src/print/less.rs | 30 ++--- crates/oxc_formatter_css/src/print/mod.rs | 2 +- .../src/print/postcss_simple_vars.rs | 8 +- crates/oxc_formatter_css/src/print/scss.rs | 107 ++++++++--------- .../oxc_formatter_css/src/print/selector.rs | 14 +-- .../oxc_formatter_css/src/print/statement.rs | 69 +++++------ crates/oxc_formatter_css/src/print/value.rs | 107 ++++++++--------- 11 files changed, 212 insertions(+), 254 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03940e2d34..32688c937f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1679,9 +1679,9 @@ dependencies = [ [[package]] name = "oxc-css-parser" -version = "0.0.18" +version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72213601ce5dff6b59b5fcecf15877018a6ed2f90662823e08241c4f04aa0a16" +checksum = "21bff6681f56d41c2c45ffd87ef7bfc572e4dff44885e1a4797f0294cd6c21e9" dependencies = [ "oxc_allocator", ] diff --git a/Cargo.toml b/Cargo.toml index f55d1731c8..ec19255942 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -241,7 +241,7 @@ nodejs-built-in-modules = "1.0.0" # Node.js built-in modules nonmax = "0.5.5" # Non-maximum numbers num-bigint = "0.5.0" # Big integers num-traits = "0.2.19" # Numeric traits -oxc-css-parser = "0.0.18" # CSS/SCSS/Less parser (raffia 0.12.3 fork: adds `template_placeholder` typed placeholders for css-in-js + valid-syntax coverage fixes (see crates/oxc_formatter_css/AGENTS.md)) +oxc-css-parser = "0.0.19" # CSS/SCSS/Less parser (raffia 0.12.3 fork: adds `template_placeholder` typed placeholders for css-in-js + valid-syntax coverage fixes (see crates/oxc_formatter_css/AGENTS.md)) oxc-graphql-parser = "0.0.9" # GraphQL parser (direct AST; apollo-parser 0.8.6 fork aligned with graphql-js 17 syntax) oxc-htmlish-parser = "0.0.2" # HTML / Vue / Angular parser (loose, span-faithful AST) oxc-markdown-parser = "0.0.3" # CommonMark + GFM parser (span-faithful, style-preserving typed AST) diff --git a/crates/oxc_formatter_css/src/format.rs b/crates/oxc_formatter_css/src/format.rs index 644180d5a4..3236221262 100644 --- a/crates/oxc_formatter_css/src/format.rs +++ b/crates/oxc_formatter_css/src/format.rs @@ -267,7 +267,7 @@ fn parse_stylesheet<'a>( let comments: &'a [CssComment] = ArenaVec::from_iter_in( parser.comments().iter().map(|c| CssComment { - span: to_span(&c.span), + span: to_span(c.span), inline: matches!(c.kind, oxc_css_parser::token::CommentKind::Line), }), &allocator, @@ -278,14 +278,11 @@ fn parse_stylesheet<'a>( } fn to_diagnostic(error: &oxc_css_parser::error::Error) -> OxcDiagnostic { - OxcDiagnostic::error(format!("Syntax error: {}", error.kind)).with_label(to_span(&error.span)) + OxcDiagnostic::error(format!("Syntax error: {}", error.kind)).with_label(to_span(error.span)) } -pub fn to_span(span: &oxc_css_parser::Span) -> Span { - Span::new( - u32::try_from(span.start).unwrap_or(u32::MAX), - u32::try_from(span.end).unwrap_or(u32::MAX), - ) +pub fn to_span(span: oxc_css_parser::Span) -> Span { + Span::new(span.start, span.end) } fn write_front_matter<'a>(fm: &FrontMatter<'a>, f: &mut CssFormatter<'_, 'a>) { diff --git a/crates/oxc_formatter_css/src/print/at_rule.rs b/crates/oxc_formatter_css/src/print/at_rule.rs index 75304d70d0..8af52a6597 100644 --- a/crates/oxc_formatter_css/src/print/at_rule.rs +++ b/crates/oxc_formatter_css/src/print/at_rule.rs @@ -15,7 +15,6 @@ use oxc_css_parser::{ SupportsConditionKind, SupportsInParens, SupportsInParensKind, SupportsNot, SupportsOr, TokenSeq, UnknownAtRulePrelude, }, - pos::Span, token::{Token, TokenWithSpan}, }; @@ -48,8 +47,7 @@ pub(super) fn write_at_rule<'a>(at_rule: &AtRule<'a>, f: &mut CssFormatter<'_, ' // Comments inside the params: // postcss keeps them embedded in the params string / media tokens; reconstruct from the source. - let region_end = - statement::params_region_end(at_rule.block.as_ref(), to_span(at_rule.span()).end, f); + let region_end = statement::params_region_end(at_rule.block.as_ref(), at_rule.span().end, f); let has_params_comments = f .context() .comments() @@ -90,7 +88,7 @@ pub(super) fn write_at_rule<'a>(at_rule: &AtRule<'a>, f: &mut CssFormatter<'_, ' // so we route them through the normal structured printer // for internal consistency over Prettier byte-equality. if unknown_string_params { - let prelude_start = to_span(prelude.span()).start; + let prelude_start = prelude.span().start; write_verbatim_at_rule_tail( name_span.end, prelude_start, @@ -173,7 +171,7 @@ pub(super) fn write_at_rule<'a>(at_rule: &AtRule<'a>, f: &mut CssFormatter<'_, ' if let Some(prelude) = &at_rule.prelude { // postcss folds a no-gap, non-paren prelude into the at-rule NAME // (`@page:first` stays tight); reproduce by checking the source gap. - let name_end = to_span(at_rule.name.span()).end; + let name_end = at_rule.name.span().end; let prelude_span = to_span(prelude.span()); let fused = name_end == prelude_span.start && !source.text_for(&prelude_span).starts_with('('); @@ -204,7 +202,7 @@ pub(super) fn write_at_rule<'a>(at_rule: &AtRule<'a>, f: &mut CssFormatter<'_, ' // is written here, so nothing inside the prelude is lost to the statement loop's cursor guard. if let Some(block) = &at_rule.block { let prelude_end = - at_rule.prelude.as_ref().map_or(name_span.end, |prelude| to_span(prelude.span()).end); + at_rule.prelude.as_ref().map_or(name_span.end, |prelude| prelude.span().end); value::write_comments_before_block(prelude_end, region_end, f); if !is_control_directive { write!(f, space()); @@ -764,17 +762,16 @@ fn write_at_rule_prelude<'a>(prelude: &AtRulePrelude<'a>, f: &mut CssFormatter<' // oxc-css-parser's CustomSelector span excludes the leading `:` write!(f, ":"); write!(f, text(source.text_for(&custom_span))); - head_separator(to_span(custom.selector.span()).end, f); + head_separator(custom.selector.span().end, f); // Selectors share THIS group: when the prelude breaks, // each selector gets its own line. for (i, complex) in custom.selector.selectors.iter().enumerate() { if i > 0 { - let comma = - custom.selector.comma_spans.get(i - 1).map(|sp| to_span(sp).start); + let comma = custom.selector.comma_spans.get(i - 1).map(|sp| sp.start); value::write_group_comma(comma, f); write!(f, soft_line_break_or_space()); } - value::flush_value_comments(to_span(complex.span()).start, f); + value::flush_value_comments(complex.span().start, f); selector::write_complex_selector(complex, f); } }); @@ -791,12 +788,12 @@ fn write_at_rule_prelude<'a>(prelude: &AtRulePrelude<'a>, f: &mut CssFormatter<' let body = format_with(move |f: &mut CssFormatter<'_, 'a>| { if let Some(prefix) = &namespace.prefix { let span = to_span(prefix.span()); - let prelude_end = to_span(namespace.span()).end; + let prelude_end = namespace.span().end; value::write_with_comments(span, prelude_end, f, |f| { write!(f, text(source.text_for(&span))); }); write!(f, space()); - value::flush_value_comments(to_span(namespace.uri.span()).start, f); + value::flush_value_comments(namespace.uri.span().start, f); } match &namespace.uri { NamespacePreludeUri::Str(InterpolableStr::Literal(str)) => { @@ -821,7 +818,7 @@ fn write_at_rule_prelude<'a>(prelude: &AtRulePrelude<'a>, f: &mut CssFormatter<' let body = format_with(move |f: &mut CssFormatter<'_, 'a>| { for (i, layer) in layers.names.iter().enumerate() { if i > 0 { - let comma = layers.comma_spans.get(i - 1).map(|sp| to_span(sp).start); + let comma = layers.comma_spans.get(i - 1).map(|sp| sp.start); value::write_group_comma(comma, f); write!(f, space()); } @@ -868,10 +865,10 @@ fn write_at_rule_prelude<'a>(prelude: &AtRulePrelude<'a>, f: &mut CssFormatter<' .map(|(i, path)| ImportPath { span: path.span, raw: path.raw, - comma_start: import.comma_spans.get(i).map(|sp| to_span(sp).start), + comma_start: import.comma_spans.get(i).map(|sp| sp.start), }) .collect(); - write_import_path_list(&paths, to_span(import.span()).end, f); + write_import_path_list(&paths, import.span().end, f); } // Only reached for SCSS-family names parsed AS CSS (see `is_value_parsed_at_rule`); // other `Unknown` preludes print verbatim via the `write_at_rule` early return. @@ -897,7 +894,7 @@ fn write_at_rule_prelude<'a>(prelude: &AtRulePrelude<'a>, f: &mut CssFormatter<' /// One `@import` path with the start of its trailing comma (see [`value::write_group_comma`]'s pairing). struct ImportPath<'a> { - span: Span, + span: oxc_css_parser::Span, raw: &'a str, comma_start: Option, } @@ -928,9 +925,9 @@ fn write_import_path_list<'a>( let mut trailing: Vec> = vec![None; n]; let mut leads: Vec> = Vec::with_capacity(n); for (i, path) in paths.iter().enumerate() { - let path_start = to_span(&path.span).start; + let path_start = path.span.start; let prev = (i > 0).then(|| &paths[i - 1]); - let prev_end = prev.map(|prev| to_span(&prev.span).end); + let prev_end = prev.map(|prev| prev.span.end); let mut lead: Vec = all .iter() .filter(|c| c.span.end <= path_start) @@ -953,10 +950,7 @@ fn write_import_path_list<'a>( let chunk_w: Vec = paths .iter() .enumerate() - .map(|(i, p)| { - let span = to_span(&p.span); - span.end - span.start + u32::from(i + 1 < n) - }) + .map(|(i, p)| p.span.end - p.span.start + u32::from(i + 1 < n)) .collect(); let mut breaks_before = vec![false; n]; let mut x = 8; // after `@import ` @@ -999,7 +993,7 @@ fn write_import_path_list<'a>( write!(f, indent(&body)); } else if has_comments { let path = &paths[0]; - let path_start = to_span(&path.span).start; + let path_start = path.span.start; let lead: Vec = f.context().comments().take_before(path_start).to_vec(); for &comment in &lead { @@ -1095,9 +1089,8 @@ fn write_token_value<'a>( write!(f, ","); let sep_blank = groups_ref[i - 1].iter().any(|t| matches!(&t.token, Token::Colon(_))) && { - let prev_end = groups_ref[i - 1].last().map_or(0, |t| to_span(&t.span).end); - let next_start = - group_tokens.first().map_or(prev_end, |t| to_span(&t.span).start); + let prev_end = groups_ref[i - 1].last().map_or(0, |t| t.span.end); + let next_start = group_tokens.first().map_or(prev_end, |t| t.span.start); comments::classify_gap(source.bytes_range(prev_end, next_start)) == comments::Gap::Blank }; @@ -1139,7 +1132,7 @@ fn write_token_comma_group_after<'a>( && tokens.len() > 1 && matches!(&tokens[1].token, Token::LParen(_)) && matches!(&tokens[0].token, Token::Ident(_)) - && !source.text_for(&to_span(&tokens[0].span)).eq_ignore_ascii_case("if"); + && !source.text_for(&to_span(tokens[0].span)).eq_ignore_ascii_case("if"); let mut filler = f.fill(); if let Some(head) = head { @@ -1169,7 +1162,7 @@ fn write_token_comma_group_after<'a>( if matches!(&curr.token, Token::LParen(_) | Token::LBracket(_) | Token::LBrace(_)) { // Opener glues to the run when fused in source, // after a colon (`$arg: (...)`), or as a call (`name(...)`). - let glued = to_span(&prev.span).end == to_span(&curr.span).start + let glued = is_glued(prev.span, curr.span) || matches!(&prev.token, Token::Colon(_)) || (run_end == 1 && hug_lparen); if !glued { @@ -1188,7 +1181,7 @@ fn write_token_comma_group_after<'a>( run_end = j; continue; } - let tight = to_span(&prev.span).end == to_span(&curr.span).start + let tight = is_glued(prev.span, curr.span) || matches!( &curr.token, Token::Comma(_) @@ -1297,7 +1290,7 @@ fn write_token_run<'a>(run: &[TokenWithSpan<'a>], hug_lparen: bool, f: &mut CssF if j > 0 { write_token_pair_space(run, j, hug_lparen, f); } - let span = to_span(&tok.span); + let span = to_span(tok.span); write!(f, text(source.text_for(&span))); j += 1; continue; @@ -1334,7 +1327,7 @@ fn write_token_run<'a>(run: &[TokenWithSpan<'a>], hug_lparen: bool, f: &mut CssF if j > 0 { write_token_pair_space(run, j, hug_lparen, f); } - let span = to_span(&tok.span); + let span = to_span(tok.span); match &tok.token { Token::Str(_) => { write_raw_str(source.text_for(&span), f); @@ -1370,7 +1363,7 @@ fn write_token_pair_space<'a>( let source = f.context().source_text(); let prev = &run[j - 1]; let tok = &run[j]; - let gap = to_span(&prev.span).end != to_span(&tok.span).start; + let gap = !is_glued(prev.span, tok.span); let wordish = |t: &Token<'_>| { matches!(t, Token::Ident(_) | Token::DollarVar(_) | Token::AtKeyword(_) | Token::RParen(_)) }; @@ -1392,7 +1385,7 @@ fn write_token_pair_space<'a>( (_, Token::LParen(_)) if j == 1 && hug_lparen => false, (Token::Colon(_) | Token::Comma(_), _) => true, (Token::Ident(_), Token::Asterisk(_)) - if !gap && source.text_for(&to_span(&prev.span)).ends_with('-') => + if !gap && source.text_for(&to_span(prev.span)).ends_with('-') => { false } @@ -1446,11 +1439,11 @@ fn write_import_prelude<'a>(import: &ImportPrelude<'a>, f: &mut CssFormatter<'_, // `oxc-css-parser` routes Scss/Sass there directly). // Rebuild the typed path list and print it through the same comment-aware fill. if let Some(paths) = import_as_path_list(import, f) { - write_import_path_list(&paths, to_span(import.span()).end, f); + write_import_path_list(&paths, import.span().end, f); return; } - let prelude_end = to_span(import.span()).end; + let prelude_end = import.span().end; // Non-standard import tail (`@import "a", "b"` / `@import "a" b c(d)`): // reference compilers accept an arbitrary post-URL mix of idents, // functions, parens, and comma-chained imports; oxc-css-parser keeps it as raw tokens @@ -1638,7 +1631,7 @@ fn push_media_condition_words<'b, 'a>( for kind in kinds { let (keyword, in_parens) = split_media_condition_kind(kind); match keyword { - Some(keyword) if is_glued(keyword.span(), &in_parens.span) => { + Some(keyword) if is_glued(keyword.span(), in_parens.span) => { words.push(ImportWord::GluedCall(keyword, in_parens)); } Some(keyword) => { @@ -1679,13 +1672,13 @@ fn import_as_path_list<'a>( Token::Comma(_) if expect_comma => { expect_comma = false; if let Some(last) = paths.last_mut() { - last.comma_start = Some(to_span(&tok.span).start); + last.comma_start = Some(tok.span.start); } } Token::Str(_) if !expect_comma => { paths.push(ImportPath { span: tok.span, - raw: source.text_for(&to_span(&tok.span)), + raw: source.text_for(&to_span(tok.span)), comma_start: None, }); expect_comma = true; @@ -1700,7 +1693,7 @@ fn import_as_path_list<'a>( fn write_import_prelude_inner<'a>(import: &ImportPrelude<'a>, f: &mut CssFormatter<'_, 'a>) { let source = f.context().source_text(); // A `//` between the parts breaks the prelude either way - let prelude_end = to_span(import.span()).end; + let prelude_end = import.span().end; value::write_with_comments(to_span(import.href.span()), prelude_end, f, |f| { write_import_href(&import.href, f); }); @@ -1718,7 +1711,7 @@ fn write_import_prelude_inner<'a>(import: &ImportPrelude<'a>, f: &mut CssFormatt }); } if let Some(media) = &import.media { - let media_span = to_span(media.span()); + let media_span = media.span(); head_separator(media_span.end, f); value::flush_value_comments(media_span.start, f); // No own group/indent: the queries share the prelude-level group @@ -1781,7 +1774,7 @@ fn write_import_modifiers<'a>( for (i, &(group_values, comma)) in groups.iter().enumerate().skip(1) { let content = format_with(move |f: &mut CssFormatter<'_, 'a>| { if let Some(first) = group_values.first() { - value::flush_value_comments(to_span(first.span()).start, f); + value::flush_value_comments(first.span().start, f); } let words = format_with(move |f: &mut CssFormatter<'_, 'a>| { write_import_modifier_group(None, group_values, f); @@ -1868,7 +1861,7 @@ fn write_import_href<'a>(href: &ImportPreludeHref<'a>, f: &mut CssFormatter<'_, /// oxc-css-parser parses the options form as a dedicated `LessImportPrelude`, /// which otherwise falls into the verbatim catch-all and skips quote normalization. fn write_less_import_prelude<'a>(import: &LessImportPrelude<'a>, f: &mut CssFormatter<'_, 'a>) { - if !has_unclaimed_comments_before(to_span(&import.span).end, f) { + if !has_unclaimed_comments_before(import.span.end, f) { let mut groups: Vec>> = vec![vec![]]; if !import.options.names.is_empty() { groups[0].push(ImportWord::LessOptions(&import.options)); @@ -1917,8 +1910,7 @@ fn write_media_query_list<'a>(list: &MediaQueryList<'a>, f: &mut CssFormatter<'_ // The indent governs the inter-query `,`-breaks and a `//` break inside a query; // without either a single query never breaks, and dropping the indent avoids // leaking a level into an embedded `${}` placeholder break (a media value is flat text, like Prettier). - let breaks_inside = - f.context().comments().iter_before(to_span(list.span()).end).any(|c| c.inline); + let breaks_inside = f.context().comments().iter_before(list.span().end).any(|c| c.inline); if list.queries.len() > 1 || breaks_inside { write!(f, group(&indent(&body))); } else { @@ -1931,7 +1923,7 @@ fn write_media_query_list<'a>(list: &MediaQueryList<'a>, f: &mut CssFormatter<'_ fn write_media_query_list_inner<'a>(list: &MediaQueryList<'a>, f: &mut CssFormatter<'_, 'a>) { for (i, query) in list.queries.iter().enumerate() { if i > 0 { - value::write_group_comma(list.comma_spans.get(i - 1).map(|sp| to_span(sp).start), f); + value::write_group_comma(list.comma_spans.get(i - 1).map(|sp| sp.start), f); write!(f, soft_line_break_or_space()); } write_media_query(query, f); @@ -1944,7 +1936,7 @@ fn write_media_query<'a>(query: &MediaQuery<'a>, f: &mut CssFormatter<'_, 'a>) { MediaQuery::ConditionOnly(condition) => write_media_condition(condition, f), MediaQuery::WithType(with_type) => { // A `//` among the words breaks the query - let query_end = to_span(query.span()).end; + let query_end = query.span().end; let write_word = |span: oxc_span::Span, f: &mut CssFormatter<'_, 'a>| { value::write_with_comments(span, query_end, f, |f| { write_maybe_lowercase(source.text_for(&span), f); @@ -1964,16 +1956,13 @@ fn write_media_query<'a>(query: &MediaQuery<'a>, f: &mut CssFormatter<'_, 'a>) { if is_glued_media_type_and(condition) && let Some(first) = condition.condition.conditions.first() { - let word = to_span(&oxc_css_parser::pos::Span { - start: condition.and.span().start, - end: first.span().end, - }); + let word = oxc_span::Span::new(condition.and.span().start, first.span().end); value::write_adjusted_verbatim(source.text_for(&word), f); write_media_condition_from(&condition.condition, 1, f); } else { write_word(to_span(condition.and.span()), f); write!(f, space()); - value::flush_value_comments(to_span(condition.condition.span()).start, f); + value::flush_value_comments(condition.condition.span().start, f); write_media_condition(&condition.condition, f); } } @@ -2005,7 +1994,7 @@ fn write_media_condition_from<'a>( skip: usize, f: &mut CssFormatter<'_, 'a>, ) { - let condition_end = to_span(condition.span()).end; + let condition_end = condition.span().end; for (i, kind) in condition.conditions.iter().enumerate().skip(skip) { if i > 0 { write!(f, space()); @@ -2024,11 +2013,8 @@ fn write_media_condition_kind<'a>(kind: &MediaConditionKind<'a>, f: &mut CssForm let source = f.context().source_text(); let (keyword, in_parens) = split_media_condition_kind(kind); if let Some(keyword) = keyword { - if is_glued(keyword.span(), &in_parens.span) { - let word = to_span(&oxc_css_parser::pos::Span { - start: keyword.span().start, - end: in_parens.span.end, - }); + if is_glued(keyword.span(), in_parens.span) { + let word = oxc_span::Span::new(keyword.span().start, in_parens.span.end); value::write_adjusted_verbatim(source.text_for(&word), f); return; } @@ -2050,7 +2036,7 @@ fn split_media_condition_kind<'b, 'a>( } fn is_glued_media_type_and(condition: &MediaConditionAfterMediaType<'_>) -> bool { - is_glued(condition.and.span(), &condition.condition.span) + is_glued(condition.and.span(), condition.condition.span) } fn write_media_in_parens<'a>(in_parens: &MediaInParens<'a>, f: &mut CssFormatter<'_, 'a>) { @@ -2078,7 +2064,7 @@ fn write_media_in_parens<'a>(in_parens: &MediaInParens<'a>, f: &mut CssFormatter // Lenient bare operand (`print and speech`): // `oxc-css-parser` accepts an unparenthesized ident as ; // the source shape must survive, so only reprint parens that exist. - if f.context().source_text().text_for(&to_span(&in_parens.span)).starts_with('(') { + if f.context().source_text().text_for(&to_span(in_parens.span)).starts_with('(') { write_general_enclosed(seq, f); } else { write_token_seq_normalized(seq, f); @@ -2116,13 +2102,13 @@ fn write_token_seq_normalized<'a>(seq: &TokenSeq<'a>, f: &mut CssFormatter<'_, ' | Token::Colon(_), ) => false, (Token::Comma(_) | Token::Colon(_), _) => true, - _ => to_span(&prev.span).end != to_span(&tok.span).start, + _ => !is_glued(prev.span, tok.span), }; if needs_space { write!(f, space()); } } - let span = to_span(&tok.span); + let span = to_span(tok.span); write!(f, text(source.text_for(&span))); } } @@ -2189,7 +2175,7 @@ fn write_supports_condition<'a>(condition: &SupportsCondition<'a>, f: &mut CssFo // A fill of keywords and parenthesized terms: // a long condition breaks AFTER `and`/`or`, one indent in // (`postcss-values` prints the params as a value group, so each word/paren is its own fill entry). - let end = to_span(condition.span()).end; + let end = condition.span().end; let keyword = |keyword| format_with(move |f| write_supports_keyword(keyword, end, f)); let term = |in_parens| format_with(move |f| write_supports_term(in_parens, end, f)); let not = |not| format_with(move |f| write_supports_not(not, end, f)); @@ -2270,7 +2256,7 @@ fn write_supports_in_parens<'a>(in_parens: &SupportsInParens<'a>, f: &mut CssFor // An inline comment inside `selector(...)` forces the parens // open and trails the selector. // oxc-css-parser's span may stop at the selector; scan to the `)`. - let span_end = to_span(in_parens.span()).end; + let span_end = in_parens.span().end; let bytes = source.as_bytes(); let mut scan = span_end.saturating_sub(1) as usize; while scan < bytes.len() && bytes[scan] != b')' { diff --git a/crates/oxc_formatter_css/src/print/less.rs b/crates/oxc_formatter_css/src/print/less.rs index 50f09e7497..78691a94f7 100644 --- a/crates/oxc_formatter_css/src/print/less.rs +++ b/crates/oxc_formatter_css/src/print/less.rs @@ -38,10 +38,10 @@ pub(super) fn write_less_variable_declaration<'a>( write!(f, "@"); let name_span = to_span(decl.name.name.span()); write!(f, text(source.text_for(&name_span))); - let colon_end = to_span(&decl.colon_span).end; + let colon_end = decl.colon_span.end; // Inline comments around the colon make postcss-less treat this as a plain at-rule: // the raw text is kept and the block loses its `;`. - let value_start = to_span(decl.value.span()).start; + let value_start = decl.value.span().start; let inline_before_colon = f.context().comments().iter_before(colon_end).any(|c| c.inline); let inline_after_colon = f .context() @@ -90,7 +90,7 @@ pub(super) fn write_less_variable_declaration<'a>( } else { write!(f, body); } - write_terminator_tail_comments(to_span(&decl.span).end, f); + write_terminator_tail_comments(decl.span.end, f); true } @@ -109,8 +109,8 @@ pub(super) fn write_less_mixin_definition<'a>( def: &LessMixinDefinition<'a>, f: &mut CssFormatter<'_, 'a>, ) { - let start = to_span(def.name.span()).start; - if has_comments_between(start, to_span(&def.block.span).start, f) { + let start = def.name.span().start; + if has_comments_between(start, def.block.span.start, f) { write_verbatim_prelude_rule(start, &def.block, true, f); return; } @@ -118,11 +118,11 @@ pub(super) fn write_less_mixin_definition<'a>( // The prelude breaks like a selector: at its word gaps, all at once, one indent in let prelude = format_with(|f: &mut CssFormatter<'_, 'a>| { write_mixin_name(&def.name, f); - if !is_glued(def.name.span(), &def.params.span) { + if !is_glued(def.name.span(), def.params.span) { write!(f, soft_line_break_or_space()); } let source = f.context().source_text(); - value::write_adjusted_verbatim(source.text_for(&to_span(&def.params.span)), f); + value::write_adjusted_verbatim(source.text_for(&to_span(def.params.span)), f); if let Some(guard) = &def.guard { write_less_guard(guard, f); } @@ -138,8 +138,8 @@ pub(super) fn write_less_conditional_qualified_rule<'a>( rule: &LessConditionalQualifiedRule<'a>, f: &mut CssFormatter<'_, 'a>, ) { - let start = to_span(&rule.span).start; - if has_comments_between(start, to_span(&rule.block.span).start, f) { + let start = rule.span.start; + if has_comments_between(start, rule.block.span.start, f) { write_verbatim_prelude_rule(start, &rule.block, true, f); return; } @@ -162,7 +162,7 @@ fn write_less_guard<'a>(guard: &LessConditions<'a>, f: &mut CssFormatter<'_, 'a> for (i, condition) in guard.conditions.iter().enumerate() { if i > 0 { write!(f, [",", soft_line_break_or_space()]); - } else if !is_glued(&guard.when_span, condition.span()) { + } else if !is_glued(guard.when_span, condition.span()) { write!(f, space()); } value::write_adjusted_verbatim(source.text_for(&to_span(condition.span())), f); @@ -181,8 +181,8 @@ pub(super) fn write_less_mixin_call_statement<'a>( f: &mut CssFormatter<'_, 'a>, ) { let source = f.context().source_text(); - let span = to_span(&call.span); - let end = call.important.as_ref().map_or(span.end, |imp| to_span(&imp.span).start); + let span = call.span; + let end = call.important.as_ref().map_or(span.end, |imp| imp.span.start); let raw = source.slice_range(span.start, end).trim_end(); let _ = f.context().comments().take_before(end); value::write_adjusted_verbatim(raw, f); @@ -197,7 +197,7 @@ fn write_less_mixin_call<'a>(call: &LessMixinCall<'a>, f: &mut CssFormatter<'_, let source = f.context().source_text(); for child in &call.callee.children { if let Some(combinator) = &child.combinator { - let span = to_span(&combinator.span); + let span = to_span(combinator.span); write!(f, [space(), text(source.text_for(&span)), space()]); } write_mixin_name(&child.name, f); @@ -236,7 +236,7 @@ fn write_less_condition<'a>(condition: &LessCondition<'a>, f: &mut CssFormatter< match condition { LessCondition::Binary(binary) => { write_less_condition(&binary.left, f); - let op_span = to_span(&binary.op.span); + let op_span = to_span(binary.op.span); write!(f, [space(), text(source.text_for(&op_span)), space()]); write_less_condition(&binary.right, f); } @@ -287,7 +287,7 @@ fn write_less_detached_ruleset<'a>( f: &mut CssFormatter<'_, 'a>, ) { // Block comments before `{` stay inline; `//` ends its line. - let block_start = to_span(&ruleset.block.span).start; + let block_start = ruleset.block.span.start; for &comment in f.context().comments().take_before(block_start) { write!(f, FormatCommentBeforeContent::new(comment, BlockCommentAfter::Space)); } diff --git a/crates/oxc_formatter_css/src/print/mod.rs b/crates/oxc_formatter_css/src/print/mod.rs index d66fdece41..dc406f49ee 100644 --- a/crates/oxc_formatter_css/src/print/mod.rs +++ b/crates/oxc_formatter_css/src/print/mod.rs @@ -56,7 +56,7 @@ fn write_placeholder<'a>(placeholder: &Placeholder<'a>, f: &mut CssFormatter<'_, /// No source gap between two spans (postcss's `hasEmptyRawBefore`): /// the signal for a glued `and(` / `not(`, a `(` that makes a function, and the like. -pub fn is_glued(left: &oxc_css_parser::pos::Span, right: &oxc_css_parser::pos::Span) -> bool { +pub fn is_glued(left: oxc_css_parser::pos::Span, right: oxc_css_parser::pos::Span) -> bool { left.end == right.start } diff --git a/crates/oxc_formatter_css/src/print/postcss_simple_vars.rs b/crates/oxc_formatter_css/src/print/postcss_simple_vars.rs index a496c83173..e9ded64d39 100644 --- a/crates/oxc_formatter_css/src/print/postcss_simple_vars.rs +++ b/crates/oxc_formatter_css/src/print/postcss_simple_vars.rs @@ -26,17 +26,17 @@ pub(super) fn write_postcss_simple_var_declaration<'a>( f: &mut CssFormatter<'_, 'a>, ) { write_postcss_simple_var(&decl.name, f); - statement::write_colon_run(to_span(&decl.name.span).end, to_span(&decl.colon_span).end, f); + statement::write_colon_run(decl.name.span.end, decl.colon_span.end, f); // An empty value prints as `$x:;`, as Prettier does. - let end = to_span(&decl.span).end; + let end = decl.span.end; if !decl.value.is_empty() { write!(f, space()); if decl.value_is_raw { // postcss-simple-vars substitutes the value textually, // so re-spacing a raw fallback would change the substituted token stream // (DIVERGENCES.md#postcss-simple-var-raw-verbatim). - let value_start = to_span(decl.value[0].span()).start; + let value_start = decl.value[0].span().start; value::write_verbatim_value(Span::new(value_start, end), f); } else { // The typed value stream (including any trailing `ImportantAnnotation` pushed by the parser) @@ -53,6 +53,6 @@ pub(super) fn write_postcss_simple_var<'a>( f: &mut CssFormatter<'_, 'a>, ) { let source = f.context().source_text(); - let span = to_span(&variable.span); + let span = to_span(variable.span); write!(f, text(source.text_for(&span))); } diff --git a/crates/oxc_formatter_css/src/print/scss.rs b/crates/oxc_formatter_css/src/print/scss.rs index 699eb1f146..9e51b3c78b 100644 --- a/crates/oxc_formatter_css/src/print/scss.rs +++ b/crates/oxc_formatter_css/src/print/scss.rs @@ -20,7 +20,7 @@ use crate::{ comments::{self, BlockCommentAfter, FormatCommentBeforeContent, FormatLineCommentSuffix}, format::to_span, print::{ - CssFormatter, format_with, statement, + CssFormatter, format_with, is_glued, statement, value::{self, ValueContext}, }, }; @@ -38,7 +38,7 @@ pub(super) fn write_sass_variable_declaration<'a>( write!(f, "$"); let name_span = to_span(decl.name.name.span()); write!(f, text(source.text_for(&name_span))); - statement::write_colon_run(name_span.end, to_span(&decl.colon_span).end, f); + statement::write_colon_run(name_span.end, decl.colon_span.end, f); write!(f, space()); let ctx = ValueContext { decl_prop: Some("$"), map_break: true, ..ValueContext::default() }; @@ -46,7 +46,7 @@ pub(super) fn write_sass_variable_declaration<'a>( // a same-line `//` stays on the colon's (`$x: // c`), an own-line one stays own-line, // and the value then continues one level under the name. // A hard-broken comma list indents itself and claims an own-line lead inside that indent. - let value_start = to_span(decl.value.span()).start; + let value_start = decl.value.span().start; let inline_lead = f.context().comments().iter_before(value_start).any(|c| c.inline); let own_line_lead = f .context() @@ -78,7 +78,7 @@ pub(super) fn write_sass_variable_declaration<'a>( write!(f, space()); write!(f, text(source.text_for(&span))); } - statement::write_terminator_tail_comments(to_span(decl.span()).end, f); + statement::write_terminator_tail_comments(decl.span().end, f); } /// `("a",)` / `$x: "a",`: a single element followed by a comma is a one-element LIST in Sass, @@ -92,7 +92,7 @@ pub(super) fn is_single_item_list(list: &SassList<'_>) -> bool { /// Start offset of the source comma after element `i` (for [`value::write_group_comma`]). pub(super) fn list_comma_start(list: &SassList<'_>, i: usize) -> Option { - list.comma_spans.as_ref().and_then(|s| s.get(i)).map(|sp| to_span(sp).start) + list.comma_spans.as_ref().and_then(|s| s.get(i)).map(|sp| sp.start) } /// Whether [`write_top_level_value`] prints `value` one comma entry per line: @@ -111,7 +111,7 @@ pub(super) fn top_level_value_breaks_hard<'a>( if elements.len() < 2 || ctx.decl_prop.is_some_and(|p| p.starts_with("--")) { return false; } - let value_span = to_span(value.span()); + let value_span = value.span(); let has_comments = f .context() .comments() @@ -168,7 +168,7 @@ pub(super) fn write_top_level_value<'a>( } other => std::slice::from_ref(other), }; - (group, comma_spans.get(i).map(|sp| to_span(sp).start)) + (group, comma_spans.get(i).map(|sp| sp.start)) }) .collect(); let force_hard_line = top_level_value_breaks_hard(value, ctx, f); @@ -185,7 +185,7 @@ pub(super) fn write_top_level_list_element<'a>( ctx: ValueContext<'a>, f: &mut CssFormatter<'_, 'a>, ) { - value::flush_value_comments(to_span(value.span()).start, f); + value::flush_value_comments(value.span().start, f); write_top_level_value(value, ctx, f); } @@ -208,7 +208,7 @@ pub(super) fn write_sass_map<'a>( // Block comments stay inline when they fit (`$map: (/* c */);`); // a `//` comment glues to the current line but forces the `)` onto its own line, // like Prettier's `lineSuffix` + `lineSuffixBoundary` (Prettier #18535). - let r_paren = to_span(map.span()).end.saturating_sub(1); + let r_paren = map.span().end.saturating_sub(1); let tail: Vec = f.context().comments().take_before(r_paren).to_vec(); if tail.is_empty() { write!(f, ["(", ")"]); @@ -243,14 +243,11 @@ pub(super) fn write_sass_map<'a>( write!(f, soft_line_break()); for (i, item) in map.items.iter().enumerate() { if i > 0 { - value::write_group_comma( - map.comma_spans.get(i - 1).map(|sp| to_span(sp).start), - f, - ); + value::write_group_comma(map.comma_spans.get(i - 1).map(|sp| sp.start), f); // A blank line between items in the source is preserved // (Prettier's isNextLineEmpty → hardline). - let prev_end = to_span(map.items[i - 1].span()).end; - let start = to_span(item.span()).start; + let prev_end = map.items[i - 1].span().end; + let start = item.span().start; if comments::classify_gap(source.bytes_range(prev_end, start)) == comments::Gap::Blank { @@ -293,17 +290,17 @@ pub(super) fn write_sass_map<'a>( return; } let trailing = f.options().allow_trailing_comma(); - let r_paren = to_span(map.span()).end.saturating_sub(1); + let r_paren = map.span().end.saturating_sub(1); let body = format_with(move |f: &mut CssFormatter<'_, 'a>| { write!(f, hard_line_break()); let source = f.context().source_text(); for (i, item) in map.items.iter().enumerate() { if i > 0 { - value::write_group_comma(map.comma_spans.get(i - 1).map(|sp| to_span(sp).start), f); + value::write_group_comma(map.comma_spans.get(i - 1).map(|sp| sp.start), f); write!(f, hard_line_break()); // Preserve one blank line between items - let prev_end = to_span(map.items[i - 1].span()).end; - let item_start = to_span(item.span()).start; + let prev_end = map.items[i - 1].span().end; + let item_start = item.span().start; let next_start = f .context() .comments() @@ -326,8 +323,8 @@ pub(super) fn write_sass_map<'a>( // Comments between items: // block comments join the item when the pair fits on one line (Prettier's fill); // `//` comments and pairs that don't fit keep their own line. - let item_start = to_span(item.span()).start; - let item_width = to_span(item.span()).end - item_start; + let item_start = item.span().start; + let item_width = item.span().end - item_start; for &comment in f.context().comments().take_before(item_start) { let comment_width = comment.span.end - comment.span.start; let fits = !value_is_block @@ -394,14 +391,14 @@ pub(super) fn write_sass_map<'a>( // and the source comma preceded the comment (true next-slot comment). if let Some(last) = map.items.last() { let source_comma_start = - map.comma_spans.get(map.items.len() - 1).map_or(u32::MAX, |sp| to_span(sp).start); + map.comma_spans.get(map.items.len() - 1).map_or(u32::MAX, |sp| sp.start); // Only inside function/include arguments does the comment move // to the next slot; `$map:` declarations keep it attached. let next_slot = trailing && ctx.in_args && f.context().comments().peek().is_some_and(|c| c.span.start > source_comma_start); if !next_slot { - value::flush_same_line_comments(to_span(last.span()).end, r_paren, f); + value::flush_same_line_comments(last.span().end, r_paren, f); } } // The rest goes below the last item, one line per `//`; @@ -458,8 +455,8 @@ pub(super) fn write_sass_list<'a>( /// (`$k, $v in (a), (b), (c)`), filling and indenting like a value. pub(super) fn write_sass_each<'a>(each: &SassEach<'a>, f: &mut CssFormatter<'_, 'a>) { let source = f.context().source_text(); - let in_span = to_span(&each.in_span); - let in_tight = in_span.end == to_span(each.expr.span()).start; + let in_span = each.in_span; + let in_tight = is_glued(in_span, each.expr.span()); // Comma-list expr: the first element joins the `... in` entry, // the rest are separate fill entries (mirrors postcss's flat comma groups). @@ -470,7 +467,7 @@ pub(super) fn write_sass_each<'a>(each: &SassEach<'a>, f: &mut CssFormatter<'_, expr => (std::slice::from_ref(expr), None), }; let expr_comma_start = - move |i: usize| expr_comma_spans.and_then(|s| s.get(i)).map(|sp| to_span(sp).start); + move |i: usize| expr_comma_spans.and_then(|s| s.get(i)).map(|sp| sp.start); let body = format_with(move |f: &mut CssFormatter<'_, 'a>| { let mut filler = f.fill(); @@ -513,10 +510,7 @@ pub(super) fn write_sass_each<'a>(each: &SassEach<'a>, f: &mut CssFormatter<'_, write!(f, group(&indent(&tail))); } else { write!(f, text(source.text_for(&span))); - value::write_group_comma( - each.comma_spans.get(i).map(|sp| to_span(sp).start), - f, - ); + value::write_group_comma(each.comma_spans.get(i).map(|sp| sp.start), f); } }); filler.entry(&soft_line_break_or_space(), &content); @@ -569,9 +563,8 @@ pub(super) fn write_sass_function<'a>(function: &SassFunction<'a>, f: &mut CssFo } fn write_sass_parameters<'a>(parameters: &SassParameters<'a>, f: &mut CssFormatter<'_, 'a>) { - let comma_start = - |i: usize| parameters.comma_spans.get(i).map(|sp: &oxc_css_parser::Span| to_span(sp).start); - let r_paren = to_span(¶meters.span).end.saturating_sub(1); + let comma_start = |i: usize| parameters.comma_spans.get(i).map(|sp| sp.start); + let r_paren = parameters.span.end.saturating_sub(1); let body = format_with(move |f: &mut CssFormatter<'_, 'a>| { write!(f, soft_line_break()); for (i, param) in parameters.params.iter().enumerate() { @@ -614,7 +607,7 @@ pub(super) fn write_sass_include<'a>(include: &SassInclude<'a>, f: &mut CssForma if let Some(arguments) = &include.arguments { let args = &arguments.args; let comma_spans = &arguments.comma_spans; - let r_paren = to_span(&arguments.span).end.saturating_sub(1); + let r_paren = arguments.span.end.saturating_sub(1); // Same first-argument gate as `write_function` (which see), over typed args let first_arg_is_kw = args.first().is_some_and(|a| matches!(a, ComponentValue::SassKeywordArgument(_))); @@ -623,7 +616,7 @@ pub(super) fn write_sass_include<'a>(include: &SassInclude<'a>, f: &mut CssForma write!(f, soft_line_break()); for (i, arg) in args.iter().enumerate() { if i > 0 { - value::write_group_comma(comma_spans.get(i - 1).map(|sp| to_span(sp).start), f); + value::write_group_comma(comma_spans.get(i - 1).map(|sp| sp.start), f); // Preserve a blank line, but only after a multi-part argument // (Prettier checks `value-comma_group`s only). let prev = &args[i - 1]; @@ -631,8 +624,8 @@ pub(super) fn write_sass_include<'a>(include: &SassInclude<'a>, f: &mut CssForma prev, ComponentValue::SassKeywordArgument(_) | ComponentValue::SassList(_) ); - let prev_end = to_span(prev.span()).end; - let start = to_span(arg.span()).start; + let prev_end = prev.span().end; + let start = arg.span().start; if prev_is_group && comments::classify_gap(source.bytes_range(prev_end, start)) == comments::Gap::Blank @@ -673,9 +666,9 @@ pub(super) fn write_sass_include<'a>(include: &SassInclude<'a>, f: &mut CssForma pub(super) fn write_sass_if_at_rule<'a>(if_rule: &SassIfAtRule<'a>, f: &mut CssFormatter<'_, 'a>) { write!(f, ["@if", space()]); write_control_condition(&if_rule.if_clause.condition, f); - write_block_after(to_span(if_rule.if_clause.condition.span()).end, &if_rule.if_clause.block, f); + write_block_after(if_rule.if_clause.condition.span().end, &if_rule.if_clause.block, f); for (clause, else_span) in if_rule.else_if_clauses.iter().zip(&if_rule.else_spans) { - write_else_join(to_span(else_span).start, f); + write_else_join(else_span.start, f); // `if` is a value word in postcss, so the condition may break after it if matches!(clause.condition, ComponentValue::SassParenthesizedExpression(_)) { write!(f, ["if", space()]); @@ -683,11 +676,12 @@ pub(super) fn write_sass_if_at_rule<'a>(if_rule: &SassIfAtRule<'a>, f: &mut CssF } else { write_condition_chain(Some("if"), &clause.condition, f); } - write_block_after(to_span(clause.condition.span()).end, &clause.block, f); + write_block_after(clause.condition.span().end, &clause.block, f); } if let Some(else_block) = &if_rule.else_clause { - let block_start = to_span(&else_block.span).start; - let else_span = if_rule.else_spans.last().map_or(Span::empty(block_start), to_span); + let block_start = else_block.span.start; + let else_span = + if_rule.else_spans.last().map_or(Span::empty(block_start), |&sp| to_span(sp)); write_else_join(else_span.start, f); write_block_after(else_span.end, else_block, f); } @@ -696,7 +690,7 @@ pub(super) fn write_sass_if_at_rule<'a>(if_rule: &SassIfAtRule<'a>, f: &mut CssF /// A clause's block, with the comments between its head (ending at `head_end`) /// and `{` placed first (`@if $a // c\n{`), so they do not fall into the block as its first leading comment. fn write_block_after<'a>(head_end: u32, block: &SimpleBlock<'a>, f: &mut CssFormatter<'_, 'a>) { - value::write_comments_before_block(head_end, to_span(&block.span).start, f); + value::write_comments_before_block(head_end, block.span.start, f); statement::write_block(block, f); } @@ -756,13 +750,13 @@ fn flatten_condition<'b, 'a>(cond: &'b ComponentValue<'a>, out: &mut Vec { flatten_condition(&binary.left, out); - out.push(CondPart::Op(to_span(&binary.op.span))); + out.push(CondPart::Op(to_span(binary.op.span))); flatten_condition(&binary.right, out); } ComponentValue::SassUnaryExpression(unary) if matches!(unary.op.kind, SassUnaryOperatorKind::Not) => { - out.push(CondPart::Op(to_span(&unary.op.span))); + out.push(CondPart::Op(to_span(unary.op.span))); flatten_condition(&unary.expr, out); } other => out.push(CondPart::Value(other)), @@ -840,7 +834,7 @@ pub(super) fn write_sass_use<'a>(sass_use: &SassUse<'a>, f: &mut CssFormatter<'_ filler.entry(&soft_line_break_or_space(), &path); if let Some(namespace) = &sass_use.namespace { let as_kw = format_with(move |f: &mut CssFormatter<'_, 'a>| { - value::flush_value_comments(to_span(&namespace.as_span).start, f); + value::flush_value_comments(namespace.as_span.start, f); write!(f, "as"); }); filler.entry(&soft_line_break_or_space(), &as_kw); @@ -849,7 +843,7 @@ pub(super) fn write_sass_use<'a>(sass_use: &SassUse<'a>, f: &mut CssFormatter<'_ value::write_text_with_leading_comments(to_span(ident.span()), f); } SassUseNamespaceKind::Unnamed(star) => { - value::flush_value_comments(to_span(&star.span).start, f); + value::flush_value_comments(star.span.start, f); write!(f, "*"); } }); @@ -877,7 +871,7 @@ pub(super) fn write_sass_forward<'a>(forward: &SassForward<'a>, f: &mut CssForma filler.entry(&soft_line_break_or_space(), &path); if let Some(prefix) = &forward.prefix { let as_kw = format_with(move |f: &mut CssFormatter<'_, 'a>| { - value::flush_value_comments(to_span(&prefix.as_span).start, f); + value::flush_value_comments(prefix.as_span.start, f); write!(f, "as"); }); filler.entry(&soft_line_break_or_space(), &as_kw); @@ -889,7 +883,7 @@ pub(super) fn write_sass_forward<'a>(forward: &SassForward<'a>, f: &mut CssForma } if let Some(visibility) = &forward.visibility { let keyword = format_with(move |f: &mut CssFormatter<'_, 'a>| { - value::flush_value_comments(to_span(&visibility.modifier.span).start, f); + value::flush_value_comments(visibility.modifier.span.start, f); match visibility.modifier.kind { SassForwardVisibilityModifierKind::Show => write!(f, "show"), SassForwardVisibilityModifierKind::Hide => write!(f, "hide"), @@ -901,10 +895,7 @@ pub(super) fn write_sass_forward<'a>(forward: &SassForward<'a>, f: &mut CssForma let entry = format_with(move |f: &mut CssFormatter<'_, 'a>| { value::write_text_with_leading_comments(to_span(member.span()), f); if i + 1 < members.len() { - value::write_group_comma( - Some(to_span(&visibility.comma_spans[i]).start), - f, - ); + value::write_group_comma(Some(visibility.comma_spans[i].start), f); } }); filler.entry(&soft_line_break_or_space(), &entry); @@ -945,16 +936,16 @@ fn write_sass_module_config<'a>(config: &SassModuleConfig<'a>, f: &mut CssFormat let source = f.context().source_text(); // Comments between the module path and `with` stay glued to the head // (`@use "a" /* c */ with (`). - write_same_line_trailing_comments(to_span(&config.with_span).start, f); + write_same_line_trailing_comments(config.with_span.start, f); write!(f, [space(), "with", space(), "("]); let body = format_with(move |f: &mut CssFormatter<'_, 'a>| { write!(f, hard_line_break()); for (i, item) in config.items.iter().enumerate() { - let item_span = to_span(&item.span); + let item_span = item.span; if i > 0 { // Prettier's `isNextLineEmpty` after an item: // a blank line after the comma survives, measured up to the next item or its first leading comment. - let comma_end = to_span(&config.comma_spans[i - 1]).end; + let comma_end = config.comma_spans[i - 1].end; // Clamped: an own-line comment left pending by the previous // item's trailing flush can start BEFORE the comma. let next_start = f @@ -987,11 +978,11 @@ fn write_sass_module_config<'a>(config: &SassModuleConfig<'a>, f: &mut CssFormat write!(f, text(source.text_for(&span))); } if i + 1 < config.items.len() { - value::write_group_comma(Some(to_span(&config.comma_spans[i]).start), f); + value::write_group_comma(Some(config.comma_spans[i].start), f); } else { // Comments before `)` (past a trailing comma, which is dropped): // same-line ones glue to the last item, own-line ones keep their line. - let bound = to_span(&config.span).end; + let bound = config.span.end; let mut prev_end = write_same_line_trailing_comments(bound, f).unwrap_or(item_span.end); for &comment in f.context().comments().take_before(bound) { diff --git a/crates/oxc_formatter_css/src/print/selector.rs b/crates/oxc_formatter_css/src/print/selector.rs index 131803f4eb..cc322319fe 100644 --- a/crates/oxc_formatter_css/src/print/selector.rs +++ b/crates/oxc_formatter_css/src/print/selector.rs @@ -87,12 +87,12 @@ pub(super) fn write_selector_list<'a>( // A comment trailing the comma stays on the same line: // a lone block comment (`a, /* b */ c`), or a run ending in a `//` (`a, /* b */ // c`). // A `//` ends the line itself; the separator below then merges into its break. - let next_start = to_span(complex.span()).start; + let next_start = complex.span().start; if let Some(comment) = f.context().comments().peek() && comment.span.end <= next_start { let source = f.context().source_text(); - let prev_end = to_span(list.selectors[i - 1].span()).end; + let prev_end = list.selectors[i - 1].span().end; if comments::classify_gap(source.bytes_range(prev_end, comment.span.start)) == comments::Gap::None { @@ -111,13 +111,13 @@ pub(super) fn write_selector_list<'a>( } } // Comments on their own line between selectors - let start = to_span(complex.span()).start; + let start = complex.span().start; for &comment in f.context().comments().take_before(start) { write!(f, FormatCommentBeforeContent::new(comment, BlockCommentAfter::HardLine)); } if placeholder_idx == Some(i) { let source = f.context().source_text(); - let end = to_span(list.selectors[list.selectors.len() - 1].span()).end; + let end = list.selectors[list.selectors.len() - 1].span().end; let raw = source.slice_range(start, end); // Comments inside the chunk are part of the verbatim text let _ = f.context().comments().take_before(end); @@ -213,7 +213,7 @@ fn write_combinator( // Deprecated shadow-DOM combinators (`/deep/` and less.js's `/name/` (`/shadow/`)); // print as written: the tools give a meaning match it case-sensitively CombinatorKind::Deep | CombinatorKind::Slashed => { - let span = to_span(&combinator.span); + let span = to_span(combinator.span); write!(f, text(f.context().source_text().text_for(&span))); } CombinatorKind::ShadowChild => write!(f, "^"), @@ -526,10 +526,10 @@ fn write_nth<'a>(nth: &Nth<'a>, f: &mut CssFormatter<'_, 'a>) { } if let Some(matcher) = &nth.matcher { write!(f, " "); - let matcher_span = to_span(&matcher.span); + let matcher_span = to_span(matcher.span); if let Some(selector) = &matcher.selector { // The `of` keyword as written, then the selector list - let keyword_end = to_span(selector.span()).start; + let keyword_end = selector.span().start; let keyword = source.slice_range(matcher_span.start, keyword_end).trim_ascii_end(); write!(f, [text(keyword), " "]); write_selector_list_inline(selector, f); diff --git a/crates/oxc_formatter_css/src/print/statement.rs b/crates/oxc_formatter_css/src/print/statement.rs index 6407adf668..78cdae66f6 100644 --- a/crates/oxc_formatter_css/src/print/statement.rs +++ b/crates/oxc_formatter_css/src/print/statement.rs @@ -23,23 +23,18 @@ use crate::{ }, format::to_span, print::{ - CssFormatter, at_rule, format_with, has_comments_between, less, postcss_simple_vars, scss, - selector, + CssFormatter, at_rule, format_with, has_comments_between, is_glued, less, + postcss_simple_vars, scss, selector, value::{self, ValueContext}, write_maybe_lowercase, }, }; -/// Start offset of a statement. -pub(super) fn stmt_start(stmt: &Statement<'_>) -> u32 { - to_span(stmt.span()).start -} - /// End offset of a statement, extended over a trailing semicolon /// (oxc-css-parser's spans exclude it; postcss's `locEnd` includes it, /// and blank-line detection counts from after the `;`). pub(super) fn stmt_end(stmt: &Statement<'_>, f: &CssFormatter<'_, '_>) -> u32 { - end_with_semicolon(to_span(stmt.span()).end, f) + end_with_semicolon(stmt.span().end, f) } /// Extends `end` over any whitespace, comments and a final `;` in the source. @@ -55,7 +50,7 @@ pub(super) fn params_region_end<'a>( let with_semi = end_with_semicolon(span_end, f); if with_semi > span_end { with_semi - 1 } else { span_end } }, - |block| to_span(&block.span).start, + |block| block.span.start, ) } @@ -104,7 +99,7 @@ pub(super) fn write_statement_sequence_bounded<'a>( // Only the attribute's own declarations, not a block's let style_attribute = upper == u32::MAX && f.context().is_style_attribute(); for (i, stmt) in statements.iter().enumerate() { - let start = stmt_start(stmt); + let start = stmt.span().start; if i > 0 { let prev_end = stmt_end(&statements[i - 1], f); // Trailing comment on the same line as the previous statement @@ -146,7 +141,7 @@ pub(super) fn write_statement_sequence_bounded<'a>( let end = stmt_end(stmt, f); // `stmt_end` already consumes a trailing `;`, // so a source `;` is present exactly when it advanced past the raw statement span. - let has_source_semicolon = end > to_span(stmt.span()).end; + let has_source_semicolon = end > stmt.span().end; if is_suppressed { write!(f, text(source.slice_range(start, end))); } else if swallowed && let Statement::Declaration(decl) = stmt { @@ -191,7 +186,7 @@ pub(super) fn write_statement<'a>(stmt: &Statement<'a>, f: &mut CssFormatter<'_, if decl.name.has_placeholder() { // A css-in-js `${foo}: ${bar}` declaration keeps the source `;` // (Prettier preserves it: `${foo}: ${bar};` vs `${foo}: ${bar}`). - let end = to_span(decl.span()).end; + let end = decl.span().end; if end_with_semicolon(end, f) > end { write!(f, ";"); } @@ -213,7 +208,7 @@ pub(super) fn write_statement<'a>(stmt: &Statement<'a>, f: &mut CssFormatter<'_, // Preserve a trailing `;` from the source // (Prettier keeps `${foo};` as `${foo};` and `${foo}` as `${foo}`): // the `;` is consumed by the statement separator, so recover it from the source gap. - let end = to_span(&placeholder.span).end; + let end = placeholder.span.end; if end_with_semicolon(end, f) > end { write!(f, ";"); } @@ -247,12 +242,9 @@ pub(super) fn write_statement<'a>(stmt: &Statement<'a>, f: &mut CssFormatter<'_, write!(f, "@"); let name_span = to_span(unknown.name.span()); write_maybe_lowercase(source.text_for(&name_span), f); - let region_end = - params_region_end(unknown.block.as_ref(), to_span(&unknown.span).end, f); - let prelude_start = unknown - .prelude - .as_ref() - .map_or(region_end, |prelude| to_span(prelude.span()).start); + let region_end = params_region_end(unknown.block.as_ref(), unknown.span.end, f); + let prelude_start = + unknown.prelude.as_ref().map_or(region_end, |prelude| prelude.span().start); at_rule::write_verbatim_at_rule_tail( name_span.end, prelude_start, @@ -267,16 +259,16 @@ pub(super) fn write_statement<'a>(stmt: &Statement<'a>, f: &mut CssFormatter<'_, for (i, sel) in keyframe_block.selectors.iter().enumerate() { if i > 0 { value::write_group_comma( - keyframe_block.comma_spans.get(i - 1).map(|sp| to_span(sp).start), + keyframe_block.comma_spans.get(i - 1).map(|sp| sp.start), f, ); write!(f, hard_line_break()); } - value::flush_value_comments(to_span(sel.span()).start, f); + value::flush_value_comments(sel.span().start, f); selector::write_keyframe_selector(sel, f); } // Trailing comments before `{` stay on the selector line - value::flush_trailing_value_comments(to_span(keyframe_block.block.span()).start, f); + value::flush_trailing_value_comments(keyframe_block.block.span().start, f); write!(f, space()); write_block(&keyframe_block.block, f); } @@ -298,13 +290,13 @@ pub(super) fn write_statement<'a>(stmt: &Statement<'a>, f: &mut CssFormatter<'_, /// Mirrors Prettier's `css-rule`. fn write_qualified_rule<'a>(rule: &QualifiedRule<'a>, f: &mut CssFormatter<'_, 'a>) { - let sel_span = to_span(rule.selector.span()); - let block_start = to_span(rule.block.span()).start; + let sel_start = rule.selector.span().start; + let block_start = rule.block.span().start; // Comments inside the selector (both `//` and `/* */`) make Prettier print the raw selector verbatim (`selector-unknown`). // Reordering them would change which compound they annotate. // A trailing `//` comment pushes `{` to the next line. - if has_comments_between(sel_span.start, block_start, f) { - write_verbatim_prelude_rule(sel_span.start, &rule.block, false, f); + if has_comments_between(sel_start, block_start, f) { + write_verbatim_prelude_rule(sel_start, &rule.block, false, f); return; } selector::write_selector_list(&rule.selector, selector::SelectorListStyle::Hard, f); @@ -320,7 +312,7 @@ fn write_qualified_rule<'a>(rule: &QualifiedRule<'a>, f: &mut CssFormatter<'_, ' /// Prettier prints the prelude verbatim, interior whitespace and newlines included, /// unlike selector garbage mode's collapsing. fn write_unknown_qualified_rule<'a>(rule: &UnknownQualifiedRule<'a>, f: &mut CssFormatter<'_, 'a>) { - write_verbatim_prelude_rule(to_span(&rule.span).start, &rule.block, false, f); + write_verbatim_prelude_rule(rule.span.start, &rule.block, false, f); } /// Prelude printed verbatim from `start` to the block, @@ -336,7 +328,7 @@ pub(super) fn write_verbatim_prelude_rule<'a>( f: &mut CssFormatter<'_, 'a>, ) { let source = f.context().source_text(); - let block_start = to_span(&block.span).start; + let block_start = block.span.start; let raw = source.slice_range(start, block_start).trim_end(); let _ = f.context().comments().take_before(block_start); if adjust { @@ -381,9 +373,8 @@ pub(super) fn write_declaration<'a>(decl: &Declaration<'a>, f: &mut CssFormatter // Prettier prints the WHOLE `raws.between` // (prop → value, `:` and any comments included) trimmed but otherwise verbatim, // with a space before a leading `//` comment and a space before the value. - let colon_end = to_span(&decl.colon_span).end; - let between_upper = - if decl.value.is_empty() { colon_end } else { to_span(decl.value[0].span()).start }; + let colon_end = decl.colon_span.end; + let between_upper = if decl.value.is_empty() { colon_end } else { decl.value[0].span().start }; let between = source.slice_range(name_span.end, between_upper); let trimmed_between = between.trim_ascii(); // `lastLineHasInlineComment`: the value drops to the next line, @@ -395,7 +386,7 @@ pub(super) fn write_declaration<'a>(decl: &Declaration<'a>, f: &mut CssFormatter // (Prettier 3.9.5 #19517; the SPACED `+_` is ours alone, postcss-less cannot parse it). // Comments around the marker keep the verbatim path. let merge_marker = - decl.less_property_merge.as_ref().map(|merge| to_span(&merge.span)).filter(|span| { + decl.less_property_merge.as_ref().map(|merge| to_span(merge.span)).filter(|span| { source.slice_range(name_span.end, span.start).trim_ascii().is_empty() && source.slice_range(span.end, between_upper).trim_ascii() == ":" }); @@ -439,7 +430,7 @@ pub(super) fn write_declaration<'a>(decl: &Declaration<'a>, f: &mut CssFormatter // which is its value: `--x: ;` substitutes a space where `--x:;` substitutes nothing, so it is kept verbatim. // Scan up to the `;` in the source. if is_custom_property { - let colon_end = to_span(&decl.colon_span).end; + let colon_end = decl.colon_span.end; let bytes = source.as_bytes(); let mut i = colon_end as usize; while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') { @@ -454,8 +445,8 @@ pub(super) fn write_declaration<'a>(decl: &Declaration<'a>, f: &mut CssFormatter // `filter: progid:...` values are printed verbatim // (the parser accepts the prefix case-insensitively; postcss rejects an uppercase one). - let value_start = to_span(decl.value[0].span()).start; - let value_end = to_span(decl.value[decl.value.len() - 1].span()).end; + let value_start = decl.value[0].span().start; + let value_end = decl.value[decl.value.len() - 1].span().end; let value_text = source.slice_range(value_start, value_end); if value_text .as_bytes() @@ -475,7 +466,7 @@ pub(super) fn write_declaration<'a>(decl: &Declaration<'a>, f: &mut CssFormatter && decl.value.len() > 1 // Only when oxc-css-parser split ONE string apart (no gaps between parts). && decl.value.windows(2).all(|w| { - to_span(w[0].span()).end == to_span(w[1].span()).start + is_glued(w[0].span(), w[1].span()) }) { // A string containing SCSS interpolation that `oxc-css-parser` tokenized apart (`"#{".5"}"`): @@ -529,7 +520,7 @@ pub(super) fn write_declaration<'a>(decl: &Declaration<'a>, f: &mut CssFormatter // The `;` position bounds wrapped trailing comments. // Only when such comments exist (keeps simple declarations on the single-component fast path). - let decl_end_pre = to_span(decl.span()).end; + let decl_end_pre = decl.span().end; let semi = end_with_semicolon(decl_end_pre, f); let bound = if semi > decl_end_pre { semi - 1 } else { decl_end_pre }; // A single interpolated component is exempt, @@ -567,7 +558,7 @@ pub(super) fn write_declaration<'a>(decl: &Declaration<'a>, f: &mut CssFormatter if let Some(important) = &decl.important { value::write_trailing_important(important, f); } - write_terminator_tail_comments(to_span(decl.span()).end, f); + write_terminator_tail_comments(decl.span().end, f); } /// The run between a variable name and its `:`: normally just the colon, @@ -725,7 +716,7 @@ fn respace_value_tokens(v: &str) -> String { /// Mirrors Prettier's block printing: `{` + indented statements + `}`. /// An empty block prints as `{\n}`. pub(super) fn write_block<'a>(block: &SimpleBlock<'a>, f: &mut CssFormatter<'_, 'a>) { - let block_span = to_span(&block.span); + let block_span = block.span; let r_curly = block_span.end.saturating_sub(1); write!(f, "{"); diff --git a/crates/oxc_formatter_css/src/print/value.rs b/crates/oxc_formatter_css/src/print/value.rs index 18296c60b0..60172ff523 100644 --- a/crates/oxc_formatter_css/src/print/value.rs +++ b/crates/oxc_formatter_css/src/print/value.rs @@ -429,7 +429,7 @@ fn is_word_glued_number(values: &[ComponentValue<'_>], i: usize) -> bool { i > 0 && number_ish(&values[i]) && (is_word_like(&values[i - 1]) || number_ish(&values[i - 1])) - && to_span(values[i - 1].span()).end == to_span(values[i].span()).start + && is_glued(values[i - 1].span(), values[i].span()) } /// Is this number-ish or a font-size-capable function? (Prettier's `isPossibleFontSize`) @@ -524,7 +524,7 @@ pub(super) fn split_comma_groups<'b, 'a>( for (i, v) in values.iter().enumerate() { if let ComponentValue::TokenWithSpan(tok) = v { if depth == 0 && matches!(&tok.token, Token::Comma(_)) { - groups.push((&values[start..i], Some(to_span(v.span()).start))); + groups.push((&values[start..i], Some(v.span().start))); start = i + 1; } else { depth += token_depth_delta(&tok.token); @@ -532,7 +532,7 @@ pub(super) fn split_comma_groups<'b, 'a>( continue; } if is_comma(v) { - groups.push((&values[start..i], Some(to_span(v.span()).start))); + groups.push((&values[start..i], Some(v.span().start))); start = i + 1; } } @@ -570,8 +570,8 @@ pub(super) fn write_declaration_value<'a>( // `shouldBreakList` forces one per line when any group has multiple elements // (a real `value-comma_group` survives flattening, comments count as members) // and the property is not a custom property. - let value_start = to_span(values[0].span()).start; - let value_end = to_span(values[values.len() - 1].span()).end; + let value_start = values[0].span().start; + let value_end = values[values.len() - 1].span().end; let has_comments = f.context().comments().iter_before(value_end).any(|c| c.span.start >= value_start); let force_hard_line = !ctx.decl_prop.is_some_and(|p| p.starts_with("--")) @@ -775,9 +775,9 @@ pub(super) fn write_value_groups<'a>( // Comments between groups lead the group they precede // (a `//` on the previous comma's line already went with the comma, see `write_group_comma`); // own-line ones keep their line. - let lead: &'a [comments::CssComment] = group_values.first().map_or(&[], |first| { - f.context().comments().take_before(to_span(first.span()).start) - }); + let lead: &'a [comments::CssComment] = group_values + .first() + .map_or(&[], |first| f.context().comments().take_before(first.span().start)); if lead.is_empty() { write_comma_group(group_values, gctx, f); } else if i == 0 { @@ -801,8 +801,8 @@ pub(super) fn write_value_groups<'a>( let width = u32::from(f.options().line_width.value()); let source = f.context().source_text(); let chunk_len = run_end(group_values, 0, gctx, source); - let group_w = to_span(group_values[chunk_len - 1].span()).end - - to_span(group_values[0].span()).start + let group_w = group_values[chunk_len - 1].span().end + - group_values[0].span().start + u32::from(!is_last && chunk_len == group_values.len()); let mut x = 4u32; // hardline indent under the value for (k, &comment) in lead.iter().enumerate() { @@ -918,7 +918,7 @@ fn write_bracket_block<'a>( f: &mut CssFormatter<'_, 'a>, ) { let groups = split_comma_groups(&bracket.value); - let r_bracket = to_span(bracket.span()).end.saturating_sub(1); + let r_bracket = bracket.span().end.saturating_sub(1); // `[1,]` is a COMMA list, `[1]` a space list (dart-sass `list.separator`): // the source comma is the value, like `(1,)` (see `scss::is_single_item_list`). // A multi-element list drops it: the separator is comma either way. @@ -1027,13 +1027,12 @@ pub(super) fn write_important_annotation( important: &ImportantAnnotation<'_>, f: &mut CssFormatter<'_, '_>, ) { - let span = to_span(&important.span); - let ident_start = to_span(&important.ident.span).start; + let ident_start = important.ident.span.start; let has_inner_comment = f .context() .comments() .peek() - .is_some_and(|c| c.span.start >= span.start && c.span.end <= ident_start); + .is_some_and(|c| c.span.start >= important.span.start && c.span.end <= ident_start); if !has_inner_comment { write!(f, "!important"); return; @@ -1049,7 +1048,7 @@ pub(super) fn write_trailing_important( important: &ImportantAnnotation<'_>, f: &mut CssFormatter<'_, '_>, ) { - flush_trailing_value_comments(to_span(&important.span).start, f); + flush_trailing_value_comments(important.span.start, f); write!(f, space()); write_important_annotation(important, f); } @@ -1077,7 +1076,7 @@ pub(super) fn write_list_element<'a>( ctx: ValueContext<'a>, f: &mut CssFormatter<'_, 'a>, ) { - flush_value_comments(to_span(el.span()).start, f); + flush_value_comments(el.span().start, f); write_component_value(el, ctx, f); } @@ -1094,7 +1093,7 @@ pub(super) fn write_comma_group<'a>( // Comments leading the FIRST value flush at this level, before any group/indent opens: // `//` comment's forced hardline inside `indent(&body)` would drop the value one level deeper // and break the fill run after it. - flush_value_comments(to_span(values[0].span()).start, f); + flush_value_comments(values[0].span().start, f); // Prettier's `flattenGroups`: // a single-element comma group collapses to the element itself (no extra group/indent level). if values.len() == 1 && ctx.tail_bound.is_none() { @@ -1116,7 +1115,7 @@ pub(super) fn write_comma_group<'a>( // A single value WITH a tail bound routes through the fill below // so its trailing comments wrap. let source = f.context().source_text(); - let upper_bound = to_span(values[values.len() - 1].span()).end; + let upper_bound = values[values.len() - 1].span().end; // Grid values that break across source lines start on their own line // (Prettier's `didBreak` + `parts.unshift(hardline)`), // but breaks caused by inline comments in the gap don't count. @@ -1124,8 +1123,8 @@ pub(super) fn write_comma_group<'a>( let grid_did_break = ctx.is_grid() && (1..values.len()).any(|i| { separator_between(values, i, ctx, source) == Separator::Hard && { - let prev_end = to_span(values[i - 1].span()).end; - let start = to_span(values[i].span()).start; + let prev_end = values[i - 1].span().end; + let start = values[i].span().start; !f.context() .comments() .iter_before(upper_bound) @@ -1161,8 +1160,8 @@ pub(super) fn write_comma_group<'a>( if i == 0 { Separator::Line } else { separator_between(values, i, ctx, source) }; // An inline comment trailing the PREVIOUS run forces a break if i > 0 { - let prev_end = to_span(values[i - 1].span()).end; - let start = to_span(values[i].span()).start; + let prev_end = values[i - 1].span().end; + let start = values[i].span().start; if pending.iter().any(|c| { c.inline && c.span.start >= prev_end @@ -1175,8 +1174,8 @@ pub(super) fn write_comma_group<'a>( let run_end = run_end(values, i, ctx, source); let run_start = i; let run = &values[i..run_end]; - let start = to_span(values[i].span()).start; - let run_end_pos = to_span(values[run_end - 1].span()).end; + let start = values[i].span().start; + let run_end_pos = values[run_end - 1].span().end; let is_last_run = run_end == values.len(); let content = format_with(move |f: &mut CssFormatter<'_, 'a>| { flush_value_comments(start, f); @@ -1198,7 +1197,7 @@ pub(super) fn write_comma_group<'a>( // Same-line `//` comments stay attached to this entry // (a separate entry would break with the expanded group). if !is_last_run { - let next_start = to_span(values[run_end].span()).start; + let next_start = values[run_end].span().start; while let Some(comment) = f.context().comments().peek() { if !comment.inline || comment.span.start < run_end_pos @@ -1233,7 +1232,7 @@ pub(super) fn write_comma_group<'a>( // Block comments between runs become their own fill items, own-line ones included: // keeping those own-line would freeze a previously wrapped layout (= not idempotent). if !is_last_run { - let next_start = to_span(values[run_end].span()).start; + let next_start = values[run_end].span().start; for &comment in pending.iter().filter(|c| { !c.inline && c.span.start >= run_end_pos && c.span.end <= next_start }) { @@ -1337,8 +1336,8 @@ fn separator_between( // a glued pair (`Tight`/`Word`) is part of ONE postcss word (`sandstone.10`, `1fr/2fr`) // and survives verbatim in grid values too, grid never splits what other properties keep glued. if ctx.is_grid() && matches!(sep, Separator::Line | Separator::Space) { - let prev_end = to_span(values[i - 1].span()).end; - let curr_start = to_span(values[i].span()).start; + let prev_end = values[i - 1].span().end; + let curr_start = values[i].span().start; return if source.bytes_contain(prev_end, curr_start, b'\n') { Separator::Hard } else { @@ -1352,12 +1351,10 @@ fn separator_between( fn base_separator(values: &[ComponentValue<'_>], i: usize, ctx: ValueContext<'_>) -> Separator { let prev = &values[i - 1]; let curr = &values[i]; - let prev_span = to_span(prev.span()); - let curr_span = to_span(curr.span()); // `hasEmptyRawBefore(iNextNode)` - let gap_empty = prev_span.end == curr_span.start; + let gap_empty = is_glued(prev.span(), curr.span()); // `hasEmptyRawBefore(iNode)` - let prev_gap_empty = i >= 2 && to_span(values[i - 2].span()).end == prev_span.start; + let prev_gap_empty = i >= 2 && is_glued(values[i - 2].span(), prev.span()); // Solidus (`/`) spacing rules if is_solidus(curr) || is_solidus(prev) { @@ -1368,7 +1365,7 @@ fn base_separator(values: &[ComponentValue<'_>], i: usize, ctx: ValueContext<'_> // Fully glued `/` (`center/80%`, `12px/1.5`): one postcss word if is_solidus(curr) && gap_empty - && values.get(i + 1).is_none_or(|n| to_span(curr.span()).end == to_span(n.span()).start) + && values.get(i + 1).is_none_or(|n| is_glued(curr.span(), n.span())) { return Separator::Tight; } @@ -1621,11 +1618,11 @@ pub(super) fn write_component_value<'a>( // The spacing around `/` prints as written, as in a declaration value ComponentValue::Ratio(ratio) => { write_number(&ratio.numerator, f); - if !is_glued(&ratio.numerator.span, &ratio.solidus_span) { + if !is_glued(ratio.numerator.span, ratio.solidus_span) { write!(f, space()); } write!(f, "/"); - if !is_glued(&ratio.solidus_span, &ratio.denominator.span) { + if !is_glued(ratio.solidus_span, ratio.denominator.span) { write!(f, space()); } write_number(&ratio.denominator, f); @@ -1700,7 +1697,7 @@ pub(super) fn write_component_value<'a>( }); let inner_ctx = ValueContext { paren_break: false, ..ctx }; let trailing = f.options().allow_trailing_comma(); - let r_paren = to_span(paren.span()).end.saturating_sub(1); + let r_paren = paren.span().end.saturating_sub(1); if ctx.paren_break && !single_scalar_item && let Some(list) = comma_list @@ -1773,7 +1770,7 @@ pub(super) fn write_component_value<'a>( // `not` already wrote its space. if !keyword_not && is_func_like(&unary.expr) - && to_span(&unary.op.span).end != to_span(unary.expr.span()).start + && !is_glued(unary.op.span, unary.expr.span()) { write!(f, " "); } @@ -1906,7 +1903,7 @@ fn write_sass_binary<'a>( if matches!(op.kind, SassBinaryOperatorKind::Multiply) { break; } - let op_span = to_span(&op.span); + let op_span = to_span(op.span); let next = &parts[run_end + 1].0; let operand = &parts[run_end].0; // Division mirrors postcss-values lexing: @@ -1916,7 +1913,7 @@ fn write_sass_binary<'a>( // while plain numbers keep the source gap (`10px/8px`, `1/2`). if matches!(op.kind, SassBinaryOperatorKind::Division) { let wf = |v: &ComponentValue<'_>| is_word_like(v) || is_func_like(v); - let left_absorbs = to_span(operand.span()).end == op_span.start && wf(operand); + let left_absorbs = operand.span().end == op_span.start && wf(operand); if !left_absorbs { let near_wordish = wf(operand) || wf(next) @@ -1928,7 +1925,7 @@ fn write_sass_binary<'a>( } } } - let next_start = to_span(next.span()).start; + let next_start = next.span().start; if matches!(source.text_for(&op_span), "+" | "-") && (is_word_like(operand) || is_func_like(operand) @@ -1939,7 +1936,7 @@ fn write_sass_binary<'a>( // Prettier keeps it glued (`$a -$b` stays `$a -$b`, NOT `$a - $b`). // It matters for the ambiguous Sass `margin: -$a -$b` list/subtraction case (dart-sass deprecates the binary reading). // Merge it into this run so no fill space lands after the operator. - if to_span(operand.span()).end != op_span.start && op_span.end == next_start { + if operand.span().end != op_span.start && op_span.end == next_start { run_end += 1; continue; } @@ -1956,9 +1953,9 @@ fn write_sass_binary<'a>( for (j, (operand, op)) in run.iter().enumerate() { write_component_value(operand, ctx, f); if let Some(op) = op { - let op_span = to_span(&op.span); - let operand_end = to_span(operand.span()).end; - let next_start = run.get(j + 1).map(|(next, _)| to_span(next.span()).start); + let op_span = to_span(op.span); + let operand_end = operand.span().end; + let next_start = run.get(j + 1).map(|(next, _)| next.span().start); // `op(paren)` fuses like a postcss function token, // which always gets a space before it; // word-like operands force spaces (`$a + $b` even when glued). @@ -2034,16 +2031,12 @@ fn write_calc<'a>(calc: &Calc<'a>, ctx: ValueContext<'a>, f: &mut CssFormatter<' // Sass modulo inside a math function's arguments (`max(1px, 7px % 4)`) CalcOperatorKind::Modulo => "%", }; - let left_end = to_span(calc.left.span()).end; - let op_span = to_span(&calc.op.span); - let right_start = to_span(calc.right.span()).start; - push_operand(&calc.left, chunks, current); - if left_end != op_span.start { + if !is_glued(calc.left.span(), calc.op.span) { current.push(Piece::Space); } current.push(Piece::Op(op_str)); - if op_span.end != right_start { + if !is_glued(calc.op.span, calc.right.span()) { chunks.push(std::mem::take(current)); } push_operand(&calc.right, chunks, current); @@ -2153,8 +2146,8 @@ fn write_less_binary_operation<'a>( Some(Piece::Operand(v)) => Some(*v), _ => None, }; - let source_before = !is_glued(op.left.span(), &op.op.span); - let source_after = !is_glued(&op.op.span, op.right.span()); + let source_before = !is_glued(op.left.span(), op.op.span); + let source_after = !is_glued(op.op.span, op.right.span()); // Spaces on both sides regardless of the source (see the doc comment), else the source glue let forced = match op.op.kind { LessOperationOperatorKind::Multiply => { @@ -2336,7 +2329,7 @@ pub(super) fn write_function<'a>( } let groups_ref = &groups; - let r_paren = to_span(func.span()).end.saturating_sub(1); + let r_paren = func.span().end.saturating_sub(1); // `var(--baz,)`: an empty fallback is meaningful, // so a source trailing comma is preserved for `var()` ONLY // (Prettier's `printTrailingComma` checks `isVarFunctionNode`; every other function drops it). @@ -2365,8 +2358,8 @@ pub(super) fn write_function<'a>( if i > 0 { // Preserve a blank line between argument groups, // but only after a multi-part group (Prettier checks `comma_groups`). - let prev_end = groups_ref[i - 1].0.last().map_or(0, |v| to_span(v.span()).end); - let next_start = group_values.first().map_or(prev_end, |v| to_span(v.span()).start); + let prev_end = groups_ref[i - 1].0.last().map_or(0, |v| v.span().end); + let next_start = group_values.first().map_or(prev_end, |v| v.span().start); let next_start = f .context() .comments() @@ -2477,8 +2470,8 @@ pub(super) fn write_url<'a>(url: &Url<'a>, f: &mut CssFormatter<'_, 'a>) { } // Unquoted url contents are printed verbatim, including inner spaces _ => { - let url_span = to_span(url.span()); - let inner_start = to_span(url.name.span()).end + 1; + let url_span = url.span(); + let inner_start = url.name.span().end + 1; // oxc-css-parser's url span may stop before trailing padding; scan to `)` let bytes = source.as_bytes(); let mut close = url_span.end.saturating_sub(1) as usize;