Skip to content

feat(css): add support for SCSS @if, @else, and @while at-rules#9417

Merged
denbezrukov merged 5 commits intomainfrom
db/scss-at-rule-2
Mar 9, 2026
Merged

feat(css): add support for SCSS @if, @else, and @while at-rules#9417
denbezrukov merged 5 commits intomainfrom
db/scss-at-rule-2

Conversation

@denbezrukov
Copy link
Copy Markdown
Contributor

This PR was created with AI assistance (Codex).

Summary

Adds SCSS control-flow at-rule support for @if, @else, and @while.

Biome now parses and formats SCSS control-flow blocks as dedicated SCSS syntax. Chained @else if is supported through the @if/@else clause path, while orphan @else is still diagnosed
and recovered as bogus syntax.

Test Plan

  • Added parser coverage for valid and invalid SCSS @if, @else, and @while
  • Added formatter coverage for SCSS control-flow at-rules
  • Ran:
    • cargo test -p biome_css_parser
    • cargo test -p biome_css_formatter

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 9, 2026

⚠️ No Changeset found

Latest commit: e9c3c12

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions Bot added A-Parser Area: parser A-Formatter Area: formatter A-Tooling Area: internal tools L-CSS Language: CSS and super languages L-Grit Language: GritQL labels Mar 9, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 9, 2026

Parser conformance results on

js/262

Test result main count This PR count Difference
Total 53071 53071 0
Passed 51851 51851 0
Failed 1178 1178 0
Panics 42 42 0
Coverage 97.70% 97.70% 0.00%

jsx/babel

Test result main count This PR count Difference
Total 38 38 0
Passed 37 37 0
Failed 1 1 0
Panics 0 0 0
Coverage 97.37% 97.37% 0.00%

markdown/commonmark

Test result main count This PR count Difference
Total 652 652 0
Passed 652 652 0
Failed 0 0 0
Panics 0 0 0
Coverage 100.00% 100.00% 0.00%

symbols/microsoft

Test result main count This PR count Difference
Total 5466 5466 0
Passed 1915 1915 0
Failed 3551 3551 0
Panics 0 0 0
Coverage 35.03% 35.03% 0.00%

ts/babel

Test result main count This PR count Difference
Total 636 636 0
Passed 568 568 0
Failed 68 68 0
Panics 0 0 0
Coverage 89.31% 89.31% 0.00%

ts/microsoft

Test result main count This PR count Difference
Total 18875 18875 0
Passed 13014 13014 0
Failed 5860 5860 0
Panics 1 1 0
Coverage 68.95% 68.95% 0.00%

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 9, 2026

Merging this PR will not alter performance

✅ 29 untouched benchmarks
⏩ 187 skipped benchmarks1


Comparing db/scss-at-rule-2 (e9c3c12) with main (69e1973)

Open in CodSpeed

Footnotes

  1. 187 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 68e95b4d-245a-411a-961f-64438698e001

📥 Commits

Reviewing files that changed from the base of the PR and between 1a024f6 and e9c3c12.

📒 Files selected for processing (1)
  • crates/biome_css_parser/src/syntax/scss/at_rule/while_at_rule.rs

Walkthrough

Adds SCSS conditional support across the repo: grammar additions (ScssIfAtRule, ScssWhileAtRule, ScssElseClause, AnyScssElseClauseBody) and codegen updates; lexer keyword for while; parser implementations including bogus-else diagnostics and while/if parsers; formatter nodes, generated FormatRule/AsFormat/IntoFormat impls, new formatter modules and dispatch arms; pattern mappings; and accompanying OK/error tests covering @if, @else and @while.

Possibly related PRs

Suggested labels

L-SCSS

Suggested reviewers

  • ematipico
  • dyc3
  • siketyan
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarises the main change: adding SCSS control-flow at-rule support for @if, @else, and @while.
Description check ✅ Passed The description clearly explains the changeset: adds SCSS control-flow at-rule parsing and formatting support, details the approach (chained @else if support, bogus syntax recovery), and documents the test plan with test commands.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch db/scss-at-rule-2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
crates/biome_css_formatter/src/scss/statements/while_at_rule.rs (1)

8-26: Consider a tiny helper for SCSS control-flow at-rules.

This layout now matches FormatScssIfAtRule almost byte-for-byte. A shared helper would keep the next control-flow rule from copy-pasting the same spacing logic again.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_formatter/src/scss/statements/while_at_rule.rs` around lines
8 - 26, The while at-rule formatting in FormatScssWhileAtRule (fmt_fields using
ScssWhileAtRuleFields: while_token, condition, block) duplicates the same layout
as FormatScssIfAtRule; extract a small helper function (e.g.,
format_scss_control_flow_at_rule) that accepts the token, condition and block
format elements and writes them with the correct spacing, then call that helper
from FormatScssWhileAtRule::fmt_fields and from FormatScssIfAtRule::fmt_fields
to remove duplication.
crates/biome_css_parser/src/syntax/scss/at_rule/if_at_rule.rs (1)

34-34: Minor style inconsistency with sibling parser.

The while_at_rule.rs imports WHILE_KW directly and uses p.bump(WHILE_KW), whereas here you use the T![if] macro. Both work identically, but for consistency within the at_rule module, consider aligning the style.

♻️ Optional: Align with while_at_rule.rs style
-use biome_css_syntax::CssSyntaxKind::{self, SCSS_IF_AT_RULE};
+use biome_css_syntax::CssSyntaxKind::{self, IF_KW, SCSS_IF_AT_RULE};
-    p.bump(T![if]);
+    p.bump(IF_KW);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_parser/src/syntax/scss/at_rule/if_at_rule.rs` at line 34,
The bump call uses the T![if] macro while sibling parsers use a keyword
constant; change p.bump(T![if]) to p.bump(IF_KW) and import IF_KW at the top
(mirroring WHILE_KW usage in while_at_rule.rs) so the at_rule module uses the
same keyword-constant style; remove the macro usage and ensure IF_KW is
available in the scope where p.bump is called.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_css_parser/src/syntax/scss/at_rule/while_at_rule.rs`:
- Around line 31-34: The while-at-rule currently parses the body with
parse_declaration_or_rule_list_block(p) and drops its ParsedSyntax result, which
skips the required-node diagnostic on a missing `{ ... }`; change this to route
the body parse through or_add_diagnostic so the parser will emit/recover as
expected (e.g., call
parse_declaration_or_rule_list_block(p).or_add_diagnostic(p,
<appropriate_error_diag>) similar to how parse_rule(p).or_add_diagnostic(p,
error) is used), keeping the existing condition parse
(parse_scss_expression_until with SCSS_WHILE_CONDITION_END_SET and
expected_scss_expression) and still advancing the token with p.bump(WHILE_KW).

---

Nitpick comments:
In `@crates/biome_css_formatter/src/scss/statements/while_at_rule.rs`:
- Around line 8-26: The while at-rule formatting in FormatScssWhileAtRule
(fmt_fields using ScssWhileAtRuleFields: while_token, condition, block)
duplicates the same layout as FormatScssIfAtRule; extract a small helper
function (e.g., format_scss_control_flow_at_rule) that accepts the token,
condition and block format elements and writes them with the correct spacing,
then call that helper from FormatScssWhileAtRule::fmt_fields and from
FormatScssIfAtRule::fmt_fields to remove duplication.

In `@crates/biome_css_parser/src/syntax/scss/at_rule/if_at_rule.rs`:
- Line 34: The bump call uses the T![if] macro while sibling parsers use a
keyword constant; change p.bump(T![if]) to p.bump(IF_KW) and import IF_KW at the
top (mirroring WHILE_KW usage in while_at_rule.rs) so the at_rule module uses
the same keyword-constant style; remove the macro usage and ensure IF_KW is
available in the scope where p.bump is called.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f2777a32-966a-4ba9-b451-cffc9810719c

📥 Commits

Reviewing files that changed from the base of the PR and between 69e1973 and c641642.

⛔ Files ignored due to path filters (15)
  • crates/biome_css_factory/src/generated/node_factory.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_css_factory/src/generated/syntax_factory.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_css_formatter/tests/specs/css/scss/at-rule/else.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_formatter/tests/specs/css/scss/at-rule/if-else.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_formatter/tests/specs/css/scss/at-rule/while.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/else.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/if.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/while.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/else.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/if.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/while.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_syntax/src/generated/kind.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_css_syntax/src/generated/macros.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_css_syntax/src/generated/nodes.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_css_syntax/src/generated/nodes_mut.rs is excluded by !**/generated/**, !**/generated/** and included by **
📒 Files selected for processing (28)
  • crates/biome_css_formatter/src/css/any/at_rule.rs
  • crates/biome_css_formatter/src/generated.rs
  • crates/biome_css_formatter/src/scss/any/else_clause_body.rs
  • crates/biome_css_formatter/src/scss/any/mod.rs
  • crates/biome_css_formatter/src/scss/auxiliary/else_clause.rs
  • crates/biome_css_formatter/src/scss/auxiliary/mod.rs
  • crates/biome_css_formatter/src/scss/statements/if_at_rule.rs
  • crates/biome_css_formatter/src/scss/statements/mod.rs
  • crates/biome_css_formatter/src/scss/statements/while_at_rule.rs
  • crates/biome_css_formatter/tests/specs/css/scss/at-rule/else.scss
  • crates/biome_css_formatter/tests/specs/css/scss/at-rule/if-else.scss
  • crates/biome_css_formatter/tests/specs/css/scss/at-rule/while.scss
  • crates/biome_css_parser/src/lexer/mod.rs
  • crates/biome_css_parser/src/syntax/at_rule/mod.rs
  • crates/biome_css_parser/src/syntax/scss/at_rule/else_clause.rs
  • crates/biome_css_parser/src/syntax/scss/at_rule/if_at_rule.rs
  • crates/biome_css_parser/src/syntax/scss/at_rule/mod.rs
  • crates/biome_css_parser/src/syntax/scss/at_rule/while_at_rule.rs
  • crates/biome_css_parser/src/syntax/scss/mod.rs
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/else.scss
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/if.scss
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/while.scss
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/else.scss
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/if.scss
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/while.scss
  • crates/biome_grit_patterns/src/grit_target_language/css_target_language/generated_mappings.rs
  • xtask/codegen/css.ungram
  • xtask/codegen/src/css_kinds_src.rs

Comment thread crates/biome_css_parser/src/syntax/scss/at_rule/while_at_rule.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/biome_css_parser/src/syntax/scss/at_rule/while_at_rule.rs (1)

45-48: Consider inlining or returning the result.

This wrapper discards the ParsedSyntax and adds indirection. Either inline the call at line 35 (matching @if), or change the signature to return ParsedSyntax so the caller can attach diagnostics.

♻️ Option A: Inline (match `@if` pattern)
-    parse_scss_while_block(p);
+    parse_declaration_or_rule_list_block(p);

Then remove parse_scss_while_block entirely.

♻️ Option B: Return result for diagnostic handling
 #[inline]
-fn parse_scss_while_block(p: &mut CssParser) {
-    parse_declaration_or_rule_list_block(p);
+fn parse_scss_while_block(p: &mut CssParser) -> ParsedSyntax {
+    parse_declaration_or_rule_list_block(p)
 }

Then at call site:

-    parse_scss_while_block(p);
+    parse_scss_while_block(p).or_add_diagnostic(p, expected_block);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_parser/src/syntax/scss/at_rule/while_at_rule.rs` around
lines 45 - 48, The wrapper parse_scss_while_block currently discards the
ParsedSyntax and only delegates to parse_declaration_or_rule_list_block, adding
unnecessary indirection; either inline the call where parse_scss_while_block is
used (remove parse_scss_while_block entirely to match the `@if` pattern) or change
parse_scss_while_block's signature to return ParsedSyntax and forward the result
from parse_declaration_or_rule_list_block so callers can attach diagnostics
(update all call sites accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/biome_css_parser/src/syntax/scss/at_rule/while_at_rule.rs`:
- Around line 45-48: The wrapper parse_scss_while_block currently discards the
ParsedSyntax and only delegates to parse_declaration_or_rule_list_block, adding
unnecessary indirection; either inline the call where parse_scss_while_block is
used (remove parse_scss_while_block entirely to match the `@if` pattern) or change
parse_scss_while_block's signature to return ParsedSyntax and forward the result
from parse_declaration_or_rule_list_block so callers can attach diagnostics
(update all call sites accordingly).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2070810c-8a21-4747-b476-612e7dc795f6

📥 Commits

Reviewing files that changed from the base of the PR and between ea8f100 and 1a024f6.

⛔ Files ignored due to path filters (1)
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/while.scss.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (2)
  • crates/biome_css_parser/src/syntax/scss/at_rule/while_at_rule.rs
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/while.scss
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/while.scss

Copy link
Copy Markdown
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of goodies!!

@denbezrukov denbezrukov merged commit 744fe78 into main Mar 9, 2026
18 checks passed
@denbezrukov denbezrukov deleted the db/scss-at-rule-2 branch March 9, 2026 16:44
@Netail Netail added the L-SCSS label Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Formatter Area: formatter A-Parser Area: parser A-Tooling Area: internal tools L-CSS Language: CSS and super languages L-Grit Language: GritQL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants