+
Skip to content

Conversation

dyc3
Copy link
Contributor

@dyc3 dyc3 commented Aug 9, 2025

Summary

This adds a parser option, tailwind_directives to the css parser to allow it to parse Tailwind v4's new css-based config. The option name is open to bike shedding.

This is pretty experimental, particularly because of the lack of a formal spec to validate against. Most test scenarios are derived from the tailwind docs.

Something to note is that this PR will get the css parser very close to the 512 SyntaxKind limit (512/512). Once this is merged, we won't be able to meaningfully extend the css parser without either refactoring it to use less syntax kinds, or extending the parsing infra to allow languages to have more than 512 syntax kinds.

Related: #3195
Closes #7223

TODO

Test Plan

Added tests.

Docs

biomejs/website#2962

Copy link

changeset-bot bot commented Aug 9, 2025

🦋 Changeset detected

Latest commit: 622f9a5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@biomejs/biome Minor
@biomejs/cli-win32-x64 Minor
@biomejs/cli-win32-arm64 Minor
@biomejs/cli-darwin-x64 Minor
@biomejs/cli-darwin-arm64 Minor
@biomejs/cli-linux-x64 Minor
@biomejs/cli-linux-arm64 Minor
@biomejs/cli-linux-x64-musl Minor
@biomejs/cli-linux-arm64-musl Minor
@biomejs/wasm-web Minor
@biomejs/wasm-bundler Minor
@biomejs/wasm-nodejs Minor
@biomejs/backend-jsonrpc Patch
@biomejs/js-api Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

coderabbitai bot commented Aug 9, 2025

Walkthrough

Adds opt-in Tailwind CSS 4.0 support across the codebase. Introduces a tailwindDirectives parser option (default off) and propagates it through configuration, service settings, JSON‑RPC schema and test harness. Extends grammar, lexer and parser with Tailwind keywords, nine at-rule parsers, functional utility names and TwValueThemeReference handling plus diagnostics. Wires new Tailwind AST nodes into the formatter and semantic analysis, adds formatter implementations and formatter module structure, and adds comprehensive tests for enabled/disabled scenarios.

Possibly related PRs

Suggested reviewers

  • ematipico

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch css-tailwind-syntax

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added A-Project Area: project A-Parser Area: parser A-Formatter Area: formatter A-Tooling Area: internal tools L-CSS Language: CSS labels Aug 9, 2025
Copy link
Contributor

github-actions bot commented Aug 9, 2025

Parser conformance results on

js/262

Test result main count This PR count Difference
Total 50628 50628 0
Passed 49358 49358 0
Failed 1270 1270 0
Panics 0 0 0
Coverage 97.49% 97.49% 0.00%

jsx/babel

Test result main count This PR count Difference
Total 40 40 0
Passed 37 37 0
Failed 3 3 0
Panics 0 0 0
Coverage 92.50% 92.50% 0.00%

symbols/microsoft

Test result main count This PR count Difference
Total 6706 6706 0
Passed 2245 2245 0
Failed 4461 4461 0
Panics 0 0 0
Coverage 33.48% 33.48% 0.00%

ts/babel

Test result main count This PR count Difference
Total 825 825 0
Passed 732 732 0
Failed 93 93 0
Panics 0 0 0
Coverage 88.73% 88.73% 0.00%

ts/microsoft

Test result main count This PR count Difference
Total 18786 18786 0
Passed 14413 14413 0
Failed 4373 4373 0
Panics 0 0 0
Coverage 76.72% 76.72% 0.00%

Copy link

codspeed-hq bot commented Aug 9, 2025

CodSpeed Performance Report

Merging #7164 will not alter performance

Comparing css-tailwind-syntax (622f9a5) with next (a3e3369)

Summary

✅ 131 untouched benchmarks

@dyc3 dyc3 force-pushed the css-tailwind-syntax branch 2 times, most recently from d85eeef to 0c122f8 Compare August 10, 2025 12:17
@github-actions github-actions bot added the A-Linter Area: linter label Aug 10, 2025
Copy link
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.

Few comments around the grammar for now

@dyc3 dyc3 force-pushed the css-tailwind-syntax branch 2 times, most recently from 42a0b32 to 9de1cd8 Compare August 10, 2025 16:32
@dyc3
Copy link
Contributor Author

dyc3 commented Aug 10, 2025

9de1cd8 seems to be causing a regression for grit metavariable parsing for some reason. which is weird because i didn't touch that.

@dyc3 dyc3 marked this pull request as ready for review August 11, 2025 15:45
@dyc3 dyc3 requested review from a team, denbezrukov and ematipico August 11, 2025 15:45
@dyc3
Copy link
Contributor Author

dyc3 commented Aug 11, 2025

This is in a good enough state to review now. I'll add a changeset later. Should this be a minor?

Also, I'll do the formatters in a follow up PR.

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

🔭 Outside diff range comments (6)
xtask/codegen/src/js_kinds_src.rs (1)

661-664: Correctness: wrong names for shift-assign tokens

The names for <<=/>>= are swapped. This can generate misleading accessors.

Apply this fix:

-                    (">>=", _) => "left_shift_assign",
-                    ("<<=", _) => "right_shift_assign",
+                    ("<<=", _) => "left_shift_assign",
+                    (">>=", _) => "right_shift_assign",
crates/biome_css_formatter/src/tailwind/auxiliary/simple_utility_name.rs (1)

1-11: Add missing module registration

Our check didn’t find simple_utility_name in crates/biome_css_formatter/src/tailwind/auxiliary/mod.rs, so the new formatter won’t be picked up. Please add:

--- a/crates/biome_css_formatter/src/tailwind/auxiliary/mod.rs
+++ b/crates/biome_css_formatter/src/tailwind/auxiliary/mod.rs
+mod simple_utility_name;
xtask/codegen/src/formatter.rs (1)

573-584: Missing Tailwind in NodeDialect::all().

The Tailwind variant is not included in the all() method, which could prevent proper module generation for Tailwind formatters.

 fn all() -> &'static [Self] {
     &[
         Self::Js,
         Self::Ts,
         Self::Jsx,
         Self::Json,
         Self::Css,
         Self::Grit,
         Self::Graphql,
         Self::Html,
+        Self::Tailwind,
     ]
 }
crates/biome_css_parser/src/lexer/mod.rs (1)

834-834: Typo in viewport height unit mapping.

The "svh" keyword is incorrectly mapped to SVW_KW instead of SVH_KW.

-            b"svh" => SVW_KW,
+            b"svh" => SVH_KW,
crates/biome_service/src/file_handlers/css.rs (1)

379-396: Critical: Tailwind directives always disabled in parser

The tailwind_directives option is hardcoded to false instead of reading from settings. This prevents the feature from working even when enabled in configuration.

Apply this fix:

     let mut options = CssParserOptions {
         allow_wrong_line_comments: settings
             .languages
             .css
             .parser
             .allow_wrong_line_comments
             .unwrap_or_default()
             .into(),
         css_modules: settings
             .languages
             .css
             .parser
             .css_modules_enabled
             .unwrap_or_default()
             .into(),
         grit_metavariables: false,
-        tailwind_directives: false,
+        tailwind_directives: settings
+            .languages
+            .css
+            .parser
+            .tailwind_directives_enabled(),
     };
crates/biome_css_formatter/src/tailwind/auxiliary/mod.rs (1)

1-6: Missing auxiliary module declarations in mod.rs

The generated mod.rs only exports three modules, but there are ten .rs files in crates/biome_css_formatter/src/tailwind/auxiliary/. Please update the code generator (or re-run it) so that all of the following modules are declared:

  • alpha_function
  • helper_function
  • modifier_function
  • simple_utility_name
  • spacing_function
  • value_arbitrary_type
  • value_function

This will ensure the auxiliary folder’s contents are fully exposed.

🧹 Nitpick comments (29)
crates/biome_css_parser/tests/css_test_suite/ok/tailwind/utility/value-literals.css (1)

2-2: Add complementary cases for --value wildcards and identifiers

