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.
This commit is contained in:
leaysgur committed 2026-11-02 08:26:19 +00:00
1 parent f680ee1f2e
commit 633ee960da
11 files changed
+212 -254

No files matched your search

Generated
+2 -2
View File
@@ -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",
]
+1 -1
View File
@@ -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)
+4 -7
View File
@@ -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>) {
+49 -63
View File
@@ -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<u32>,
}
@@ -928,9 +925,9 @@ fn write_import_path_list<'a>(
let mut trailing: Vec<Option<u32>> = vec![None; n];
let mut leads: Vec<Vec<comments::CssComment>> = 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<comments::CssComment> = all
.iter()
.filter(|c| c.span.end <= path_start)
@@ -953,10 +950,7 @@ fn write_import_path_list<'a>(
let chunk_w: Vec<u32> = 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<comments::CssComment> =
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<ImportWord<'_, 'a>>> = 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 <general-enclosed>;
// 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')' {
+15 -15
View File
@@ -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));
}
+1 -1
View File
@@ -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
}
@@ -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)));
}
+49 -58
View File
@@ -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<u32> {
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<comments::CssComment> = 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(&parameters.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<CondPar
match cond {
ComponentValue::SassBinaryExpression(binary) => {
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) {
@@ -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);
+30 -39
View File
@@ -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, "{");
+50 -57
View File
@@ -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;