-
-
Notifications
You must be signed in to change notification settings - Fork 715
feat(parse/css): add option to parse Tailwind v4 directives #7164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🦋 Changeset detectedLatest commit: 622f9a5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
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 |
WalkthroughAdds 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
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 unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
Parser conformance results onjs/262
jsx/babel
symbols/microsoft
ts/babel
ts/microsoft
|
CodSpeed Performance ReportMerging #7164 will not alter performanceComparing Summary
|
d85eeef
to
0c122f8
Compare
There was a problem hiding this 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
42a0b32
to
9de1cd8
Compare
9de1cd8 seems to be causing a regression for grit metavariable parsing for some reason. which is weird because i didn't touch that. |
This is in a good enough state to review now. I'll add a changeset later. Should this be a Also, I'll do the formatters in a follow up PR. |
There was a problem hiding this 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 tokensThe 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 registrationOur check didn’t find
simple_utility_name
incrates/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 inNodeDialect::all()
.The
Tailwind
variant is not included in theall()
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 ofSVH_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 parserThe
tailwind_directives
option is hardcoded tofalse
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.rsThe generated
mod.rs
only exports three modules, but there are ten.rs
files incrates/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 identifiersThis 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 behaviourGiven 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 intentZero-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 variantNice 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 coverageAdd 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 disabledConsider 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 coverageGood 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 variantsAdd 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 cleanlySolid 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 directivesThis 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 testsNice 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 coveredThis 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 rightGood 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 exercisedGreat. 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 exercisedSolid. 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-pathComplements 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 propagationThis 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 docscrates/biome_css_parser/tests/css_test_suite/ok/tailwind/theme/theme-with-star-vars.css (1)
1-4
: LGTM: star-suffixed custom properties in @themeNeat. 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 coverageCovers 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 identsTrimming 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 patternThis formatter uses
FormatRule<TwValueList>
rather than theFormatNodeRule<TwValueList>
pattern used by other Tailwind formatters. While both approaches work, consider aligning with theFormatNodeRule
pattern for consistency across the Tailwind formatter modules.
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) | ||
} | ||
} |
There was a problem hiding this comment.
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 incrates/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.
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![:]))) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
/// 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)) | ||
} |
There was a problem hiding this comment.
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.
/// 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)) | |
} |
crates/biome_css_parser/tests/css_test_suite/ok/tailwind/utility/enhanced-value-function.css
Show resolved
Hide resolved
crates/biome_css_parser/tests/css_test_suite/ok/tailwind/utility/value-literals.css
Show resolved
Hide resolved
A couple of options about how we can land this PR:
There's to consider the mapping of the language. The VSCode extension assigns the |
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. |
Removing the option from Would it be acceptable to just hide the option instead? |
If we decide to merge the PR after we release v2.2, it's safe to keep it, and merge the PR to |
224c5eb
to
615729d
Compare
615729d
to
1c8d19d
Compare
There was a problem hiding this 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
There was a problem hiding this 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 useis_at_identifier
(not justT![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 inat_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.
⛔ 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.
There was a problem hiding this 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 patternEach 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 othersThis 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.
⛔ 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-gatingNice 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 scopedIntroducing 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 unlikelyI 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 thep.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 correctConsumes the name if present and parses a declaration-or-rule-list block. Matches Tailwind docs. LGTM.
36-55
: @Utility parser: solid validation and recoveryGood 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 rightThe “ident - *” sequence is parsed into TW_FUNCTIONAL_UTILITY_NAME; otherwise a regular identifier. Clear and robust.
75-93
: @variant parser: mirrors @Utility’s validationConsistent identifier validation and block parsing. All good.
95-126
: @custom-variant: both shorthand and longhand coveredNice 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 effectiveParses a selector in parentheses, expects closing paren and semicolon. No complaints.
143-155
: @apply: correct lexing context and list handlingUsing 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 pragmaticParsing 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 terminatorString-or-diagnostic then expect ‘;’. All consistent.
200-212
: @plugin: mirrors @config correctlySame pattern as @config; consistent behaviour is good.
228-240
: @reference: consistent with @config/@pluginString-or-diagnostic with trailing semicolon; consistent and predictable.
1-16
: All good—JSON usestailwindDirectives
via serde’srename_all = "camelCase"
The Rust field istailwind_directives
, but#[serde(rename_all = "camelCase")]
inCssParserConfiguration
maps it totailwindDirectives
. Tests already use that key, and the diagnostic hints match. No changes needed.
There was a problem hiding this 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; ifparse_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
: Usep.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.
⛔ 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 withjust 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 viaSyntaxFeature
.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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Impeccable work! 🫶
There was a problem hiding this 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 extrap.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
: Useis_at_identifier
for robustnessAlign 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 listLists should terminate on
EOF
to avoid runaway recovery when the semicolon is missing. You already includeEOF
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
, andreference
.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.
📒 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 payloadUsing
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-gatedThe 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
—inCssSyntaxFeatures::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 requiredChecked 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>
There was a problem hiding this 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 @sourceThis 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 nodeInstead 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 identifierMirror 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 nameSame 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 wellParseNodeList 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.
📒 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 soundOptional ident handled correctly, then declaration-or-rule-list block. No concerns.
55-70
: Functional utility lookahead is fineThe -* detection via lookahead is straightforward and errs via expect on partial matches. Looks good.
126-139
: Shorthand parser is tidySelector with diagnostics, then ) and ;. Happy path and errors are both covered.
141-153
: @apply: correct lexing context and list parsingUsing CssLexContext::TailwindUtility is the right call; list + semicolon looks good.
1-16
: No gating issues: Tailwind at-rules are feature-gatedAll Tailwind-specific at-rules in
crates/biome_css_parser/src/syntax/at_rule/mod.rs
(lines 118–163) are already wrapped withCssSyntaxFeatures::Tailwind.parse_exclusive_syntax(..., |p, m| tailwind_disabled(p, m.range(p)))No additional wrapping needed.
Co-authored-by: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com>
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 closeto 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
--modifier
function - https://tailwindcss.com/docs/adding-custom-styles#modifiers@custom-variant
parsing[*]
in--value
and--modifier
Test Plan
Added tests.
Docs
biomejs/website#2962