From 7b993cbe7ca7c0429901e4a7e634db2e648b5755 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Wed, 29 Oct 2025 11:34:45 -0400 Subject: [PATCH] fix(parse/css): parse tailwind `@source inline(...)` --- .changeset/ten-ears-stand.md | 5 + .../src/generated/node_factory.rs | 24 +- .../src/generated/syntax_factory.rs | 42 +++- crates/biome_css_formatter/src/generated.rs | 63 ++++++ .../src/tailwind/any/mod.rs | 1 + .../src/tailwind/any/source.rs | 15 ++ .../src/tailwind/auxiliary/mod.rs | 1 + .../src/tailwind/auxiliary/source_inline.rs | 26 +++ .../src/tailwind/statements/source_at_rule.rs | 4 +- .../specs/css/tailwind/source-inline.css | 2 + .../specs/css/tailwind/source-inline.css.snap | 56 +++++ crates/biome_css_parser/src/lexer/mod.rs | 1 + .../src/syntax/at_rule/tailwind.rs | 24 +- .../src/syntax/parse_error.rs | 4 + .../error/tailwind/when-enabled/source.css | 2 + .../tailwind/when-enabled/source.css.snap | 102 +++++++++ .../ok/tailwind/simple.css.snap | 2 +- .../ok/tailwind/source-inline.css | 2 + .../ok/tailwind/source-inline.css.snap | 89 ++++++++ .../ok/tailwind/source-not.css.snap | 2 +- .../ok/tailwind/source.css.snap | 2 +- crates/biome_css_syntax/src/generated/kind.rs | 6 +- .../biome_css_syntax/src/generated/macros.rs | 4 + .../biome_css_syntax/src/generated/nodes.rs | 205 +++++++++++++++++- .../src/generated/nodes_mut.rs | 28 ++- .../css_target_language/generated_mappings.rs | 1 + xtask/codegen/css.ungram | 13 +- xtask/codegen/src/css_kinds_src.rs | 2 + 28 files changed, 709 insertions(+), 19 deletions(-) create mode 100644 .changeset/ten-ears-stand.md create mode 100644 crates/biome_css_formatter/src/tailwind/any/source.rs create mode 100644 crates/biome_css_formatter/src/tailwind/auxiliary/source_inline.rs create mode 100644 crates/biome_css_formatter/tests/specs/css/tailwind/source-inline.css create mode 100644 crates/biome_css_formatter/tests/specs/css/tailwind/source-inline.css.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-enabled/source.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-enabled/source.css.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-inline.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-inline.css.snap diff --git a/.changeset/ten-ears-stand.md b/.changeset/ten-ears-stand.md new file mode 100644 index 000000000000..a3ad4adb0407 --- /dev/null +++ b/.changeset/ten-ears-stand.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#7854](https://github.com/biomejs/biome/issues/7854): The CSS parser, with `tailwindDirectives` enabled, will now parse `@source inline("underline");`. diff --git a/crates/biome_css_factory/src/generated/node_factory.rs b/crates/biome_css_factory/src/generated/node_factory.rs index 7b5de58772b3..62624ad03b85 100644 --- a/crates/biome_css_factory/src/generated/node_factory.rs +++ b/crates/biome_css_factory/src/generated/node_factory.rs @@ -2514,19 +2514,19 @@ pub fn tw_slot_at_rule(slot_token: SyntaxToken, semicolon_token: SyntaxToken) -> } pub fn tw_source_at_rule( source_token: SyntaxToken, - path: CssString, + source: AnyTwSource, semicolon_token: SyntaxToken, ) -> TwSourceAtRuleBuilder { TwSourceAtRuleBuilder { source_token, - path, + source, semicolon_token, not_token: None, } } pub struct TwSourceAtRuleBuilder { source_token: SyntaxToken, - path: CssString, + source: AnyTwSource, semicolon_token: SyntaxToken, not_token: Option, } @@ -2541,12 +2541,28 @@ impl TwSourceAtRuleBuilder { [ Some(SyntaxElement::Token(self.source_token)), self.not_token.map(|token| SyntaxElement::Token(token)), - Some(SyntaxElement::Node(self.path.into_syntax())), + Some(SyntaxElement::Node(self.source.into_syntax())), Some(SyntaxElement::Token(self.semicolon_token)), ], )) } } +pub fn tw_source_inline( + inline_token: SyntaxToken, + l_paren_token: SyntaxToken, + content: CssString, + r_paren_token: SyntaxToken, +) -> TwSourceInline { + TwSourceInline::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::TW_SOURCE_INLINE, + [ + Some(SyntaxElement::Token(inline_token)), + Some(SyntaxElement::Token(l_paren_token)), + Some(SyntaxElement::Node(content.into_syntax())), + Some(SyntaxElement::Token(r_paren_token)), + ], + )) +} pub fn tw_theme_at_rule( theme_token: SyntaxToken, block: AnyCssDeclarationOrRuleBlock, diff --git a/crates/biome_css_factory/src/generated/syntax_factory.rs b/crates/biome_css_factory/src/generated/syntax_factory.rs index 2ebc5678b29b..57e07f530dfc 100644 --- a/crates/biome_css_factory/src/generated/syntax_factory.rs +++ b/crates/biome_css_factory/src/generated/syntax_factory.rs @@ -5164,7 +5164,7 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.next_slot(); if let Some(element) = ¤t_element - && CssString::can_cast(element.kind()) + && AnyTwSource::can_cast(element.kind()) { slots.mark_present(); current_element = elements.next(); @@ -5185,6 +5185,46 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(TW_SOURCE_AT_RULE, children) } + TW_SOURCE_INLINE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element + && element.kind() == T![inline] + { + 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 let Some(element) = ¤t_element + && CssString::can_cast(element.kind()) + { + 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_SOURCE_INLINE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(TW_SOURCE_INLINE, children) + } TW_THEME_AT_RULE => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default(); diff --git a/crates/biome_css_formatter/src/generated.rs b/crates/biome_css_formatter/src/generated.rs index 7e5c4d33664d..1566f3e288e1 100644 --- a/crates/biome_css_formatter/src/generated.rs +++ b/crates/biome_css_formatter/src/generated.rs @@ -5822,6 +5822,44 @@ impl IntoFormat for biome_css_syntax::TwSourceAtRule { ) } } +impl FormatRule + for crate::tailwind::auxiliary::source_inline::FormatTwSourceInline +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::TwSourceInline, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::TwSourceInline { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::TwSourceInline, + crate::tailwind::auxiliary::source_inline::FormatTwSourceInline, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::tailwind::auxiliary::source_inline::FormatTwSourceInline::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::TwSourceInline { + type Format = FormatOwnedWithRule< + biome_css_syntax::TwSourceInline, + crate::tailwind::auxiliary::source_inline::FormatTwSourceInline, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::tailwind::auxiliary::source_inline::FormatTwSourceInline::default(), + ) + } +} impl FormatRule for crate::tailwind::statements::theme_at_rule::FormatTwThemeAtRule { @@ -9595,6 +9633,31 @@ impl IntoFormat for biome_css_syntax::AnyTwCustomVariantSelect FormatOwnedWithRule :: new (self , crate :: tailwind :: any :: custom_variant_selector :: FormatAnyTwCustomVariantSelector :: default ()) } } +impl AsFormat for biome_css_syntax::AnyTwSource { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::AnyTwSource, + crate::tailwind::any::source::FormatAnyTwSource, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::tailwind::any::source::FormatAnyTwSource::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::AnyTwSource { + type Format = FormatOwnedWithRule< + biome_css_syntax::AnyTwSource, + crate::tailwind::any::source::FormatAnyTwSource, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::tailwind::any::source::FormatAnyTwSource::default(), + ) + } +} impl AsFormat for biome_css_syntax::AnyTwUtilityName { type Format<'a> = FormatRefWithRule< 'a, diff --git a/crates/biome_css_formatter/src/tailwind/any/mod.rs b/crates/biome_css_formatter/src/tailwind/any/mod.rs index 45e793a0ea0f..01a6e727b030 100644 --- a/crates/biome_css_formatter/src/tailwind/any/mod.rs +++ b/crates/biome_css_formatter/src/tailwind/any/mod.rs @@ -1,4 +1,5 @@ //! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. pub(crate) mod custom_variant_selector; +pub(crate) mod source; pub(crate) mod utility_name; diff --git a/crates/biome_css_formatter/src/tailwind/any/source.rs b/crates/biome_css_formatter/src/tailwind/any/source.rs new file mode 100644 index 000000000000..2ced02c962e7 --- /dev/null +++ b/crates/biome_css_formatter/src/tailwind/any/source.rs @@ -0,0 +1,15 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_css_syntax::AnyTwSource; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyTwSource; +impl FormatRule for FormatAnyTwSource { + type Context = CssFormatContext; + fn fmt(&self, node: &AnyTwSource, f: &mut CssFormatter) -> FormatResult<()> { + match node { + AnyTwSource::CssString(node) => node.format().fmt(f), + AnyTwSource::TwSourceInline(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_css_formatter/src/tailwind/auxiliary/mod.rs b/crates/biome_css_formatter/src/tailwind/auxiliary/mod.rs index 259540f89066..24afcd1a1d90 100644 --- a/crates/biome_css_formatter/src/tailwind/auxiliary/mod.rs +++ b/crates/biome_css_formatter/src/tailwind/auxiliary/mod.rs @@ -2,4 +2,5 @@ pub(crate) mod custom_variant_shorthand; pub(crate) mod functional_utility_name; +pub(crate) mod source_inline; pub(crate) mod value_theme_reference; diff --git a/crates/biome_css_formatter/src/tailwind/auxiliary/source_inline.rs b/crates/biome_css_formatter/src/tailwind/auxiliary/source_inline.rs new file mode 100644 index 000000000000..b0987a143d85 --- /dev/null +++ b/crates/biome_css_formatter/src/tailwind/auxiliary/source_inline.rs @@ -0,0 +1,26 @@ +use crate::prelude::*; +use biome_css_syntax::{TwSourceInline, TwSourceInlineFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatTwSourceInline; +impl FormatNodeRule for FormatTwSourceInline { + fn fmt_fields(&self, node: &TwSourceInline, f: &mut CssFormatter) -> FormatResult<()> { + let TwSourceInlineFields { + inline_token, + l_paren_token, + content, + r_paren_token, + } = node.as_fields(); + + write!( + f, + [ + inline_token.format(), + l_paren_token.format(), + &content.format(), + r_paren_token.format(), + ] + ) + } +} diff --git a/crates/biome_css_formatter/src/tailwind/statements/source_at_rule.rs b/crates/biome_css_formatter/src/tailwind/statements/source_at_rule.rs index 027fb07413bc..f6da54151c5e 100644 --- a/crates/biome_css_formatter/src/tailwind/statements/source_at_rule.rs +++ b/crates/biome_css_formatter/src/tailwind/statements/source_at_rule.rs @@ -8,7 +8,7 @@ impl FormatNodeRule for FormatTwSourceAtRule { let TwSourceAtRuleFields { source_token, not_token, - path, + source, semicolon_token, } = node.as_fields(); @@ -16,6 +16,6 @@ impl FormatNodeRule for FormatTwSourceAtRule { if let Some(not_token) = not_token { write!(f, [not_token.format(), space()])?; } - write!(f, [path.format(), semicolon_token.format()]) + write!(f, [source.format(), semicolon_token.format()]) } } diff --git a/crates/biome_css_formatter/tests/specs/css/tailwind/source-inline.css b/crates/biome_css_formatter/tests/specs/css/tailwind/source-inline.css new file mode 100644 index 000000000000..8220151fda36 --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/tailwind/source-inline.css @@ -0,0 +1,2 @@ +@source inline("underline font-bold text-center"); +@source inline("underline font-bold text-center bg-blue-500 hover:bg-blue-700 text-white font-large py-2 px-4 rounded shadow-md hover:shadow-lg transition duration-300 ease-in-out"); diff --git a/crates/biome_css_formatter/tests/specs/css/tailwind/source-inline.css.snap b/crates/biome_css_formatter/tests/specs/css/tailwind/source-inline.css.snap new file mode 100644 index 000000000000..dc1539ee43f6 --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/tailwind/source-inline.css.snap @@ -0,0 +1,56 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: css/tailwind/source-inline.css +--- +# Input + +```css +@source inline("underline font-bold text-center"); +@source inline("underline font-bold text-center bg-blue-500 hover:bg-blue-700 text-white font-large py-2 px-4 rounded shadow-md hover:shadow-lg transition duration-300 ease-in-out"); + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Quote style: Double Quotes +----- + +```css +@source inline("underline font-bold text-center"); +@source inline("underline font-bold text-center bg-blue-500 hover:bg-blue-700 text-white font-large py-2 px-4 rounded shadow-md hover:shadow-lg transition duration-300 ease-in-out"); +``` + +# Lines exceeding max width of 80 characters +``` + 2: @source inline("underline font-bold text-center bg-blue-500 hover:bg-blue-700 text-white font-large py-2 px-4 rounded shadow-md hover:shadow-lg transition duration-300 ease-in-out"); +``` + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Quote style: Double Quotes +----- + +```css +@source inline("underline font-bold text-center"); +@source inline("underline font-bold text-center bg-blue-500 hover:bg-blue-700 text-white font-large py-2 px-4 rounded shadow-md hover:shadow-lg transition duration-300 ease-in-out"); +``` + +# Lines exceeding max width of 80 characters +``` + 2: @source inline("underline font-bold text-center bg-blue-500 hover:bg-blue-700 text-white font-large py-2 px-4 rounded shadow-md hover:shadow-lg transition duration-300 ease-in-out"); +``` diff --git a/crates/biome_css_parser/src/lexer/mod.rs b/crates/biome_css_parser/src/lexer/mod.rs index 423066ac6311..c0c04aa2e99f 100644 --- a/crates/biome_css_parser/src/lexer/mod.rs +++ b/crates/biome_css_parser/src/lexer/mod.rs @@ -940,6 +940,7 @@ impl<'src> CssLexer<'src> { b"config" => CONFIG_KW, b"plugin" => PLUGIN_KW, b"slot" => SLOT_KW, + b"inline" => INLINE_KW, _ => IDENT, } } 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 4eca08e60d27..55a3c9a4fb28 100644 --- a/crates/biome_css_parser/src/syntax/at_rule/tailwind.rs +++ b/crates/biome_css_parser/src/syntax/at_rule/tailwind.rs @@ -3,7 +3,9 @@ use crate::parser::CssParser; use crate::syntax::block::{ parse_declaration_block, parse_declaration_or_rule_list_block, parse_rule_block, }; -use crate::syntax::parse_error::{expected_identifier, expected_selector, expected_string}; +use crate::syntax::parse_error::{ + expected_identifier, expected_selector, expected_string, expected_tw_source, +}; use crate::syntax::selector::parse_selector; use crate::syntax::{is_at_identifier, parse_identifier, parse_regular_identifier, parse_string}; use biome_css_syntax::CssSyntaxKind::{self, *}; @@ -228,12 +230,30 @@ pub(crate) fn parse_source_at_rule(p: &mut CssParser) -> ParsedSyntax { if p.at(T![not]) { p.bump(T![not]); } - parse_string(p).or_add_diagnostic(p, expected_string); + if p.at(T![inline]) { + parse_source_inline(p).or_add_diagnostic(p, expected_tw_source); + } else { + parse_string(p).or_add_diagnostic(p, expected_tw_source); + } p.expect(T![;]); Present(m.complete(p, TW_SOURCE_AT_RULE)) } +pub(crate) fn parse_source_inline(p: &mut CssParser) -> ParsedSyntax { + if !p.at(T![inline]) { + return Absent; + } + + let m = p.start(); + p.expect(T![inline]); + p.expect(T!['(']); + parse_string(p).or_add_diagnostic(p, expected_string); + p.expect(T![')']); + + Present(m.complete(p, TW_SOURCE_INLINE)) +} + // @reference "../../app.css"; pub(crate) fn parse_reference_at_rule(p: &mut CssParser) -> ParsedSyntax { if !p.at(T![reference]) { diff --git a/crates/biome_css_parser/src/syntax/parse_error.rs b/crates/biome_css_parser/src/syntax/parse_error.rs index fe908ea3cd45..440a0a981759 100644 --- a/crates/biome_css_parser/src/syntax/parse_error.rs +++ b/crates/biome_css_parser/src/syntax/parse_error.rs @@ -239,3 +239,7 @@ pub(crate) fn tailwind_disabled(p: &CssParser, range: TextRange) -> ParseDiagnos "Enable ""`tailwindDirectives`"" in the css parser options, or remove this if you are not using Tailwind CSS." }) } + +pub(crate) fn expected_tw_source(p: &CssParser, range: TextRange) -> ParseDiagnostic { + expected_any(&["string literal", "inline(\"...\")"], range, p) +} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-enabled/source.css b/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-enabled/source.css new file mode 100644 index 000000000000..2ac6ade91acf --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-enabled/source.css @@ -0,0 +1,2 @@ +@source ; +@source not ; diff --git a/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-enabled/source.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-enabled/source.css.snap new file mode 100644 index 000000000000..53f4bc064712 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/tailwind/when-enabled/source.css.snap @@ -0,0 +1,102 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +@source ; +@source not ; + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: TwSourceAtRule { + source_token: SOURCE_KW@1..8 "source" [] [Whitespace(" ")], + not_token: missing (optional), + source: missing (required), + semicolon_token: SEMICOLON@8..9 ";" [] [], + }, + }, + CssAtRule { + at_token: AT@9..11 "@" [Newline("\n")] [], + rule: TwSourceAtRule { + source_token: SOURCE_KW@11..18 "source" [] [Whitespace(" ")], + not_token: NOT_KW@18..22 "not" [] [Whitespace(" ")], + source: missing (required), + semicolon_token: SEMICOLON@22..23 ";" [] [], + }, + }, + ], + eof_token: EOF@23..24 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..24 + 0: (empty) + 1: CSS_RULE_LIST@0..23 + 0: CSS_AT_RULE@0..9 + 0: AT@0..1 "@" [] [] + 1: TW_SOURCE_AT_RULE@1..9 + 0: SOURCE_KW@1..8 "source" [] [Whitespace(" ")] + 1: (empty) + 2: (empty) + 3: SEMICOLON@8..9 ";" [] [] + 1: CSS_AT_RULE@9..23 + 0: AT@9..11 "@" [Newline("\n")] [] + 1: TW_SOURCE_AT_RULE@11..23 + 0: SOURCE_KW@11..18 "source" [] [Whitespace(" ")] + 1: NOT_KW@18..22 "not" [] [Whitespace(" ")] + 2: (empty) + 3: SEMICOLON@22..23 ";" [] [] + 2: EOF@23..24 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +source.css:1:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a string literal, or an inline("...") but instead found ';'. + + > 1 │ @source ; + │ ^ + 2 │ @source not ; + 3 │ + + i Expected a string literal, or an inline("...") here. + + > 1 │ @source ; + │ ^ + 2 │ @source not ; + 3 │ + +source.css:2:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a string literal, or an inline("...") but instead found ';'. + + 1 │ @source ; + > 2 │ @source not ; + │ ^ + 3 │ + + i Expected a string literal, or an inline("...") here. + + 1 │ @source ; + > 2 │ @source not ; + │ ^ + 3 │ + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/simple.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/simple.css.snap index 0c7e323245fd..1c2a55db26ef 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/simple.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/simple.css.snap @@ -225,7 +225,7 @@ CssRoot { rule: TwSourceAtRule { source_token: SOURCE_KW@284..291 "source" [] [Whitespace(" ")], not_token: missing (optional), - path: CssString { + source: CssString { value_token: CSS_STRING_LITERAL@291..303 "\"./base.css\"" [] [], }, semicolon_token: SEMICOLON@303..304 ";" [] [], diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-inline.css b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-inline.css new file mode 100644 index 000000000000..427f0d3cf8f1 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-inline.css @@ -0,0 +1,2 @@ +@source inline("underline"); +@source not inline("underline"); diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-inline.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-inline.css.snap new file mode 100644 index 000000000000..70feda62bc68 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-inline.css.snap @@ -0,0 +1,89 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +@source inline("underline"); +@source not inline("underline"); + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: TwSourceAtRule { + source_token: SOURCE_KW@1..8 "source" [] [Whitespace(" ")], + not_token: missing (optional), + source: TwSourceInline { + inline_token: INLINE_KW@8..14 "inline" [] [], + l_paren_token: L_PAREN@14..15 "(" [] [], + content: CssString { + value_token: CSS_STRING_LITERAL@15..26 "\"underline\"" [] [], + }, + r_paren_token: R_PAREN@26..27 ")" [] [], + }, + semicolon_token: SEMICOLON@27..28 ";" [] [], + }, + }, + CssAtRule { + at_token: AT@28..30 "@" [Newline("\n")] [], + rule: TwSourceAtRule { + source_token: SOURCE_KW@30..37 "source" [] [Whitespace(" ")], + not_token: NOT_KW@37..41 "not" [] [Whitespace(" ")], + source: TwSourceInline { + inline_token: INLINE_KW@41..47 "inline" [] [], + l_paren_token: L_PAREN@47..48 "(" [] [], + content: CssString { + value_token: CSS_STRING_LITERAL@48..59 "\"underline\"" [] [], + }, + r_paren_token: R_PAREN@59..60 ")" [] [], + }, + semicolon_token: SEMICOLON@60..61 ";" [] [], + }, + }, + ], + eof_token: EOF@61..62 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..62 + 0: (empty) + 1: CSS_RULE_LIST@0..61 + 0: CSS_AT_RULE@0..28 + 0: AT@0..1 "@" [] [] + 1: TW_SOURCE_AT_RULE@1..28 + 0: SOURCE_KW@1..8 "source" [] [Whitespace(" ")] + 1: (empty) + 2: TW_SOURCE_INLINE@8..27 + 0: INLINE_KW@8..14 "inline" [] [] + 1: L_PAREN@14..15 "(" [] [] + 2: CSS_STRING@15..26 + 0: CSS_STRING_LITERAL@15..26 "\"underline\"" [] [] + 3: R_PAREN@26..27 ")" [] [] + 3: SEMICOLON@27..28 ";" [] [] + 1: CSS_AT_RULE@28..61 + 0: AT@28..30 "@" [Newline("\n")] [] + 1: TW_SOURCE_AT_RULE@30..61 + 0: SOURCE_KW@30..37 "source" [] [Whitespace(" ")] + 1: NOT_KW@37..41 "not" [] [Whitespace(" ")] + 2: TW_SOURCE_INLINE@41..60 + 0: INLINE_KW@41..47 "inline" [] [] + 1: L_PAREN@47..48 "(" [] [] + 2: CSS_STRING@48..59 + 0: CSS_STRING_LITERAL@48..59 "\"underline\"" [] [] + 3: R_PAREN@59..60 ")" [] [] + 3: SEMICOLON@60..61 ";" [] [] + 2: EOF@61..62 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-not.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-not.css.snap index 476384cdf8cb..f43a040e4fbc 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-not.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source-not.css.snap @@ -21,7 +21,7 @@ CssRoot { rule: TwSourceAtRule { source_token: SOURCE_KW@1..8 "source" [] [Whitespace(" ")], not_token: NOT_KW@8..12 "not" [] [Whitespace(" ")], - path: CssString { + source: CssString { value_token: CSS_STRING_LITERAL@12..36 "\"../routes/utils/ds.tsx\"" [] [], }, semicolon_token: SEMICOLON@36..37 ";" [] [], diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source.css.snap index e4392cb9f4b4..be4e23e876c6 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/tailwind/source.css.snap @@ -21,7 +21,7 @@ CssRoot { rule: TwSourceAtRule { source_token: SOURCE_KW@1..8 "source" [] [Whitespace(" ")], not_token: missing (optional), - path: CssString { + source: CssString { value_token: CSS_STRING_LITERAL@8..44 "\"../node_modules/@my-company/ui-lib\"" [] [], }, semicolon_token: SEMICOLON@44..45 ";" [] [], diff --git a/crates/biome_css_syntax/src/generated/kind.rs b/crates/biome_css_syntax/src/generated/kind.rs index e44922ccbded..68d3e9b36dfa 100644 --- a/crates/biome_css_syntax/src/generated/kind.rs +++ b/crates/biome_css_syntax/src/generated/kind.rs @@ -136,6 +136,7 @@ pub enum CssSyntaxKind { CONFIG_KW, PLUGIN_KW, SLOT_KW, + INLINE_KW, INITIAL_KW, INHERIT_KW, UNSET_KW, @@ -483,6 +484,7 @@ pub enum CssSyntaxKind { TW_VALUE_THEME_REFERENCE, TW_FUNCTIONAL_UTILITY_NAME, TW_CUSTOM_VARIANT_SHORTHAND, + TW_SOURCE_INLINE, CSS_UNKNOWN_BLOCK_AT_RULE, CSS_UNKNOWN_VALUE_AT_RULE, CSS_UNKNOWN_AT_RULE_COMPONENT_LIST, @@ -710,6 +712,7 @@ impl CssSyntaxKind { "config" => CONFIG_KW, "plugin" => PLUGIN_KW, "slot" => SLOT_KW, + "inline" => INLINE_KW, "initial" => INITIAL_KW, "inherit" => INHERIT_KW, "unset" => UNSET_KW, @@ -949,6 +952,7 @@ impl CssSyntaxKind { CONFIG_KW => "config", PLUGIN_KW => "plugin", SLOT_KW => "slot", + INLINE_KW => "inline", INITIAL_KW => "initial", INHERIT_KW => "inherit", UNSET_KW => "unset", @@ -1067,4 +1071,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 } ; [