diff --git a/.changeset/some-walls-decide.md b/.changeset/some-walls-decide.md new file mode 100644 index 000000000000..02c9ee7629fc --- /dev/null +++ b/.changeset/some-walls-decide.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#7913](https://github.com/biomejs/biome/issues/7913): The CSS parser, with `tailwindDirectives` enabled, will now correctly handle `@slot`. diff --git a/crates/biome_css_factory/src/generated/node_factory.rs b/crates/biome_css_factory/src/generated/node_factory.rs index 9c594dbfc5f0..7b5de58772b3 100644 --- a/crates/biome_css_factory/src/generated/node_factory.rs +++ b/crates/biome_css_factory/src/generated/node_factory.rs @@ -2503,6 +2503,15 @@ pub fn tw_reference_at_rule( ], )) } +pub fn tw_slot_at_rule(slot_token: SyntaxToken, semicolon_token: SyntaxToken) -> TwSlotAtRule { + TwSlotAtRule::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::TW_SLOT_AT_RULE, + [ + Some(SyntaxElement::Token(slot_token)), + Some(SyntaxElement::Token(semicolon_token)), + ], + )) +} pub fn tw_source_at_rule( source_token: SyntaxToken, path: CssString, diff --git a/crates/biome_css_factory/src/generated/syntax_factory.rs b/crates/biome_css_factory/src/generated/syntax_factory.rs index 6e8d38b2be33..2ebc5678b29b 100644 --- a/crates/biome_css_factory/src/generated/syntax_factory.rs +++ b/crates/biome_css_factory/src/generated/syntax_factory.rs @@ -5119,6 +5119,32 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(TW_REFERENCE_AT_RULE, children) } + TW_SLOT_AT_RULE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T![slot] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T ! [;] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + TW_SLOT_AT_RULE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(TW_SLOT_AT_RULE, children) + } TW_SOURCE_AT_RULE => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); diff --git a/crates/biome_css_formatter/src/css/any/at_rule.rs b/crates/biome_css_formatter/src/css/any/at_rule.rs index f1d235b52903..caed796b0004 100644 --- a/crates/biome_css_formatter/src/css/any/at_rule.rs +++ b/crates/biome_css_formatter/src/css/any/at_rule.rs @@ -37,6 +37,7 @@ impl FormatRule for FormatAnyCssAtRule { AnyCssAtRule::TwCustomVariantAtRule(node) => node.format().fmt(f), AnyCssAtRule::TwPluginAtRule(node) => node.format().fmt(f), AnyCssAtRule::TwReferenceAtRule(node) => node.format().fmt(f), + AnyCssAtRule::TwSlotAtRule(node) => node.format().fmt(f), AnyCssAtRule::TwSourceAtRule(node) => node.format().fmt(f), AnyCssAtRule::TwThemeAtRule(node) => node.format().fmt(f), AnyCssAtRule::TwUtilityAtRule(node) => node.format().fmt(f), diff --git a/crates/biome_css_formatter/src/generated.rs b/crates/biome_css_formatter/src/generated.rs index 944f071f139d..7e5c4d33664d 100644 --- a/crates/biome_css_formatter/src/generated.rs +++ b/crates/biome_css_formatter/src/generated.rs @@ -5750,6 +5750,40 @@ impl IntoFormat for biome_css_syntax::TwReferenceAtRule { ) } } +impl FormatRule + for crate::tailwind::statements::slot_at_rule::FormatTwSlotAtRule +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt(&self, node: &biome_css_syntax::TwSlotAtRule, f: &mut CssFormatter) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::TwSlotAtRule { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::TwSlotAtRule, + crate::tailwind::statements::slot_at_rule::FormatTwSlotAtRule, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::tailwind::statements::slot_at_rule::FormatTwSlotAtRule::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::TwSlotAtRule { + type Format = FormatOwnedWithRule< + biome_css_syntax::TwSlotAtRule, + crate::tailwind::statements::slot_at_rule::FormatTwSlotAtRule, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::tailwind::statements::slot_at_rule::FormatTwSlotAtRule::default(), + ) + } +} impl FormatRule for crate::tailwind::statements::source_at_rule::FormatTwSourceAtRule { diff --git a/crates/biome_css_formatter/src/tailwind/statements/mod.rs b/crates/biome_css_formatter/src/tailwind/statements/mod.rs index f2644b126b5e..7f538e6c7ec9 100644 --- a/crates/biome_css_formatter/src/tailwind/statements/mod.rs +++ b/crates/biome_css_formatter/src/tailwind/statements/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod config_at_rule; pub(crate) mod custom_variant_at_rule; pub(crate) mod plugin_at_rule; pub(crate) mod reference_at_rule; +pub(crate) mod slot_at_rule; pub(crate) mod source_at_rule; pub(crate) mod theme_at_rule; pub(crate) mod utility_at_rule; diff --git a/crates/biome_css_formatter/src/tailwind/statements/slot_at_rule.rs b/crates/biome_css_formatter/src/tailwind/statements/slot_at_rule.rs new file mode 100644 index 000000000000..62d34ad606cc --- /dev/null +++ b/crates/biome_css_formatter/src/tailwind/statements/slot_at_rule.rs @@ -0,0 +1,16 @@ +use crate::prelude::*; +use biome_css_syntax::{TwSlotAtRule, TwSlotAtRuleFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatTwSlotAtRule; +impl FormatNodeRule for FormatTwSlotAtRule { + fn fmt_fields(&self, node: &TwSlotAtRule, f: &mut CssFormatter) -> FormatResult<()> { + let TwSlotAtRuleFields { + slot_token, + semicolon_token, + } = node.as_fields(); + + write!(f, [slot_token.format(), semicolon_token.format()]) + } +} diff --git a/crates/biome_css_parser/src/lexer/mod.rs b/crates/biome_css_parser/src/lexer/mod.rs index dd92fba29fdd..423066ac6311 100644 --- a/crates/biome_css_parser/src/lexer/mod.rs +++ b/crates/biome_css_parser/src/lexer/mod.rs @@ -939,6 +939,7 @@ impl<'src> CssLexer<'src> { b"reference" => REFERENCE_KW, b"config" => CONFIG_KW, b"plugin" => PLUGIN_KW, + b"slot" => SLOT_KW, _ => IDENT, } } diff --git a/crates/biome_css_parser/src/syntax/at_rule/mod.rs b/crates/biome_css_parser/src/syntax/at_rule/mod.rs index c4d204b03dc3..3eab482c3250 100644 --- a/crates/biome_css_parser/src/syntax/at_rule/mod.rs +++ b/crates/biome_css_parser/src/syntax/at_rule/mod.rs @@ -47,8 +47,8 @@ use crate::syntax::at_rule::starting_style::parse_starting_style_at_rule; use crate::syntax::at_rule::supports::parse_supports_at_rule; use crate::syntax::at_rule::tailwind::{ parse_apply_at_rule, parse_config_at_rule, parse_custom_variant_at_rule, parse_plugin_at_rule, - parse_reference_at_rule, parse_source_at_rule, parse_theme_at_rule, parse_utility_at_rule, - parse_variant_at_rule, + parse_reference_at_rule, parse_slot_at_rule, parse_source_at_rule, parse_theme_at_rule, + parse_utility_at_rule, parse_variant_at_rule, }; use crate::syntax::at_rule::unknown::{is_at_unknown_at_rule, parse_unknown_at_rule}; use crate::syntax::at_rule::value::parse_value_at_rule; @@ -162,6 +162,11 @@ pub(crate) fn parse_any_at_rule(p: &mut CssParser) -> ParsedSyntax { tailwind_disabled(p, m.range(p)) }) .or_else(|| parse_unknown_at_rule(p)), + T![slot] => CssSyntaxFeatures::Tailwind + .parse_exclusive_syntax(p, parse_slot_at_rule, |p, m| { + tailwind_disabled(p, m.range(p)) + }) + .or_else(|| parse_unknown_at_rule(p)), _ if is_at_unknown_at_rule(p) => parse_unknown_at_rule(p), _ => Absent, } diff --git a/crates/biome_css_parser/src/syntax/at_rule/tailwind.rs b/crates/biome_css_parser/src/syntax/at_rule/tailwind.rs index 4dd09e040459..4eca08e60d27 100644 --- a/crates/biome_css_parser/src/syntax/at_rule/tailwind.rs +++ b/crates/biome_css_parser/src/syntax/at_rule/tailwind.rs @@ -247,3 +247,16 @@ pub(crate) fn parse_reference_at_rule(p: &mut CssParser) -> ParsedSyntax { Present(m.complete(p, TW_REFERENCE_AT_RULE)) } + +// @slot; +pub(crate) fn parse_slot_at_rule(p: &mut CssParser) -> ParsedSyntax { + if !p.at(T![slot]) { + return Absent; + } + + let m = p.start(); + p.bump(T![slot]); + p.expect(T![;]); + + Present(m.complete(p, TW_SLOT_AT_RULE)) +} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/slot.css b/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/slot.css new file mode 100644 index 000000000000..6f82cb981aea --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/slot.css @@ -0,0 +1 @@ +@slot; diff --git a/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/slot.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/slot.css.snap new file mode 100644 index 000000000000..f06b42446803 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-disabled/slot.css.snap @@ -0,0 +1,61 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +@slot; + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssBogusAtRule { + items: [ + SLOT_KW@1..5 "slot" [] [], + SEMICOLON@5..6 ";" [] [], + ], + }, + }, + ], + eof_token: EOF@6..7 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..7 + 0: (empty) + 1: CSS_RULE_LIST@0..6 + 0: CSS_AT_RULE@0..6 + 0: AT@0..1 "@" [] [] + 1: CSS_BOGUS_AT_RULE@1..6 + 0: SLOT_KW@1..5 "slot" [] [] + 1: SEMICOLON@5..6 ";" [] [] + 2: EOF@6..7 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +slot.css:1:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Tailwind-specific syntax is disabled. + + > 1 │ @slot; + │ ^^^^^ + 2 │ + + i Enable `tailwindDirectives` in the css parser options, or remove this if you are not using Tailwind CSS. + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/custom-variants/block-slot.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/custom-variants/block-slot.css.snap index 75bca233638b..1841f8b4e66c 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/custom-variants/block-slot.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/custom-variants/block-slot.css.snap @@ -96,13 +96,8 @@ CssRoot { items: CssDeclarationOrRuleList [ CssAtRule { at_token: AT@70..74 "@" [Newline("\n"), Whitespace("\t\t")] [], - rule: CssUnknownValueAtRule { - name: CssIdentifier { - value_token: IDENT@74..78 "slot" [] [], - }, - components: CssUnknownAtRuleComponentList { - items: [], - }, + rule: TwSlotAtRule { + slot_token: SLOT_KW@74..78 "slot" [] [], semicolon_token: SEMICOLON@78..79 ";" [] [], }, }, @@ -181,11 +176,9 @@ CssRoot { 1: CSS_DECLARATION_OR_RULE_LIST@70..79 0: CSS_AT_RULE@70..79 0: AT@70..74 "@" [Newline("\n"), Whitespace("\t\t")] [] - 1: CSS_UNKNOWN_VALUE_AT_RULE@74..79 - 0: CSS_IDENTIFIER@74..78 - 0: IDENT@74..78 "slot" [] [] - 1: CSS_UNKNOWN_AT_RULE_COMPONENT_LIST@78..78 - 2: SEMICOLON@78..79 ";" [] [] + 1: TW_SLOT_AT_RULE@74..79 + 0: SLOT_KW@74..78 "slot" [] [] + 1: SEMICOLON@78..79 ";" [] [] 2: R_CURLY@79..82 "}" [Newline("\n"), Whitespace("\t")] [] 2: R_CURLY@82..84 "}" [Newline("\n")] [] 2: EOF@84..85 "" [Newline("\n")] [] diff --git a/crates/biome_css_syntax/src/generated/kind.rs b/crates/biome_css_syntax/src/generated/kind.rs index 4e053d15d113..e44922ccbded 100644 --- a/crates/biome_css_syntax/src/generated/kind.rs +++ b/crates/biome_css_syntax/src/generated/kind.rs @@ -135,6 +135,7 @@ pub enum CssSyntaxKind { REFERENCE_KW, CONFIG_KW, PLUGIN_KW, + SLOT_KW, INITIAL_KW, INHERIT_KW, UNSET_KW, @@ -478,6 +479,7 @@ pub enum CssSyntaxKind { TW_REFERENCE_AT_RULE, TW_CONFIG_AT_RULE, TW_PLUGIN_AT_RULE, + TW_SLOT_AT_RULE, TW_VALUE_THEME_REFERENCE, TW_FUNCTIONAL_UTILITY_NAME, TW_CUSTOM_VARIANT_SHORTHAND, @@ -707,6 +709,7 @@ impl CssSyntaxKind { "reference" => REFERENCE_KW, "config" => CONFIG_KW, "plugin" => PLUGIN_KW, + "slot" => SLOT_KW, "initial" => INITIAL_KW, "inherit" => INHERIT_KW, "unset" => UNSET_KW, @@ -945,6 +948,7 @@ impl CssSyntaxKind { REFERENCE_KW => "reference", CONFIG_KW => "config", PLUGIN_KW => "plugin", + SLOT_KW => "slot", INITIAL_KW => "initial", INHERIT_KW => "inherit", UNSET_KW => "unset", @@ -1063,4 +1067,4 @@ impl CssSyntaxKind { } #[doc = r" Utility macro for creating a SyntaxKind through simple macro syntax"] #[macro_export] -macro_rules ! T { [;] => { $ crate :: CssSyntaxKind :: SEMICOLON } ; [,] => { $ crate :: CssSyntaxKind :: COMMA } ; ['('] => { $ crate :: CssSyntaxKind :: L_PAREN } ; [')'] => { $ crate :: CssSyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: CssSyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: CssSyntaxKind :: R_CURLY } ; ['['] => { $ crate :: CssSyntaxKind :: L_BRACK } ; [']'] => { $ crate :: CssSyntaxKind :: R_BRACK } ; [<] => { $ crate :: CssSyntaxKind :: L_ANGLE } ; [>] => { $ crate :: CssSyntaxKind :: R_ANGLE } ; [~] => { $ crate :: CssSyntaxKind :: TILDE } ; [#] => { $ crate :: CssSyntaxKind :: HASH } ; [&] => { $ crate :: CssSyntaxKind :: AMP } ; [|] => { $ crate :: CssSyntaxKind :: PIPE } ; [||] => { $ crate :: CssSyntaxKind :: PIPE2 } ; [+] => { $ crate :: CssSyntaxKind :: PLUS } ; [*] => { $ crate :: CssSyntaxKind :: STAR } ; [/] => { $ crate :: CssSyntaxKind :: SLASH } ; [^] => { $ crate :: CssSyntaxKind :: CARET } ; [%] => { $ crate :: CssSyntaxKind :: PERCENT } ; [.] => { $ crate :: CssSyntaxKind :: DOT } ; [:] => { $ crate :: CssSyntaxKind :: COLON } ; [::] => { $ crate :: CssSyntaxKind :: COLON2 } ; [=] => { $ crate :: CssSyntaxKind :: EQ } ; [!] => { $ crate :: CssSyntaxKind :: BANG } ; [!=] => { $ crate :: CssSyntaxKind :: NEQ } ; [-] => { $ crate :: CssSyntaxKind :: MINUS } ; [<=] => { $ crate :: CssSyntaxKind :: LTEQ } ; [>=] => { $ crate :: CssSyntaxKind :: GTEQ } ; [+=] => { $ crate :: CssSyntaxKind :: PLUSEQ } ; [|=] => { $ crate :: CssSyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: CssSyntaxKind :: AMPEQ } ; [^=] => { $ crate :: CssSyntaxKind :: CARETEQ } ; [/=] => { $ crate :: CssSyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: CssSyntaxKind :: STAREQ } ; [%=] => { $ crate :: CssSyntaxKind :: PERCENTEQ } ; [@] => { $ crate :: CssSyntaxKind :: AT } ; ["$="] => { $ crate :: CssSyntaxKind :: DOLLAR_EQ } ; [~=] => { $ crate :: CssSyntaxKind :: TILDE_EQ } ; [-->] => { $ crate :: CssSyntaxKind :: CDC } ; [] => { $ crate :: CssSyntaxKind :: CDC } ; [