This covers multi-arg strings. Consider adding:

  • A wildcard variant: --value([], "inherit") to exercise the [] path mentioned in the PR.
  • An identifiers variant (if allowed): --value(inherit, initial, unset) to ensure both token forms parse as intended.

Happy to draft the extra fixtures under ok/tailwind/utility and a disabled/diagnostics counterpart if you want parity tests.

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/reference.css (2)

18-20: Add variants for quoting and whitespace robustness.

Consider extending with single quotes and extra whitespace to harden parsing around trivia and different string delimiters.

Proposed additions:

 /* @reference without file extension */
 @reference "./mixins";
 
+/* @reference with single quotes */
+@reference './single-quotes.css';
+
+/* @reference with additional whitespace */
+@reference     "./spaced.css"   ;
+
+/* @reference with escaped characters in string */
+@reference "./with\\ spaces.css";

10-14: Optional: add a bare package name and a deeper scoped path.

If the grammar accepts any string, adding these helps ensure we don’t accidentally special-case only paths with slashes or extensions.

Proposed additions:

 /* @reference with scoped package */
 @reference "../node_modules/@company/design-tokens/index.css";
 
+/* @reference with bare package name */
+@reference "normalize.css";
+
+/* @reference with deeper scoped package path */
+@reference "@company/design-tokens/tokens/colors.css";
crates/biome_css_formatter/src/tailwind/auxiliary/spacing_function.rs (1)

1-10: Add a formatter round-trip test to lock behaviour

Given this prints verbatim, a simple golden/round-trip test would guard against accidental normalisation later (e.g., spacing between tokens, comments, empty args).

Would you like me to draft a formatter test fixture mirroring the parser one?

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/spacing_function.css (1)

1-75: Consider a formatter snapshot/round-trip for spacing()

A companion formatter test (enabled mode) would help ensure spacing() survives formatting unchanged across the variety of contexts covered here.

crates/biome_css_formatter/src/tailwind/auxiliary/modifier_function.rs (1)

4-5: Nit: derive Copy and add a brief rustdoc for intent

Zero-sized rule types typically derive Copy in this codebase. A short rustdoc helps future readers understand why this is verbatim.

Apply:

-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Copy, Default)]
-pub(crate) struct FormatTwModifierFunction;
+/// Tailwind v4 (experimental): format modifier functions verbatim until rules stabilise.
+pub(crate) struct FormatTwModifierFunction;
crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/source.css (1)

1-1: Good negative test; consider one more variant

Nice addition for the disabled-path. Consider adding a second case with a url() form or spaces around the string to ensure the diagnostic triggers consistently across argument forms.

crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/utility.css (1)

1-1: Solid disabled-path check; widen coverage

Add a second utility using parameters or modifiers (e.g. with brackets or slash modifiers) to assert the same diagnostic fires for complex names too.

crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/theme.css (1)

1-3: Nice; also test nested content under disabled

Consider an additional disabled test where @theme contains nested at-rules (e.g. keyframes) to confirm the gate triggers regardless of inner content.

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/theme/theme-with-keyframes.css (1)

1-11: Great nested-case coverage

Good call testing keyframes inside theme. A sibling nested rule (e.g. @font-face) would further exercise nesting, but this is already valuable.

crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/apply.css (1)

1-3: Good failure path; consider syntax edge variants

Add a case without the trailing semicolon or multiple @apply lines to confirm diagnostics aren’t sensitive to minor syntax variations.

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/theme/theme-with-name.css (1)

1-3: Looks good: named @theme parses cleanly

Solid positive path. Consider adding a companion test for a quoted name (e.g. "@theme "foo"") or anonymous theme to harden coverage.

crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/reference.css (1)

1-1: Good negative case for disabled Tailwind directives

This should trigger the expected “tailwind disabled” diagnostic. You might also add variants using single quotes and url() to broaden coverage.

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/utility/simple-utility.css (1)

1-5: Comprehensive utility parsing smoke tests

Nice coverage, including an empty @Utility. As a follow-up, a test with multiple utilities back-to-back without blank lines could help assert formatter spacing decisions.

Also applies to: 7-17, 19-21, 23-23

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/utility/arbitrary-star.css (1)

1-3: Arbitrary utility + [*] value placeholder covered

This squarely hits the new grammar. Consider an extra case with whitespace inside the brackets or quoting to ensure tokenisation remains stable.

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source.css (1)

1-1: @source happy path looks right

Good addition. A sibling test with url("../...") could ensure both string and url() are accepted as expected.

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/utility/modifier.css (1)

1-3: Modifier function parsing exercised

Great. Adding a companion test for --modifier([*], [length]) would cover the arbitrary-value placeholder within modifier as well.

crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-enabled/value-incomplete.css (1)

1-3: Nice negative: incomplete --value()

Good case to validate recovery and a crisp diagnostic. Suggest adding a sibling for --modifier( with a missing closing parenthesis to cover both paths.

crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/plugin.css (1)

1-1: LGTM: disabled @plugin is exercised

Solid. Consider adding single-quoted and unquoted variants (if allowed) to ensure consistent diagnostics across argument forms.

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/config.css (1)

1-1: LGTM: enabled @config happy-path

Complements the disabled test nicely. As a follow-up, a relative vs absolute path pair could help ensure we don’t over-constrain string values.

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/utility/with-param.css (1)

1-3: Good coverage for parameterised utility and star propagation

This exercises both selector tab-* and --value(--tab-size-*). Consider adding:

  • A case with multiple parameters, e.g. foo-*-*
  • A value using [*] bracketed usage to mirror the docs
crates/biome_css_parser/tests/css_test_suite/ok/tailwind/theme/theme-with-star-vars.css (1)

1-4: LGTM: star-suffixed custom properties in @theme

Neat. It would be useful to add a negative where * is in the middle (e.g. --color-re*d) to ensure we only allow the documented trailing form.

crates/biome_css_parser/tests/css_test_suite/ok/tailwind/variant.css (1)

1-14: Great: nested @variant coverage

Covers nesting depth and typical variants. Consider an error-suite counterpart for an unknown variant to validate diagnostics, plus a mixed-content block (rules + nested @variant) to exercise list parsing.

xtask/codegen/src/js_kinds_src.rs (1)

713-715: Keyword ident sanitising is fine; consider guarding empty idents

Trimming underscores after hyphen replacement avoids awkward leading/trailing _. Edge‑case guard (debug assert) would help catch accidental empty idents during codegen.

-                if kind_source.keywords.contains(&name) {
-                    format_ident!("{}_token", name.replace('-', "_").trim_matches('_'))
+                if kind_source.keywords.contains(&name) {
+                    let sanitized = name.replace('-', "_");
+                    let trimmed = sanitized.trim_matches('_');
+                    debug_assert!(
+                        !trimmed.is_empty(),
+                        "sanitised keyword produced an empty ident: {name:?}"
+                    );
+                    format_ident!("{}_token", trimmed)
xtask/codegen/src/css_kinds_src.rs (1)

503-517: Mind the 512 SyntaxKind cap – CSS kinds now at 497 entries
After adding 13 TW_* nodes, the CSS kinds list stands at 497 variants (42 punct, 189 keywords, 11 literals, 7 tokens, 248 nodes), leaving just 15 slots before we hit the hard cap.

  • Short-term: see if any TW_* entries can reuse existing generic kinds (verbatim handling in the formatter).
  • Mid-term: consider bumping the kind-space limit in codegen/rowan or partitioning “feature namespaces” to avoid this ceiling.
crates/biome_css_parser/src/syntax/parse_error.rs (1)

235-240: Tune the diagnostic phrasing and markup for consistency.

  • Prefer “CSS parser options” casing.
  • Consider using Code markup for the option name.
-    p.err_builder(markup! { "Tailwind-specific syntax is disabled."}, range)
+    p.err_builder(markup! { "Tailwind-specific syntax is disabled." }, range)
         .with_hint(markup! {
-            "Enable "<Emphasis>"`tailwind_directives`"</Emphasis>" in the css parser options."
+            "Enable "<Code>"tailwind_directives"</Code>" in the CSS parser options."
         })
crates/biome_css_formatter/src/tailwind/lists/apply_class_list.rs (1)

1-10: Avoid dropping/merging separators; prefer verbatim or explicit spacing.

f.join().entries(...).finish() uses no separator; if entries don’t render trailing trivia, classes may be concatenated.

Option A (consistent with other Tw formatters; preserves comments/spacing):

-    fn fmt(&self, node: &TwApplyClassList, f: &mut CssFormatter) -> FormatResult<()> {
-        f.join().entries(node.iter().formatted()).finish()
-    }
+    fn fmt(&self, node: &TwApplyClassList, f: &mut CssFormatter) -> FormatResult<()> {
+        format_css_verbatim_node(node.syntax()).fmt(f)
+    }

Option B (normalise to single spaces):

-    fn fmt(&self, node: &TwApplyClassList, f: &mut CssFormatter) -> FormatResult<()> {
-        f.join().entries(node.iter().formatted()).finish()
-    }
+    fn fmt(&self, node: &TwApplyClassList, f: &mut CssFormatter) -> FormatResult<()> {
+        let mut join = f.join_with(space());
+        for item in node.iter() {
+            join.entry(item.format());
+        }
+        join.finish()
+    }
crates/biome_css_parser/src/syntax/property/mod.rs (1)

175-184: DRY: reuse the theme-reference helper to wrap the dashed ident.

This avoids duplicating the token consumption logic and keeps the behaviour consistent with value parsing.

-    if is_at_dashed_identifier(p) {
-        let ident = parse_dashed_identifier(p).ok();
-        if let Some(ident) = ident
-            && p.options().is_tailwind_directives_enabled()
-            && p.at(T![-])
-        {
-            let m = ident.precede(p);
-            p.expect(T![-]);
-            p.expect(T![*]);
-            m.complete(p, TW_VALUE_THEME_REFERENCE);
-        }
+    if is_at_dashed_identifier(p) {
+        let ident = parse_dashed_identifier(p).ok();
+        if p.options().is_tailwind_directives_enabled() {
+            // Only attempt wrapping when the -* tail is present
+            if p.at(T![-]) && p.nth_at(1, T![*]) {
+                if let Some(ident) = ident {
+                    let m = ident.precede(p);
+                    p.expect(T![-]);
+                    p.expect(T![*]);
+                    m.complete(p, TW_VALUE_THEME_REFERENCE);
+                }
+            }
+        }

If you prefer, import and call parse_tailwind_value_theme_reference(...) here and use its Present/Absent to decide whether to wrap (may require a small API adjustment).

crates/biome_css_formatter/src/tailwind/lists/value_list.rs (1)

1-11: Uses FormatRule instead of FormatNodeRule pattern

This formatter uses FormatRule<TwValueList> rather than the FormatNodeRule<TwValueList> pattern used by other Tailwind formatters. While both approaches work, consider aligning with the FormatNodeRule pattern for consistency across the Tailwind formatter modules.

Comment on lines 1 to 10
use crate::prelude::*;
use biome_css_syntax::TwModifierFunction;
use biome_rowan::AstNode;
#[derive(Debug, Clone, Default)]
pub(crate) struct FormatTwModifierFunction;
impl FormatNodeRule<TwModifierFunction> for FormatTwModifierFunction {
fn fmt_fields(&self, node: &TwModifierFunction, f: &mut CssFormatter) -> FormatResult<()> {
format_css_verbatim_node(node.syntax()).fmt(f)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify prelude re-export and add a formatter snapshot

  • Ensure format_css_verbatim_node is re-exported via crate::prelude; otherwise import it explicitly.
  • Add/verify a snapshot test that a Tw modifier function formats verbatim (including comment preservation).

Run:

If the prelude doesn’t re-export the function, add:

  • use crate::verbatim::format_css_verbatim_node;

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Check prelude re-export of `format_css_verbatim_node`
rg -n -S 'pub .*format_css_verbatim_node' crates/biome_css_formatter/src || true
rg -n 'format_css_verbatim_node\(' crates/biome_css_formatter | sed -n '1,200p'

# 2) Look for formatter tests touching Tw modifier functions
rg -n --glob '!target' -S 'TwModifierFunction|--modifier' crates || true
fd -t f 'modifier_function.rs' -a

Length of output: 4527


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Locate prelude.rs to confirm its path
fd -t f prelude.rs -a crates/biome_css_formatter/src

# 2) Verify if `format_css_verbatim_node` is re-exported in prelude
rg -n 'format_css_verbatim_node' crates/biome_css_formatter/src/prelude.rs || true
rg -n 'pub use .*format_css_verbatim_node' crates/biome_css_formatter/src/prelude.rs || true

# 3) Check for existing formatter snapshot tests in biome_css_formatter
rg -n '\.snap' crates/biome_css_formatter || true
rg -n 'assert_snapshot' -S crates/biome_css_formatter/src || true

# 4) Look for a tests directory in the formatter crate
fd -t d tests crates/biome_css_formatter || true

Length of output: 937


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List spec test directories in the formatter crate
fd -t d crates/biome_css_formatter/tests/specs

Length of output: 526


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List directories under the formatter’s tests folder to locate `specs`
find crates/biome_css_formatter/tests -maxdepth 3 -type d

Length of output: 705


Add direct import and snapshot test for TwModifierFunction

  • The function format_css_verbatim_node is not re-exported in crates/biome_css_formatter/src/prelude.rs, so in
    crates/biome_css_formatter/src/tailwind/auxiliary/modifier_function.rs add:
    use crate::verbatim::format_css_verbatim_node;
  • Add a new spec and snapshot under
    crates/biome_css_formatter/tests/specs/css/tailwind/—for example:
    • modifier_function.css containing a --modifier(...) example (with comments)
    • modifier_function.css.snap asserting verbatim formatting (comments preserved)
  • Ensure spec_test.rs picks up the new files and passes.
🤖 Prompt for AI Agents
In crates/biome_css_formatter/src/tailwind/auxiliary/modifier_function.rs around
lines 1 to 10, add a direct import for the verbatim formatter (use
crate::verbatim::format_css_verbatim_node;) because it is not re-exported via
prelude; then add a new spec file tests/specs/css/tailwind/modifier_function.css
containing a --modifier(...) example including comments and a matching snapshot
tests/specs/css/tailwind/modifier_function.css.snap asserting verbatim output
(comments preserved); finally ensure the shared spec_test.rs test harness picks
up the new spec (update any test glob or include list if needed) so the snapshot
test runs and passes.

Comment on lines 161 to 164
fn is_at_generic_property(p: &mut CssParser) -> bool {
is_at_identifier(p) && p.nth_at(1, T![:])
is_at_identifier(p)
&& (p.nth_at(1, T![:]) || (p.nth_at(1, T![-]) && p.nth_at(2, T![*]) && p.nth_at(3, T![:])))
}
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

Bug: Tailwind lookahead accepted even when the feature is disabled.

is_at_generic_property currently returns true for “ident - * :” regardless of options, which leads parse_generic_property to expect ':' while '-' is next, emitting a spurious error when Tailwind is off.

Gate the -* path on the option:

 fn is_at_generic_property(p: &mut CssParser) -> bool {
-    is_at_identifier(p)
-        && (p.nth_at(1, T![:]) || (p.nth_at(1, T![-]) && p.nth_at(2, T![*]) && p.nth_at(3, T![:])))
+    is_at_identifier(p)
+        && (
+            p.nth_at(1, T![:])
+            || (
+                p.options().is_tailwind_directives_enabled()
+                && p.nth_at(1, T![-]) && p.nth_at(2, T![*]) && p.nth_at(3, T![:])
+            )
+        )
 }

Please add a test asserting that “--tab-size-*:” is an error when Tailwind syntax is disabled and is accepted when enabled.

🤖 Prompt for AI Agents
In crates/biome_css_parser/src/syntax/property/mod.rs around lines 161 to 164,
is_at_generic_property currently returns true for the lookahead pattern "ident -
* :" regardless of parser options, causing parse_generic_property to expect ':'
and emit an error when the Tailwind feature is disabled; modify the function to
only consider the "ident - * :" path when the Tailwind option is enabled (gate
the p.nth_at(1, T![-]) && p.nth_at(2, T![*]) && p.nth_at(3, T![:]) branch with a
check on the parser/options flag), and add a unit test that asserts
"--tab-size-*:" is a parse error when Tailwind syntax is disabled and is
accepted when enabled.

Comment on lines +263 to +276
/// Parses theme references: --tab-size-*
pub(crate) fn parse_tailwind_value_theme_reference(p: &mut CssParser) -> ParsedSyntax {
if !is_at_dashed_identifier(p) {
return Absent;
}

let m = p.start();

parse_dashed_identifier(p).ok();
p.expect(T![-]);
p.expect(T![*]);

Present(m.complete(p, TW_VALUE_THEME_REFERENCE))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add a lookahead guard (and optionally gate on the Tailwind flag) to avoid spurious diagnostics.

As written, the function Absent-check only looks for a dashed identifier; callers that probe it too early will emit expect-errors on '-' or ''. Guard for the full “--foo-” shape (and optionally the feature flag) to make it safe and self-contained.

Apply this minimal guard to prevent false positives:

-pub(crate) fn parse_tailwind_value_theme_reference(p: &mut CssParser) -> ParsedSyntax {
-    if !is_at_dashed_identifier(p) {
+pub(crate) fn parse_tailwind_value_theme_reference(p: &mut CssParser) -> ParsedSyntax {
+    if !(is_at_dashed_identifier(p) && p.nth_at(1, T![-]) && p.nth_at(2, T![*])) {
         return Absent;
     }

Optionally also gate on the parser option (if not already enforced at call sites):

-    if !(is_at_dashed_identifier(p) && p.nth_at(1, T![-]) && p.nth_at(2, T![*])) {
+    if !(p.options().is_tailwind_directives_enabled()
+        && is_at_dashed_identifier(p)
+        && p.nth_at(1, T![-])
+        && p.nth_at(2, T![*]))
+    {
         return Absent;
     }

Follow-up: consider reusing this helper in property parsing to avoid duplication (see crates/biome_css_parser/src/syntax/property/mod.rs).

📝 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
/// Parses theme references: --tab-size-*
pub(crate) fn parse_tailwind_value_theme_reference(p: &mut CssParser) -> ParsedSyntax {
if !is_at_dashed_identifier(p) {
return Absent;
}
let m = p.start();
parse_dashed_identifier(p).ok();
p.expect(T![-]);
p.expect(T![*]);
Present(m.complete(p, TW_VALUE_THEME_REFERENCE))
}
/// Parses theme references: --tab-size-*
pub(crate) fn parse_tailwind_value_theme_reference(p: &mut CssParser) -> ParsedSyntax {
if !(is_at_dashed_identifier(p) && p.nth_at(1, T![-]) && p.nth_at(2, T![*])) {
return Absent;
}
let m = p.start();
parse_dashed_identifier(p).ok();
p.expect(T![-]);
p.expect(T![*]);
Present(m.complete(p, TW_VALUE_THEME_REFERENCE))
}

@ematipico
Copy link
Member

A couple of options about how we can land this PR:

  1. Since the formatter isn't implemented yet, we can remove the parser option so users won't see any change. Without the formatter implemented, there's just the parsing, which gives only half of the value (users would expect their code to be formatted)
  2. Implement the formatter too, so we can land the parsing option too.

There's to consider the mapping of the language. The VSCode extension assigns the tailwindcss language ID. Many users use that extension, will expect Biome to handle that too. This means we need to update from_language_id and add that.

@dyc3
Copy link
Contributor Author

dyc3 commented Aug 11, 2025

Did not know that regarding vscode.

I'll remove the option from the configuration for this PR, and implement the formatters, and the language id thing in a follow up PR. I'll put the changeset in that PR as well.

@dyc3
Copy link
Contributor Author

dyc3 commented Aug 12, 2025

Removing the option from CssParserConfiguration is proving to be a little problematic, because the snapshot tests read options.json as if it was Configuration, and the option missing makes the deserialization fail.

Would it be acceptable to just hide the option instead?

@ematipico
Copy link
Member

If we decide to merge the PR after we release v2.2, it's safe to keep it, and merge the PR to next

@dyc3 dyc3 force-pushed the css-tailwind-syntax branch from 224c5eb to 615729d Compare August 19, 2025 14:18
@dyc3 dyc3 requested a review from ematipico August 19, 2025 14:18
@dyc3 dyc3 force-pushed the css-tailwind-syntax branch from 615729d to 1c8d19d Compare August 19, 2025 14:19
Copy link
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.

Great work! I can see that with the syntax features, now the diagnostics and CSTs are way better :)

This is a new feature, which means that:

  • it requires a changeset that explains the new feature to the users
  • It requires a docs PR to update the new configuration

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

♻️ Duplicate comments (1)
crates/biome_css_parser/src/syntax/mod.rs (1)

590-600: Bracketed * handling is now robust.

Using the exclusive gate here avoids cascades and keeps recovery local. Thanks for addressing the earlier feedback.

🧹 Nitpick comments (3)
crates/biome_css_parser/src/syntax/at_rule/mod.rs (1)

117-164: Solid feature-gated dispatch for Tailwind at-rules; consider a tiny DRY helper.

The repeated pattern:

  • feature.gate(parse_fn, tailwind_disabled).or_else(parse_unknown)

is clear but verbose. A small helper would reduce noise and keep all Tailwind at-rule gating consistent.

For example (add near the top of this module):

#[inline]
fn parse_tailwind_exclusive(
    p: &mut CssParser,
    f: fn(&mut CssParser) -> ParsedSyntax,
) -> ParsedSyntax {
    CssSyntaxFeatures::Tailwind
        .parse_exclusive_syntax(p, f, |p, m| tailwind_disabled(p, m.range(p)))
        .or_else(|| parse_unknown_at_rule(p))
}

Then each arm becomes:

T![theme] => parse_tailwind_exclusive(p, parse_theme_at_rule),

Follow-up: parse_source_at_rule currently self-gates on options (see tailwind.rs Lines 216-218). Relying solely on this dispatcher avoids duplicated checks and guarantees the same diagnostic path. See inline suggestion there.

crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (2)

57-72: Functional utility detection should use is_at_identifier (not just T![ident]).

Contextual keywords can also lex as identifiers in this parser. Use the higher-level predicate for consistency with the earlier guard and other parsers.

Apply this diff:

 fn parse_utility_name(p: &mut CssParser) -> ParsedSyntax {
     // Check if this is a functional utility (ends with -*)
-    if p.at(T![ident]) && p.nth_at(1, T![-]) && p.nth_at(2, T![*]) {
+    if is_at_identifier(p) && p.nth_at(1, T![-]) && p.nth_at(2, T![*]) {
         // Functional utility: tab-*
         let m = p.start();

         parse_regular_identifier(p).ok();
         p.expect(T![-]);
         p.expect(T![*]);

         Present(m.complete(p, TW_FUNCTIONAL_UTILITY_NAME))
     } else {
         // Simple utility: center-flex
         parse_regular_identifier(p)
     }
 }

214-226: Remove in-function Tailwind option check; let the feature gate drive diagnostics.

This function currently short-circuits on !is_tailwind_directives_enabled(), duplicating the gate handled in at_rule/mod.rs. Keeping gating in one place guarantees consistent diagnostics and avoids silent Absent returns if this is ever called elsewhere.

Apply this diff:

 pub(crate) fn parse_source_at_rule(p: &mut CssParser) -> ParsedSyntax {
-    if !p.options().is_tailwind_directives_enabled() || !p.at(T![source]) {
+    if !p.at(T![source]) {
         return Absent;
     }

     let m = p.start();
     p.bump(T![source]);
     parse_string(p).or_add_diagnostic(p, expected_string);
     p.expect(T![;]);

     Present(m.complete(p, TW_SOURCE_AT_RULE))
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 49073f1 and 1c8d19d.

⛔ Files ignored due to path filters (7)
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/apply.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/config.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/plugin.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/reference.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/theme.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/utility.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/variant.css.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (4)
  • crates/biome_css_parser/src/syntax/at_rule/mod.rs (3 hunks)
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (1 hunks)
  • crates/biome_css_parser/src/syntax/mod.rs (3 hunks)
  • crates/biome_css_parser/src/syntax/parse_error.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_css_parser/src/syntax/parse_error.rs
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{rs,toml}

📄 CodeRabbit Inference Engine (CONTRIBUTING.md)

Format Rust and TOML files before committing (use just f/just format).

Files:

  • crates/biome_css_parser/src/syntax/at_rule/mod.rs
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
  • crates/biome_css_parser/src/syntax/mod.rs
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_css_parser/src/syntax/at_rule/mod.rs
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
  • crates/biome_css_parser/src/syntax/mod.rs
crates/biome_*/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_css_parser/src/syntax/at_rule/mod.rs
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
  • crates/biome_css_parser/src/syntax/mod.rs
🧠 Learnings (3)
📚 Learning: 2025-08-17T08:57:34.726Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.726Z
Learning: Parse rules should return ParsedSyntax; return Present only if tokens were consumed, otherwise return Absent without advancing the parser or emitting errors

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/mod.rs
📚 Learning: 2025-08-17T08:57:34.726Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.726Z
Learning: Use conditional syntax handling (e.g., StrictMode.excluding_syntax and or_invalid_to_bogus) to diagnose unsupported constructs and attach parsed tokens

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
🧬 Code Graph Analysis (3)
crates/biome_css_parser/src/syntax/at_rule/mod.rs (2)
crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (9)
  • parse_apply_at_rule (144-155)
  • parse_config_at_rule (187-198)
  • parse_custom_variant_at_rule (96-126)
  • parse_plugin_at_rule (201-212)
  • parse_reference_at_rule (229-240)
  • parse_source_at_rule (215-226)
  • parse_theme_at_rule (18-32)
  • parse_utility_at_rule (36-55)
  • parse_variant_at_rule (75-93)
crates/biome_css_parser/src/syntax/parse_error.rs (2)
  • expected_any_at_rule (184-209)
  • tailwind_disabled (235-240)
crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (3)
crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs (1)
  • parse_declaration_or_rule_list_block (19-21)
crates/biome_css_parser/src/syntax/parse_error.rs (2)
  • expected_identifier (9-11)
  • expected_string (50-52)
crates/biome_css_parser/src/syntax/mod.rs (4)
  • is_at_identifier (422-424)
  • parse_identifier (440-450)
  • parse_regular_identifier (433-435)
  • parse_string (535-545)
crates/biome_css_parser/src/syntax/mod.rs (2)
crates/biome_css_parser/src/syntax/parse_error.rs (1)
  • tailwind_disabled (235-240)
crates/biome_css_parser/src/syntax/value/function.rs (1)
  • parse_tailwind_value_theme_reference (264-276)
⏰ 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). (9)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_js_formatter)
🔇 Additional comments (11)
crates/biome_css_parser/src/syntax/at_rule/mod.rs (3)

23-23: Nice modular split.

Scoping Tailwind into its own module keeps at-rule parsing tidy and contained.


48-52: Wiring looks correct.

All Tailwind parsers are imported explicitly; easy to track and refactor later.


57-58: Good use of SyntaxFeatures gate.

Importing the feature switch and diagnostic here keeps error reporting consistent across at-rules.

crates/biome_css_parser/src/syntax/mod.rs (4)

13-15: Consistent diagnostic import.

Pulling in tailwind_disabled here keeps value/selector paths aligned with at-rule diagnostics.


22-24: Correct hook for Tailwind theme references.

Importing the parser for --foo-* references is the right place to integrate.


37-50: Right abstraction: SyntaxFeatures for Tailwind.

This mirrors the HTML parser pattern and cleanly hides configuration under a feature gate. Nice one.


340-348: Sharp lookahead for --dashed-* theme references.

The exclusive gate + fallback to regular dashed identifiers keeps standard CSS working when Tailwind is off, and upgrades cleanly when enabled.

crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (4)

34-55: Utility at-rule: good structure and recovery.

Validates a name up front, parses the block, and downgrades to bogus on missing identifiers. Clean and predictable.


143-155: @apply parsing and recovery are spot on.

  • Switching to the TailwindUtility lex context for the list is the right move.
  • The list parser terminates well and recovers on line breaks/semicolon. Good ergonomics for users.

Also applies to: 157-185


186-212: @config and @plugin: good minimal parsers.

String-or-diagnostic + required semicolon keeps these tight and friendly.


228-240: @reference: straightforward and consistent with @config/@plugin.

All good.

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

🧹 Nitpick comments (3)
crates/biome_css_parser/src/syntax/at_rule/mod.rs (1)

117-165: Tailwind dispatch looks correct; consider DRYing the repeated pattern

Each branch repeats the same “exclusive syntax or unknown” flow. Consider a small helper to reduce boilerplate and future copy/paste churn.

Here’s a minimal helper and usage:

// Place near the top of this module
fn parse_tw_or_unknown(
    p: &mut CssParser,
    f: fn(&mut CssParser) -> ParsedSyntax,
) -> ParsedSyntax {
    CssSyntaxFeatures::Tailwind
        .parse_exclusive_syntax(p, f, |p, m| tailwind_disabled(p, m.range(p)))
        .or_else(|| parse_unknown_at_rule(p))
}

Then in the match:

-        T![theme] => CssSyntaxFeatures::Tailwind
-            .parse_exclusive_syntax(p, parse_theme_at_rule, |p, m| {
-                tailwind_disabled(p, m.range(p))
-            })
-            .or_else(|| parse_unknown_at_rule(p)),
+        T![theme] => parse_tw_or_unknown(p, parse_theme_at_rule),

Repeat similarly for utility, variant, custom_variant, apply, source, reference, config, plugin.

crates/biome_css_parser/src/syntax/mod.rs (1)

590-600: When Tailwind is disabled, confirm we don’t cascade errors on leading “*”

This branch remaps “” to an ident only when the Tailwind feature is enabled. If disabled, parse_exclusive_syntax will emit the diagnostic, but won’t consume “” unless it also wraps it in a bogus node. If it returns Absent, the “*” remains and can cascade errors.

  • Please confirm parse_exclusive_syntax consumes the “*” (e.g., by attaching a bogus node) when the feature is disabled. If it doesn’t, consider consuming it locally to avoid cascades:
-        if p.at(T![*]) {
-            return CssSyntaxFeatures::Tailwind.parse_exclusive_syntax(
-                p,
-                |p| {
-                    let m = p.start();
-                    p.bump_remap(T![ident]);
-                    return Present(m.complete(p, CSS_CUSTOM_IDENTIFIER));
-                },
-                |p, m| tailwind_disabled(p, m.range(p)),
-            );
-        }
+        if p.at(T![*]) {
+            return CssSyntaxFeatures::Tailwind
+                .parse_exclusive_syntax(
+                    p,
+                    |p| {
+                        let m = p.start();
+                        p.bump_remap(T![ident]);
+                        Present(m.complete(p, CSS_CUSTOM_IDENTIFIER))
+                    },
+                    |p, m| tailwind_disabled(p, m.range(p)),
+                )
+                .or_else(|| {
+                    // Fallback when disabled: consume `*` to prevent cascading errors.
+                    let m = p.start();
+                    p.bump(T![*]);
+                    Present(m.complete(p, CSS_BOGUS_CUSTOM_IDENTIFIER))
+                });
+        }

To verify the behaviour without changing code, run the error-suite tests for “when tailwind disabled” (if available) and inspect whether only a single diagnostic appears for a bracketed list starting with “*”.

crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (1)

214-226: Redundant Tailwind option check; make it consistent with the others

This is the only Tailwind at-rule that checks the feature flag internally. It’s redundant because at_rule/mod.rs already gates via CssSyntaxFeatures::Tailwind. Keeping it can prevent the tailwind_disabled diagnostic from firing in the exclusive path.

Apply this diff to align it with the others:

 pub(crate) fn parse_source_at_rule(p: &mut CssParser) -> ParsedSyntax {
-    if !p.options().is_tailwind_directives_enabled() || !p.at(T![source]) {
+    if !p.at(T![source]) {
         return Absent;
     }
 
     let m = p.start();
     p.bump(T![source]);
     parse_string(p).or_add_diagnostic(p, expected_string);
     p.expect(T![;]);
 
     Present(m.complete(p, TW_SOURCE_AT_RULE))
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 49073f1 and 1c8d19d.

⛔ Files ignored due to path filters (7)
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/apply.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/config.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/plugin.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/reference.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/theme.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/utility.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/variant.css.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (4)
  • crates/biome_css_parser/src/syntax/at_rule/mod.rs (3 hunks)
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (1 hunks)
  • crates/biome_css_parser/src/syntax/mod.rs (3 hunks)
  • crates/biome_css_parser/src/syntax/parse_error.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_css_parser/src/syntax/parse_error.rs
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{rs,toml}

📄 CodeRabbit Inference Engine (CONTRIBUTING.md)

Format Rust and TOML files before committing (use just f/just format).

Files:

  • crates/biome_css_parser/src/syntax/at_rule/mod.rs
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
  • crates/biome_css_parser/src/syntax/mod.rs
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_css_parser/src/syntax/at_rule/mod.rs
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
  • crates/biome_css_parser/src/syntax/mod.rs
crates/biome_*/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_css_parser/src/syntax/at_rule/mod.rs
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
  • crates/biome_css_parser/src/syntax/mod.rs
🧠 Learnings (3)
📚 Learning: 2025-08-17T08:57:34.726Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.726Z
Learning: Parse rules should return ParsedSyntax; return Present only if tokens were consumed, otherwise return Absent without advancing the parser or emitting errors

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/mod.rs
📚 Learning: 2025-08-17T08:57:34.726Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.726Z
Learning: Use conditional syntax handling (e.g., StrictMode.excluding_syntax and or_invalid_to_bogus) to diagnose unsupported constructs and attach parsed tokens

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
🧬 Code Graph Analysis (3)
crates/biome_css_parser/src/syntax/at_rule/mod.rs (2)
crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (9)
  • parse_apply_at_rule (144-155)
  • parse_config_at_rule (187-198)
  • parse_custom_variant_at_rule (96-126)
  • parse_plugin_at_rule (201-212)
  • parse_reference_at_rule (229-240)
  • parse_source_at_rule (215-226)
  • parse_theme_at_rule (18-32)
  • parse_utility_at_rule (36-55)
  • parse_variant_at_rule (75-93)
crates/biome_css_parser/src/syntax/parse_error.rs (2)
  • expected_any_at_rule (184-209)
  • tailwind_disabled (235-240)
crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (3)
crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs (1)
  • parse_declaration_or_rule_list_block (19-21)
crates/biome_css_parser/src/syntax/parse_error.rs (2)
  • expected_identifier (9-11)
  • expected_string (50-52)
crates/biome_css_parser/src/syntax/mod.rs (4)
  • is_at_identifier (422-424)
  • parse_identifier (440-450)
  • parse_regular_identifier (433-435)
  • parse_string (535-545)
crates/biome_css_parser/src/syntax/mod.rs (2)
crates/biome_css_parser/src/syntax/parse_error.rs (1)
  • tailwind_disabled (235-240)
crates/biome_css_parser/src/syntax/value/function.rs (1)
  • parse_tailwind_value_theme_reference (264-276)
⏰ 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). (6)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_formatter)
🔇 Additional comments (15)
crates/biome_css_parser/src/syntax/at_rule/mod.rs (1)

23-23: Good modularisation and feature-gating

Nice split into a dedicated tailwind module and clean gating via CssSyntaxFeatures. Importing tailwind_disabled alongside expected_any_at_rule keeps diagnostics crisp. No nits here.

Also applies to: 48-52, 57-58

crates/biome_css_parser/src/syntax/mod.rs (2)

13-15: SyntaxFeatures integration is tidy and scoped

Introducing CssSyntaxFeatures::Tailwind and wiring is_supported to the parser option keeps tailwind as an implementation detail. Importing tailwind_disabled and the theme-reference parser here is consistent with usage. Looks good.

Also applies to: 22-24, 31-32, 37-50


340-348: Tailwind theme reference gating is solid—lexer edge case unlikely

I verified that the CSS lexer’s identifier logic (in crates/biome_css_parser/src/lexer/mod.rs) strictly stops at non-identifier characters (it only allows letters, digits, hyphens and escapes), and ‘’ is never consumed as part of an ident. Therefore the p.nth_at(1, T![-]) && p.nth_at(2, T![*]) check can’t be fooled by a lexer emitting “--foo-” in one token.

No changes needed here.

crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (12)

17-32: @theme parser: straightforward and correct

Consumes the name if present and parses a declaration-or-rule-list block. Matches Tailwind docs. LGTM.


36-55: @Utility parser: solid validation and recovery

Good early check for identifier and graceful fallback to CSS_BOGUS_AT_RULE with a diagnostic. The declaration block parse is in the right place. Nicely done.


57-72: Functional utility name parsing looks right

The “ident - *” sequence is parsed into TW_FUNCTIONAL_UTILITY_NAME; otherwise a regular identifier. Clear and robust.


75-93: @variant parser: mirrors @Utility’s validation

Consistent identifier validation and block parsing. All good.


95-126: @custom-variant: both shorthand and longhand covered

Nice split: shorthand via parse_custom_variant_shorthand and longhand via parse_rule_block. This aligns with examples from Tailwind v4 docs.


128-141: Shorthand parser is minimal and effective

Parses a selector in parentheses, expects closing paren and semicolon. No complaints.


143-155: @apply: correct lexing context and list handling

Using CssLexContext::TailwindUtility before parsing the class list is the right trick here. Semicolon required; recovery is handled by the list parser. Ship it.


157-185: ApplyClassList: recovery looks pragmatic

Parsing TailwindUtility identifiers with recovery to CSS_BOGUS_CUSTOM_IDENTIFIER on ; or EOF is sensible. The line-break recovery guard is a nice touch.


186-198: @config: correct string payload and terminator

String-or-diagnostic then expect ‘;’. All consistent.


200-212: @plugin: mirrors @config correctly

Same pattern as @config; consistent behaviour is good.


228-240: @reference: consistent with @config/@plugin

String-or-diagnostic with trailing semicolon; consistent and predictable.


1-16: All good—JSON uses tailwindDirectives via serde’s rename_all = "camelCase"
The Rust field is tailwind_directives, but #[serde(rename_all = "camelCase")] in CssParserConfiguration maps it to tailwindDirectives. Tests already use that key, and the diagnostic hints match. No changes needed.

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

♻️ Duplicate comments (1)
crates/biome_css_parser/src/syntax/mod.rs (1)

590-600: Two nits: improve error range, and please confirm the disabled path consumes *.

  • As above, prefer p.cur_range() for clearer diagnostics.
  • When Tailwind is disabled, make sure the * token is consumed to avoid cascades. There was a prior report about this; if parse_exclusive_syntax doesn’t eat the token on the unsupported path, we’ll regress.
-            return CssSyntaxFeatures::Tailwind.parse_exclusive_syntax(
+            return CssSyntaxFeatures::Tailwind.parse_exclusive_syntax(
                 p,
                 |p| {
                     let m = p.start();
                     p.bump_remap(T![ident]);
                     Present(m.complete(p, CSS_CUSTOM_IDENTIFIER))
                 },
-                |p, m| tailwind_disabled(p, m.range(p)),
+                |p, _| tailwind_disabled(p, p.cur_range()),
             );

If parse_exclusive_syntax does not consume on the unsupported path, consider this fallback to avoid cascades:

-                |p, _| tailwind_disabled(p, p.cur_range()),
+                |p, _| {
+                    let range = p.cur_range();
+                    p.error(tailwind_disabled(p, range));
+                    // Consume the token to avoid cascading errors.
+                    let m = p.start();
+                    p.bump(T![*]);
+                    Present(m.complete(p, CSS_BOGUS_CUSTOM_IDENTIFIER))
+                },

To double-check, please run the error tests to ensure only a single diagnostic is emitted and the parser recovers:

#!/usr/bin/env bash
# Inspect tests that exercise Tailwind-in-brackets when disabled and confirm they pass.
# Also search for tests covering `--foo-*` when disabled.
rg -nC2 --type=rust 'when-disabled' crates/biome_css_parser/tests
rg -nC2 -g 'crates/biome_css_parser/tests/**' --type-not=rust '\-\-[a-zA-Z0-9_-]+\-\*'
🧹 Nitpick comments (2)
.changeset/tasty-hairs-shop.md (1)

5-7: Tighten the wording and add the “experimental/default-off” note.

Small grammar/clarity tweak and call out that the support is experimental and disabled by default.

-Added a new CSS parser option `tailwindDirectives`. Enabling this option will allow all of Tailwind v4's syntax additions to be parsed by Biome.
+Added a new CSS parser option `tailwindDirectives`. Enabling this option allows Biome to parse all Tailwind CSS v4 syntax additions.
+
+This support is experimental and disabled by default.
crates/biome_css_parser/src/syntax/mod.rs (1)

340-348: Use p.cur_range() for better diagnostics when the feature is disabled.

m.range(p) may be zero-length here, making the error highlight less useful. Use the current token range instead.

-            CssSyntaxFeatures::Tailwind.parse_exclusive_syntax(
-                p,
-                parse_tailwind_value_theme_reference,
-                |p, m| tailwind_disabled(p, m.range(p)),
-            )
+            CssSyntaxFeatures::Tailwind.parse_exclusive_syntax(
+                p,
+                parse_tailwind_value_theme_reference,
+                |p, _| tailwind_disabled(p, p.cur_range()),
+            )
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1c8d19d and 8e0c7fd.

⛔ Files ignored due to path filters (1)
  • crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/source.css.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (3)
  • .changeset/tasty-hairs-shop.md (1 hunks)
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (1 hunks)
  • crates/biome_css_parser/src/syntax/mod.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{rs,toml}

📄 CodeRabbit Inference Engine (CONTRIBUTING.md)

Format Rust and TOML files before committing (use just f/just format).

Files:

  • crates/biome_css_parser/src/syntax/mod.rs
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_css_parser/src/syntax/mod.rs
crates/biome_*/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_css_parser/src/syntax/mod.rs
.changeset/*.md

📄 CodeRabbit Inference Engine (CONTRIBUTING.md)

.changeset/*.md: Create changesets with just new-changeset; store them in .changeset/ with correct frontmatter (package keys and change type).
In changeset descriptions, follow content conventions: user-facing changes only; past tense for what you did; present tense for current behavior; link issues for fixes; link rules/assists; include representative code blocks; end every sentence with a period.
When adding headers in a changeset, only use #### or ##### levels.

Files:

  • .changeset/tasty-hairs-shop.md
🧬 Code Graph Analysis (1)
crates/biome_css_parser/src/syntax/mod.rs (2)
crates/biome_css_parser/src/syntax/parse_error.rs (1)
  • tailwind_disabled (235-240)
crates/biome_css_parser/src/syntax/value/function.rs (1)
  • parse_tailwind_value_theme_reference (264-276)
🪛 LanguageTool
.changeset/tasty-hairs-shop.md

[grammar] ~5-~5: Consider using “all Tailwind” or “all of the Tailwind”.
Context: ...tives`. Enabling this option will allow all of Tailwind v4's syntax additions to be parsed by B...

(ALL_MOST_SOME_OF_NOUN)

⏰ 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). (23)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Documentation
  • GitHub Check: autofix
🔇 Additional comments (6)
.changeset/tasty-hairs-shop.md (2)

2-3: Change type looks appropriate (minor).

Introducing a new parser option (default-off) is a user-facing addition; “minor” fits. No action needed.


9-17: Nice, the JSON example is clear.

Config snippet matches the option name and location. Looks good.

crates/biome_css_parser/src/syntax/mod.rs (4)

13-15: Good: imports include the Tailwind diagnostic helper.

Wires in tailwind_disabled for clear guidance when the feature is off.


22-24: Good: value parser import for Tailwind theme references.

Keeps the Tailwind bits locally scoped to the value parsing path.


31-31: Good: feature-gating via SyntaxFeature.

Brings in the right abstraction to gate syntax at runtime.


37-50: Sound feature gate design.

Using an enum for syntax features gives room to grow without churn. Implementation correctly defers to runtime options.

Copy link
Contributor

@denbezrukov denbezrukov left a comment

Choose a reason for hiding this comment

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

Impeccable work! 🫶

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

🧹 Nitpick comments (4)
crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (4)

26-28: Drop the redundant pre-check for the optional identifier

parse_regular_identifier(p).ok() already no-ops if no identifier is present. The extra p.at(T![ident]) guard is unnecessary noise.

Apply this diff:

-    if p.at(T![ident]) {
-        parse_regular_identifier(p).ok();
-    }
+    parse_regular_identifier(p).ok();

59-59: Use is_at_identifier for robustness

Align with the rest of the parser and prefer is_at_identifier(p) over checking the raw token. It handles edge cases (escapes, vendor prefixes) consistently.

-    if p.at(T![ident]) && p.nth_at(1, T![-]) && p.nth_at(2, T![*]) {
+    if is_at_identifier(p) && p.nth_at(1, T![-]) && p.nth_at(2, T![*]) {

168-171: End-of-file should also terminate the list

Lists should terminate on EOF to avoid runaway recovery when the semicolon is missing. You already include EOF in the recovery set; reflect it here too.

-    fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool {
-        p.at(T![;])
-    }
+    fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool {
+        p.at(T![;]) || p.at(EOF)
+    }

188-198: DRY the string+semicolon at-rules

@config, @plugin, @source, and @reference share identical structure. Consider a tiny helper to reduce repetition and centralise diagnostics.

Apply this pattern (helper shown outside the selected ranges):

// helper (place near the top of the module)
fn parse_string_terminated_at_rule(
    p: &mut CssParser,
    at_token: SyntaxTokenSetToken, // e.g. T![config]
    kind: CssSyntaxKind,           // e.g. TW_CONFIG_AT_RULE
) -> ParsedSyntax {
    let m = p.start();
    p.bump(at_token);
    parse_string(p).or_add_diagnostic(p, expected_string);
    p.expect(T![;]);
    Present(m.complete(p, kind))
}

Then simplify each:

 pub(crate) fn parse_config_at_rule(p: &mut CssParser) -> ParsedSyntax {
     if !p.at(T![config]) {
         return Absent;
     }
-
-    let m = p.start();
-    p.bump(T![config]);
-    parse_string(p).or_add_diagnostic(p, expected_string);
-    p.expect(T![;]);
-
-    Present(m.complete(p, TW_CONFIG_AT_RULE))
+    parse_string_terminated_at_rule(p, T![config], TW_CONFIG_AT_RULE)
 }

Repeat similarly for plugin, source, and reference.

Also applies to: 200-212, 215-226, 229-240

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8e0c7fd and 41e23d6.

📒 Files selected for processing (1)
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{rs,toml}

📄 CodeRabbit Inference Engine (CONTRIBUTING.md)

Format Rust and TOML files before committing (use just f/just format).

Files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
crates/biome_*/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
🧠 Learnings (3)
📚 Learning: 2025-08-17T08:57:34.751Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.751Z
Learning: Use conditional syntax handling (e.g., StrictMode.excluding_syntax and or_invalid_to_bogus) to diagnose unsupported constructs and attach parsed tokens

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
📚 Learning: 2025-08-17T08:57:34.751Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.751Z
Learning: When parsing optional nodes, use parse_node(p).ok(); for required nodes, use .or_add_diagnostic(...) to add errors and missing markers

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
🧬 Code Graph Analysis (1)
crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (3)
crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs (1)
  • parse_declaration_or_rule_list_block (19-21)
crates/biome_css_parser/src/syntax/parse_error.rs (3)
  • expected_identifier (9-11)
  • expected_selector (58-60)
  • expected_string (50-52)
crates/biome_css_parser/src/syntax/mod.rs (4)
  • is_at_identifier (422-424)
  • parse_identifier (440-450)
  • parse_regular_identifier (433-435)
  • parse_string (535-545)
⏰ 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). (24)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: End-to-end tests
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Check Dependencies
  • GitHub Check: autofix
🔇 Additional comments (3)
crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (3)

145-155: Nice: correct lexing context for @apply payload

Using CssLexContext::TailwindUtility for the @apply name list is the right call and will keep lexing of utility identifiers sane.


17-33: All Tailwind at-rules are feature-gated

The dispatcher in crates/biome_css_parser/src/syntax/at_rule/mod.rs (lines 118–163) wraps every Tailwind at-rule parser—including parse_theme_at_rule—in CssSyntaxFeatures::Tailwind.parse_exclusive_syntax, so we emit diagnostics when disabled and avoid misparsing. No further changes required.


52-53: Keep declarations-only parsing for @Utility — no change required

Checked the repo: the grammar declares TwUtilityAtRule.block as AnyCssDeclarationBlock (xtask/codegen/css.ungram around lines 1875–1879) and test snapshots under crates/biome_css_parser/tests/css_test_suite/ok/tailwind/utility/* (simple-utility.css, with-param.css, enhanced-value-function.css, arbitrary-star.css, modifier.css, etc.) are parsed as CssDeclarationBlock with no nested rules/at-rules. Error tests also show bogus blocks only for malformed inputs.

Actionable note:

  • Leave crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (the parse_declaration_block(p); call at lines ~52–53) as-is.

Co-authored-by: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com>
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: 0

♻️ Duplicate comments (1)
crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (1)

212-224: Thanks for removing the redundant enablement check on @source

This now cleanly checks the token and parses; gating belongs at the dispatcher. All good.

🧹 Nitpick comments (5)
crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (5)

42-53: Prefer or_add_diagnostic over pre-check to keep a missing name node

Instead of manually checking is_at_identifier and bailing with CSS_BOGUS_AT_RULE, let the name parser add the diagnostic and a missing node. This keeps the AST shape stable and aligns with parser guidelines.

Apply this diff:

-    // Parse utility name - can be simple or functional
-    if !is_at_identifier(p) {
-        p.error(expected_identifier(p, p.cur_range()));
-        return Present(m.complete(p, CSS_BOGUS_AT_RULE));
-    }
-
-    parse_utility_name(p).ok();
+    // Parse utility name - can be simple or functional
+    parse_utility_name(p).or_add_diagnostic(p, expected_identifier);

72-91: Same here: rely on .or_add_diagnostic for the required identifier

Mirror the approach suggested for @Utility to avoid early CSS_BOGUS_AT_RULE and preserve structure when the name is missing.

-    if !is_at_identifier(p) {
-        p.error(expected_identifier(p, p.cur_range()));
-        return Present(m.complete(p, CSS_BOGUS_AT_RULE));
-    }
-
-    parse_regular_identifier(p).ok();
+    parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier);

93-124: @custom-variant: use .or_add_diagnostic for the variant name

Same rationale: better recovery and a consistent AST when the ident is missing.

-    if !is_at_identifier(p) {
-        p.error(expected_identifier(p, p.cur_range()));
-        return Present(m.complete(p, CSS_BOGUS_AT_RULE));
-    }
-
-    parse_regular_identifier(p).ok();
+    parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier);

166-169: Terminate @apply class list on EOF as well

ParseNodeList typically should treat EOF as a list terminator to avoid pathological loops on unterminated input. You’re already recovering on EOF; let’s also end the list there.

-        p.at(T![;])
+        p.at(T![;]) || p.at(EOF)

184-239: Minor DRY: four identical “string + ;” at-rules

@config, @plugin, @source, @reference share the same body shape. If you fancy trimming repetition, a tiny helper or macro can reduce boilerplate. Not a blocker.

For example, you could introduce a small macro:

// outside the impls in this module
macro_rules! parse_string_terminated_at_rule {
    ($p:expr, $tok:tt, $kind:ident) => {{
        let m = $p.start();
        $p.bump(T![$tok]);
        parse_string($p).or_add_diagnostic($p, expected_string);
        $p.expect(T![;]);
        Present(m.complete($p, $kind))
    }};
}

Then use:

pub(crate) fn parse_config_at_rule(p: &mut CssParser) -> ParsedSyntax {
    if !p.at(T![config]) { return Absent; }
    parse_string_terminated_at_rule!(p, config, TW_CONFIG_AT_RULE)
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 41e23d6 and 622f9a5.

📒 Files selected for processing (1)
  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{rs,toml}

📄 CodeRabbit Inference Engine (CONTRIBUTING.md)

Format Rust and TOML files before committing (use just f/just format).

Files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
crates/biome_*/**

📄 CodeRabbit Inference Engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
🧠 Learnings (5)
📚 Learning: 2025-08-17T08:57:34.751Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.751Z
Learning: Use conditional syntax handling (e.g., StrictMode.excluding_syntax and or_invalid_to_bogus) to diagnose unsupported constructs and attach parsed tokens

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/cst.rs : Define FormatHtmlSyntaxNode in cst.rs and implement FormatRule<HtmlSyntaxNode>, plus AsFormat and IntoFormat for HtmlSyntaxNode using the provided mapping code

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
📚 Learning: 2025-08-17T08:57:34.751Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.751Z
Learning: When parsing optional nodes, use parse_node(p).ok(); for required nodes, use .or_add_diagnostic(...) to add errors and missing markers

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
📚 Learning: 2025-08-17T08:57:34.751Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.751Z
Learning: Do not progress the parser when a parse_* rule returns Absent, especially inside loops

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
📚 Learning: 2025-08-17T08:57:34.751Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.751Z
Learning: Parse rules should return ParsedSyntax; return Present only if tokens were consumed, otherwise return Absent without advancing the parser or emitting errors

Applied to files:

  • crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
⏰ 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). (9)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-windows-2022)
🔇 Additional comments (5)
crates/biome_css_parser/src/syntax/at_rule/tailwind.rs (5)

17-30: @theme: parsing looks sound

Optional ident handled correctly, then declaration-or-rule-list block. No concerns.


55-70: Functional utility lookahead is fine

The -* detection via lookahead is straightforward and errs via expect on partial matches. Looks good.


126-139: Shorthand parser is tidy

Selector with diagnostics, then ) and ;. Happy path and errors are both covered.


141-153: @apply: correct lexing context and list parsing

Using CssLexContext::TailwindUtility is the right call; list + semicolon looks good.


1-16: No gating issues: Tailwind at-rules are feature-gated

All Tailwind-specific at-rules in crates/biome_css_parser/src/syntax/at_rule/mod.rs (lines 118–163) are already wrapped with

CssSyntaxFeatures::Tailwind.parse_exclusive_syntax(..., |p, m| tailwind_disabled(p, m.range(p)))

No additional wrapping needed.

@dyc3 dyc3 merged commit f66b0c5 into next Aug 20, 2025
29 checks passed
@dyc3 dyc3 deleted the css-tailwind-syntax branch August 20, 2025 23:02
dyc3 added a commit that referenced this pull request Aug 20, 2025
Co-authored-by: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com>
nedtwigg added a commit to diffplug/gitcasso that referenced this pull request Sep 10, 2025
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-Project Area: project A-Tooling Area: internal tools L-CSS Language: CSS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

unknownAtRules error for tailwind v4

3 participants

点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载