diff --git a/crates/biome_css_factory/src/generated/node_factory.rs b/crates/biome_css_factory/src/generated/node_factory.rs index 2f59f5aa43cc..429a5759eeba 100644 --- a/crates/biome_css_factory/src/generated/node_factory.rs +++ b/crates/biome_css_factory/src/generated/node_factory.rs @@ -737,6 +737,12 @@ pub fn css_generic_property( ], )) } +pub fn css_grit_metavariable(value_token: SyntaxToken) -> CssGritMetavariable { + CssGritMetavariable::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_GRIT_METAVARIABLE, + [Some(SyntaxElement::Token(value_token))], + )) +} pub fn css_id_selector(hash_token: SyntaxToken, name: CssCustomIdentifier) -> CssIdSelector { CssIdSelector::unwrap_cast(SyntaxNode::new_detached( CssSyntaxKind::CSS_ID_SELECTOR, diff --git a/crates/biome_css_factory/src/generated/syntax_factory.rs b/crates/biome_css_factory/src/generated/syntax_factory.rs index 824bc7ab209d..5c450656446b 100644 --- a/crates/biome_css_factory/src/generated/syntax_factory.rs +++ b/crates/biome_css_factory/src/generated/syntax_factory.rs @@ -1492,6 +1492,25 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(CSS_GENERIC_PROPERTY, children) } + CSS_GRIT_METAVARIABLE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<1usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == GRIT_METAVARIABLE { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_GRIT_METAVARIABLE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_GRIT_METAVARIABLE, children) + } CSS_ID_SELECTOR => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); diff --git a/crates/biome_css_formatter/src/css/any/declaration_or_rule.rs b/crates/biome_css_formatter/src/css/any/declaration_or_rule.rs index 1aa331f1f822..3a85ef75c0ab 100644 --- a/crates/biome_css_formatter/src/css/any/declaration_or_rule.rs +++ b/crates/biome_css_formatter/src/css/any/declaration_or_rule.rs @@ -11,6 +11,7 @@ impl FormatRule for FormatAnyCssDeclarationOrRule { AnyCssDeclarationOrRule::AnyCssRule(node) => node.format().fmt(f), AnyCssDeclarationOrRule::CssBogus(node) => node.format().fmt(f), AnyCssDeclarationOrRule::CssDeclarationWithSemicolon(node) => node.format().fmt(f), + AnyCssDeclarationOrRule::CssGritMetavariable(node) => node.format().fmt(f), } } } diff --git a/crates/biome_css_formatter/src/css/any/media_query.rs b/crates/biome_css_formatter/src/css/any/media_query.rs index d7df4bc85a2e..c564c266fdda 100644 --- a/crates/biome_css_formatter/src/css/any/media_query.rs +++ b/crates/biome_css_formatter/src/css/any/media_query.rs @@ -10,6 +10,7 @@ impl FormatRule for FormatAnyCssMediaQuery { match node { AnyCssMediaQuery::AnyCssMediaTypeQuery(node) => node.format().fmt(f), AnyCssMediaQuery::CssBogusMediaQuery(node) => node.format().fmt(f), + AnyCssMediaQuery::CssGritMetavariable(node) => node.format().fmt(f), AnyCssMediaQuery::CssMediaConditionQuery(node) => node.format().fmt(f), } } diff --git a/crates/biome_css_formatter/src/css/any/selector.rs b/crates/biome_css_formatter/src/css/any/selector.rs index b2284c224f3b..20dff64f7de2 100644 --- a/crates/biome_css_formatter/src/css/any/selector.rs +++ b/crates/biome_css_formatter/src/css/any/selector.rs @@ -11,6 +11,7 @@ impl FormatRule for FormatAnyCssSelector { AnyCssSelector::CssBogusSelector(node) => node.format().fmt(f), AnyCssSelector::CssComplexSelector(node) => node.format().fmt(f), AnyCssSelector::CssCompoundSelector(node) => node.format().fmt(f), + AnyCssSelector::CssGritMetavariable(node) => node.format().fmt(f), } } } diff --git a/crates/biome_css_formatter/src/css/any/value.rs b/crates/biome_css_formatter/src/css/any/value.rs index ad9316306fd0..140630ba8dd8 100644 --- a/crates/biome_css_formatter/src/css/any/value.rs +++ b/crates/biome_css_formatter/src/css/any/value.rs @@ -14,6 +14,7 @@ impl FormatRule for FormatAnyCssValue { AnyCssValue::CssColor(node) => node.format().fmt(f), AnyCssValue::CssCustomIdentifier(node) => node.format().fmt(f), AnyCssValue::CssDashedIdentifier(node) => node.format().fmt(f), + AnyCssValue::CssGritMetavariable(node) => node.format().fmt(f), AnyCssValue::CssIdentifier(node) => node.format().fmt(f), AnyCssValue::CssNumber(node) => node.format().fmt(f), AnyCssValue::CssRatio(node) => node.format().fmt(f), diff --git a/crates/biome_css_formatter/src/css/auxiliary/grit_metavariable.rs b/crates/biome_css_formatter/src/css/auxiliary/grit_metavariable.rs new file mode 100644 index 000000000000..f4162fbd24ed --- /dev/null +++ b/crates/biome_css_formatter/src/css/auxiliary/grit_metavariable.rs @@ -0,0 +1,12 @@ +use crate::prelude::*; +use biome_css_syntax::{CssGritMetavariable, CssGritMetavariableFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssGritMetavariable; +impl FormatNodeRule for FormatCssGritMetavariable { + fn fmt_fields(&self, node: &CssGritMetavariable, f: &mut CssFormatter) -> FormatResult<()> { + let CssGritMetavariableFields { value_token } = node.as_fields(); + write!(f, [value_token.format()]) + } +} diff --git a/crates/biome_css_formatter/src/css/auxiliary/mod.rs b/crates/biome_css_formatter/src/css/auxiliary/mod.rs index b49df5ae5c91..3704c7f41593 100644 --- a/crates/biome_css_formatter/src/css/auxiliary/mod.rs +++ b/crates/biome_css_formatter/src/css/auxiliary/mod.rs @@ -29,6 +29,7 @@ pub(crate) mod font_feature_values_block; pub(crate) mod font_feature_values_item; pub(crate) mod function; pub(crate) mod generic_delimiter; +pub(crate) mod grit_metavariable; pub(crate) mod import_anonymous_layer; pub(crate) mod import_named_layer; pub(crate) mod import_supports; diff --git a/crates/biome_css_formatter/src/generated.rs b/crates/biome_css_formatter/src/generated.rs index 9d63bc1f38a3..558e590f1725 100644 --- a/crates/biome_css_formatter/src/generated.rs +++ b/crates/biome_css_formatter/src/generated.rs @@ -1747,6 +1747,46 @@ impl IntoFormat for biome_css_syntax::CssGenericProperty { ) } } +impl FormatRule + for crate::css::auxiliary::grit_metavariable::FormatCssGritMetavariable +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssGritMetavariable, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssGritMetavariable { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssGritMetavariable, + crate::css::auxiliary::grit_metavariable::FormatCssGritMetavariable, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::auxiliary::grit_metavariable::FormatCssGritMetavariable::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::CssGritMetavariable { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssGritMetavariable, + crate::css::auxiliary::grit_metavariable::FormatCssGritMetavariable, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::auxiliary::grit_metavariable::FormatCssGritMetavariable::default(), + ) + } +} impl FormatRule for crate::css::selectors::id_selector::FormatCssIdSelector { diff --git a/crates/biome_css_parser/src/lexer/mod.rs b/crates/biome_css_parser/src/lexer/mod.rs index 1217828ddb89..a047cb450da9 100644 --- a/crates/biome_css_parser/src/lexer/mod.rs +++ b/crates/biome_css_parser/src/lexer/mod.rs @@ -323,6 +323,11 @@ impl<'src> CssLexer<'src> { self.advance(1); self.consume_byte(T!["$="]) } + UNI if self.options.is_grit_metavariable_enabled() + && self.is_grit_metavariable_start() => + { + self.consume_grit_metavariable() + } IDT | UNI | BSL if self.is_ident_start() => self.consume_identifier(), MUL => self.consume_mul(), @@ -1315,6 +1320,58 @@ impl<'src> CssLexer<'src> { _ => false, } } + + /// Check if the lexer starts a grit metavariable + fn is_grit_metavariable_start(&mut self) -> bool { + let current_char = self.current_char_unchecked(); + if current_char == 'μ' { + let current_char_length = current_char.len_utf8(); + // μ[a-zA-Z_][a-zA-Z0-9_]* + if matches!( + self.byte_at(current_char_length), + Some(b'a'..=b'z' | b'A'..=b'Z' | b'_') + ) { + return true; + } + + // μ... + if self.byte_at(current_char_length) == Some(b'.') + && self.byte_at(current_char_length + 1) == Some(b'.') + && self.byte_at(current_char_length + 2) == Some(b'.') + { + return true; + } + } + false + } + + /// Consume a grit metavariable(μ[a-zA-Z_][a-zA-Z0-9_]*|μ...) + /// https://github.com/getgrit/gritql/blob/8f3f077d078ccaf0618510bba904a06309c2435e/resources/language-metavariables/tree-sitter-css/grammar.js#L388 + fn consume_grit_metavariable(&mut self) -> CssSyntaxKind { + debug_assert!(self.is_grit_metavariable_start()); + + // SAFETY: We know the current character is μ. + let current_char = self.current_char_unchecked(); + self.advance(current_char.len_utf8()); + + if self.current_byte() == Some(b'.') { + // SAFETY: We know that the current token is μ... + self.advance(3); + } else { + // μ[a-zA-Z_][a-zA-Z0-9_]* + self.advance(1); + while let Some(chr) = self.current_byte() { + match chr { + b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' => { + self.advance(1); + } + _ => break, + } + } + } + + GRIT_METAVARIABLE + } } impl<'src> ReLexer<'src> for CssLexer<'src> { diff --git a/crates/biome_css_parser/src/parser.rs b/crates/biome_css_parser/src/parser.rs index 8c48b98ee25e..7f5bca1393d6 100644 --- a/crates/biome_css_parser/src/parser.rs +++ b/crates/biome_css_parser/src/parser.rs @@ -29,6 +29,10 @@ pub struct CssParserOptions { /// Enables parsing of CSS Modules specific features. /// Defaults to `false`. pub css_modules: bool, + + /// Enables parsing of Grit metavariables. + /// Defaults to `false`. + pub grit_metavariable: bool, } impl CssParserOptions { @@ -44,10 +48,21 @@ impl CssParserOptions { self } + /// Enables parsing of Grit metavariables. + pub fn allow_grit_metavariables(mut self) -> Self { + self.grit_metavariable = true; + self + } + /// Checks if parsing of CSS Modules features is disabled. pub fn is_css_modules_disabled(&self) -> bool { !self.css_modules } + + /// Checks if parsing of Grit metavariables is enabled. + pub fn is_grit_metavariable_enabled(&self) -> bool { + self.grit_metavariable + } } impl<'source> CssParser<'source> { diff --git a/crates/biome_css_parser/src/syntax/at_rule/media.rs b/crates/biome_css_parser/src/syntax/at_rule/media.rs index dc6803ea564e..590d2091a2f0 100644 --- a/crates/biome_css_parser/src/syntax/at_rule/media.rs +++ b/crates/biome_css_parser/src/syntax/at_rule/media.rs @@ -2,7 +2,10 @@ use super::parse_error::expected_media_query; use crate::parser::CssParser; use crate::syntax::at_rule::feature::parse_any_query_feature; use crate::syntax::block::parse_conditional_block; -use crate::syntax::{is_at_identifier, is_nth_at_identifier, parse_regular_identifier}; +use crate::syntax::{ + is_at_grit_metavariable, is_at_identifier, is_nth_at_identifier, parse_grit_metavariable, + parse_regular_identifier, +}; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; use biome_parser::parse_lists::ParseSeparatedList; @@ -77,6 +80,8 @@ impl ParseSeparatedList for MediaQueryList { fn parse_any_media_query(p: &mut CssParser) -> ParsedSyntax { if is_at_media_type_query(p) { parse_any_media_type_query(p) + } else if is_at_grit_metavariable(p) { + parse_grit_metavariable(p) } else { let m = p.start(); parse_any_media_condition(p).ok(); // TODO handle error diff --git a/crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs b/crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs index b561bfdc7722..f17a61066f3a 100644 --- a/crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs +++ b/crates/biome_css_parser/src/syntax/block/declaration_or_rule_list_block.rs @@ -3,8 +3,9 @@ use crate::syntax::at_rule::{is_at_at_rule, parse_at_rule}; use crate::syntax::block::ParseBlockBody; use crate::syntax::parse_error::expected_any_declaration_or_at_rule; use crate::syntax::{ - is_at_declaration, is_at_nested_qualified_rule, parse_declaration_with_semicolon, - parse_nested_qualified_rule, try_parse, + is_at_declaration, is_at_grit_metavariable, is_at_nested_qualified_rule, + parse_declaration_with_semicolon, parse_grit_metavariable, parse_nested_qualified_rule, + try_parse, }; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; @@ -35,7 +36,10 @@ impl ParseBlockBody for DeclarationOrRuleListBlock { #[inline] fn is_at_declaration_or_rule_item(p: &mut CssParser) -> bool { - is_at_at_rule(p) || is_at_nested_qualified_rule(p) || is_at_declaration(p) + is_at_at_rule(p) + || is_at_nested_qualified_rule(p) + || is_at_declaration(p) + || is_at_grit_metavariable(p) } struct DeclarationOrRuleListParseRecovery; @@ -124,6 +128,8 @@ impl ParseNodeList for DeclarationOrRuleList { parse_declaration_with_semicolon(p) } else if is_at_nested_qualified_rule(p) { parse_nested_qualified_rule(p) + } else if is_at_grit_metavariable(p) { + parse_grit_metavariable(p) } else { Absent } diff --git a/crates/biome_css_parser/src/syntax/mod.rs b/crates/biome_css_parser/src/syntax/mod.rs index dab4e9945035..0776b36f1f6b 100644 --- a/crates/biome_css_parser/src/syntax/mod.rs +++ b/crates/biome_css_parser/src/syntax/mod.rs @@ -248,6 +248,26 @@ fn parse_declaration_important(p: &mut CssParser) -> ParsedSyntax { Present(m.complete(p, CSS_DECLARATION_IMPORTANT)) } +#[inline] +fn is_at_grit_metavariable(p: &mut CssParser) -> bool { + p.at(GRIT_METAVARIABLE) +} + +#[inline] +fn is_nth_at_grit_metavariable(p: &mut CssParser, n: usize) -> bool { + p.nth_at(n, GRIT_METAVARIABLE) +} + +#[inline] +fn parse_grit_metavariable(p: &mut CssParser) -> ParsedSyntax { + if !is_at_grit_metavariable(p) { + return Absent; + } + let m = p.start(); + p.bump(GRIT_METAVARIABLE); + Present(m.complete(p, CSS_GRIT_METAVARIABLE)) +} + #[inline] pub(crate) fn is_at_any_value(p: &mut CssParser) -> bool { is_at_any_function(p) @@ -259,6 +279,7 @@ pub(crate) fn is_at_any_value(p: &mut CssParser) -> bool { || is_at_ratio(p) || is_at_color(p) || is_at_bracketed_value(p) + || is_at_grit_metavariable(p) } #[inline] @@ -283,6 +304,8 @@ pub(crate) fn parse_any_value(p: &mut CssParser) -> ParsedSyntax { parse_color(p) } else if is_at_bracketed_value(p) { parse_bracketed_value(p) + } else if is_at_grit_metavariable(p) { + parse_grit_metavariable(p) } else { Absent } diff --git a/crates/biome_css_parser/src/syntax/selector/mod.rs b/crates/biome_css_parser/src/syntax/selector/mod.rs index 0340791521bd..9a161d4a5adb 100644 --- a/crates/biome_css_parser/src/syntax/selector/mod.rs +++ b/crates/biome_css_parser/src/syntax/selector/mod.rs @@ -28,6 +28,8 @@ use biome_parser::prelude::ParsedSyntax; use biome_parser::prelude::ParsedSyntax::{Absent, Present}; use biome_parser::{token_set, CompletedMarker, Parser, ParserProgress, TokenSet}; +use super::{is_nth_at_grit_metavariable, parse_grit_metavariable}; + /// Determines the lexical context for parsing CSS selectors. /// /// This function is applied when lexing CSS selectors. It decides whether the @@ -197,7 +199,7 @@ impl ParseRecovery for SelectorListParseRecovery { /// the elements to which a set of CSS rules apply. #[inline] pub(crate) fn is_nth_at_selector(p: &mut CssParser, n: usize) -> bool { - is_nth_at_compound_selector(p, n) + is_nth_at_compound_selector(p, n) || is_nth_at_grit_metavariable(p, n) } /// Parses a CSS selector. @@ -213,12 +215,15 @@ pub(crate) fn parse_selector(p: &mut CssParser) -> ParsedSyntax { if !is_nth_at_selector(p, 0) { return Absent; } - - // In CSS, we have compound selectors and complex selectors. - // Compound selectors are simple, unseparated chains of selectors, - // while complex selectors are compound selectors separated by combinators. - // After parsing the compound selector, it then checks if this compound selector is a part of a complex selector. - parse_compound_selector(p).and_then(|selector| parse_complex_selector(p, selector)) + if is_nth_at_grit_metavariable(p, 0) { + parse_grit_metavariable(p) + } else { + // In CSS, we have compound selectors and complex selectors. + // Compound selectors are simple, unseparated chains of selectors, + // while complex selectors are compound selectors separated by combinators. + // After parsing the compound selector, it then checks if this compound selector is a part of a complex selector. + parse_compound_selector(p).and_then(|selector| parse_complex_selector(p, selector)) + } } const COMPLEX_SELECTOR_COMBINATOR_SET: TokenSet = diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css b/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css new file mode 100644 index 000000000000..49cf369ab780 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css @@ -0,0 +1,15 @@ +.foo { + color: μcolor; +} + +.foo { + μbar +} + +.foo { + @media μbaz {} +} + +μqux {} + +μ... {} \ No newline at end of file diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css.snap new file mode 100644 index 000000000000..fb430265fee9 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css.snap @@ -0,0 +1,253 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +.foo { + color: μcolor; +} + +.foo { + μbar +} + +.foo { + @media μbaz {} +} + +μqux {} + +μ... {} +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@0..1 "." [] [], + name: CssCustomIdentifier { + value_token: IDENT@1..5 "foo" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@5..6 "{" [] [], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@6..16 "color" [Newline("\n"), Whitespace(" ")] [], + }, + colon_token: COLON@16..18 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssGritMetavariable { + value_token: GRIT_METAVARIABLE@18..25 "μcolor" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@25..26 ";" [] [], + }, + ], + r_curly_token: R_CURLY@26..28 "}" [Newline("\n")] [], + }, + }, + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@28..31 "." [Newline("\n"), Newline("\n")] [], + name: CssCustomIdentifier { + value_token: IDENT@31..35 "foo" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@35..36 "{" [] [], + items: CssDeclarationOrRuleList [ + CssGritMetavariable { + value_token: GRIT_METAVARIABLE@36..46 "μbar" [Newline("\n"), Whitespace(" ")] [], + }, + ], + r_curly_token: R_CURLY@46..48 "}" [Newline("\n")] [], + }, + }, + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@48..51 "." [Newline("\n"), Newline("\n")] [], + name: CssCustomIdentifier { + value_token: IDENT@51..55 "foo" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@55..56 "{" [] [], + items: CssDeclarationOrRuleList [ + CssAtRule { + at_token: AT@56..62 "@" [Newline("\n"), Whitespace(" ")] [], + rule: CssMediaAtRule { + media_token: MEDIA_KW@62..68 "media" [] [Whitespace(" ")], + queries: CssMediaQueryList [ + CssGritMetavariable { + value_token: GRIT_METAVARIABLE@68..74 "μbaz" [] [Whitespace(" ")], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@74..75 "{" [] [], + items: CssDeclarationOrRuleList [], + r_curly_token: R_CURLY@75..76 "}" [] [], + }, + }, + }, + ], + r_curly_token: R_CURLY@76..78 "}" [Newline("\n")] [], + }, + }, + CssQualifiedRule { + prelude: CssSelectorList [ + CssGritMetavariable { + value_token: GRIT_METAVARIABLE@78..86 "μqux" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@86..87 "{" [] [], + items: CssDeclarationOrRuleList [], + r_curly_token: R_CURLY@87..88 "}" [] [], + }, + }, + CssQualifiedRule { + prelude: CssSelectorList [ + CssGritMetavariable { + value_token: GRIT_METAVARIABLE@88..96 "μ..." [Newline("\n"), Newline("\n")] [Whitespace(" ")], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@96..97 "{" [] [], + items: CssDeclarationOrRuleList [], + r_curly_token: R_CURLY@97..98 "}" [] [], + }, + }, + ], + eof_token: EOF@98..98 "" [] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..98 + 0: (empty) + 1: CSS_RULE_LIST@0..98 + 0: CSS_QUALIFIED_RULE@0..28 + 0: CSS_SELECTOR_LIST@0..5 + 0: CSS_COMPOUND_SELECTOR@0..5 + 0: CSS_NESTED_SELECTOR_LIST@0..0 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@0..5 + 0: CSS_CLASS_SELECTOR@0..5 + 0: DOT@0..1 "." [] [] + 1: CSS_CUSTOM_IDENTIFIER@1..5 + 0: IDENT@1..5 "foo" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@5..28 + 0: L_CURLY@5..6 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@6..26 + 0: CSS_DECLARATION_WITH_SEMICOLON@6..26 + 0: CSS_DECLARATION@6..25 + 0: CSS_GENERIC_PROPERTY@6..25 + 0: CSS_IDENTIFIER@6..16 + 0: IDENT@6..16 "color" [Newline("\n"), Whitespace(" ")] [] + 1: COLON@16..18 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@18..25 + 0: CSS_GRIT_METAVARIABLE@18..25 + 0: GRIT_METAVARIABLE@18..25 "μcolor" [] [] + 1: (empty) + 1: SEMICOLON@25..26 ";" [] [] + 2: R_CURLY@26..28 "}" [Newline("\n")] [] + 1: CSS_QUALIFIED_RULE@28..48 + 0: CSS_SELECTOR_LIST@28..35 + 0: CSS_COMPOUND_SELECTOR@28..35 + 0: CSS_NESTED_SELECTOR_LIST@28..28 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@28..35 + 0: CSS_CLASS_SELECTOR@28..35 + 0: DOT@28..31 "." [Newline("\n"), Newline("\n")] [] + 1: CSS_CUSTOM_IDENTIFIER@31..35 + 0: IDENT@31..35 "foo" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@35..48 + 0: L_CURLY@35..36 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@36..46 + 0: CSS_GRIT_METAVARIABLE@36..46 + 0: GRIT_METAVARIABLE@36..46 "μbar" [Newline("\n"), Whitespace(" ")] [] + 2: R_CURLY@46..48 "}" [Newline("\n")] [] + 2: CSS_QUALIFIED_RULE@48..78 + 0: CSS_SELECTOR_LIST@48..55 + 0: CSS_COMPOUND_SELECTOR@48..55 + 0: CSS_NESTED_SELECTOR_LIST@48..48 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@48..55 + 0: CSS_CLASS_SELECTOR@48..55 + 0: DOT@48..51 "." [Newline("\n"), Newline("\n")] [] + 1: CSS_CUSTOM_IDENTIFIER@51..55 + 0: IDENT@51..55 "foo" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@55..78 + 0: L_CURLY@55..56 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@56..76 + 0: CSS_AT_RULE@56..76 + 0: AT@56..62 "@" [Newline("\n"), Whitespace(" ")] [] + 1: CSS_MEDIA_AT_RULE@62..76 + 0: MEDIA_KW@62..68 "media" [] [Whitespace(" ")] + 1: CSS_MEDIA_QUERY_LIST@68..74 + 0: CSS_GRIT_METAVARIABLE@68..74 + 0: GRIT_METAVARIABLE@68..74 "μbaz" [] [Whitespace(" ")] + 2: CSS_DECLARATION_OR_RULE_BLOCK@74..76 + 0: L_CURLY@74..75 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@75..75 + 2: R_CURLY@75..76 "}" [] [] + 2: R_CURLY@76..78 "}" [Newline("\n")] [] + 3: CSS_QUALIFIED_RULE@78..88 + 0: CSS_SELECTOR_LIST@78..86 + 0: CSS_GRIT_METAVARIABLE@78..86 + 0: GRIT_METAVARIABLE@78..86 "μqux" [Newline("\n"), Newline("\n")] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@86..88 + 0: L_CURLY@86..87 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@87..87 + 2: R_CURLY@87..88 "}" [] [] + 4: CSS_QUALIFIED_RULE@88..98 + 0: CSS_SELECTOR_LIST@88..96 + 0: CSS_GRIT_METAVARIABLE@88..96 + 0: GRIT_METAVARIABLE@88..96 "μ..." [Newline("\n"), Newline("\n")] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@96..98 + 0: L_CURLY@96..97 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@97..97 + 2: R_CURLY@97..98 "}" [] [] + 2: EOF@98..98 "" [] [] + +``` diff --git a/crates/biome_css_parser/tests/spec_test.rs b/crates/biome_css_parser/tests/spec_test.rs index 0b0e8163fb63..8b88ac43f042 100644 --- a/crates/biome_css_parser/tests/spec_test.rs +++ b/crates/biome_css_parser/tests/spec_test.rs @@ -40,7 +40,10 @@ pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_ let content = fs::read_to_string(test_case_path) .expect("Expected test path to be a readable file in UTF8 encoding"); - let mut options = CssParserOptions::default(); + let mut options = CssParserOptions::default() + // it is an internal option that cannot be configured via options.json + // TODO: find a way to make it configurable + .allow_grit_metavariables(); let options_path = Path::new(test_directory).join("options.json"); @@ -171,18 +174,15 @@ pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_ #[test] pub fn quick_test() { let code = r#" -.test { -&&&&& { -color: red; -} - } +μ... {} "#; let root = parse_css( code, CssParserOptions::default() .allow_wrong_line_comments() - .allow_css_modules(), + .allow_css_modules() + .allow_grit_metavariables(), ); let syntax = root.syntax(); dbg!(&syntax, root.diagnostics(), root.has_errors()); diff --git a/crates/biome_css_syntax/src/generated/kind.rs b/crates/biome_css_syntax/src/generated/kind.rs index 0fe4c268908d..5a0443256199 100644 --- a/crates/biome_css_syntax/src/generated/kind.rs +++ b/crates/biome_css_syntax/src/generated/kind.rs @@ -249,6 +249,7 @@ pub enum CssSyntaxKind { WHITESPACE, COMMENT, MULTILINE_COMMENT, + GRIT_METAVARIABLE, CSS_ROOT, CSS_RULE_LIST, CSS_QUALIFIED_RULE, @@ -476,6 +477,7 @@ pub enum CssSyntaxKind { CSS_BOGUS_CUSTOM_IDENTIFIER, CSS_BOGUS_KEYFRAMES_NAME, CSS_BOGUS_UNICODE_RANGE_VALUE, + CSS_GRIT_METAVARIABLE, #[doc(hidden)] __LAST, } diff --git a/crates/biome_css_syntax/src/generated/macros.rs b/crates/biome_css_syntax/src/generated/macros.rs index e423b98c8524..f4691472cd51 100644 --- a/crates/biome_css_syntax/src/generated/macros.rs +++ b/crates/biome_css_syntax/src/generated/macros.rs @@ -217,6 +217,10 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::CssGenericProperty::new_unchecked(node) }; $body } + $crate::CssSyntaxKind::CSS_GRIT_METAVARIABLE => { + let $pattern = unsafe { $crate::CssGritMetavariable::new_unchecked(node) }; + $body + } $crate::CssSyntaxKind::CSS_ID_SELECTOR => { let $pattern = unsafe { $crate::CssIdSelector::new_unchecked(node) }; $body diff --git a/crates/biome_css_syntax/src/generated/nodes.rs b/crates/biome_css_syntax/src/generated/nodes.rs index e6e5f42da355..8c6f3e42b8c8 100644 --- a/crates/biome_css_syntax/src/generated/nodes.rs +++ b/crates/biome_css_syntax/src/generated/nodes.rs @@ -2100,6 +2100,42 @@ pub struct CssGenericPropertyFields { pub value: CssGenericComponentValueList, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct CssGritMetavariable { + pub(crate) syntax: SyntaxNode, +} +impl CssGritMetavariable { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> CssGritMetavariableFields { + CssGritMetavariableFields { + value_token: self.value_token(), + } + } + pub fn value_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for CssGritMetavariable { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct CssGritMetavariableFields { + pub value_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct CssIdSelector { pub(crate) syntax: SyntaxNode, } @@ -7295,6 +7331,7 @@ pub enum AnyCssDeclarationOrRule { AnyCssRule(AnyCssRule), CssBogus(CssBogus), CssDeclarationWithSemicolon(CssDeclarationWithSemicolon), + CssGritMetavariable(CssGritMetavariable), } impl AnyCssDeclarationOrRule { pub fn as_any_css_rule(&self) -> Option<&AnyCssRule> { @@ -7315,6 +7352,12 @@ impl AnyCssDeclarationOrRule { _ => None, } } + pub fn as_css_grit_metavariable(&self) -> Option<&CssGritMetavariable> { + match &self { + AnyCssDeclarationOrRule::CssGritMetavariable(item) => Some(item), + _ => None, + } + } } #[derive(Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize))] @@ -7846,6 +7889,7 @@ impl AnyCssMediaOrCombinableCondition { pub enum AnyCssMediaQuery { AnyCssMediaTypeQuery(AnyCssMediaTypeQuery), CssBogusMediaQuery(CssBogusMediaQuery), + CssGritMetavariable(CssGritMetavariable), CssMediaConditionQuery(CssMediaConditionQuery), } impl AnyCssMediaQuery { @@ -7861,6 +7905,12 @@ impl AnyCssMediaQuery { _ => None, } } + pub fn as_css_grit_metavariable(&self) -> Option<&CssGritMetavariable> { + match &self { + AnyCssMediaQuery::CssGritMetavariable(item) => Some(item), + _ => None, + } + } pub fn as_css_media_condition_query(&self) -> Option<&CssMediaConditionQuery> { match &self { AnyCssMediaQuery::CssMediaConditionQuery(item) => Some(item), @@ -8458,6 +8508,7 @@ pub enum AnyCssSelector { CssBogusSelector(CssBogusSelector), CssComplexSelector(CssComplexSelector), CssCompoundSelector(CssCompoundSelector), + CssGritMetavariable(CssGritMetavariable), } impl AnyCssSelector { pub fn as_css_bogus_selector(&self) -> Option<&CssBogusSelector> { @@ -8478,6 +8529,12 @@ impl AnyCssSelector { _ => None, } } + pub fn as_css_grit_metavariable(&self) -> Option<&CssGritMetavariable> { + match &self { + AnyCssSelector::CssGritMetavariable(item) => Some(item), + _ => None, + } + } } #[derive(Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize))] @@ -8779,6 +8836,7 @@ pub enum AnyCssValue { CssColor(CssColor), CssCustomIdentifier(CssCustomIdentifier), CssDashedIdentifier(CssDashedIdentifier), + CssGritMetavariable(CssGritMetavariable), CssIdentifier(CssIdentifier), CssNumber(CssNumber), CssRatio(CssRatio), @@ -8822,6 +8880,12 @@ impl AnyCssValue { _ => None, } } + pub fn as_css_grit_metavariable(&self) -> Option<&CssGritMetavariable> { + match &self { + AnyCssValue::CssGritMetavariable(item) => Some(item), + _ => None, + } + } pub fn as_css_identifier(&self) -> Option<&CssIdentifier> { match &self { AnyCssValue::CssIdentifier(item) => Some(item), @@ -10943,6 +11007,47 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for CssGritMetavariable { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(CSS_GRIT_METAVARIABLE as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == CSS_GRIT_METAVARIABLE + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for CssGritMetavariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CssGritMetavariable") + .field( + "value_token", + &support::DebugSyntaxResult(self.value_token()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: CssGritMetavariable) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: CssGritMetavariable) -> SyntaxElement { + n.syntax.into() + } +} impl AstNode for CssIdSelector { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -17068,14 +17173,20 @@ impl From for AnyCssDeclarationOrRule { AnyCssDeclarationOrRule::CssDeclarationWithSemicolon(node) } } +impl From for AnyCssDeclarationOrRule { + fn from(node: CssGritMetavariable) -> AnyCssDeclarationOrRule { + AnyCssDeclarationOrRule::CssGritMetavariable(node) + } +} impl AstNode for AnyCssDeclarationOrRule { type Language = Language; const KIND_SET: SyntaxKindSet = AnyCssRule::KIND_SET .union(CssBogus::KIND_SET) - .union(CssDeclarationWithSemicolon::KIND_SET); + .union(CssDeclarationWithSemicolon::KIND_SET) + .union(CssGritMetavariable::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { match kind { - CSS_BOGUS | CSS_DECLARATION_WITH_SEMICOLON => true, + CSS_BOGUS | CSS_DECLARATION_WITH_SEMICOLON | CSS_GRIT_METAVARIABLE => true, k if AnyCssRule::can_cast(k) => true, _ => false, } @@ -17088,6 +17199,9 @@ impl AstNode for AnyCssDeclarationOrRule { syntax, }) } + CSS_GRIT_METAVARIABLE => { + AnyCssDeclarationOrRule::CssGritMetavariable(CssGritMetavariable { syntax }) + } _ => { if let Some(any_css_rule) = AnyCssRule::cast(syntax) { return Some(AnyCssDeclarationOrRule::AnyCssRule(any_css_rule)); @@ -17101,6 +17215,7 @@ impl AstNode for AnyCssDeclarationOrRule { match self { AnyCssDeclarationOrRule::CssBogus(it) => &it.syntax, AnyCssDeclarationOrRule::CssDeclarationWithSemicolon(it) => &it.syntax, + AnyCssDeclarationOrRule::CssGritMetavariable(it) => &it.syntax, AnyCssDeclarationOrRule::AnyCssRule(it) => it.syntax(), } } @@ -17108,6 +17223,7 @@ impl AstNode for AnyCssDeclarationOrRule { match self { AnyCssDeclarationOrRule::CssBogus(it) => it.syntax, AnyCssDeclarationOrRule::CssDeclarationWithSemicolon(it) => it.syntax, + AnyCssDeclarationOrRule::CssGritMetavariable(it) => it.syntax, AnyCssDeclarationOrRule::AnyCssRule(it) => it.into_syntax(), } } @@ -17118,6 +17234,7 @@ impl std::fmt::Debug for AnyCssDeclarationOrRule { AnyCssDeclarationOrRule::AnyCssRule(it) => std::fmt::Debug::fmt(it, f), AnyCssDeclarationOrRule::CssBogus(it) => std::fmt::Debug::fmt(it, f), AnyCssDeclarationOrRule::CssDeclarationWithSemicolon(it) => std::fmt::Debug::fmt(it, f), + AnyCssDeclarationOrRule::CssGritMetavariable(it) => std::fmt::Debug::fmt(it, f), } } } @@ -17127,6 +17244,7 @@ impl From for SyntaxNode { AnyCssDeclarationOrRule::AnyCssRule(it) => it.into(), AnyCssDeclarationOrRule::CssBogus(it) => it.into(), AnyCssDeclarationOrRule::CssDeclarationWithSemicolon(it) => it.into(), + AnyCssDeclarationOrRule::CssGritMetavariable(it) => it.into(), } } } @@ -18792,6 +18910,11 @@ impl From for AnyCssMediaQuery { AnyCssMediaQuery::CssBogusMediaQuery(node) } } +impl From for AnyCssMediaQuery { + fn from(node: CssGritMetavariable) -> AnyCssMediaQuery { + AnyCssMediaQuery::CssGritMetavariable(node) + } +} impl From for AnyCssMediaQuery { fn from(node: CssMediaConditionQuery) -> AnyCssMediaQuery { AnyCssMediaQuery::CssMediaConditionQuery(node) @@ -18801,10 +18924,11 @@ impl AstNode for AnyCssMediaQuery { type Language = Language; const KIND_SET: SyntaxKindSet = AnyCssMediaTypeQuery::KIND_SET .union(CssBogusMediaQuery::KIND_SET) + .union(CssGritMetavariable::KIND_SET) .union(CssMediaConditionQuery::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { match kind { - CSS_BOGUS_MEDIA_QUERY | CSS_MEDIA_CONDITION_QUERY => true, + CSS_BOGUS_MEDIA_QUERY | CSS_GRIT_METAVARIABLE | CSS_MEDIA_CONDITION_QUERY => true, k if AnyCssMediaTypeQuery::can_cast(k) => true, _ => false, } @@ -18814,6 +18938,9 @@ impl AstNode for AnyCssMediaQuery { CSS_BOGUS_MEDIA_QUERY => { AnyCssMediaQuery::CssBogusMediaQuery(CssBogusMediaQuery { syntax }) } + CSS_GRIT_METAVARIABLE => { + AnyCssMediaQuery::CssGritMetavariable(CssGritMetavariable { syntax }) + } CSS_MEDIA_CONDITION_QUERY => { AnyCssMediaQuery::CssMediaConditionQuery(CssMediaConditionQuery { syntax }) } @@ -18831,6 +18958,7 @@ impl AstNode for AnyCssMediaQuery { fn syntax(&self) -> &SyntaxNode { match self { AnyCssMediaQuery::CssBogusMediaQuery(it) => &it.syntax, + AnyCssMediaQuery::CssGritMetavariable(it) => &it.syntax, AnyCssMediaQuery::CssMediaConditionQuery(it) => &it.syntax, AnyCssMediaQuery::AnyCssMediaTypeQuery(it) => it.syntax(), } @@ -18838,6 +18966,7 @@ impl AstNode for AnyCssMediaQuery { fn into_syntax(self) -> SyntaxNode { match self { AnyCssMediaQuery::CssBogusMediaQuery(it) => it.syntax, + AnyCssMediaQuery::CssGritMetavariable(it) => it.syntax, AnyCssMediaQuery::CssMediaConditionQuery(it) => it.syntax, AnyCssMediaQuery::AnyCssMediaTypeQuery(it) => it.into_syntax(), } @@ -18848,6 +18977,7 @@ impl std::fmt::Debug for AnyCssMediaQuery { match self { AnyCssMediaQuery::AnyCssMediaTypeQuery(it) => std::fmt::Debug::fmt(it, f), AnyCssMediaQuery::CssBogusMediaQuery(it) => std::fmt::Debug::fmt(it, f), + AnyCssMediaQuery::CssGritMetavariable(it) => std::fmt::Debug::fmt(it, f), AnyCssMediaQuery::CssMediaConditionQuery(it) => std::fmt::Debug::fmt(it, f), } } @@ -18857,6 +18987,7 @@ impl From for SyntaxNode { match n { AnyCssMediaQuery::AnyCssMediaTypeQuery(it) => it.into(), AnyCssMediaQuery::CssBogusMediaQuery(it) => it.into(), + AnyCssMediaQuery::CssGritMetavariable(it) => it.into(), AnyCssMediaQuery::CssMediaConditionQuery(it) => it.into(), } } @@ -20530,15 +20661,24 @@ impl From for AnyCssSelector { AnyCssSelector::CssCompoundSelector(node) } } +impl From for AnyCssSelector { + fn from(node: CssGritMetavariable) -> AnyCssSelector { + AnyCssSelector::CssGritMetavariable(node) + } +} impl AstNode for AnyCssSelector { type Language = Language; const KIND_SET: SyntaxKindSet = CssBogusSelector::KIND_SET .union(CssComplexSelector::KIND_SET) - .union(CssCompoundSelector::KIND_SET); + .union(CssCompoundSelector::KIND_SET) + .union(CssGritMetavariable::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { matches!( kind, - CSS_BOGUS_SELECTOR | CSS_COMPLEX_SELECTOR | CSS_COMPOUND_SELECTOR + CSS_BOGUS_SELECTOR + | CSS_COMPLEX_SELECTOR + | CSS_COMPOUND_SELECTOR + | CSS_GRIT_METAVARIABLE ) } fn cast(syntax: SyntaxNode) -> Option { @@ -20550,6 +20690,9 @@ impl AstNode for AnyCssSelector { CSS_COMPOUND_SELECTOR => { AnyCssSelector::CssCompoundSelector(CssCompoundSelector { syntax }) } + CSS_GRIT_METAVARIABLE => { + AnyCssSelector::CssGritMetavariable(CssGritMetavariable { syntax }) + } _ => return None, }; Some(res) @@ -20559,6 +20702,7 @@ impl AstNode for AnyCssSelector { AnyCssSelector::CssBogusSelector(it) => &it.syntax, AnyCssSelector::CssComplexSelector(it) => &it.syntax, AnyCssSelector::CssCompoundSelector(it) => &it.syntax, + AnyCssSelector::CssGritMetavariable(it) => &it.syntax, } } fn into_syntax(self) -> SyntaxNode { @@ -20566,6 +20710,7 @@ impl AstNode for AnyCssSelector { AnyCssSelector::CssBogusSelector(it) => it.syntax, AnyCssSelector::CssComplexSelector(it) => it.syntax, AnyCssSelector::CssCompoundSelector(it) => it.syntax, + AnyCssSelector::CssGritMetavariable(it) => it.syntax, } } } @@ -20575,6 +20720,7 @@ impl std::fmt::Debug for AnyCssSelector { AnyCssSelector::CssBogusSelector(it) => std::fmt::Debug::fmt(it, f), AnyCssSelector::CssComplexSelector(it) => std::fmt::Debug::fmt(it, f), AnyCssSelector::CssCompoundSelector(it) => std::fmt::Debug::fmt(it, f), + AnyCssSelector::CssGritMetavariable(it) => std::fmt::Debug::fmt(it, f), } } } @@ -20584,6 +20730,7 @@ impl From for SyntaxNode { AnyCssSelector::CssBogusSelector(it) => it.into(), AnyCssSelector::CssComplexSelector(it) => it.into(), AnyCssSelector::CssCompoundSelector(it) => it.into(), + AnyCssSelector::CssGritMetavariable(it) => it.into(), } } } @@ -21455,6 +21602,11 @@ impl From for AnyCssValue { AnyCssValue::CssDashedIdentifier(node) } } +impl From for AnyCssValue { + fn from(node: CssGritMetavariable) -> AnyCssValue { + AnyCssValue::CssGritMetavariable(node) + } +} impl From for AnyCssValue { fn from(node: CssIdentifier) -> AnyCssValue { AnyCssValue::CssIdentifier(node) @@ -21488,6 +21640,7 @@ impl AstNode for AnyCssValue { .union(CssColor::KIND_SET) .union(CssCustomIdentifier::KIND_SET) .union(CssDashedIdentifier::KIND_SET) + .union(CssGritMetavariable::KIND_SET) .union(CssIdentifier::KIND_SET) .union(CssNumber::KIND_SET) .union(CssRatio::KIND_SET) @@ -21499,6 +21652,7 @@ impl AstNode for AnyCssValue { | CSS_COLOR | CSS_CUSTOM_IDENTIFIER | CSS_DASHED_IDENTIFIER + | CSS_GRIT_METAVARIABLE | CSS_IDENTIFIER | CSS_NUMBER | CSS_RATIO @@ -21519,6 +21673,9 @@ impl AstNode for AnyCssValue { CSS_DASHED_IDENTIFIER => { AnyCssValue::CssDashedIdentifier(CssDashedIdentifier { syntax }) } + CSS_GRIT_METAVARIABLE => { + AnyCssValue::CssGritMetavariable(CssGritMetavariable { syntax }) + } CSS_IDENTIFIER => AnyCssValue::CssIdentifier(CssIdentifier { syntax }), CSS_NUMBER => AnyCssValue::CssNumber(CssNumber { syntax }), CSS_RATIO => AnyCssValue::CssRatio(CssRatio { syntax }), @@ -21542,6 +21699,7 @@ impl AstNode for AnyCssValue { AnyCssValue::CssColor(it) => &it.syntax, AnyCssValue::CssCustomIdentifier(it) => &it.syntax, AnyCssValue::CssDashedIdentifier(it) => &it.syntax, + AnyCssValue::CssGritMetavariable(it) => &it.syntax, AnyCssValue::CssIdentifier(it) => &it.syntax, AnyCssValue::CssNumber(it) => &it.syntax, AnyCssValue::CssRatio(it) => &it.syntax, @@ -21557,6 +21715,7 @@ impl AstNode for AnyCssValue { AnyCssValue::CssColor(it) => it.syntax, AnyCssValue::CssCustomIdentifier(it) => it.syntax, AnyCssValue::CssDashedIdentifier(it) => it.syntax, + AnyCssValue::CssGritMetavariable(it) => it.syntax, AnyCssValue::CssIdentifier(it) => it.syntax, AnyCssValue::CssNumber(it) => it.syntax, AnyCssValue::CssRatio(it) => it.syntax, @@ -21576,6 +21735,7 @@ impl std::fmt::Debug for AnyCssValue { AnyCssValue::CssColor(it) => std::fmt::Debug::fmt(it, f), AnyCssValue::CssCustomIdentifier(it) => std::fmt::Debug::fmt(it, f), AnyCssValue::CssDashedIdentifier(it) => std::fmt::Debug::fmt(it, f), + AnyCssValue::CssGritMetavariable(it) => std::fmt::Debug::fmt(it, f), AnyCssValue::CssIdentifier(it) => std::fmt::Debug::fmt(it, f), AnyCssValue::CssNumber(it) => std::fmt::Debug::fmt(it, f), AnyCssValue::CssRatio(it) => std::fmt::Debug::fmt(it, f), @@ -21593,6 +21753,7 @@ impl From for SyntaxNode { AnyCssValue::CssColor(it) => it.into(), AnyCssValue::CssCustomIdentifier(it) => it.into(), AnyCssValue::CssDashedIdentifier(it) => it.into(), + AnyCssValue::CssGritMetavariable(it) => it.into(), AnyCssValue::CssIdentifier(it) => it.into(), AnyCssValue::CssNumber(it) => it.into(), AnyCssValue::CssRatio(it) => it.into(), @@ -22517,6 +22678,11 @@ impl std::fmt::Display for CssGenericProperty { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for CssGritMetavariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for CssIdSelector { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/biome_css_syntax/src/generated/nodes_mut.rs b/crates/biome_css_syntax/src/generated/nodes_mut.rs index 6637fad8e52c..557e7e6861d3 100644 --- a/crates/biome_css_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_css_syntax/src/generated/nodes_mut.rs @@ -841,6 +841,14 @@ impl CssGenericProperty { ) } } +impl CssGritMetavariable { + pub fn with_value_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } +} impl CssIdSelector { pub fn with_hash_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( diff --git a/crates/biome_service/src/file_handlers/css.rs b/crates/biome_service/src/file_handlers/css.rs index 2812513133a3..4a8561e52622 100644 --- a/crates/biome_service/src/file_handlers/css.rs +++ b/crates/biome_service/src/file_handlers/css.rs @@ -221,6 +221,7 @@ fn parse( css_modules: settings .and_then(|s| s.languages.css.parser.css_modules) .unwrap_or_default(), + grit_metavariable: false, }; if let Some(settings) = settings { options = settings diff --git a/xtask/codegen/css.ungram b/xtask/codegen/css.ungram index 5ee5e5c59f88..800f38570d4e 100644 --- a/xtask/codegen/css.ungram +++ b/xtask/codegen/css.ungram @@ -96,6 +96,7 @@ AnyCssSelector = CssComplexSelector | CssCompoundSelector | CssBogusSelector + | CssGritMetavariable // div a {} // ^^^^^ @@ -441,6 +442,7 @@ AnyCssDeclarationOrRule = AnyCssRule | CssDeclarationWithSemicolon | CssBogus + | CssGritMetavariable // @page :left { background: red; @media (500px <= width <= 500px) { } } // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1009,6 +1011,7 @@ AnyCssMediaQuery = CssMediaConditionQuery | AnyCssMediaTypeQuery | CssBogusMediaQuery + | CssGritMetavariable // @media screen, (width > 500px), print {} // ^^^^^^^^^^^^^^^ @@ -1624,6 +1627,7 @@ AnyCssValue = | CssColor | CssBracketedValue | CssUnicodeRange + | CssGritMetavariable // https://drafts.csswg.org/css-syntax/#typedef-dimension-token @@ -1795,3 +1799,6 @@ CssNumber = value: 'css_number_literal' CssUnicodeCodepoint = value: 'css_unicode_codepoint_literal' CssUnicodeRangeWildcard = value: 'css_unicode_range_wildcard_literal' CssUrlValueRaw = value: 'css_url_value_raw_literal' + +// https://github.com/getgrit/gritql/blob/main/resources/language-metavariables/tree-sitter-css/grammar.js +CssGritMetavariable = value: 'grit_metavariable' \ No newline at end of file diff --git a/xtask/codegen/src/css_kinds_src.rs b/xtask/codegen/src/css_kinds_src.rs index 46bb9613682c..8693d82de21f 100644 --- a/xtask/codegen/src/css_kinds_src.rs +++ b/xtask/codegen/src/css_kinds_src.rs @@ -271,6 +271,7 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "WHITESPACE", "COMMENT", "MULTILINE_COMMENT", + "GRIT_METAVARIABLE", ], nodes: &[ "CSS_ROOT", @@ -505,5 +506,7 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "CSS_BOGUS_CUSTOM_IDENTIFIER", "CSS_BOGUS_KEYFRAMES_NAME", "CSS_BOGUS_UNICODE_RANGE_VALUE", + // Grit metavariable + "CSS_GRIT_METAVARIABLE", ], };