Skip to content

feat(css): support preserved SCSS custom property - #11029

Merged
denbezrukov merged 11 commits into
mainfrom
dbezrukov/raw-syntax
Jul 22, 2026
Merged

denbezrukov merged 11 commits into
mainfrom
dbezrukov/raw-syntax

Conversation

@denbezrukov

Copy link
Copy Markdown
Contributor

This PR was created with AI assistance (Codex).

Summary

Adds structured parsing and formatting for preserved SCSS custom-property values.

Custom-property values remain CSS text instead of being parsed as SassScript. Only interpolation evaluates Sass expressions:

$gap: 8px;

:root {
  --literal: $gap + 1px;
  --computed: #{$gap / 2};
  --theme: {color:red;padding:calc(2px + #{$gap});};
}

The new syntax model supports identifiers, numbers, dimensions, delimiters, strings, URLs, interpolation, functions, balanced blocks, comments, and !important. It is also used for custom properties in @supports.

The formatter preserves source-sensitive raw content, while the semantic model compares the structured values correctly.

Test Plan

  • cargo test -p biome_css_formatter -p biome_css_parser

@changeset-bot

changeset-bot Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a0df8e1

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

Copy link
Copy Markdown
Contributor

✅ Organic activity

No automation signals detected in the analyzed events.

View full analysis →

This is an automated analysis by AgentScan

@denbezrukov denbezrukov changed the title Dbezrukov/raw syntax feat(css): support preserved SCSS custom property values Jul 21, 2026
@github-actions github-actions Bot added A-Linter Area: linter 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 Jul 21, 2026
@denbezrukov
denbezrukov force-pushed the dbezrukov/raw-syntax branch from c1627f4 to a0df8e1 Compare July 21, 2026 20:46
@github-actions

Copy link
Copy Markdown
Contributor

Parser conformance results on

js/262

Test result main count This PR count Difference
Total 53430 53430 0
Passed 52149 52149 0
Failed 1239 1239 0
Panics 42 42 0
Coverage 97.60% 97.60% 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 5467 5467 0
Passed 1915 1915 0
Failed 3552 3552 0
Panics 0 0 0
Coverage 35.03% 35.03% 0.00%

ts/babel

Test result main count This PR count Difference
Total 676 676 0
Passed 592 592 0
Failed 84 84 0
Panics 0 0 0
Coverage 87.57% 87.57% 0.00%

ts/microsoft

Test result main count This PR count Difference
Total 18876 18876 0
Passed 13010 13010 0
Failed 5865 5865 0
Panics 1 1 0
Coverage 68.92% 68.92% 0.00%

@codspeed

codspeed Bot commented Jul 21, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 38 untouched benchmarks
⏩ 235 skipped benchmarks1


Comparing dbezrukov/raw-syntax (a0df8e1) with main (c9acb25)

Open in CodSpeed

Footnotes

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

coderabbitai Bot commented Jul 21, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The change adds structured parsing and formatting for raw CSS and SCSS custom-property values, including nested containers, delimiters, interpolation, comments, URLs, and !important handling. Lexer contexts and parser entry points are extended throughout CSS and SCSS syntax handling. Formatter rules preserve source-sensitive spacing and comments. Semantic models now retain and compare custom-property initial values. Related lint rules skip unsupported custom-property forms, and parser, formatter, and semantic regression fixtures are added.

Possibly related PRs

  • biomejs/biome#9948: Extends the shared CSS lexer and scan layer used for custom-property value lexing.
  • biomejs/biome#10453: Touches the formatter’s generic property colon/value boundary handling.
  • biomejs/biome#10455: Modifies SCSS nested-property parsing for interpolated custom-property names.

Suggested labels: A-Parser, A-Formatter, A-Tooling, A-Linter, L-CSS

Suggested reviewers: dyc3, ematipico

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately captures the main change: preserved SCSS custom-property support.
Description check ✅ Passed The description matches the changeset and clearly describes the parser, formatter, and semantic-model updates.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dbezrukov/raw-syntax

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/lexer/scan_cursor.rs (1)

509-518: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Third copy of the CSS-whitespace byte set — worth a shared helper.

b'\t' | b' ' | b'\n' | b'\r' | 0x0C now shows up in skip_whitespace (new) and in is_scss_raw_url_body's whitespace branch (new), on top of the pre-existing copy in consume_escape_sequence. Three copies of a spec-defined character class is asking for silent drift the next time someone tweaks whitespace handling.

♻️ Proposed helper
+#[inline]
+fn is_css_whitespace_byte(byte: u8) -> bool {
+    matches!(byte, b'\t' | b' ' | b'\n' | b'\r' | 0x0C)
+}
+
 fn skip_whitespace(&mut self) {
-    while self
-        .current_byte()
-        .is_some_and(|byte| matches!(byte, b'\t' | b' ' | b'\n' | b'\r' | 0x0C))
-    {
+    while self.current_byte().is_some_and(is_css_whitespace_byte) {
         self.advance(1);
     }
 }

Also applies to: 1007-1041

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/biome_css_parser/src/lexer/scan_cursor.rs` around lines 509 - 518,
Introduce a shared helper for the CSS-whitespace byte set and reuse it in
skip_whitespace, is_scss_raw_url_body, and consume_escape_sequence. Remove the
duplicated inline matches while preserving each caller’s existing
whitespace-handling behavior.
crates/biome_css_parser/src/syntax/at_rule/supports/mod.rs (1)

371-377: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Explain why a preceding block comment waives the empty-value diagnostic.

The !p.source().has_preceding_block_comment() guard is non-obvious — worth a // comment stating why a comment-only value (e.g. @supports (--foo: /* note */)) shouldn't get expected_component_value, so nobody "simplifies" this away later.

Based on path instructions: **/*.rs: "// provides rationale; do not narrate change history or address reviewers."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/biome_css_parser/src/syntax/at_rule/supports/mod.rs` around lines 371
- 377, In the empty-value diagnostic condition within the supports at-rule
parser, add a concise // comment explaining that a preceding block comment
represents a comment-only value, such as `@supports` (--foo: /* note */), and
therefore must suppress expected_component_value. Keep the
!p.source().has_preceding_block_comment() guard unchanged.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/biome_css_parser/src/lexer/scan_cursor.rs`:
- Around line 509-518: Introduce a shared helper for the CSS-whitespace byte set
and reuse it in skip_whitespace, is_scss_raw_url_body, and
consume_escape_sequence. Remove the duplicated inline matches while preserving
each caller’s existing whitespace-handling behavior.

In `@crates/biome_css_parser/src/syntax/at_rule/supports/mod.rs`:
- Around line 371-377: In the empty-value diagnostic condition within the
supports at-rule parser, add a concise // comment explaining that a preceding
block comment represents a comment-only value, such as `@supports` (--foo: /* note
*/), and therefore must suppress expected_component_value. Keep the
!p.source().has_preceding_block_comment() guard unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0160c680-b2ff-4aeb-a640-6bb37dd052d3

📥 Commits

Reviewing files that changed from the base of the PR and between c9acb25 and c1627f4.

⛔ Files ignored due to path filters (28)
  • 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/prettier/scss/comments/custom-properties.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_formatter/tests/specs/prettier/scss/variables/apply-rule.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_formatter/tests/specs/prettier/scss/variables/postcss-8-improment.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_formatter/tests/specs/scss/declaration/interpolated-custom-property-nested-properties.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_formatter/tests/specs/scss/declaration/raw-custom-property-values.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/property/scss_exclusive_values.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-custom-property-missing-value.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-interpolated-property-missing-value.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/scss/declaration/interpolated-custom-property-unclosed.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/scss/declaration/raw-custom-property-recovery.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/scss/value/if-disambiguation-recovery.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/declaration-block-interpolation.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/page.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/supports-expression.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/declaration/interpolated-custom-property-nested-properties.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/declaration/interpolated-custom-property-values.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/declaration/interpolation.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/declaration/raw-custom-property-values.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/declaration/valid-modern-values.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/value/css-if-function.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/value/if-disambiguation.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/value/interpolated-value-boundary.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 (56)
  • crates/biome_css_analyze/src/lint/a11y/use_generic_font_names.rs
  • crates/biome_css_analyze/src/lint/correctness/no_invalid_grid_areas.rs
  • crates/biome_css_analyze/src/lint/suspicious/no_duplicate_font_names.rs
  • crates/biome_css_formatter/src/comments.rs
  • crates/biome_css_formatter/src/css/any/custom_property_component.rs
  • crates/biome_css_formatter/src/css/any/generic_property_value_or_expression.rs
  • crates/biome_css_formatter/src/css/any/mod.rs
  • crates/biome_css_formatter/src/css/auxiliary/custom_property_braced_block.rs
  • crates/biome_css_formatter/src/css/auxiliary/custom_property_bracketed_block.rs
  • crates/biome_css_formatter/src/css/auxiliary/custom_property_delimiter.rs
  • crates/biome_css_formatter/src/css/auxiliary/custom_property_function.rs
  • crates/biome_css_formatter/src/css/auxiliary/custom_property_parenthesized_block.rs
  • crates/biome_css_formatter/src/css/auxiliary/custom_property_value.rs
  • crates/biome_css_formatter/src/css/auxiliary/mod.rs
  • crates/biome_css_formatter/src/css/lists/custom_property_component_list.rs
  • crates/biome_css_formatter/src/css/lists/mod.rs
  • crates/biome_css_formatter/src/css/properties/generic_property.rs
  • crates/biome_css_formatter/src/css/value/number.rs
  • crates/biome_css_formatter/src/css/value/regular_dimension.rs
  • crates/biome_css_formatter/src/css/value/unknown_dimension.rs
  • crates/biome_css_formatter/src/generated.rs
  • crates/biome_css_formatter/src/utils/custom_property.rs
  • crates/biome_css_formatter/src/utils/mod.rs
  • crates/biome_css_formatter/tests/specs/scss/declaration/interpolated-custom-property-nested-properties.scss
  • crates/biome_css_formatter/tests/specs/scss/declaration/raw-custom-property-values.scss
  • crates/biome_css_parser/src/lexer/mod.rs
  • crates/biome_css_parser/src/lexer/scan_cursor.rs
  • crates/biome_css_parser/src/lexer/tests.rs
  • crates/biome_css_parser/src/syntax/at_rule/keyframes.rs
  • crates/biome_css_parser/src/syntax/at_rule/supports/mod.rs
  • crates/biome_css_parser/src/syntax/mod.rs
  • crates/biome_css_parser/src/syntax/property/custom.rs
  • crates/biome_css_parser/src/syntax/property/mod.rs
  • crates/biome_css_parser/src/syntax/scss/at_rule/import_at_rule.rs
  • crates/biome_css_parser/src/syntax/scss/declaration/nesting.rs
  • crates/biome_css_parser/src/syntax/scss/expression/interpolation.rs
  • crates/biome_css_parser/src/syntax/scss/value/any.rs
  • crates/biome_css_parser/src/syntax/scss/value/interpolated_string.rs
  • crates/biome_css_parser/src/syntax/scss/value/interpolated_value.rs
  • crates/biome_css_parser/src/syntax/selector/attribute.rs
  • crates/biome_css_parser/src/syntax/selector/pseudo_class/function_value_list.rs
  • crates/biome_css_parser/src/syntax/value/dimension.rs
  • crates/biome_css_parser/src/syntax/value/function/call.rs
  • crates/biome_css_parser/src/syntax/value/url.rs
  • crates/biome_css_parser/src/token_source.rs
  • crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-custom-property-missing-value.scss
  • crates/biome_css_parser/tests/css_test_suite/error/scss/declaration/raw-custom-property-recovery.scss
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/declaration/interpolated-custom-property-nested-properties.scss
  • crates/biome_css_parser/tests/css_test_suite/ok/scss/declaration/raw-custom-property-values.scss
  • crates/biome_css_semantic/src/events.rs
  • crates/biome_css_semantic/src/semantic_model/mod.rs
  • crates/biome_css_semantic/src/semantic_model/model.rs
  • crates/biome_css_semantic/src/tests/eq.rs
  • 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

@denbezrukov denbezrukov changed the title feat(css): support preserved SCSS custom property values feat(css): support preserved SCSS custom property Jul 22, 2026

@ematipico ematipico left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Getting closer! 🚀

@denbezrukov
denbezrukov merged commit 2909809 into main Jul 22, 2026
31 of 32 checks passed
@denbezrukov
denbezrukov deleted the dbezrukov/raw-syntax branch July 22, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Formatter Area: formatter A-Linter Area: linter 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.

2 participants