Skip to content

feat(css): allow delimiters in bracketed value lists#9145

Merged
denbezrukov merged 2 commits intonextfrom
db/scss-bracketed-value-delimiters
Feb 19, 2026
Merged

feat(css): allow delimiters in bracketed value lists#9145
denbezrukov merged 2 commits intonextfrom
db/scss-bracketed-value-delimiters

Conversation

@denbezrukov
Copy link
Contributor

@denbezrukov denbezrukov commented Feb 19, 2026

Summary

This PR aligns bracketed list separator parsing with SCSS semantics.

  @use "sass:list";

  $brackets: [full-start, content-start, content-end, full-end];

  @mixin layout-columns($lines) {
    @if not list.is-bracketed($lines) {
      @error "Expected a bracketed list.";
    }

    grid-template-columns:
      [#{list.nth($lines, 1)}] 1fr
      [#{list.nth($lines, 2)}] minmax(0, 72ch)
      [#{list.nth($lines, 3)}] 1fr
      [#{list.nth($lines, 4)}];
  }

  .page {
    display: grid;
    @include layout-columns($brackets);
  }
  • In BracketedValueList, , and / inside [...] are now parsed as CSS_GENERIC_DELIMITER only when
    SCSS syntax is enabled (CssSyntaxFeatures::Scss.parse_exclusive_syntax).
  • In plain CSS, the same syntax now reports a diagnostic (Sass list separators are an SCSS
    only feature) instead of being accepted.

Test Plan

  • cargo test -p biome_css_parser
  • cargo test -p biome_css_formatter

@changeset-bot
Copy link

changeset-bot bot commented Feb 19, 2026

⚠️ No Changeset found

Latest commit: 30b2c93

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 labels Feb 19, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

No actionable comments were generated in the recent review. 🎉


Walkthrough

Adds parser and formatter support for CSS/SCSS bracketed value items. Grammar gains a new non-terminal AnyCssBracketedValueItem used by CssBracketedValueList. Formatter includes a new rule for AnyCssBracketedValueItem and CssBracketedValueList now delegates to write_component_value_list. Parser introduces BracketedValueList with SCSS-aware separator tracking (comma vs slash), new BracketedValueSeparator enum, mixed-separator error reporting, and associated tests for OK and error cases.

Possibly related PRs

Suggested labels

L-SCSS

Suggested reviewers

  • dyc3
  • siketyan
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main change: allowing delimiters (commas and slashes) in bracketed value lists for SCSS support, which aligns with all the file modifications.
Description check ✅ Passed The description clearly explains the feature: SCSS bracketed list separator parsing is now supported when SCSS syntax is enabled, with a practical example and test plan.

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

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch db/scss-bracketed-value-delimiters

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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
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 (2)
crates/biome_css_parser/src/syntax/mod.rs (2)

620-634: LGTM — clean implementation following the established Tailwind pattern.

One small note: p.bump_any() works fine here given the guard, but p.bump(T![,]) / p.bump(T![/]) in two arms (or a single explicit bump via p.eat) would be marginally more self-documenting.

Also, the inline comment example $list: [a, b / c]; mixes two separator types (, and /) in one list level, which the SCSS spec doesn't permit. A single-separator example like $list: [a, b, c]; (or [a / b / c]) would be less confusing for future contributors.

✨ Cleaner bump & corrected example
-        if p.at(T![,]) || p.at(T![/]) {
-            // Preserve explicit separators inside bracketed values so Sass list
-            // separators survive parsing (e.g. `[a, b / c]`).
-            // Example: `$list: [a, b / c];`
-            // Docs: https://sass-lang.com/documentation/values/lists
-            return CssSyntaxFeatures::Scss.parse_exclusive_syntax(
-                p,
-                |p| {
-                    let m = p.start();
-                    p.bump_any();
-                    Present(m.complete(p, CSS_GENERIC_DELIMITER))
-                },
-                |p, m| scss_only_syntax_error(p, "Sass list separators", m.range(p)),
-            );
-        }
+        if p.at(T![,]) || p.at(T![/]) {
+            // Preserve explicit Sass list separators inside bracketed values
+            // (e.g. `[a, b, c]` or `[a / b / c]`).
+            // Docs: https://sass-lang.com/documentation/values/lists
+            return CssSyntaxFeatures::Scss.parse_exclusive_syntax(
+                p,
+                |p| {
+                    let m = p.start();
+                    if p.at(T![,]) {
+                        p.bump(T![,]);
+                    } else {
+                        p.bump(T![/]);
+                    }
+                    Present(m.complete(p, CSS_GENERIC_DELIMITER))
+                },
+                |p, m| scss_only_syntax_error(p, "Sass list separators", m.range(p)),
+            );
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_parser/src/syntax/mod.rs` around lines 620 - 634, Replace
the generic p.bump_any() in the CssSyntaxFeatures::Scss.parse_exclusive_syntax
closure with an explicit token bump so intent is clear: use p.bump(T![,]) or
p.bump(T![/]) (or a conditional p.eat for either token) to consume the delimiter
matched by the guard (T![,] || T![/]); keep the scss_only_syntax_error usage and
m.complete(p, CSS_GENERIC_DELIMITER) intact. Also update the inline example
comment from `$list: [a, b / c];` to a single-separator example like `$list: [a,
b, c];` (or `[a / b / c]`) to avoid implying mixed separators are allowed.

661-669: BracketedValueListRecovery doesn't recognise , / / as recovery stop-points in SCSS mode.

is_at_recovered only checks for ] or an identifier. Now that , and / are first-class list items in SCSS mode, if recovery fires for a bad token (e.g., [bad 1, a]), it will walk straight through the following , or / separator instead of stopping there, silently discarding it. Worth updating the recovery check to also stop at these tokens, at least when SCSS is active.

♻️ Proposed fix
 impl ParseRecovery for BracketedValueListRecovery {
     type Kind = CssSyntaxKind;
     type Parser<'source> = CssParser<'source>;
     const RECOVERED_KIND: Self::Kind = CSS_BOGUS_CUSTOM_IDENTIFIER;

     fn is_at_recovered(&self, p: &mut Self::Parser<'_>) -> bool {
-        // If the next token is the end of the list or the next element, we're at a recovery point.
-        p.at(T![']']) || is_at_identifier(p)
+        // Stop at the closing bracket, the next identifier, or a Sass list
+        // separator (comma / slash) so recovery doesn't eat SCSS delimiters.
+        p.at(T![']']) || is_at_identifier(p) || p.at(T![,]) || p.at(T![/])
     }
 }

Based on learnings: "Implement error recovery in list parsing using or_recover() to wrap unparseable tokens in a BOGUS_* node and consume tokens until a recovery token is found."

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

In `@crates/biome_css_parser/src/syntax/mod.rs` around lines 661 - 669, Update
BracketedValueListRecovery::is_at_recovered so it also treats list separators as
recovery stop-points in SCSS mode: in the is_at_recovered(&self, p: &mut
CssParser) check, return true not only for T![']'] and is_at_identifier(p) but
also when running in SCSS mode and the next token is a comma or slash (e.g.,
T![','] or T!['/']). Use the existing runtime flag or parser method that
indicates SCSS mode (the same check other SCSS-aware code uses) so these
separators are only treated as recovery boundaries when SCSS is active; keep the
rest of the recovery logic unchanged in BracketedValueListRecovery.
🤖 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/mod.rs`:
- Around line 620-634: Replace the generic p.bump_any() in the
CssSyntaxFeatures::Scss.parse_exclusive_syntax closure with an explicit token
bump so intent is clear: use p.bump(T![,]) or p.bump(T![/]) (or a conditional
p.eat for either token) to consume the delimiter matched by the guard (T![,] ||
T![/]); keep the scss_only_syntax_error usage and m.complete(p,
CSS_GENERIC_DELIMITER) intact. Also update the inline example comment from
`$list: [a, b / c];` to a single-separator example like `$list: [a, b, c];` (or
`[a / b / c]`) to avoid implying mixed separators are allowed.
- Around line 661-669: Update BracketedValueListRecovery::is_at_recovered so it
also treats list separators as recovery stop-points in SCSS mode: in the
is_at_recovered(&self, p: &mut CssParser) check, return true not only for
T![']'] and is_at_identifier(p) but also when running in SCSS mode and the next
token is a comma or slash (e.g., T![','] or T!['/']). Use the existing runtime
flag or parser method that indicates SCSS mode (the same check other SCSS-aware
code uses) so these separators are only treated as recovery boundaries when SCSS
is active; keep the rest of the recovery logic unchanged in
BracketedValueListRecovery.

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 19, 2026

Merging this PR will not alter performance

✅ 29 untouched benchmarks
⏩ 126 skipped benchmarks1


Comparing db/scss-bracketed-value-delimiters (30b2c93) with next (9f744da)

Open in CodSpeed

Footnotes

  1. 126 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.

@denbezrukov denbezrukov merged commit 7eb5e81 into next Feb 19, 2026
16 checks passed
@denbezrukov denbezrukov deleted the db/scss-bracketed-value-delimiters branch February 19, 2026 13:49
denbezrukov added a commit that referenced this pull request Feb 19, 2026
@Netail Netail added the L-SCSS Language: SCSS label Feb 24, 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 L-SCSS Language: SCSS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants