Skip to content

fix(css): improve CSS if() function parsing recovery#8321

Merged
ematipico merged 9 commits intobiomejs:mainfrom
ryan-m-walker:fix/improve-css-if-function-parsing-recovery
Jan 26, 2026
Merged

fix(css): improve CSS if() function parsing recovery#8321
ematipico merged 9 commits intobiomejs:mainfrom
ryan-m-walker:fix/improve-css-if-function-parsing-recovery

Conversation

@ryan-m-walker
Copy link
Contributor

Summary

Based on feedback from @ematipico in PR #8255 I realized that I had forgotten to add the new if bogus nodes to the to_bogus function in my PR that added the functionality. This PR adds those nodes to the functoin and also improves the parseing recovery of the if() function parsing. It adds better diagnostics for failed parsing nodes and also now correctly recovers on branch lists dividers and boolean expressions. Based on feedback from the other PR I also added the mandator comments for the css.ungram file.

Test Plan

New error snapshot tests added and all existing tests passing

Docs

n/a

AI Usage Disclosure

Claude Code was used some to help debug and research the speculative parsing conflict issue. Most of the actual written code is my own.

@changeset-bot
Copy link

changeset-bot bot commented Dec 1, 2025

⚠️ No Changeset found

Latest commit: 4cefbbb

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 Dec 1, 2025
Comment on lines 424 to 429
// The DeclarationOrRuleList parser that this function call will be parsed in higher up
// the node tree uses speculative parsing and the semicolon character to determine when
// parsing is complete or when to recover from a parsing error. This comes in conflict with
// error recovery parsing for if branches since this parser also uses the semicolon as a recovery
// point. To get around this, we handle our own manual recovery here by consuming all unexpected
// tokens into the CssBogusIfBranch node until we are able to recover.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me know if the reasoning here is clear or it there is a better way to handle this that I might have missed

Copy link
Member

Choose a reason for hiding this comment

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

@denbezrukov do you have any advice for this case?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, the problem with DeclarationOrRuleList is that we can't know whether the next item is a declaration or a rule. Currently it works by checking after parsing whether we’re in the correct parsing state: if the last token was ; or we’re at }, which marks the end of any declaration, then we assume the parse was correct.

So if we don't disable recovery, declaration parsing can recover the parser back into this “correct” state (last token is ; or }).

At this point we have a few options:

  • If we're not sure whether recovery might break rule parsing, it's safer to drop recovery entirely.
  • We can try disabling recovery for ; and } tokens, but this could lead to cases where we parse the whole file simply because we try to recover.
  • We can introduce a function similar to try_parse but with speculative parsing enabled and wrap this function. This would only be used when we're 100% sure it's impossible for a rule to appear in that position.
  • We can try a solution that checks whether recovery reached ; or }, and introduce something similar to try_parse but without speculative parsing.

I think that the last solution could be a preferable way how to handle the problem. There could be some pitfalls here.

Copy link
Member

Choose a reason for hiding this comment

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

@ryan-m-walker any news? what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hey so sorry I missed this response somehow in the busyness of the end of the year...

@denbezrukov I'm curious if since the bypassing of speculative parsing is happening within parse_if_branch we can feel pretty sure that we're not encountering a rule? We should only hit my manual recovery code if we have already detected we are in an if() node and it should yield back to the wider parse_any_declaration_with_semicolon parsing once it is done recovering and parsing the if function node. the issue is is that if we can't recover and consume any semi-colons within if(...) they might get confused by parse_any_declaration_with_semicolon as valid ends of declaration with semicolon nodes.

just to illustrate the issue a bit more concretely the following invalid CSS gets parsed to the following grammar without the manual override recovery code.

.if-branch-list-recovery {
	a: if(style(--foo: bar): black; !bogus; else: white)
}
0: CSS_ROOT@0..84
  0: (empty)
  1: CSS_RULE_LIST@0..83
    0: CSS_QUALIFIED_RULE@0..83
      0: CSS_SELECTOR_LIST@0..26
        0: CSS_COMPOUND_SELECTOR@0..26
          0: CSS_NESTED_SELECTOR_LIST@0..0
          1: (empty)
          2: CSS_SUB_SELECTOR_LIST@0..26
            0: CSS_CLASS_SELECTOR@0..26
              0: DOT@0..2 "." [Newline("\n")] []
              1: CSS_CUSTOM_IDENTIFIER@2..26
                0: IDENT@2..26 "if-branch-list-recovery" [] [Whitespace(" ")]
      1: CSS_BOGUS_BLOCK@26..83
        0: L_CURLY@26..27 "{" [] []
        1: CSS_DECLARATION_OR_RULE_LIST@27..83
          0: CSS_DECLARATION_WITH_SEMICOLON@27..61
            0: CSS_DECLARATION@27..61
              0: CSS_GENERIC_PROPERTY@27..61
                0: CSS_IDENTIFIER@27..30
                  0: IDENT@27..30 "a" [Newline("\n"), Whitespace("\t")] []
                1: COLON@30..32 ":" [] [Whitespace(" ")]
                2: CSS_GENERIC_COMPONENT_VALUE_LIST@32..61
                  0: CSS_IF_FUNCTION@32..61
                    0: IF_KW@32..34 "if" [] []
                    1: L_PAREN@34..35 "(" [] []
                    2: CSS_IF_BRANCH_LIST@35..61
                      0: CSS_IF_BRANCH@35..59
                        0: CSS_IF_STYLE_TEST@35..52
                          0: STYLE_KW@35..40 "style" [] []
                          1: L_PAREN@40..41 "(" [] []
                          2: CSS_DECLARATION@41..51
                            0: CSS_GENERIC_PROPERTY@41..51
                              0: CSS_DASHED_IDENTIFIER@41..46
                                0: IDENT@41..46 "--foo" [] []
                              1: COLON@46..48 ":" [] [Whitespace(" ")]
                              2: CSS_GENERIC_COMPONENT_VALUE_LIST@48..51
                                0: CSS_IDENTIFIER@48..51
                                  0: IDENT@48..51 "bar" [] []
                            1: (empty)
                          3: R_PAREN@51..52 ")" [] []
                        1: COLON@52..54 ":" [] [Whitespace(" ")]
                        2: CSS_GENERIC_COMPONENT_VALUE_LIST@54..59
                          0: CSS_IDENTIFIER@54..59
                            0: IDENT@54..59 "black" [] []
                      1: SEMICOLON@59..61 ";" [] [Whitespace(" ")]
                    3: (empty)
              1: (empty)
            1: (empty)
          1: CSS_BOGUS@61..62
            0: BANG@61..62 "!" [] []
          2: CSS_NESTED_QUALIFIED_RULE@62..83
            0: CSS_RELATIVE_SELECTOR_LIST@62..83
              0: CSS_RELATIVE_SELECTOR@62..67
                0: (empty)
                1: CSS_COMPOUND_SELECTOR@62..67
                  0: CSS_NESTED_SELECTOR_LIST@62..62
                  1: CSS_TYPE_SELECTOR@62..67
                    0: (empty)
                    1: CSS_IDENTIFIER@62..67
                      0: IDENT@62..67 "bogus" [] []
                  2: CSS_SUB_SELECTOR_LIST@67..67
              1: (empty)
              2: CSS_BOGUS_SELECTOR@67..69
                0: SEMICOLON@67..69 ";" [] [Whitespace(" ")]
              3: (empty)
              4: CSS_RELATIVE_SELECTOR@69..80
                0: (empty)
                1: CSS_COMPOUND_SELECTOR@69..80
                  0: CSS_NESTED_SELECTOR_LIST@69..69
                  1: CSS_TYPE_SELECTOR@69..73
                    0: (empty)
                    1: CSS_IDENTIFIER@69..73
                      0: IDENT@69..73 "else" [] []
                  2: CSS_SUB_SELECTOR_LIST@73..80
                    0: CSS_PSEUDO_CLASS_SELECTOR@73..80
                      0: COLON@73..75 ":" [] [Whitespace(" ")]
                      1: CSS_PSEUDO_CLASS_IDENTIFIER@75..80
                        0: CSS_IDENTIFIER@75..80
                          0: IDENT@75..80 "white" [] []
              5: (empty)
              6: CSS_BOGUS_SELECTOR@80..83
                0: R_PAREN@80..81 ")" [] []
                1: R_CURLY@81..83 "}" [Newline("\n")] []
            1: CSS_BOGUS_BLOCK@83..83

thanks for the help and sorry again for the late response 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

Let’s try to add a check and disable this double-parsing logic if we’re sure it’s a declaration with an if expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool, I added this commit. Let me know if this is along the lines of what you were thinking or not

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 1, 2025

Walkthrough

Adds support for a new CssBogusIfTestBooleanExpr across parser, syntax, codegen and formatter. Introduces a bogus formatter node and generated FormatRule/AsFormat/IntoFormat impls, extends CssSyntaxKind bogus mappings and codegen grammar for if-test boolean expressions, updates parser recovery to token-set-driven recoveries for if() branches and boolean expressions, adds a parse diagnostic and a parser-state flag for encountering if(), and expands tests covering if() recovery cases.

Possibly related PRs

Suggested reviewers

  • dyc3
  • ematipico
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary change: improving CSS if() function parsing recovery, which is well-supported by the changeset.
Description check ✅ Passed The description is directly related to the changeset, explaining the additions of bogus nodes, improved parsing recovery, better diagnostics, and branch-list recovery.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@codspeed-hq
Copy link

codspeed-hq bot commented Dec 1, 2025

Merging this PR will not alter performance

✅ 29 untouched benchmarks
⏩ 126 skipped benchmarks1


Comparing ryan-m-walker:fix/improve-css-if-function-parsing-recovery (4cefbbb) with main (98ca2ae)

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.

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/biome_css_parser/src/syntax/value/if.rs (2)

231-279: Recovery in (<boolean-expr>) is too eager + uses the wrong diagnostic.

Two concerns here:

  • Using expected_if_test_boolean_not_expr for a parenthesised boolean expression is misleading (and likely yields confusing snapshots).
  • Recovering only to ) risks consuming a lot of input if ) is missing; consider also stopping at : / ; / } / EOF to keep recovery performance-neutral. Based on learnings, error recovery should avoid degrading valid-code parsing and shouldn’t over-consume on malformed input.
Proposed fix
         parse_any_if_test_boolean_expr(p)
             .or_recover_with_token_set(
                 p,
-                &ParseRecoveryTokenSet::new(CSS_BOGUS_IF_TEST_BOOLEAN_EXPR, token_set![T![')']]),
-                expected_if_test_boolean_not_expr,
+                &ParseRecoveryTokenSet::new(
+                    CSS_BOGUS_IF_TEST_BOOLEAN_EXPR,
+                    token_set![T![')'], T![:], T![;], T!['}'], EOF],
+                ),
+                expected_if_test_boolean_expr_group,
             )
             .ok();

410-447: Good gating in parse_if_branch, but ensure boolean-chain recovery doesn’t eat :.

The new is_at_any_if_condition short-circuit is a nice way to avoid accidental progress and helps ParseSeparatedList recovery behave. The remaining sharp edge is that boolean-chain recovery (used by and/or) should treat : as a recovery boundary; otherwise malformed ... and <missing>: can lose the colon before p.expect(T![:]).

🤖 Fix all issues with AI agents
In @crates/biome_css_parser/src/syntax/value/if.rs:
- Around line 454-463: Update
AnyIfTestBooleanExprChainParseRecovery::is_at_recovered to treat ':' as a
recovery boundary by adding a check for T![:] alongside the existing checks
(T![')'], is_at_if_test_boolean_not_expr, is_at_if_test_boolean_and_expr,
is_at_if_test_boolean_or_expr, and has_preceding_line_break). Also align the
bogus node kinds used in the fallback nodes created inside the boolean and/or
loop logic: replace uses of CSS_BOGUS with CSS_BOGUS_IF_TEST_BOOLEAN_EXPR in the
fallback creation sites (the bogus nodes emitted in the AND/OR loop handling) so
the bogus node kind matches RECOVERED_KIND.
🧹 Nitpick comments (1)
crates/biome_css_parser/tests/css_test_suite/error/function/if.css (1)

84-137: Good recovery coverage; consider clearer “intentionally invalid” labelling.

These new fixtures look on-point for exercising branch/divider recovery and nested if() scenarios. Small nit: renaming .todo (or adding a short comment) would avoid it looking like an actual TODO left behind.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c92cba and 18d737e.

⛔ Files ignored due to path filters (1)
  • crates/biome_css_parser/tests/css_test_suite/error/function/if.css.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (4)
  • crates/biome_css_parser/src/state.rs
  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_css_parser/src/syntax/value/if.rs
  • crates/biome_css_parser/tests/css_test_suite/error/function/if.css
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use inline rustdoc documentation for rules, assists, and their options
Use the dbg!() macro for debugging output in Rust tests and code
Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests

Files:

  • crates/biome_css_parser/src/state.rs
  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_css_parser/src/syntax/value/if.rs
🧠 Learnings (22)
📓 Common learnings
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Use `ConditionalParsedSyntax` for syntax that is only valid in specific contexts (e.g., strict mode, file types, language versions) and call `or_invalid_to_bogus()` to convert to a bogus node if not supported
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/*.ungram : Nodes for enclosing syntax errors must have the `Bogus` word, e.g., `HtmlBogusAttribute`, and must be part of a variant
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : 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

Applied to files:

  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_css_parser/tests/css_test_suite/error/function/if.css
  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Parse rule functions must be prefixed with `parse_` and use the name defined in the grammar file, e.g., `parse_for_statement` or `parse_expression`

Applied to files:

  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Parse rules must return `ParsedSyntax::Absent` if the rule can't predict by the next token(s) if they form the expected node, and must not progress the parser in this case

Applied to files:

  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Use `ConditionalParsedSyntax` for syntax that is only valid in specific contexts (e.g., strict mode, file types, language versions) and call `or_invalid_to_bogus()` to convert to a bogus node if not supported

Applied to files:

  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_css_parser/tests/css_test_suite/error/function/if.css
  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Use `p.eat(token)` for optional tokens, `p.expect(token)` for required tokens, `parse_rule(p).ok(p)` for optional nodes, and `parse_rule(p).or_add_diagnostic(p, error)` for required nodes

Applied to files:

  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Use `ParseSeparatedList` and `ParseNodeList` for parsing lists with error recovery to avoid infinite loops

Applied to files:

  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Parse rules must take a mutable reference to the parser as their only parameter and return a `ParsedSyntax`

Applied to files:

  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Code blocks in rule documentation must specify a language identifier and be tagged with `expect_diagnostic` for invalid examples or remain untagged for valid examples

Applied to files:

  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Mark rules with `issue_number` in the `declare_lint_rule!` macro if they are work-in-progress to indicate missing features

Applied to files:

  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
📚 Learning: 2025-12-22T09:27:13.161Z
Learnt from: ematipico
Repo: biomejs/biome PR: 8537
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:167-210
Timestamp: 2025-12-22T09:27:13.161Z
Learning: In crates/biome_analyze/**/*analyze/src/**/*.rs, the `fix_kind` field in `declare_lint_rule!` should only be specified when the rule implements the `action` function. Rules that only emit diagnostics without providing code fixes should not include `fix_kind` in their metadata.

Applied to files:

  • crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Do not attempt to 'fix' the code; if a token/node is known to be mandatory but is missing, return `None` instead

Applied to files:

  • crates/biome_css_parser/tests/css_test_suite/error/function/if.css
📚 Learning: 2025-12-04T13:29:49.287Z
Learnt from: dyc3
Repo: biomejs/biome PR: 8291
File: crates/biome_html_formatter/tests/specs/prettier/vue/html-vue/elastic-header.html:10-10
Timestamp: 2025-12-04T13:29:49.287Z
Learning: Files under `crates/biome_html_formatter/tests/specs/prettier` are test fixtures synced from Prettier and should not receive detailed code quality reviews (e.g., HTTP vs HTTPS, formatting suggestions, etc.). These files are test data meant to validate formatter behavior and should be preserved as-is.

Applied to files:

  • crates/biome_css_parser/tests/css_test_suite/error/function/if.css
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When formatting AST nodes, use mandatory tokens from the AST instead of hardcoding token strings (e.g., use `node.l_paren_token().format()` instead of `token("(")`)

Applied to files:

  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Implement a token source struct that wraps the lexer and implements `TokenSourceWithBufferedLexer` and `LexerWithCheckpoint` for lookahead and re-lexing capabilities

Applied to files:

  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : For tokens that are not mandatory, use helper functions instead of hardcoding

Applied to files:

  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : A parser struct must implement the `Parser` trait and save the token source, parser context, and optional parser options

Applied to files:

  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/biome_rule_options/lib/**/*.rs : Rule option types must derive `Deserializable`, `Serialize`, `Deserialize`, and optionally `JsonSchema` traits

Applied to files:

  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Implement performance-neutral error recovery mechanisms that do not degrade parsing performance of valid code

Applied to files:

  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/lexer/mod.rs : Implement a `Lexer` trait from `biome_parser` crate for the lexer struct that consumes characters from source code and emits tokens

Applied to files:

  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-10-25T07:22:18.540Z
Learnt from: ematipico
Repo: biomejs/biome PR: 7852
File: crates/biome_css_parser/src/syntax/property/mod.rs:161-168
Timestamp: 2025-10-25T07:22:18.540Z
Learning: In the Biome CSS parser, lexer token emission should not be gated behind parser options like `is_tailwind_directives_enabled()`. The lexer must emit correct tokens regardless of parser options to enable accurate diagnostics and error messages when the syntax is used incorrectly.

Applied to files:

  • crates/biome_css_parser/src/syntax/value/if.rs
📚 Learning: 2025-11-09T12:47:46.298Z
Learnt from: ematipico
Repo: biomejs/biome PR: 8031
File: crates/biome_html_parser/src/syntax/svelte.rs:140-147
Timestamp: 2025-11-09T12:47:46.298Z
Learning: In the Biome HTML parser, `expect` and `expect_with_context` consume the current token and then lex the next token. The context parameter in `expect_with_context` controls how the next token (after the consumed one) is lexed, not the current token being consumed. For example, in Svelte parsing, after `bump_with_context(T!["{:"], HtmlLexContext::Svelte)`, the next token is already lexed in the Svelte context, so `expect(T![else])` is sufficient unless the token after `else` also needs to be lexed in a specific context.

Applied to files:

  • crates/biome_css_parser/src/syntax/value/if.rs
🧬 Code graph analysis (3)
crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs (1)
crates/biome_css_parser/src/syntax/mod.rs (2)
  • try_parse (643-658)
  • parse_any_declaration_with_semicolon (227-235)
crates/biome_css_parser/tests/css_test_suite/error/function/if.css (1)
crates/biome_js_parser/src/syntax/stmt.rs (1)
  • semi (86-107)
crates/biome_css_parser/src/syntax/value/if.rs (4)
crates/biome_css_parser/src/syntax/value/parse_error.rs (3)
  • expected_if_test_boolean_not_expr (34-39)
  • expected_if_test_boolean_expr_group (27-32)
  • expected_if_branch (23-25)
crates/biome_css_parser/src/state.rs (1)
  • new (35-41)
crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs (1)
  • is_at_recovered (51-53)
crates/biome_css_parser/src/syntax/value/function.rs (1)
  • is_at_recovered (108-110)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Parser conformance
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: End-to-end tests
  • GitHub Check: Check Dependencies
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: autofix
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_css_formatter)
🔇 Additional comments (5)
crates/biome_css_parser/src/syntax/value/if.rs (3)

5-35: Nice: centralised branch recovery token set.

IF_BRANCH_RECOVERY_TOKEN_SET makes the list recovery intent much clearer and keeps the “stop points” consistent.


301-310: Good: not <expr-group> recovery stops at :/) and uses a specific bogus kind.

This is the sort of targeted recovery that keeps subsequent parsing (especially : in branches) on track. Based on learnings, this is the right shape for recovery in list-ish constructs.


471-476: LGTM: list recovery now uses the shared token set.

This looks like a straightforward improvement to keep branch-list recovery consistent across the file.

crates/biome_css_parser/src/state.rs (1)

23-31: Good documentation and initialisation for the new speculative-parsing flag.

This reads well and makes the if()/semicolon conflict explicit.

Also applies to: 39-40

crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs (1)

75-121: Solid workaround for the if() semicolon/speculative-parsing conflict; sanity-check flag lifetime.

The reset → forced speculative failure → non-speculative retry looks correct and nicely targeted. One thing to watch: because try_parse rewinds tokens but not arbitrary parser state, it’s worth ensuring encountered_if_function is only ever set during speculative parsing (so it can’t “stick” across unrelated parses).

@ematipico ematipico modified the milestone: Biome v2.4 Jan 11, 2026
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: 1

🤖 Fix all issues with AI agents
In `@crates/biome_css_parser/src/syntax/value/if.rs`:
- Around line 266-273: The recovery for the parenthesised boolean group is using
the wrong expectation token—update the recovery to reference a boolean-expr
group instead of the “not” variant: in the call chain around
parse_any_if_test_boolean_expr(...) .or_recover_with_token_set(...), replace the
expectation identifier expected_if_test_boolean_not_expr with the appropriate
boolean-expr expectation (e.g. expected_if_test_boolean_expr or the project’s
boolean-expr-group expectation constant) and ensure the
ParseRecoveryTokenSet/CSS_BOGUS_IF_TEST_BOOLEAN_EXPR message reflects
"boolean-expr group" rather than "not" so the recovery matches parenthesised
boolean expressions.

Comment on lines +266 to +273

parse_any_if_test_boolean_expr(p)
.or_recover_with_token_set(
p,
&ParseRecoveryTokenSet::new(CSS_BOGUS_IF_TEST_BOOLEAN_EXPR, token_set![T![')']]),
expected_if_test_boolean_not_expr,
)
.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

Diagnostic mismatch inside parenthesised boolean group.
When parsing ( <boolean-expr> ), the recovery should reference a boolean‑expr group, not just “not” specifically.

🔧 Proposed fix
-            expected_if_test_boolean_not_expr,
+            expected_if_test_boolean_expr_group,
🤖 Prompt for AI Agents
In `@crates/biome_css_parser/src/syntax/value/if.rs` around lines 266 - 273, The
recovery for the parenthesised boolean group is using the wrong expectation
token—update the recovery to reference a boolean-expr group instead of the “not”
variant: in the call chain around parse_any_if_test_boolean_expr(...)
.or_recover_with_token_set(...), replace the expectation identifier
expected_if_test_boolean_not_expr with the appropriate boolean-expr expectation
(e.g. expected_if_test_boolean_expr or the project’s boolean-expr-group
expectation constant) and ensure the
ParseRecoveryTokenSet/CSS_BOGUS_IF_TEST_BOOLEAN_EXPR message reflects
"boolean-expr group" rather than "not" so the recovery matches parenthesised
boolean expressions.

@ematipico ematipico merged commit 21dba4a into biomejs:main Jan 26, 2026
17 checks passed
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants