Skip to content

chore(scss): cherry-picks#9149

Merged
dyc3 merged 9 commits intomainfrom
db/scss-cherry-picks
Feb 20, 2026
Merged

chore(scss): cherry-picks#9149
dyc3 merged 9 commits intomainfrom
db/scss-cherry-picks

Conversation

@denbezrukov
Copy link
Contributor

Scss cherry-picks from the next branch

@changeset-bot
Copy link

changeset-bot bot commented Feb 19, 2026

⚠️ No Changeset found

Latest commit: 5cf9f0f

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-Linter Area: linter A-Parser Area: parser A-Formatter Area: formatter A-Tooling Area: internal tools L-JavaScript Language: JavaScript and super languages L-CSS Language: CSS L-HTML Language: HTML and super languages L-Grit Language: GritQL L-Tailwind Language: Tailwind CSS labels Feb 19, 2026
@github-actions
Copy link
Contributor

Parser conformance results on

js/262

Test result main count This PR count Difference
Total 52909 52909 0
Passed 51689 51689 0
Failed 1178 1178 0
Panics 42 42 0
Coverage 97.69% 97.69% 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 5464 5464 0
Passed 1915 1915 0
Failed 3549 3549 0
Panics 0 0 0
Coverage 35.05% 35.05% 0.00%

ts/babel

Test result main count This PR count Difference
Total 629 629 0
Passed 563 563 0
Failed 66 66 0
Panics 0 0 0
Coverage 89.51% 89.51% 0.00%

ts/microsoft

Test result main count This PR count Difference
Total 18871 18871 0
Passed 13012 13012 0
Failed 5858 5858 0
Panics 1 1 0
Coverage 68.95% 68.95% 0.00%

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 19, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 96 skipped benchmarks1


Comparing db/scss-cherry-picks (5cf9f0f) with main (8ca3f7f)2

Open in CodSpeed

Footnotes

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

  2. No successful run was found on main (312b6db) during the generation of this report, so 8ca3f7f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@denbezrukov denbezrukov changed the title Db/scss cherry picks chore(scss): cherry-picks Feb 19, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

Walkthrough

This pull request extends the Biome CSS parser, formatter, and linter to comprehensively support SCSS-specific constructs. Key additions include SCSS nesting declarations (nested properties with blocks), SCSS qualified names for module-scoped functions (e.g., math.div()), and CSS unary expressions. The parser and formatter now handle bracketed value lists with SCSS separators (commas and slashes), function names can reference qualified identifiers, and all lexer checkpoints across the codebase now track whitespace state. Supporting infrastructure updates include new formatter rules, parser syntax additions, updated linter helpers for robustness, and extensive test coverage for new scenarios.

Possibly related PRs

  • #9145 – Implements SCSS bracketed value delimiter tracking and error diagnostics, overlapping with the bracketed value list changes in this PR.
  • #9135 – Adds SCSS nesting declaration support across parser and formatter, sharing identical structural changes to match arms and parsing logic.
  • #9093 – Introduces CSS unary expression parsing and formatting, directly aligned with the unary operator support added here.

Suggested labels

A-Linter, A-Parser, A-Formatter, L-CSS, L-SCSS

Suggested reviewers

  • ematipico
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Db/scss cherry picks' refers to cherry-picking SCSS-related changes from the next branch, which aligns with the PR's core objective of integrating SCSS enhancements.
Description check ✅ Passed The description 'Scss cherry-picks from the next branch' directly relates to the changeset, which introduces extensive SCSS parsing and formatting support.

✏️ 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-cherry-picks

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.

Actionable comments posted: 4

🧹 Nitpick comments (8)
crates/biome_css_parser/tests/css_test_suite/error/scss/value/bracketed-recovery-separators.scss (1)

1-2: Consider adding a space-separated bracketed list case.

Comma (,) and slash (/) separators are covered, but SCSS bracketed lists also support space separators (e.g. [a b c]). This path isn't exercised in the current test file, so adding a case here would round out the error recovery coverage.

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

In
`@crates/biome_css_parser/tests/css_test_suite/error/scss/value/bracketed-recovery-separators.scss`
around lines 1 - 2, Add a space-separated bracketed-list test case to exercise
SCSS bracketed lists with spaces: add a new variable similar to $comma_list and
$slash_list (e.g. $space_list) that uses a space-separated bracketed list like
[a b c]; place it alongside $comma_list and $slash_list in the test to ensure
the parser's bracketed-recovery path for space separators is covered.
crates/biome_css_analyze/src/assist/source/use_sorted_properties.rs (1)

245-246: Consider NestedRuleOrAtRule for ScssNestingDeclaration.

UnknownKind is the last bucket in NodeKindOrder, placing nesting declarations (e.g., font: bold { … }) even after real at-rules and nested qualified rules. Since a ScssNestingDeclaration wraps a nested block, NestedRuleOrAtRule would give it the same slot as CssAtRule / CssNestedQualifiedRule and better match user intuition. The current choice is safe and consistent with ScssDeclaration, but worth revisiting.

♻️ Suggested alternative
-            AnyCssDeclarationOrRule::ScssNestingDeclaration(_) => NodeKindOrder::UnknownKind,
+            AnyCssDeclarationOrRule::ScssNestingDeclaration(_) => NodeKindOrder::NestedRuleOrAtRule,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_analyze/src/assist/source/use_sorted_properties.rs` around
lines 245 - 246, The match arm for
AnyCssDeclarationOrRule::ScssNestingDeclaration currently returns
NodeKindOrder::UnknownKind, which places SCSS nesting declarations in the last
bucket; change it to return NodeKindOrder::NestedRuleOrAtRule so
ScssNestingDeclaration shares the same bucket as CssAtRule and
CssNestedQualifiedRule, updating the match in use_sorted_properties.rs where
AnyCssDeclarationOrRule is mapped to NodeKindOrder (replace UnknownKind with
NestedRuleOrAtRule for ScssNestingDeclaration).
crates/biome_css_parser/tests/css_test_suite/ok/scss/value/qualified-names.scss (1)

7-9: Nit: three trailing blank lines are a touch excessive.

One is conventional; two extras snuck in. Harmless, but tidy fixtures are easier to diff.

🧹 Suggested trim
 }
-
-
-
+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@crates/biome_css_parser/tests/css_test_suite/ok/scss/value/qualified-names.scss`
around lines 7 - 9, The file qualified-names.scss has three trailing blank
lines; trim the end-of-file to a single newline (remove the two extra blank
lines) so the fixture ends with a single conventional trailing newline for
cleaner diffs.
crates/biome_css_parser/src/syntax/scss/declaration/variable.rs (1)

59-66: Trailing semicolon heuristic relies on a single-token lookahead.

The check p.nth_at(1, T!['}']) on line 60 allows omitting the semicolon only when the very next token is }. If there's a comment or whitespace token between the semicolon and }, this heuristic might not fire, forcing a semicolon even when one isn't strictly needed. That said, this matches the existing pattern and is likely fine for practical SCSS input — just flagging for awareness.

🤖 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/declaration/variable.rs` around lines
59 - 66, The trailing-semicolon heuristic in variable parsing relies on a
single-token lookahead via p.nth_at(1, T!['}']) which fails if trivia
(comments/whitespace) sits between the semicolon and `}`; update the check in
crates::biome_css_parser::src::syntax::scss::declaration::variable (the block
using p.at/ p.eat/ p.expect and p.nth_at(1, T!['}'])) to instead scan forward to
the next non-trivia token (skip comments/whitespace) and test whether that token
is T!['}']; if it is, allow an optional p.eat(T![;]) otherwise call
p.expect(T![;]) so behavior matches intent even with intervening trivia.
crates/biome_css_parser/src/syntax/scss/mod.rs (1)

57-60: Hardcoded lookahead distance only covers the ident.ident( branch.

is_at_scss_qualified_name_function uses p.nth_at(3, T!['(']), which correctly matches math.div( (tokens at positions 0–3). However, is_at_scss_qualified_name also matches ident.$ident where the ( would be at position 4, not 3.

In practice, SCSS function calls on qualified names are always module.func() (never module.$var()), so this is correct for real-world usage. A brief comment explaining the assumption would prevent future confusion.

📝 Suggested comment
 #[inline]
 pub(crate) fn is_at_scss_qualified_name_function(p: &mut CssParser) -> bool {
+    // Only matches `ident.ident(` — SCSS module function calls.
+    // The `ident.$ident` branch of qualified names is for variable access, not function calls.
     is_at_scss_qualified_name(p) && p.nth_at(3, T!['('])
 }
🤖 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/mod.rs` around lines 57 - 60,
is_at_scss_qualified_name_function currently uses a hardcoded lookahead
p.nth_at(3, T!['(']) which only covers the ident.ident( case and would miss
ident.$ident( where the '(' is at position 4; update the code by adding a
concise comment next to is_at_scss_qualified_name_function (and/or
is_at_scss_qualified_name) documenting the intentional assumption that SCSS
qualified-name function calls are always module.func() (ident.ident()) and never
module.$var(), so the lookahead of 3 is correct for real-world usage and should
not be changed.
crates/biome_css_parser/src/syntax/scss/declaration/nesting.rs (1)

83-99: Edge case in complete_declaration_with_semicolon semicolon handling.

When current token is not ; and p.nth_at(1, T!['}']) is true (i.e., some unexpected token sits before }), the p.eat(T![;]) silently no-ops and no error is emitted. This means a bogus token before } won't trigger a missing-semicolon diagnostic.

Given that the grammar declares ';'? for CssDeclarationWithSemicolon, this is arguably fine — but it's a subtlety worth a brief inline comment to explain the intended behaviour.

Suggested comment
     if !p.at(T!['}']) {
         if p.nth_at(1, T!['}']) {
+            // Semicolon is optional before the closing brace;
+            // eat it if present, otherwise silently skip.
             p.eat(T![;]);
         } else {
             p.expect(T![;]);
         }
     }
🤖 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/declaration/nesting.rs` around lines
83 - 99, The function complete_declaration_with_semicolon silently accepts a
missing semicolon when p.nth_at(1, T!['}']) is true (it calls p.eat(T![;]) which
no-ops) so a stray token before a closing brace won't emit a missing-semicolon
diagnostic; update complete_declaration_with_semicolon to add a brief inline
comment explaining this intentional behavior (that CssDeclarationWithSemicolon
allows an optional ';' and we intentionally avoid emitting an error here by
calling p.eat when a '}' follows) and reference the tokens checks
(p.at(T!['}']), p.nth_at(1, T!['}']), p.eat(T![;]), p.expect(T![;])) so future
readers understand why no error is produced for that edge case.
crates/biome_css_parser/src/syntax/value/function.rs (1)

74-78: SCSS qualified name check bypasses !is_at_url_function guard.

When SCSS is enabled and is_at_scss_qualified_name(p) is true, the early return skips the !is_at_url_function(p) check on line 78. This is fine in practice since module.fn() won't match a URL function pattern, but it's worth a brief comment explaining the precedence.

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

In `@crates/biome_css_parser/src/syntax/value/function.rs` around lines 74 - 78,
The SCSS-qualified-name branch short-circuits the final URL guard
(CssSyntaxFeatures::Scss check + is_at_scss_qualified_name -> return
is_at_scss_qualified_name_function(p)), which bypasses the subsequent
!is_at_url_function(p) test; add a brief comment above that early return
explaining that SCSS qualified names take precedence and cannot be URL functions
(so the URL guard is intentionally skipped), and ensure references to
is_at_scss_qualified_name, is_at_scss_qualified_name_function,
is_nth_at_function, and is_at_url_function are clear so future readers
understand the ordering and rationale.
crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs (1)

111-143: Ambiguous nesting resolution: confirm the SCSS_NESTING_DECLARATION rejection path.

Line 122 rejects SCSS_NESTING_DECLARATION results during speculative parsing to force a fallback to the selector path. If both the selector path (line 136) and the declaration-first path fail, line 143 unconditionally re-parses as parse_scss_nesting_declaration. This double-parse is inherently expensive on the failure path. Given that this only triggers for ambiguous cases (no whitespace after colon + identifier/colon follows), the impact should be bounded.

One concern: when the speculative parse at line 118 fails (returning Err), the parser rewinds via try_parse. Then try_parse_nested_qualified_rule_without_selector_recovery at line 136 also speculates and potentially rewinds. Finally, line 143 does a third parse of the same tokens. For deeply nested SCSS with many ambiguous properties, this triple-parse could add up. Worth keeping an eye on in benchmarks.

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

In `@crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs`
around lines 111 - 143, The ambiguous SCSS nesting branch currently may parse
the declaration up to three times: an initial try_parse, a selector try_parse,
then an unconditional parse_scss_nesting_declaration; change the flow to cache
and reuse a successful declaration from the first try_parse instead of
reparsing: when you call try_parse(... parse_scss_nesting_declaration ...) keep
the returned declaration on Ok(declaration) and return it (as now), but if
try_parse returns Err leave unchanged; after
try_parse_nested_qualified_rule_without_selector_recovery fails, only call
parse_scss_nesting_declaration once — do not re-invoke it if you already
obtained the declaration from the first try_parse; reference functions/idents:
is_at_scss_nesting_declaration, is_at_ambiguous_scss_nesting_item, try_parse,
parse_scss_nesting_declaration,
try_parse_nested_qualified_rule_without_selector_recovery,
SCSS_NESTING_DECLARATION, CSS_DECLARATION_WITH_SEMICOLON, self.end_kind.
🤖 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_analyze/src/lint/correctness/no_unknown_unit.rs`:
- Around line 117-122: The current code in run() uses the ? operator when
extracting function_name_token from
ancestor.cast::<CssFunction>()?.name().ok()?.as_css_identifier().and_then(...),
which causes run() to return early and silently skip diagnostics for units
inside SCSS qualified functions (e.g., math.image-set), so either document that
behaviour or change the early-return to skip this ancestor and continue
scanning; specifically, modify the extraction around
ancestor.cast::<CssFunction>() and the subsequent .name()/as_css_identifier()
chain (the function_name_token logic) to handle failures with a match/if-let and
use continue when the cast/name/identifier is absent, or add a clear comment
above the block explaining that missing/unknown function identifiers are
intentionally ignored.

In `@crates/biome_css_parser/src/lexer/mod.rs`:
- Around line 79-80: The doc comment for the struct field after_whitespace is
inaccurate: it says "trivia" but the implementation only flips this flag when
encountering WHITESPACE. Either update the comment to say "whitespace" only, or
broaden the implementation to set after_whitespace for all trivia token kinds
(e.g., NEWLINE, COMMENT, MULTILINE_COMMENT) where WHITESPACE is currently
handled; locate the place that currently sets after_whitespace (the lexer branch
handling WHITESPACE) and add the same flag update when emitting NEWLINE,
COMMENT, and MULTILINE_COMMENT tokens or change the comment text on
after_whitespace to reflect it tracks only whitespace.

In `@crates/biome_css_parser/src/syntax/value/function.rs`:
- Line 248: UNARY_OPERATION_TOKEN currently includes T![*] incorrectly; update
the token_set! invocation for UNARY_OPERATION_TOKEN (the token_set![T![+],
T![-], T![*]] expression) to only include T![+] and T![-] so unary operator
recognition accepts only + and -; leave BINARY_OPERATION_TOKEN unchanged and run
tests to verify unary-precedence behavior.

In
`@crates/biome_css_parser/tests/css_test_suite/error/scss/value/bracketed-mixed-separators.scss`:
- Line 2: Rename the SCSS test variable $also_mixed to use kebab-case
($also-mixed) so the fixture only triggers the intended mixed-separator error;
update any occurrences of the symbol $also_mixed in the test content (e.g., the
declaration "$also_mixed: [x / y, z];" and any references) to $also-mixed to
avoid collateral lint diagnostics from dollar-variable-pattern rules.

---

Nitpick comments:
In `@crates/biome_css_analyze/src/assist/source/use_sorted_properties.rs`:
- Around line 245-246: The match arm for
AnyCssDeclarationOrRule::ScssNestingDeclaration currently returns
NodeKindOrder::UnknownKind, which places SCSS nesting declarations in the last
bucket; change it to return NodeKindOrder::NestedRuleOrAtRule so
ScssNestingDeclaration shares the same bucket as CssAtRule and
CssNestedQualifiedRule, updating the match in use_sorted_properties.rs where
AnyCssDeclarationOrRule is mapped to NodeKindOrder (replace UnknownKind with
NestedRuleOrAtRule for ScssNestingDeclaration).

In `@crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs`:
- Around line 111-143: The ambiguous SCSS nesting branch currently may parse the
declaration up to three times: an initial try_parse, a selector try_parse, then
an unconditional parse_scss_nesting_declaration; change the flow to cache and
reuse a successful declaration from the first try_parse instead of reparsing:
when you call try_parse(... parse_scss_nesting_declaration ...) keep the
returned declaration on Ok(declaration) and return it (as now), but if try_parse
returns Err leave unchanged; after
try_parse_nested_qualified_rule_without_selector_recovery fails, only call
parse_scss_nesting_declaration once — do not re-invoke it if you already
obtained the declaration from the first try_parse; reference functions/idents:
is_at_scss_nesting_declaration, is_at_ambiguous_scss_nesting_item, try_parse,
parse_scss_nesting_declaration,
try_parse_nested_qualified_rule_without_selector_recovery,
SCSS_NESTING_DECLARATION, CSS_DECLARATION_WITH_SEMICOLON, self.end_kind.

In `@crates/biome_css_parser/src/syntax/scss/declaration/nesting.rs`:
- Around line 83-99: The function complete_declaration_with_semicolon silently
accepts a missing semicolon when p.nth_at(1, T!['}']) is true (it calls
p.eat(T![;]) which no-ops) so a stray token before a closing brace won't emit a
missing-semicolon diagnostic; update complete_declaration_with_semicolon to add
a brief inline comment explaining this intentional behavior (that
CssDeclarationWithSemicolon allows an optional ';' and we intentionally avoid
emitting an error here by calling p.eat when a '}' follows) and reference the
tokens checks (p.at(T!['}']), p.nth_at(1, T!['}']), p.eat(T![;]),
p.expect(T![;])) so future readers understand why no error is produced for that
edge case.

In `@crates/biome_css_parser/src/syntax/scss/declaration/variable.rs`:
- Around line 59-66: The trailing-semicolon heuristic in variable parsing relies
on a single-token lookahead via p.nth_at(1, T!['}']) which fails if trivia
(comments/whitespace) sits between the semicolon and `}`; update the check in
crates::biome_css_parser::src::syntax::scss::declaration::variable (the block
using p.at/ p.eat/ p.expect and p.nth_at(1, T!['}'])) to instead scan forward to
the next non-trivia token (skip comments/whitespace) and test whether that token
is T!['}']; if it is, allow an optional p.eat(T![;]) otherwise call
p.expect(T![;]) so behavior matches intent even with intervening trivia.

In `@crates/biome_css_parser/src/syntax/scss/mod.rs`:
- Around line 57-60: is_at_scss_qualified_name_function currently uses a
hardcoded lookahead p.nth_at(3, T!['(']) which only covers the ident.ident( case
and would miss ident.$ident( where the '(' is at position 4; update the code by
adding a concise comment next to is_at_scss_qualified_name_function (and/or
is_at_scss_qualified_name) documenting the intentional assumption that SCSS
qualified-name function calls are always module.func() (ident.ident()) and never
module.$var(), so the lookahead of 3 is correct for real-world usage and should
not be changed.

In `@crates/biome_css_parser/src/syntax/value/function.rs`:
- Around line 74-78: The SCSS-qualified-name branch short-circuits the final URL
guard (CssSyntaxFeatures::Scss check + is_at_scss_qualified_name -> return
is_at_scss_qualified_name_function(p)), which bypasses the subsequent
!is_at_url_function(p) test; add a brief comment above that early return
explaining that SCSS qualified names take precedence and cannot be URL functions
(so the URL guard is intentionally skipped), and ensure references to
is_at_scss_qualified_name, is_at_scss_qualified_name_function,
is_nth_at_function, and is_at_url_function are clear so future readers
understand the ordering and rationale.

In
`@crates/biome_css_parser/tests/css_test_suite/error/scss/value/bracketed-recovery-separators.scss`:
- Around line 1-2: Add a space-separated bracketed-list test case to exercise
SCSS bracketed lists with spaces: add a new variable similar to $comma_list and
$slash_list (e.g. $space_list) that uses a space-separated bracketed list like
[a b c]; place it alongside $comma_list and $slash_list in the test to ensure
the parser's bracketed-recovery path for space separators is covered.

In
`@crates/biome_css_parser/tests/css_test_suite/ok/scss/value/qualified-names.scss`:
- Around line 7-9: The file qualified-names.scss has three trailing blank lines;
trim the end-of-file to a single newline (remove the two extra blank lines) so
the fixture ends with a single conventional trailing newline for cleaner diffs.

Comment on lines 117 to +122
let function_name_token = ancestor
.cast::<CssFunction>()?
.name()
.ok()?
.value_token()
.ok()?;
.cast::<CssFunction>()?
.name()
.ok()?
.as_css_identifier()
.and_then(|name| name.value_token().ok())?;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the actual file and context
cd crates/biome_css_analyze && head -180 src/lint/correctness/no_unknown_unit.rs | tail -80

Repository: biomejs/biome

Length of output: 4311


🏁 Script executed:

# Check the full context of the run function and how the match statement works
sed -n '100,160p' crates/biome_css_analyze/src/lint/correctness/no_unknown_unit.rs

Repository: biomejs/biome

Length of output: 3573


🏁 Script executed:

# Search for test files that might test SCSS qualified functions
find crates/biome_css_analyze/tests -type f -name "*.rs" | head -5

Repository: biomejs/biome

Length of output: 146


🏁 Script executed:

# Look for any test data or fixtures with image-set or math functions
rg -r 'image-set|math\.' --type-add 'test:tests/**' crates/biome_css_analyze/tests/ -l

Repository: biomejs/biome

Length of output: 8983


🏁 Script executed:

cat crates/biome_css_analyze/tests/specs/correctness/noUnknownUnit/valid.css.snap

Repository: biomejs/biome

Length of output: 2719


🏁 Script executed:

cat crates/biome_css_analyze/tests/specs/correctness/noUnknownUnit/invalid.css.snap

Repository: biomejs/biome

Length of output: 15920


Code silently skips diagnostics for x unit inside SCSS qualified function names.

When a function name cannot be cast to CssIdentifier (e.g. math.image-set(1x)), the ? operator returns None from run() entirely, suppressing diagnostics. This permissive behaviour avoids false positives, but it also skips checking any remaining ancestors in the loop—meaning x inside any unrecognised SCSS qualified function goes unchecked.

No tests currently cover SCSS qualified functions, so the intended behaviour here is unclear. If this is intentional, add a comment to document it; if not, use continue instead of the early return.

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

In `@crates/biome_css_analyze/src/lint/correctness/no_unknown_unit.rs` around
lines 117 - 122, The current code in run() uses the ? operator when extracting
function_name_token from
ancestor.cast::<CssFunction>()?.name().ok()?.as_css_identifier().and_then(...),
which causes run() to return early and silently skip diagnostics for units
inside SCSS qualified functions (e.g., math.image-set), so either document that
behaviour or change the early-return to skip this ancestor and continue
scanning; specifically, modify the extraction around
ancestor.cast::<CssFunction>() and the subsequent .name()/as_css_identifier()
chain (the function_name_token logic) to handle failures with a match/if-let and
use continue when the cast/name/identifier is absent, or add a clear comment
above the block explaining that missing/unknown function identifiers are
intentionally ignored.

Comment on lines +79 to +80
/// `true` if there has been trivia between the last non-trivia token and the next non-trivia token.
after_whitespace: bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Doc comment says "trivia" but implementation only tracks WHITESPACE.

The comment states this is set when there has been trivia between tokens, but lines 157–158 only set it for WHITESPACE, not for NEWLINE, COMMENT, or MULTILINE_COMMENT. Consider aligning the comment with the actual semantics:

Suggested wording
-    /// `true` if there has been trivia between the last non-trivia token and the next non-trivia token.
+    /// `true` if there has been whitespace (non-newline) between the last non-trivia token and the next non-trivia token.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// `true` if there has been trivia between the last non-trivia token and the next non-trivia token.
after_whitespace: bool,
/// `true` if there has been whitespace (non-newline) between the last non-trivia token and the next non-trivia token.
after_whitespace: bool,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_css_parser/src/lexer/mod.rs` around lines 79 - 80, The doc
comment for the struct field after_whitespace is inaccurate: it says "trivia"
but the implementation only flips this flag when encountering WHITESPACE. Either
update the comment to say "whitespace" only, or broaden the implementation to
set after_whitespace for all trivia token kinds (e.g., NEWLINE, COMMENT,
MULTILINE_COMMENT) where WHITESPACE is currently handled; locate the place that
currently sets after_whitespace (the lexer branch handling WHITESPACE) and add
the same flag update when emitting NEWLINE, COMMENT, and MULTILINE_COMMENT
tokens or change the comment text on after_whitespace to reflect it tracks only
whitespace.

Copy link
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

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

Looks good. Just make sure to not squash and merge

@denbezrukov
Copy link
Contributor Author

Can I bypass rules and merge? 🤔

@dyc3 dyc3 merged commit f440103 into main Feb 20, 2026
30 of 31 checks passed
@dyc3 dyc3 deleted the db/scss-cherry-picks branch February 20, 2026 12:14
@denbezrukov
Copy link
Contributor Author

@dyc3 Thank you!

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 L-Grit Language: GritQL L-HTML Language: HTML and super languages L-JavaScript Language: JavaScript and super languages L-SCSS Language: SCSS L-Tailwind Language: Tailwind CSS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants