Skip to content

feat(parse/tailwind): parse arbitrary values as css#10299

Merged
dyc3 merged 1 commit into
mainfrom
dyc3/tw-parse-arbitrary-css-values
May 8, 2026
Merged

feat(parse/tailwind): parse arbitrary values as css#10299
dyc3 merged 1 commit into
mainfrom
dyc3/tw-parse-arbitrary-css-values

Conversation

@dyc3
Copy link
Copy Markdown
Contributor

@dyc3 dyc3 commented May 7, 2026

Summary

This implements more precise parsing of arbitrary values in tailwind candidates.

There's 3 important decisions I made:

  • I chose to make the syntax nodes mirror how the CSS parser defines them, because they are the same concept.
  • I had the syntax nodes keep the Css prefix instead of using the Tw because its more clear what is tailwind syntax and css syntax.
  • I did not use the embedded parsing infrastructure, because we need access to this information in lint rules, and wiring that up would be more of a pain.

The performance regressions are expected to a certain extent because we are now doing more complicated parsing, where previously we were doing effectively nothing.

Implemented initially by gpt 5.5, but i had to make significant changes to it.

related: #10291

Test Plan

snapshots and new tests

Docs

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 7, 2026

⚠️ No Changeset found

Latest commit: 737e800

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-Tooling Area: internal tools L-Tailwind Language: Tailwind CSS labels May 7, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

Parser conformance results on

js/262

Test result main count This PR count Difference
Total 53206 53206 0
Passed 51988 51988 0
Failed 1176 1176 0
Panics 42 42 0
Coverage 97.71% 97.71% 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 658 658 0
Passed 574 574 0
Failed 84 84 0
Panics 0 0 0
Coverage 87.23% 87.23% 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-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 7, 2026

Merging this PR will degrade performance by 30.42%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 6 (👁 6) regressed benchmarks
✅ 4 untouched benchmarks
⏩ 246 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
👁 cached[arbitrary_classes.txt] 249.1 µs 335.3 µs -25.69%
👁 cached[extreme_stress.txt] 842.7 µs 1,014.4 µs -16.92%
👁 cached[stress.txt] 2.2 ms 2.3 ms -6.6%
👁 uncached[stress.txt] 2.8 ms 3 ms -6.96%
👁 uncached[arbitrary_classes.txt] 311.3 µs 447.5 µs -30.42%
👁 uncached[extreme_stress.txt] 1.1 ms 1.3 ms -19.68%

Comparing dyc3/tw-parse-arbitrary-css-values (737e800) with main (91ed677)

Open in CodSpeed

Footnotes

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

@dyc3 dyc3 force-pushed the dyc3/tw-parse-arbitrary-css-values branch from fcf4af6 to b3f08bf Compare May 7, 2026 18:02
@dyc3 dyc3 marked this pull request as ready for review May 7, 2026 18:17
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d508e2fe-7300-4eca-b542-6767c3c849a0

📥 Commits

Reviewing files that changed from the base of the PR and between 7caea11 and 737e800.

⛔ Files ignored due to path filters (34)
  • crates/biome_tailwind_factory/src/generated/node_factory.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_factory/src/generated/syntax_factory.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-value-in-arbitrary.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-arbitrary-values.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-number-exponents.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-functions.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-math-functions.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-number-exponents.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-urls.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-values.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/gradient.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/image-url.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/inset.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/misc-xy.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/shadow.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-0.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-2.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-3.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-css-function.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/css-arbitrary-candidates.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/group-data.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-0.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-2.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-selector-functional-candidate.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/consecutive-arbitrary-variants.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-arbirary-param.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_syntax/src/generated/kind.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_syntax/src/generated/macros.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_syntax/src/generated/nodes.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_syntax/src/generated/nodes_mut.rs is excluded by !**/generated/**, !**/generated/** and included by **
📒 Files selected for processing (20)
  • .claude/skills/parser-development/SKILL.md
  • crates/biome_tailwind_parser/src/lexer/mod.rs
  • crates/biome_tailwind_parser/src/lexer/tests.rs
  • crates/biome_tailwind_parser/src/syntax/css_value.rs
  • crates/biome_tailwind_parser/src/syntax/mod.rs
  • crates/biome_tailwind_parser/src/syntax/value.rs
  • crates/biome_tailwind_parser/src/token_source.rs
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-arbitrary-values.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-number-exponents.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-functions.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-math-functions.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-number-exponents.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-urls.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-values.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-css-function.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/css-arbitrary-candidates.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-selector-functional-candidate.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/consecutive-arbitrary-variants.txt
  • xtask/codegen/src/tailwind_kinds_src.rs
  • xtask/codegen/tailwind.ungram
✅ Files skipped from review due to trivial changes (9)
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/consecutive-arbitrary-variants.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-functions.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-selector-functional-candidate.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/css-arbitrary-candidates.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-number-exponents.txt
  • .claude/skills/parser-development/SKILL.md
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-css-function.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-number-exponents.txt
  • crates/biome_tailwind_parser/src/lexer/tests.rs
🚧 Files skipped from review as they are similar to previous changes (8)
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-urls.txt
  • xtask/codegen/tailwind.ungram
  • crates/biome_tailwind_parser/src/syntax/css_value.rs
  • xtask/codegen/src/tailwind_kinds_src.rs
  • crates/biome_tailwind_parser/src/syntax/value.rs
  • crates/biome_tailwind_parser/src/token_source.rs
  • crates/biome_tailwind_parser/src/syntax/mod.rs
  • crates/biome_tailwind_parser/src/lexer/mod.rs

Walkthrough

This PR extends the Tailwind parser with structured CSS value parsing for arbitrary values and candidates. It replaces opaque TW_VALUE tokens with a complete CSS grammar (CssGenericComponentValueList), introducing dedicated lexing contexts (CssValue, CssUrlRawValue, ArbitraryVariant, ArbitraryCandidate), comprehensive token kinds for CSS operators and units, and recursive descent parsers for CSS literals, functions, dimensions, expressions, and URL bodies. Integration points update parse_arbitrary_value and parse_arbitrary_candidate to dispatch through the new structured CSS parser rather than consuming generic tokens, enabling precise error reporting and nested expression support.

Possibly related PRs

  • biomejs/biome#9746: Adds context-specific lexer states and force_relex_heading_content method, parallel to this PR's re_lex_current_in_context pattern for context-aware token re-lexing.

Suggested reviewers

  • ematipico
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarises the main change: implementing CSS parsing for Tailwind arbitrary values rather than opaque token handling.
Description check ✅ Passed Description explains the motivation (more precise parsing), key decisions (mirroring CSS syntax nodes, naming conventions, avoiding embedded parsing), and acknowledges AI assistance.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dyc3/tw-parse-arbitrary-css-values

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 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.

Inline comments:
In `@crates/biome_tailwind_parser/src/lexer/mod.rs`:
- Around line 162-177: In consume_css_url_raw_value, the loop currently stops on
')' or whitespace without honoring backslash escapes; update the loop inside
consume_css_url_raw_value to detect a backslash escape (e.g., when
current_byte() == b'\\'), consume the backslash and then consume the following
code point (so escaped ')' or whitespace are treated as part of the raw value),
and only treat unescaped ')' or whitespace as terminators; also add regression
tests covering bg-[url(foo\)bar)] and bg-[url(foo\ bar)] to validate the
escaped-paren and escaped-space behavior.
- Around line 453-460: The loop in consume_bracketed_thing fails to ignore
bracket characters inside quoted strings, so modify consume_bracketed_thing to
track quote state and escapes (mirroring the logic in consume_css_string): add
variables for current quote char and escape flag, update those inside the while
loop before bracket checks (toggle quote on unescaped quote chars, set/clear
escape on backslashes) and only consider dispatched == BTO/BTC or looking_for
when not inside a quote and when escape is not active; keep existing
bracket_depth logic otherwise. After implementing, add a regression test that
parses inputs like `[data-label="]"]` and `[data-label='\\'\\\"]']` to verify
brackets inside quoted attribute values do not prematurely terminate
tokenization.

In `@crates/biome_tailwind_parser/src/syntax/css_value.rs`:
- Around line 279-283: The recovery token set passed to
parsed_element.or_recover_with_token_set currently stops on commas and ')' only,
allowing recovery to continue past ']' inside constructs like calc(...); update
the ParseRecoveryTokenSet::new call (the one using CSS_BOGUS_PROPERTY_VALUE and
token_set![T![,], T![')']]) to also include the closing bracket token T![']]' so
recovery will stop at ']' as well.
- Around line 315-321: The current code treats every binary operator as
right-associative by immediately parsing the right-hand side with
parse_any_expression when is_at_binary_operator is true, producing incorrect
trees for mixed precedence (e.g. parse_unary_expression_operand -> binary
operator -> parse_any_expression makes 1 * 2 + 3 parse as 1 * (2 + 3)). Change
this to a simple precedence-climbing approach: after parsing the left operand
with parse_unary_expression_operand, check is_at_binary_operator and
loop/recursively consume subsequent binary operators by distinguishing
low-precedence (+/-) vs high-precedence (*/%) operators (using
BINARY_OPERATION_TOKEN and TailwindLexContext::CssValue to read the operator),
build left-associative CSS_BINARY_EXPRESSION nodes by creating a binary node
from the current left and the next parsed right operand (call
parse_unary_expression_operand for higher-precedence RHS when needed or parse
the next operand and then continue), and only call parse_any_expression for
full-expression contexts where appropriate (ensure parse_any_expression use is
replaced so mixed-precedence expressions produce (1 * 2) + 3 rather than 1 * (2
+ 3)).
- Around line 327-350: The unary operator token set is wrong: remove '*' from
UNARY_OPERATION_TOKEN so only '+' and '-' are treated as unary; update the
token_set declaration for UNARY_OPERATION_TOKEN to token_set![T![+], T![-]] so
is_at_unary_operator, parse_unary_expression, and any callers (e.g.,
parse_any_expression) no longer accept `*foo` as a CSS_UNARY_EXPRESSION; no
other changes to BINARY_OPERATION_TOKEN or
COMPONENT_VALUE_EXPRESSION_RECOVERY_SET are needed.

In `@xtask/codegen/tailwind.ungram`:
- Around line 280-287: The field types are too narrow: update
CssUnaryExpression's argument from AnyCssValue to
CssListOfComponentValuesExpression, and update CssParenthesizedExpression's
expression from CssComponentValueList to CssListOfComponentValuesExpression so
the generated accessors match the parser output and won't return None for valid
parse trees.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d62904ef-e76b-44a1-847f-77f849759540

📥 Commits

Reviewing files that changed from the base of the PR and between 8d19e7f and b3f08bf.

⛔ Files ignored due to path filters (34)
  • crates/biome_tailwind_factory/src/generated/node_factory.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_factory/src/generated/syntax_factory.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-value-in-arbitrary.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-arbitrary-values.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-number-exponents.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-functions.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-math-functions.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-number-exponents.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-urls.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-values.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/gradient.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/image-url.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/inset.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/misc-xy.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/shadow.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-0.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-2.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-3.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-css-function.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/css-arbitrary-candidates.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/group-data.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-0.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-2.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-selector-functional-candidate.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/consecutive-arbitrary-variants.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-arbirary-param.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_syntax/src/generated/kind.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_syntax/src/generated/macros.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_syntax/src/generated/nodes.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_syntax/src/generated/nodes_mut.rs is excluded by !**/generated/**, !**/generated/** and included by **
📒 Files selected for processing (19)
  • crates/biome_tailwind_parser/src/lexer/mod.rs
  • crates/biome_tailwind_parser/src/lexer/tests.rs
  • crates/biome_tailwind_parser/src/syntax/css_value.rs
  • crates/biome_tailwind_parser/src/syntax/mod.rs
  • crates/biome_tailwind_parser/src/syntax/value.rs
  • crates/biome_tailwind_parser/src/token_source.rs
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-arbitrary-values.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-number-exponents.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-functions.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-math-functions.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-number-exponents.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-urls.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-values.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-css-function.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/css-arbitrary-candidates.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-selector-functional-candidate.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/consecutive-arbitrary-variants.txt
  • xtask/codegen/src/tailwind_kinds_src.rs
  • xtask/codegen/tailwind.ungram

Comment thread crates/biome_tailwind_parser/src/lexer/mod.rs
Comment thread crates/biome_tailwind_parser/src/lexer/mod.rs
Comment thread crates/biome_tailwind_parser/src/syntax/css_value.rs
Comment thread crates/biome_tailwind_parser/src/syntax/css_value.rs
Comment thread crates/biome_tailwind_parser/src/syntax/css_value.rs
Comment thread xtask/codegen/tailwind.ungram
@dyc3 dyc3 requested review from a team May 7, 2026 18:29
@dyc3 dyc3 force-pushed the dyc3/tw-parse-arbitrary-css-values branch from b3f08bf to 6b164c8 Compare May 7, 2026 18:39
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
crates/biome_tailwind_parser/src/syntax/mod.rs (1)

179-186: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Emit error only if closing bracket is found.

The error on line 180 is emitted before validating the ] token. If parse_css_generic_component_value_list returns false and the closing bracket is missing, the rewind on line 185 doesn't roll back that diagnostic—it survives as a spurious error.

Whilst the existing test [width:] passes because the bracket is present, an edge case like [width: at EOF would trigger this leak. Move the error emission after the successful expect(T![']']) or use a diagnostic guard approach to avoid it.

🤖 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_tailwind_parser/src/syntax/mod.rs` around lines 179 - 186, The
diagnostic is emitted unconditionally when
parse_css_generic_component_value_list(p) returns false, which leaks the error
if the closing bracket is missing and the parser rewinds; move the
p.error(expected_value(p, p.cur_range())) call so it only runs after confirming
the closing bracket with p.expect(T![']']), or alternatively wrap the error
emission in a guard that is only committed when m is not abandoned; update the
block around parse_css_generic_component_value_list, p.expect, m.abandon,
p.rewind, checkpoint and Absent accordingly so errors are only recorded on
successful consumption of the trailing ']' token.
🧹 Nitpick comments (1)
crates/biome_tailwind_parser/src/syntax/mod.rs (1)

188-196: 💤 Low value

Dead condition — the second if p.at(T![/]) is always true here.

After the if !p.at(T![/]) { return … } early-return on Line 188, execution only continues when p.at(T![/]) is already true. The second guard is dead code.

✂️ Suggested simplification
-    if !p.at(T![/]) {
-        return Present(m.complete(p, TW_ARBITRARY_CANDIDATE));
-    }
-
-    if p.at(T![/]) {
-        parse_modifier(p).or_add_diagnostic(p, expected_modifier);
-    }
+    if p.at(T![/]) {
+        parse_modifier(p).or_add_diagnostic(p, expected_modifier);
+    }
🤖 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_tailwind_parser/src/syntax/mod.rs` around lines 188 - 196, The
second conditional checking p.at(T![/]) is dead because the earlier if
!p.at(T![/]) returns; remove that redundant guard and directly call
parse_modifier(p).or_add_diagnostic(p, expected_modifier) before returning
Present(m.complete(p, TW_ARBITRARY_CANDIDATE)); update the block around
m.complete/Present so the flow is: if not p.at('/'){ return
Present(m.complete(...)) } parse_modifier(...).or_add_diagnostic(...);
Present(m.complete(...)); keeping the same symbols m.complete,
TW_ARBITRARY_CANDIDATE, parse_modifier, expected_modifier and Present.
🤖 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.

Inline comments:
In @.claude/skills/parser-development/SKILL.md:
- Around line 199-200: Update the misleading comment so it matches the parser
logic: replace the comment "Stop at array closing bracket or file end" with a
precise note like "Stop at array closing bracket" (referencing the parser check
p.at(T![']']) in the same block) — or alternatively, if the intent was to
include EOF, modify the condition to check p.at(T![']']) || p.eof(); ensure the
comment and the code (the line using p.at(T![']'])) stay consistent.

In `@xtask/codegen/tailwind.ungram`:
- Around line 257-260: AnyCssUrlValue currently includes CssParameterList which
incorrectly allows constructs like url(expr, expr) into the AST; change the
grammar so AnyCssUrlValue only accepts CssUrlValueRaw or CssString and remove
CssParameterList from its alternatives, and ensure parsing of non-URL/raw/string
content falls back to CssBogusPropertyValue (update the parser rule that
produces AnyCssUrlValue and the error/recovery path so invalid parameter lists
are emitted as CssBogusPropertyValue rather than as CssParameterList).

---

Outside diff comments:
In `@crates/biome_tailwind_parser/src/syntax/mod.rs`:
- Around line 179-186: The diagnostic is emitted unconditionally when
parse_css_generic_component_value_list(p) returns false, which leaks the error
if the closing bracket is missing and the parser rewinds; move the
p.error(expected_value(p, p.cur_range())) call so it only runs after confirming
the closing bracket with p.expect(T![']']), or alternatively wrap the error
emission in a guard that is only committed when m is not abandoned; update the
block around parse_css_generic_component_value_list, p.expect, m.abandon,
p.rewind, checkpoint and Absent accordingly so errors are only recorded on
successful consumption of the trailing ']' token.

---

Nitpick comments:
In `@crates/biome_tailwind_parser/src/syntax/mod.rs`:
- Around line 188-196: The second conditional checking p.at(T![/]) is dead
because the earlier if !p.at(T![/]) returns; remove that redundant guard and
directly call parse_modifier(p).or_add_diagnostic(p, expected_modifier) before
returning Present(m.complete(p, TW_ARBITRARY_CANDIDATE)); update the block
around m.complete/Present so the flow is: if not p.at('/'){ return
Present(m.complete(...)) } parse_modifier(...).or_add_diagnostic(...);
Present(m.complete(...)); keeping the same symbols m.complete,
TW_ARBITRARY_CANDIDATE, parse_modifier, expected_modifier and Present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e33d8002-0db2-406c-83e6-3f75ab63436e

📥 Commits

Reviewing files that changed from the base of the PR and between b3f08bf and 6b164c8.

⛔ Files ignored due to path filters (34)
  • crates/biome_tailwind_factory/src/generated/node_factory.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_factory/src/generated/syntax_factory.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-value-in-arbitrary.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-arbitrary-values.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-number-exponents.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-functions.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-math-functions.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-number-exponents.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-urls.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-values.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/gradient.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/image-url.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/inset.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/misc-xy.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/shadow.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-0.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-2.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-3.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-css-function.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/css-arbitrary-candidates.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/group-data.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-0.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-2.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-1.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-selector-functional-candidate.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/consecutive-arbitrary-variants.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-arbirary-param.txt.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_syntax/src/generated/kind.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_syntax/src/generated/macros.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_syntax/src/generated/nodes.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_tailwind_syntax/src/generated/nodes_mut.rs is excluded by !**/generated/**, !**/generated/** and included by **
📒 Files selected for processing (20)
  • .claude/skills/parser-development/SKILL.md
  • crates/biome_tailwind_parser/src/lexer/mod.rs
  • crates/biome_tailwind_parser/src/lexer/tests.rs
  • crates/biome_tailwind_parser/src/syntax/css_value.rs
  • crates/biome_tailwind_parser/src/syntax/mod.rs
  • crates/biome_tailwind_parser/src/syntax/value.rs
  • crates/biome_tailwind_parser/src/token_source.rs
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-arbitrary-values.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-number-exponents.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-functions.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-math-functions.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-number-exponents.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-urls.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-values.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-css-function.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/css-arbitrary-candidates.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-selector-functional-candidate.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/consecutive-arbitrary-variants.txt
  • xtask/codegen/src/tailwind_kinds_src.rs
  • xtask/codegen/tailwind.ungram
✅ Files skipped from review due to trivial changes (8)
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-css-function.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/error/css-number-exponents.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-selector-functional-candidate.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-number-exponents.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-urls.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/css-functions.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/css-arbitrary-candidates.txt
  • crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/consecutive-arbitrary-variants.txt
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_tailwind_parser/src/syntax/value.rs

Comment thread .claude/skills/parser-development/SKILL.md
Comment on lines +257 to +260
AnyCssUrlValue =
CssUrlValueRaw
| CssString
| CssParameterList
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

CssParameterList is not a valid URL value form.

AnyCssUrlValue should only ever be a raw literal or a quoted string. Listing CssParameterList as a third alternative makes url(expr, expr) structurally valid in the AST, which isn't a real CSS URL form. Error content that doesn't lex as a raw URL or string body should fall back to CssBogusPropertyValue, not be parsed as a comma-separated expression list.

🛠️ Suggested fix
 AnyCssUrlValue =
     CssUrlValueRaw
     | CssString
-    | CssParameterList
+    | CssBogusPropertyValue
📝 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
AnyCssUrlValue =
CssUrlValueRaw
| CssString
| CssParameterList
AnyCssUrlValue =
CssUrlValueRaw
| CssString
| CssBogusPropertyValue
🤖 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 `@xtask/codegen/tailwind.ungram` around lines 257 - 260, AnyCssUrlValue
currently includes CssParameterList which incorrectly allows constructs like
url(expr, expr) into the AST; change the grammar so AnyCssUrlValue only accepts
CssUrlValueRaw or CssString and remove CssParameterList from its alternatives,
and ensure parsing of non-URL/raw/string content falls back to
CssBogusPropertyValue (update the parser rule that produces AnyCssUrlValue and
the error/recovery path so invalid parameter lists are emitted as
CssBogusPropertyValue rather than as CssParameterList).

Copy link
Copy Markdown
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

Unfortunately I don't have sufficient knowledge, so treat this approval as a rubber stamp (please accept the regressions before merging)

@dyc3 dyc3 force-pushed the dyc3/tw-parse-arbitrary-css-values branch from 6b164c8 to 7caea11 Compare May 8, 2026 14:05
@github-actions github-actions Bot added the L-CSS Language: CSS and super languages label May 8, 2026
@dyc3 dyc3 force-pushed the dyc3/tw-parse-arbitrary-css-values branch from 7caea11 to 737e800 Compare May 8, 2026 15:12
@github-actions github-actions Bot removed the L-CSS Language: CSS and super languages label May 8, 2026
@dyc3 dyc3 merged commit 4dbd88e into main May 8, 2026
28 checks passed
@dyc3 dyc3 deleted the dyc3/tw-parse-arbitrary-css-values branch May 8, 2026 15:31
jiwon79 added a commit to jiwon79/biome that referenced this pull request May 10, 2026
NamedTyped only carries Number/Percentage/Ratio in the preset, so
delete the 12 dormant Color/Length/Angle/Image/etc. predicates and
their helpers. ValueType::matches now dispatches only those three
and returns false for the rest. ArbitraryTyped/Arbitrary branches
stay in the preset for the follow-up PR that wires them up via the
parser nodes from biomejs#10299.
jiwon79 added a commit to jiwon79/biome that referenced this pull request May 10, 2026
NamedTyped only carries Number/Percentage/Ratio in the preset, so
delete the 12 dormant Color/Length/Angle/Image/etc. predicates and
their helpers. ValueType::matches now dispatches only those three
and returns false for the rest. ArbitraryTyped/Arbitrary branches
stay in the preset for the follow-up PR that wires them up via the
parser nodes from biomejs#10299.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Parser Area: parser A-Tooling Area: internal tools L-Tailwind Language: Tailwind CSS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants