diff --git a/Cargo.lock b/Cargo.lock index 5dc17c5a539f..31a1912caecf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,6 +276,7 @@ dependencies = [ name = "biome_css_formatter" version = "0.5.7" dependencies = [ + "biome_configuration", "biome_css_parser", "biome_css_syntax", "biome_diagnostics", diff --git a/crates/biome_css_factory/src/generated/node_factory.rs b/crates/biome_css_factory/src/generated/node_factory.rs index aa7f8ca05bc0..181c87ececd2 100644 --- a/crates/biome_css_factory/src/generated/node_factory.rs +++ b/crates/biome_css_factory/src/generated/node_factory.rs @@ -774,7 +774,7 @@ pub fn css_import_supports( } pub fn css_keyframes_at_rule( keyframes_token: SyntaxToken, - name: AnyCssKeyframeName, + name: AnyCssKeyframesName, block: AnyCssKeyframesBlock, ) -> CssKeyframesAtRule { CssKeyframesAtRule::unwrap_cast(SyntaxNode::new_detached( @@ -826,6 +826,46 @@ pub fn css_keyframes_percentage_selector( [Some(SyntaxElement::Node(selector.into_syntax()))], )) } +pub fn css_keyframes_scope_function( + scope_token: SyntaxToken, + l_paren_token: SyntaxToken, + name: AnyCssKeyframesIdentifier, + r_paren_token: SyntaxToken, +) -> CssKeyframesScopeFunction { + CssKeyframesScopeFunction::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_KEYFRAMES_SCOPE_FUNCTION, + [ + Some(SyntaxElement::Token(scope_token)), + Some(SyntaxElement::Token(l_paren_token)), + Some(SyntaxElement::Node(name.into_syntax())), + Some(SyntaxElement::Token(r_paren_token)), + ], + )) +} +pub fn css_keyframes_scope_prefix( + scope_token: SyntaxToken, + name: AnyCssKeyframesIdentifier, +) -> CssKeyframesScopePrefix { + CssKeyframesScopePrefix::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_KEYFRAMES_SCOPE_PREFIX, + [ + Some(SyntaxElement::Token(scope_token)), + Some(SyntaxElement::Node(name.into_syntax())), + ], + )) +} +pub fn css_keyframes_scoped_name( + colon_token: SyntaxToken, + scope: AnyCssKeyframesScope, +) -> CssKeyframesScopedName { + CssKeyframesScopedName::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_KEYFRAMES_SCOPED_NAME, + [ + Some(SyntaxElement::Token(colon_token)), + Some(SyntaxElement::Node(scope.into_syntax())), + ], + )) +} pub fn css_layer_at_rule(layer_token: SyntaxToken, layer: AnyCssLayer) -> CssLayerAtRule { CssLayerAtRule::unwrap_cast(SyntaxNode::new_detached( CssSyntaxKind::CSS_LAYER_AT_RULE, @@ -2640,6 +2680,16 @@ where slots, )) } +pub fn css_bogus_keyframes_name(slots: I) -> CssBogusKeyframesName +where + I: IntoIterator>, + I::IntoIter: ExactSizeIterator, +{ + CssBogusKeyframesName::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_BOGUS_KEYFRAMES_NAME, + slots, + )) +} pub fn css_bogus_layer(slots: I) -> CssBogusLayer where I: IntoIterator>, diff --git a/crates/biome_css_factory/src/generated/syntax_factory.rs b/crates/biome_css_factory/src/generated/syntax_factory.rs index bbea4d978952..6001dc5553ce 100644 --- a/crates/biome_css_factory/src/generated/syntax_factory.rs +++ b/crates/biome_css_factory/src/generated/syntax_factory.rs @@ -23,6 +23,7 @@ impl SyntaxFactory for CssSyntaxFactory { | CSS_BOGUS_FONT_FAMILY_NAME | CSS_BOGUS_FONT_FEATURE_VALUES_ITEM | CSS_BOGUS_KEYFRAMES_ITEM + | CSS_BOGUS_KEYFRAMES_NAME | CSS_BOGUS_LAYER | CSS_BOGUS_MEDIA_QUERY | CSS_BOGUS_PAGE_SELECTOR_PSEUDO @@ -1581,7 +1582,7 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.next_slot(); if let Some(element) = ¤t_element { - if AnyCssKeyframeName::can_cast(element.kind()) { + if AnyCssKeyframesName::can_cast(element.kind()) { slots.mark_present(); current_element = elements.next(); } @@ -1699,6 +1700,98 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(CSS_KEYFRAMES_PERCENTAGE_SELECTOR, children) } + CSS_KEYFRAMES_SCOPE_FUNCTION => { + 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 { + if matches!(element.kind(), T![global] | T![local]) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T!['('] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if AnyCssKeyframesIdentifier::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T![')'] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_KEYFRAMES_SCOPE_FUNCTION.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_KEYFRAMES_SCOPE_FUNCTION, children) + } + CSS_KEYFRAMES_SCOPE_PREFIX => { + 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 { + if matches!(element.kind(), T![global] | T![local]) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if AnyCssKeyframesIdentifier::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_KEYFRAMES_SCOPE_PREFIX.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_KEYFRAMES_SCOPE_PREFIX, children) + } + CSS_KEYFRAMES_SCOPED_NAME => { + 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 { + if element.kind() == T ! [:] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if AnyCssKeyframesScope::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_KEYFRAMES_SCOPED_NAME.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_KEYFRAMES_SCOPED_NAME, children) + } CSS_LAYER_AT_RULE => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); diff --git a/crates/biome_css_formatter/Cargo.toml b/crates/biome_css_formatter/Cargo.toml index af52608dcbee..c05a544a7017 100644 --- a/crates/biome_css_formatter/Cargo.toml +++ b/crates/biome_css_formatter/Cargo.toml @@ -20,6 +20,7 @@ biome_rowan = { workspace = true } biome_suppression = { workspace = true } [dev-dependencies] +biome_configuration = { path = "../biome_configuration" } biome_css_parser = { path = "../biome_css_parser" } biome_formatter_test = { path = "../biome_formatter_test" } biome_parser = { path = "../biome_parser" } diff --git a/crates/biome_css_formatter/src/css/any/keyframe_name.rs b/crates/biome_css_formatter/src/css/any/keyframe_name.rs deleted file mode 100644 index 1733dc2290df..000000000000 --- a/crates/biome_css_formatter/src/css/any/keyframe_name.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! 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::AnyCssKeyframeName; -#[derive(Debug, Clone, Default)] -pub(crate) struct FormatAnyCssKeyframeName; -impl FormatRule for FormatAnyCssKeyframeName { - type Context = CssFormatContext; - fn fmt(&self, node: &AnyCssKeyframeName, f: &mut CssFormatter) -> FormatResult<()> { - match node { - AnyCssKeyframeName::CssCustomIdentifier(node) => node.format().fmt(f), - AnyCssKeyframeName::CssString(node) => node.format().fmt(f), - } - } -} diff --git a/crates/biome_css_formatter/src/css/any/keyframes_identifier.rs b/crates/biome_css_formatter/src/css/any/keyframes_identifier.rs new file mode 100644 index 000000000000..7bd68b2da965 --- /dev/null +++ b/crates/biome_css_formatter/src/css/any/keyframes_identifier.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::AnyCssKeyframesIdentifier; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyCssKeyframesIdentifier; +impl FormatRule for FormatAnyCssKeyframesIdentifier { + type Context = CssFormatContext; + fn fmt(&self, node: &AnyCssKeyframesIdentifier, f: &mut CssFormatter) -> FormatResult<()> { + match node { + AnyCssKeyframesIdentifier::CssCustomIdentifier(node) => node.format().fmt(f), + AnyCssKeyframesIdentifier::CssString(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_css_formatter/src/css/any/keyframes_name.rs b/crates/biome_css_formatter/src/css/any/keyframes_name.rs new file mode 100644 index 000000000000..e74bee2afc46 --- /dev/null +++ b/crates/biome_css_formatter/src/css/any/keyframes_name.rs @@ -0,0 +1,16 @@ +//! 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::AnyCssKeyframesName; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyCssKeyframesName; +impl FormatRule for FormatAnyCssKeyframesName { + type Context = CssFormatContext; + fn fmt(&self, node: &AnyCssKeyframesName, f: &mut CssFormatter) -> FormatResult<()> { + match node { + AnyCssKeyframesName::AnyCssKeyframesIdentifier(node) => node.format().fmt(f), + AnyCssKeyframesName::CssBogusKeyframesName(node) => node.format().fmt(f), + AnyCssKeyframesName::CssKeyframesScopedName(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_css_formatter/src/css/any/keyframes_scope.rs b/crates/biome_css_formatter/src/css/any/keyframes_scope.rs new file mode 100644 index 000000000000..ed4d187baac5 --- /dev/null +++ b/crates/biome_css_formatter/src/css/any/keyframes_scope.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::AnyCssKeyframesScope; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyCssKeyframesScope; +impl FormatRule for FormatAnyCssKeyframesScope { + type Context = CssFormatContext; + fn fmt(&self, node: &AnyCssKeyframesScope, f: &mut CssFormatter) -> FormatResult<()> { + match node { + AnyCssKeyframesScope::CssKeyframesScopeFunction(node) => node.format().fmt(f), + AnyCssKeyframesScope::CssKeyframesScopePrefix(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_css_formatter/src/css/any/mod.rs b/crates/biome_css_formatter/src/css/any/mod.rs index 24d4db6fb108..d217ddc9bc90 100644 --- a/crates/biome_css_formatter/src/css/any/mod.rs +++ b/crates/biome_css_formatter/src/css/any/mod.rs @@ -29,9 +29,11 @@ pub(crate) mod generic_component_value; pub(crate) mod import_layer; pub(crate) mod import_supports_condition; pub(crate) mod import_url; -pub(crate) mod keyframe_name; pub(crate) mod keyframes_block; +pub(crate) mod keyframes_identifier; pub(crate) mod keyframes_item; +pub(crate) mod keyframes_name; +pub(crate) mod keyframes_scope; pub(crate) mod keyframes_selector; pub(crate) mod layer; pub(crate) mod media_and_combinable_condition; diff --git a/crates/biome_css_formatter/src/css/auxiliary/keyframes_scope_function.rs b/crates/biome_css_formatter/src/css/auxiliary/keyframes_scope_function.rs new file mode 100644 index 000000000000..a821fb9f3d89 --- /dev/null +++ b/crates/biome_css_formatter/src/css/auxiliary/keyframes_scope_function.rs @@ -0,0 +1,30 @@ +use crate::prelude::*; +use biome_css_syntax::{CssKeyframesScopeFunction, CssKeyframesScopeFunctionFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssKeyframesScopeFunction; +impl FormatNodeRule for FormatCssKeyframesScopeFunction { + fn fmt_fields( + &self, + node: &CssKeyframesScopeFunction, + f: &mut CssFormatter, + ) -> FormatResult<()> { + let CssKeyframesScopeFunctionFields { + scope, + l_paren_token, + name, + r_paren_token, + } = node.as_fields(); + + write!( + f, + [ + scope.format(), + l_paren_token.format(), + name.format(), + r_paren_token.format(), + ] + ) + } +} diff --git a/crates/biome_css_formatter/src/css/auxiliary/keyframes_scope_prefix.rs b/crates/biome_css_formatter/src/css/auxiliary/keyframes_scope_prefix.rs new file mode 100644 index 000000000000..1e8e8ec6d355 --- /dev/null +++ b/crates/biome_css_formatter/src/css/auxiliary/keyframes_scope_prefix.rs @@ -0,0 +1,13 @@ +use crate::prelude::*; +use biome_css_syntax::{CssKeyframesScopePrefix, CssKeyframesScopePrefixFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssKeyframesScopePrefix; +impl FormatNodeRule for FormatCssKeyframesScopePrefix { + fn fmt_fields(&self, node: &CssKeyframesScopePrefix, f: &mut CssFormatter) -> FormatResult<()> { + let CssKeyframesScopePrefixFields { scope, name } = node.as_fields(); + + write!(f, [scope.format(), space(), name.format(),]) + } +} diff --git a/crates/biome_css_formatter/src/css/auxiliary/keyframes_scoped_name.rs b/crates/biome_css_formatter/src/css/auxiliary/keyframes_scoped_name.rs new file mode 100644 index 000000000000..ae660299b102 --- /dev/null +++ b/crates/biome_css_formatter/src/css/auxiliary/keyframes_scoped_name.rs @@ -0,0 +1,13 @@ +use crate::prelude::*; +use biome_css_syntax::{CssKeyframesScopedName, CssKeyframesScopedNameFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssKeyframesScopedName; +impl FormatNodeRule for FormatCssKeyframesScopedName { + fn fmt_fields(&self, node: &CssKeyframesScopedName, f: &mut CssFormatter) -> FormatResult<()> { + let CssKeyframesScopedNameFields { colon_token, scope } = node.as_fields(); + + write!(f, [colon_token.format(), scope.format(),]) + } +} diff --git a/crates/biome_css_formatter/src/css/auxiliary/mod.rs b/crates/biome_css_formatter/src/css/auxiliary/mod.rs index 36f80a09f892..6ea364809f25 100644 --- a/crates/biome_css_formatter/src/css/auxiliary/mod.rs +++ b/crates/biome_css_formatter/src/css/auxiliary/mod.rs @@ -31,6 +31,9 @@ pub(crate) mod import_named_layer; pub(crate) mod import_supports; pub(crate) mod keyframes_block; pub(crate) mod keyframes_item; +pub(crate) mod keyframes_scope_function; +pub(crate) mod keyframes_scope_prefix; +pub(crate) mod keyframes_scoped_name; pub(crate) mod layer_declaration; pub(crate) mod layer_reference; pub(crate) mod list_of_component_values_expression; diff --git a/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_import_clause.rs b/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_import_clause.rs index abcf0ba3f724..fdb2d16ed2bb 100644 --- a/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_import_clause.rs +++ b/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_import_clause.rs @@ -1,6 +1,7 @@ use crate::prelude::*; -use biome_css_syntax::CssValueAtRuleImportClause; -use biome_rowan::AstNode; +use biome_css_syntax::{CssValueAtRuleImportClause, CssValueAtRuleImportClauseFields}; +use biome_formatter::write; + #[derive(Debug, Clone, Default)] pub(crate) struct FormatCssValueAtRuleImportClause; impl FormatNodeRule for FormatCssValueAtRuleImportClause { @@ -9,6 +10,21 @@ impl FormatNodeRule for FormatCssValueAtRuleImportCl node: &CssValueAtRuleImportClause, f: &mut CssFormatter, ) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let CssValueAtRuleImportClauseFields { + specifiers, + from_token, + source, + } = node.as_fields(); + + write!( + f, + [ + specifiers.format(), + space(), + from_token.format(), + space(), + source.format() + ] + ) } } diff --git a/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_import_specifier.rs b/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_import_specifier.rs index 0c425afba1a3..f375d51e31b7 100644 --- a/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_import_specifier.rs +++ b/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_import_specifier.rs @@ -1,6 +1,7 @@ use crate::prelude::*; -use biome_css_syntax::CssValueAtRuleImportSpecifier; -use biome_rowan::AstNode; +use biome_css_syntax::{CssValueAtRuleImportSpecifier, CssValueAtRuleImportSpecifierFields}; +use biome_formatter::write; + #[derive(Debug, Clone, Default)] pub(crate) struct FormatCssValueAtRuleImportSpecifier; impl FormatNodeRule for FormatCssValueAtRuleImportSpecifier { @@ -9,6 +10,8 @@ impl FormatNodeRule for FormatCssValueAtRuleImpor node: &CssValueAtRuleImportSpecifier, f: &mut CssFormatter, ) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let CssValueAtRuleImportSpecifierFields { name } = node.as_fields(); + + write!(f, [name.format()]) } } diff --git a/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_named_import_specifier.rs b/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_named_import_specifier.rs index e03805c9f016..6cf783f945ae 100644 --- a/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_named_import_specifier.rs +++ b/crates/biome_css_formatter/src/css/auxiliary/value_at_rule_named_import_specifier.rs @@ -1,6 +1,9 @@ use crate::prelude::*; -use biome_css_syntax::CssValueAtRuleNamedImportSpecifier; -use biome_rowan::AstNode; +use biome_css_syntax::{ + CssValueAtRuleNamedImportSpecifier, CssValueAtRuleNamedImportSpecifierFields, +}; +use biome_formatter::write; + #[derive(Debug, Clone, Default)] pub(crate) struct FormatCssValueAtRuleNamedImportSpecifier; impl FormatNodeRule @@ -11,6 +14,21 @@ impl FormatNodeRule node: &CssValueAtRuleNamedImportSpecifier, f: &mut CssFormatter, ) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let CssValueAtRuleNamedImportSpecifierFields { + name, + as_token, + local_name, + } = node.as_fields(); + + write!( + f, + [ + name.format(), + space(), + as_token.format(), + space(), + local_name.format() + ] + ) } } diff --git a/crates/biome_css_formatter/src/css/bogus/bogus_keyframes_name.rs b/crates/biome_css_formatter/src/css/bogus/bogus_keyframes_name.rs new file mode 100644 index 000000000000..219203d567d0 --- /dev/null +++ b/crates/biome_css_formatter/src/css/bogus/bogus_keyframes_name.rs @@ -0,0 +1,5 @@ +use crate::FormatBogusNodeRule; +use biome_css_syntax::CssBogusKeyframesName; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssBogusKeyframesName; +impl FormatBogusNodeRule for FormatCssBogusKeyframesName {} diff --git a/crates/biome_css_formatter/src/css/bogus/mod.rs b/crates/biome_css_formatter/src/css/bogus/mod.rs index a4c981b9d3e1..f5c2cec53f65 100644 --- a/crates/biome_css_formatter/src/css/bogus/mod.rs +++ b/crates/biome_css_formatter/src/css/bogus/mod.rs @@ -10,6 +10,7 @@ pub(crate) mod bogus_document_matcher; pub(crate) mod bogus_font_family_name; pub(crate) mod bogus_font_feature_values_item; pub(crate) mod bogus_keyframes_item; +pub(crate) mod bogus_keyframes_name; pub(crate) mod bogus_layer; pub(crate) mod bogus_media_query; pub(crate) mod bogus_page_selector_pseudo; diff --git a/crates/biome_css_formatter/src/css/lists/value_at_rule_import_specifier_list.rs b/crates/biome_css_formatter/src/css/lists/value_at_rule_import_specifier_list.rs index 0d3b0b27f6f7..708159cac3a6 100644 --- a/crates/biome_css_formatter/src/css/lists/value_at_rule_import_specifier_list.rs +++ b/crates/biome_css_formatter/src/css/lists/value_at_rule_import_specifier_list.rs @@ -1,5 +1,6 @@ use crate::prelude::*; use biome_css_syntax::CssValueAtRuleImportSpecifierList; + #[derive(Debug, Clone, Default)] pub(crate) struct FormatCssValueAtRuleImportSpecifierList; impl FormatRule for FormatCssValueAtRuleImportSpecifierList { @@ -9,6 +10,13 @@ impl FormatRule for FormatCssValueAtRuleImpor node: &CssValueAtRuleImportSpecifierList, f: &mut CssFormatter, ) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let separator = space(); + let mut joiner = f.join_with(&separator); + + for formatted in node.format_separated(",") { + joiner.entry(&formatted); + } + + joiner.finish() } } diff --git a/crates/biome_css_formatter/src/css/statements/value_at_rule.rs b/crates/biome_css_formatter/src/css/statements/value_at_rule.rs index e0a698b0c64c..75162d5efd66 100644 --- a/crates/biome_css_formatter/src/css/statements/value_at_rule.rs +++ b/crates/biome_css_formatter/src/css/statements/value_at_rule.rs @@ -1,10 +1,25 @@ use crate::prelude::*; -use biome_css_syntax::CssValueAtRule; -use biome_rowan::AstNode; +use biome_css_syntax::{CssValueAtRule, CssValueAtRuleFields}; +use biome_formatter::write; + #[derive(Debug, Clone, Default)] pub(crate) struct FormatCssValueAtRule; impl FormatNodeRule for FormatCssValueAtRule { fn fmt_fields(&self, node: &CssValueAtRule, f: &mut CssFormatter) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let CssValueAtRuleFields { + value_token, + clause, + semicolon_token, + } = node.as_fields(); + + write!( + f, + [ + value_token.format(), + space(), + clause.format(), + semicolon_token.format(), + ] + ) } } diff --git a/crates/biome_css_formatter/src/generated.rs b/crates/biome_css_formatter/src/generated.rs index 9d5c79bb74d2..2d80f21b1a20 100644 --- a/crates/biome_css_formatter/src/generated.rs +++ b/crates/biome_css_formatter/src/generated.rs @@ -2019,6 +2019,120 @@ impl IntoFormat for biome_css_syntax::CssKeyframesPercentageSe FormatOwnedWithRule :: new (self , crate :: css :: selectors :: keyframes_percentage_selector :: FormatCssKeyframesPercentageSelector :: default ()) } } +impl FormatRule + for crate::css::auxiliary::keyframes_scope_function::FormatCssKeyframesScopeFunction +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssKeyframesScopeFunction, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssKeyframesScopeFunction { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssKeyframesScopeFunction, + crate::css::auxiliary::keyframes_scope_function::FormatCssKeyframesScopeFunction, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule :: new (self , crate :: css :: auxiliary :: keyframes_scope_function :: FormatCssKeyframesScopeFunction :: default ()) + } +} +impl IntoFormat for biome_css_syntax::CssKeyframesScopeFunction { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssKeyframesScopeFunction, + crate::css::auxiliary::keyframes_scope_function::FormatCssKeyframesScopeFunction, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule :: new (self , crate :: css :: auxiliary :: keyframes_scope_function :: FormatCssKeyframesScopeFunction :: default ()) + } +} +impl FormatRule + for crate::css::auxiliary::keyframes_scope_prefix::FormatCssKeyframesScopePrefix +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssKeyframesScopePrefix, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssKeyframesScopePrefix { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssKeyframesScopePrefix, + crate::css::auxiliary::keyframes_scope_prefix::FormatCssKeyframesScopePrefix, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::auxiliary::keyframes_scope_prefix::FormatCssKeyframesScopePrefix::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::CssKeyframesScopePrefix { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssKeyframesScopePrefix, + crate::css::auxiliary::keyframes_scope_prefix::FormatCssKeyframesScopePrefix, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::auxiliary::keyframes_scope_prefix::FormatCssKeyframesScopePrefix::default(), + ) + } +} +impl FormatRule + for crate::css::auxiliary::keyframes_scoped_name::FormatCssKeyframesScopedName +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssKeyframesScopedName, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssKeyframesScopedName { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssKeyframesScopedName, + crate::css::auxiliary::keyframes_scoped_name::FormatCssKeyframesScopedName, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::auxiliary::keyframes_scoped_name::FormatCssKeyframesScopedName::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::CssKeyframesScopedName { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssKeyframesScopedName, + crate::css::auxiliary::keyframes_scoped_name::FormatCssKeyframesScopedName, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::auxiliary::keyframes_scoped_name::FormatCssKeyframesScopedName::default(), + ) + } +} impl FormatRule for crate::css::statements::layer_at_rule::FormatCssLayerAtRule { @@ -6071,6 +6185,46 @@ impl IntoFormat for biome_css_syntax::CssBogusKeyframesItem { ) } } +impl FormatRule + for crate::css::bogus::bogus_keyframes_name::FormatCssBogusKeyframesName +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssBogusKeyframesName, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatBogusNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssBogusKeyframesName { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssBogusKeyframesName, + crate::css::bogus::bogus_keyframes_name::FormatCssBogusKeyframesName, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::bogus::bogus_keyframes_name::FormatCssBogusKeyframesName::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::CssBogusKeyframesName { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssBogusKeyframesName, + crate::css::bogus::bogus_keyframes_name::FormatCssBogusKeyframesName, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::bogus::bogus_keyframes_name::FormatCssBogusKeyframesName::default(), + ) + } +} impl FormatRule for crate::css::bogus::bogus_layer::FormatCssBogusLayer { @@ -7356,57 +7510,57 @@ impl IntoFormat for biome_css_syntax::AnyCssImportUrl { ) } } -impl AsFormat for biome_css_syntax::AnyCssKeyframeName { +impl AsFormat for biome_css_syntax::AnyCssKeyframesBlock { type Format<'a> = FormatRefWithRule< 'a, - biome_css_syntax::AnyCssKeyframeName, - crate::css::any::keyframe_name::FormatAnyCssKeyframeName, + biome_css_syntax::AnyCssKeyframesBlock, + crate::css::any::keyframes_block::FormatAnyCssKeyframesBlock, >; fn format(&self) -> Self::Format<'_> { #![allow(clippy::default_constructed_unit_structs)] FormatRefWithRule::new( self, - crate::css::any::keyframe_name::FormatAnyCssKeyframeName::default(), + crate::css::any::keyframes_block::FormatAnyCssKeyframesBlock::default(), ) } } -impl IntoFormat for biome_css_syntax::AnyCssKeyframeName { +impl IntoFormat for biome_css_syntax::AnyCssKeyframesBlock { type Format = FormatOwnedWithRule< - biome_css_syntax::AnyCssKeyframeName, - crate::css::any::keyframe_name::FormatAnyCssKeyframeName, + biome_css_syntax::AnyCssKeyframesBlock, + crate::css::any::keyframes_block::FormatAnyCssKeyframesBlock, >; fn into_format(self) -> Self::Format { #![allow(clippy::default_constructed_unit_structs)] FormatOwnedWithRule::new( self, - crate::css::any::keyframe_name::FormatAnyCssKeyframeName::default(), + crate::css::any::keyframes_block::FormatAnyCssKeyframesBlock::default(), ) } } -impl AsFormat for biome_css_syntax::AnyCssKeyframesBlock { +impl AsFormat for biome_css_syntax::AnyCssKeyframesIdentifier { type Format<'a> = FormatRefWithRule< 'a, - biome_css_syntax::AnyCssKeyframesBlock, - crate::css::any::keyframes_block::FormatAnyCssKeyframesBlock, + biome_css_syntax::AnyCssKeyframesIdentifier, + crate::css::any::keyframes_identifier::FormatAnyCssKeyframesIdentifier, >; fn format(&self) -> Self::Format<'_> { #![allow(clippy::default_constructed_unit_structs)] FormatRefWithRule::new( self, - crate::css::any::keyframes_block::FormatAnyCssKeyframesBlock::default(), + crate::css::any::keyframes_identifier::FormatAnyCssKeyframesIdentifier::default(), ) } } -impl IntoFormat for biome_css_syntax::AnyCssKeyframesBlock { +impl IntoFormat for biome_css_syntax::AnyCssKeyframesIdentifier { type Format = FormatOwnedWithRule< - biome_css_syntax::AnyCssKeyframesBlock, - crate::css::any::keyframes_block::FormatAnyCssKeyframesBlock, + biome_css_syntax::AnyCssKeyframesIdentifier, + crate::css::any::keyframes_identifier::FormatAnyCssKeyframesIdentifier, >; fn into_format(self) -> Self::Format { #![allow(clippy::default_constructed_unit_structs)] FormatOwnedWithRule::new( self, - crate::css::any::keyframes_block::FormatAnyCssKeyframesBlock::default(), + crate::css::any::keyframes_identifier::FormatAnyCssKeyframesIdentifier::default(), ) } } @@ -7437,6 +7591,60 @@ impl IntoFormat for biome_css_syntax::AnyCssKeyframesItem { ) } } +impl AsFormat for biome_css_syntax::AnyCssKeyframesName { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::AnyCssKeyframesName, + crate::css::any::keyframes_name::FormatAnyCssKeyframesName, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::any::keyframes_name::FormatAnyCssKeyframesName::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::AnyCssKeyframesName { + type Format = FormatOwnedWithRule< + biome_css_syntax::AnyCssKeyframesName, + crate::css::any::keyframes_name::FormatAnyCssKeyframesName, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::any::keyframes_name::FormatAnyCssKeyframesName::default(), + ) + } +} +impl AsFormat for biome_css_syntax::AnyCssKeyframesScope { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::AnyCssKeyframesScope, + crate::css::any::keyframes_scope::FormatAnyCssKeyframesScope, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::any::keyframes_scope::FormatAnyCssKeyframesScope::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::AnyCssKeyframesScope { + type Format = FormatOwnedWithRule< + biome_css_syntax::AnyCssKeyframesScope, + crate::css::any::keyframes_scope::FormatAnyCssKeyframesScope, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::any::keyframes_scope::FormatAnyCssKeyframesScope::default(), + ) + } +} impl AsFormat for biome_css_syntax::AnyCssKeyframesSelector { type Format<'a> = FormatRefWithRule< 'a, diff --git a/crates/biome_css_formatter/tests/spec_test.rs b/crates/biome_css_formatter/tests/spec_test.rs index 8138a48a1982..b5da1aea24f9 100644 --- a/crates/biome_css_formatter/tests/spec_test.rs +++ b/crates/biome_css_formatter/tests/spec_test.rs @@ -1,5 +1,7 @@ +use biome_configuration::{PartialConfiguration, PartialCssConfiguration, PartialCssFormatter}; use biome_css_formatter::context::CssFormatOptions; use biome_formatter_test::spec::{SpecSnapshot, SpecTestFile}; +use biome_service::workspace::UpdateSettingsParams; use std::path::Path; mod language { @@ -25,8 +27,24 @@ mod language { /// * `null` -> input: `tests/specs/null.css`, expected output: `tests/specs/null.css.snap` pub fn run(spec_input_file: &str, _expected_file: &str, test_directory: &str, _file_type: &str) { let root_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/specs/")); + let settings = UpdateSettingsParams { + configuration: PartialConfiguration { + css: Some(PartialCssConfiguration { + formatter: Some(PartialCssFormatter { + enabled: Some(true), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + }, + vcs_base_path: None, + gitignore_matches: vec![], + workspace_directory: None, + }; - let Some(test_file) = SpecTestFile::try_from_file(spec_input_file, root_path) else { + let Some(test_file) = SpecTestFile::try_from_file(spec_input_file, root_path, Some(settings)) + else { return; }; diff --git a/crates/biome_css_formatter/tests/specs/css/atrule/keyframes.css b/crates/biome_css_formatter/tests/specs/css/atrule/keyframes.css index 8e24b9f936c4..8def0d6bf921 100644 --- a/crates/biome_css_formatter/tests/specs/css/atrule/keyframes.css +++ b/crates/biome_css_formatter/tests/specs/css/atrule/keyframes.css @@ -95,87 +95,87 @@ } } @keyframes - + identifier - + { - + 0% - + { - + top - + : - + 0 - + ; - + left - + : - + 0 - + ; - + } - + 30% - + { - + top - + : - + 50px - + ; - + } - + 68% - + , - + 72% - + { - + left - + : - + 50px - + ; - + } - + 100% - + { - + top - + : - + 100px - + ; - + left - + : - + 100% - + ; - + } - + } @keyframes identifier { from { @@ -198,29 +198,51 @@ } } @keyframes - + identifier - + { - + from - + { - + margin-top: 50px; - + } - + to - + { - + margin-top: 100px; - + } - + } @-webkit-keyframes identifier { 0% { opacity: 0; top: 4rem; } 100% { opacity: 1; top: 0; } - } \ No newline at end of file + } + + +@keyframes :local( +test) {} +@keyframes :local( +"test" +) {} +@keyframes +:local +test {} +@keyframes :local +"test" {} +@keyframes +:global(test) {} +@keyframes :global +("test" +) {} +@keyframes :global +test {} +@keyframes +:global "test" +{} diff --git a/crates/biome_css_formatter/tests/specs/css/atrule/keyframes.css.snap b/crates/biome_css_formatter/tests/specs/css/atrule/keyframes.css.snap index dfbac3bef909..7c0235a43c03 100644 --- a/crates/biome_css_formatter/tests/specs/css/atrule/keyframes.css.snap +++ b/crates/biome_css_formatter/tests/specs/css/atrule/keyframes.css.snap @@ -2,7 +2,6 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: css/atrule/keyframes.css --- - # Input ```css @@ -103,87 +102,87 @@ info: css/atrule/keyframes.css } } @keyframes - + identifier - + { - + 0% - + { - + top - + : - + 0 - + ; - + left - + : - + 0 - + ; - + } - + 30% - + { - + top - + : - + 50px - + ; - + } - + 68% - + , - + 72% - + { - + left - + : - + 50px - + ; - + } - + 100% - + { - + top - + : - + 100px - + ; - + left - + : - + 100% - + ; - + } - + } @keyframes identifier { from { @@ -206,32 +205,55 @@ info: css/atrule/keyframes.css } } @keyframes - + identifier - + { - + from - + { - + margin-top: 50px; - + } - + to - + { - + margin-top: 100px; - + } - + } @-webkit-keyframes identifier { 0% { opacity: 0; top: 4rem; } 100% { opacity: 1; top: 0; } } + + +@keyframes :local( +test) {} +@keyframes :local( +"test" +) {} +@keyframes +:local +test {} +@keyframes :local +"test" {} +@keyframes +:global(test) {} +@keyframes :global +("test" +) {} +@keyframes :global +test {} +@keyframes +:global "test" +{} + ``` @@ -392,6 +414,21 @@ Quote style: Double Quotes top: 0; } } -``` - +@keyframes :local(test) { +} +@keyframes :local("test") { +} +@keyframes :local test { +} +@keyframes :local "test" { +} +@keyframes :global(test) { +} +@keyframes :global("test") { +} +@keyframes :global test { +} +@keyframes :global "test" { +} +``` diff --git a/crates/biome_css_formatter/tests/specs/css/atrule/value.css b/crates/biome_css_formatter/tests/specs/css/atrule/value.css new file mode 100644 index 000000000000..77d5cdc079cc --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/atrule/value.css @@ -0,0 +1,40 @@ +@value +colors: +"./colors.css"; +@value +primary, +secondary from +colors +; +@value +small +as +bp-small, +medium, +large as +bp-large from +"./breakpoints.css"; +@value +selectorValue: +secondary-color; +@value +small: +(max-width: +599px); +@value +medium: +(min-width: 600px) and (max-width: 959px); +@value +large: +(min-width: 960px); +@value +primary: +#BF4040; +@value +secondary: +#1F4F7F; +@value +common-gradient: +transparent 75%, +var(--ring-line-color) 75%, +currentColor 79%; diff --git a/crates/biome_css_formatter/tests/specs/css/atrule/value.css.snap b/crates/biome_css_formatter/tests/specs/css/atrule/value.css.snap new file mode 100644 index 000000000000..2e9a7ae3479e --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/atrule/value.css.snap @@ -0,0 +1,101 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: css/atrule/value.css +--- +# Input + +```css +@value +colors: +"./colors.css"; +@value +primary, +secondary from +colors +; +@value +small +as +bp-small, +medium, +large as +bp-large from +"./breakpoints.css"; +@value +selectorValue: +secondary-color; +@value +small: +(max-width: +599px); +@value +medium: +(min-width: 600px) and (max-width: 959px); +@value +large: +(min-width: 960px); +@value +primary: +#BF4040; +@value +secondary: +#1F4F7F; +@value +common-gradient: +transparent 75%, +var(--ring-line-color) 75%, +currentColor 79%; + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Quote style: Double Quotes +----- + +```css +@value colors: +"./colors.css"; +@value primary, secondary from colors; +@value small as bp-small, medium, large as bp-large from "./breakpoints.css"; +@value selectorValue: +secondary-color; +@value small: +(max-width: +599px); +@value medium: +(min-width: 600px) and (max-width: 959px); +@value large: +(min-width: 960px); +@value primary: +#BF4040; +@value secondary: +#1F4F7F; +@value common-gradient: +transparent 75%, +var(--ring-line-color) 75%, +currentColor 79%; +``` + + + +## Unimplemented nodes/tokens + +" colors:\n\"./colors.css\"" => 6..29 +" selectorValue:\nsecondary-color" => 154..185 +" small:\n(max-width:\n599px)" => 193..219 +" medium:\n(min-width: 600px) and (max-width: 959px)" => 227..277 +" large:\n(min-width: 960px)" => 285..311 +" primary:\n#BF4040" => 319..336 +" secondary:\n#1F4F7F" => 344..363 +" common-gradient:\ntransparent 75%,\nvar(--ring-line-color) 75%,\ncurrentColor 79%" => 371..450 diff --git a/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css.snap b/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css.snap index 142cf2c7aae9..2aac6246a275 100644 --- a/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css.snap +++ b/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css.snap @@ -71,7 +71,7 @@ div { ----- Indent style: Tab -Indent width: 2 +Indent width: 0 Line ending: LF Line width: 80 Quote style: Single Quotes diff --git a/crates/biome_css_formatter/tests/specs/prettier/css/modules/modules.css.snap b/crates/biome_css_formatter/tests/specs/prettier/css/modules/modules.css.snap index 25d089092d29..78c83979aead 100644 --- a/crates/biome_css_formatter/tests/specs/prettier/css/modules/modules.css.snap +++ b/crates/biome_css_formatter/tests/specs/prettier/css/modules/modules.css.snap @@ -263,25 +263,5 @@ modules.css:41:8 parse ━━━━━━━━━━━━━━━━━━━ i Remove ( -modules.css:48:12 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Expected an identifier but instead found ':global(spin)'. - - 46 │ } - 47 │ - > 48 │ @keyframes :global(spin) { - │ ^^^^^^^^^^^^^ - 49 │ from { - 50 │ transform: rotate(0deg); - - i Expected an identifier here. - - 46 │ } - 47 │ - > 48 │ @keyframes :global(spin) { - │ ^^^^^^^^^^^^^ - 49 │ from { - 50 │ transform: rotate(0deg); - ``` diff --git a/crates/biome_css_parser/Cargo.toml b/crates/biome_css_parser/Cargo.toml index bdc68dbd8b60..f89ebcbc2fc6 100644 --- a/crates/biome_css_parser/Cargo.toml +++ b/crates/biome_css_parser/Cargo.toml @@ -21,10 +21,10 @@ biome_unicode_table = { workspace = true } tracing = { workspace = true } [dev-dependencies] -biome_configuration = { workspace = true } -biome_deserialize = { workspace = true } -biome_fs = { workspace = true } -biome_service = { workspace = true } +biome_configuration = { path = "../biome_configuration" } +biome_deserialize = { path = "../biome_deserialize" } +biome_fs = { path = "../biome_fs" } +biome_service = { path = "../biome_service" } biome_test_utils = { path = "../biome_test_utils" } insta = { workspace = true } quickcheck = { workspace = true } diff --git a/crates/biome_css_parser/src/syntax/at_rule/keyframes.rs b/crates/biome_css_parser/src/syntax/at_rule/keyframes.rs index e7d66b960ff1..06eb6775fa68 100644 --- a/crates/biome_css_parser/src/syntax/at_rule/keyframes.rs +++ b/crates/biome_css_parser/src/syntax/at_rule/keyframes.rs @@ -4,22 +4,64 @@ use crate::syntax::at_rule::parse_error::{ expected_keyframes_item, expected_keyframes_item_selector, }; use crate::syntax::block::{parse_declaration_block, ParseBlockBody}; +use crate::syntax::css_modules::{ + expected_any_css_module_scope, local_or_global_not_allowed, CSS_MODULES_SCOPE_SET, +}; use crate::syntax::parse_error::expected_non_css_wide_keyword_identifier; use crate::syntax::value::dimension::{is_at_percentage_dimension, parse_percentage_dimension}; -use crate::syntax::{is_at_declaration, is_at_identifier, parse_custom_identifier, parse_string}; +use crate::syntax::{ + is_at_declaration, is_at_identifier, is_at_string, parse_custom_identifier, parse_string, +}; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; use biome_parser::parse_lists::{ParseNodeList, ParseSeparatedList}; -use biome_parser::parse_recovery::{ParseRecovery, ParseRecoveryTokenSet, RecoveryResult}; +use biome_parser::parse_recovery::{ParseRecovery, RecoveryResult}; use biome_parser::parsed_syntax::ParsedSyntax::Present; use biome_parser::prelude::ParsedSyntax::Absent; use biome_parser::prelude::*; +/// Checks if the current parser position is at a `@keyframes` at-rule. +/// +/// This function determines if the parser is currently positioned at the start of a `@keyframes` +/// rule, which is part of the CSS syntax. #[inline] pub(crate) fn is_at_keyframes_at_rule(p: &mut CssParser) -> bool { p.at(T![keyframes]) } - +/// Parses a `@keyframes` at-rule in CSS. +/// +/// This function parses a `@keyframes` rule, which can be scoped locally or globally using the +/// `:local` and `:global` pseudo-classes specific to CSS Modules. +/// +/// For more information, see the [CSS Animations Specification](https://drafts.csswg.org/css-animations/#keyframes). +/// # Examples +/// Basic usage in CSS: +/// ```css +/// @keyframes my-animation { +/// from { +/// color: red; +/// } +/// to { +/// color: blue; +/// } +/// } +/// @keyframes :local(my-local-animation) { +/// from { +/// opacity: 0; +/// } +/// to { +/// opacity: 1; +/// } +/// } +/// @keyframes :global "my-global-animation" { +/// from { +/// opacity: 0; +/// } +/// to { +/// opacity: 1; +/// } +/// } +/// ``` #[inline] pub(crate) fn parse_keyframes_at_rule(p: &mut CssParser) -> ParsedSyntax { if !is_at_keyframes_at_rule(p) { @@ -30,32 +72,112 @@ pub(crate) fn parse_keyframes_at_rule(p: &mut CssParser) -> ParsedSyntax { p.bump(T![keyframes]); - let name = if is_at_identifier(p) { - parse_custom_identifier(p, CssLexContext::Regular) - } else { - parse_string(p) - }; - - let kind = if name - .or_recover_with_token_set( - p, - &ParseRecoveryTokenSet::new(CSS_BOGUS, KEYFRAMES_NAME_RECOVERY_SET) - .enable_recovery_on_line_break(), - expected_non_css_wide_keyword_identifier, - ) - .is_ok() - { - CSS_KEYFRAMES_AT_RULE + if is_at_keyframes_scoped_name(p) { + // is_at_keyframes_scoped_name guaranties that it will parse a keyframes scoped name + parse_keyframes_scoped_name(p).ok(); } else { - CSS_BOGUS_AT_RULE + parse_keyframes_identifier(p) + .or_add_diagnostic(p, expected_non_css_wide_keyword_identifier); }; KeyframesBlock.parse_block_body(p); + Present(m.complete(p, CSS_KEYFRAMES_AT_RULE)) +} + +/// Checks if the current parser position is at a keyframes scoped name. +/// +/// This function determines if the parser is currently positioned at the start of a keyframes scoped name, +/// which is indicated by the presence of a `:` character. +fn is_at_keyframes_scoped_name(p: &mut CssParser) -> bool { + p.at(T![:]) +} + +/// Parses a keyframes scoped name in CSS Modules. +/// +/// This function parses a keyframes scoped name, which can be either `:local` or `:global`, +/// specific to CSS Modules. If CSS Modules are not enabled, it generates a diagnostic error and skips the scoped name. +fn parse_keyframes_scoped_name(p: &mut CssParser) -> ParsedSyntax { + if !is_at_keyframes_scoped_name(p) { + return Absent; + } + + let m = p.start(); + + p.bump(T![:]); + + if !p.options().css_modules { + // :local and :global are not standard CSS features + // provide a hint on how to enable parsing of these pseudo-classes + p.error(local_or_global_not_allowed(p, p.cur_range())); + + // Skip the entire pseudo-class function selector + // Skip until the next opening curly brace + while !p.at(T!['{']) { + p.bump_any(); + } + + return Present(m.complete(p, CSS_BOGUS_KEYFRAMES_NAME)); + } + + let kind = { + let m = p.start(); + + // If we are at an invalid CSS module scope, + // we generate a diagnostic error and skip the invalid scope. + if !p.eat_ts(CSS_MODULES_SCOPE_SET) { + p.error(expected_any_css_module_scope(p, p.cur_range())); + p.bump_any(); + } + + let kind = if p.eat(T!['(']) { + CSS_KEYFRAMES_SCOPE_FUNCTION + } else { + CSS_KEYFRAMES_SCOPE_PREFIX + }; + + let name = parse_keyframes_identifier(p) + .or_add_diagnostic(p, expected_non_css_wide_keyword_identifier); + + if kind == CSS_KEYFRAMES_SCOPE_FUNCTION { + // If we have a function, we expect a closing parenthesis + p.expect(T![')']); + } + + m.complete(p, kind); + + if name.is_some() { + CSS_KEYFRAMES_SCOPED_NAME + } else { + // if we have an invalid name return a bogus keyframes name + CSS_BOGUS_KEYFRAMES_NAME + } + }; + Present(m.complete(p, kind)) } -const KEYFRAMES_NAME_RECOVERY_SET: TokenSet = token_set![T!['{']]; +/// Checks if the current parser position is at a keyframes identifier. +/// +/// This function determines if the parser is currently positioned at the start of a keyframes identifier, +/// which can be either a standard identifier or a string. +fn is_at_keyframes_identifier(p: &mut CssParser) -> bool { + is_at_identifier(p) || is_at_string(p) +} + +/// Parses a keyframes identifier in CSS. +/// This function parses a keyframes identifier, which can be either a standard identifier or a string. +fn parse_keyframes_identifier(p: &mut CssParser) -> ParsedSyntax { + if !is_at_keyframes_identifier(p) { + return Absent; + } + + if is_at_identifier(p) { + parse_custom_identifier(p, CssLexContext::Regular) + } else { + parse_string(p) + } +} struct KeyframesBlock; @@ -223,11 +345,20 @@ fn is_at_keyframes_selector_list_end(p: &mut CssParser) -> bool { p.at(T!['{']) || is_at_declaration(p) || p.at(T!['}']) } +/// A set of tokens representing the keyframes item selectors `from` and `to`. const KEYFRAMES_ITEM_SELECTOR_IDENT_SET: TokenSet = token_set!(T![from], T![to]); +/// Checks if the current parser position is at a keyframes item selector. +/// +/// This function determines if the parser is currently positioned at the start of a keyframes item selector, +/// which can be either `from`, `to`, or a percentage dimension. fn is_at_keyframes_item_selector(p: &mut CssParser) -> bool { p.at_ts(KEYFRAMES_ITEM_SELECTOR_IDENT_SET) || is_at_percentage_dimension(p) } + +/// Parses a keyframes item selector in CSS. +/// +/// This function parses a keyframes item selector, which can be either `from`, `to`, or a percentage dimension. #[inline] fn parse_keyframes_item_selector(p: &mut CssParser) -> ParsedSyntax { if !is_at_keyframes_item_selector(p) { diff --git a/crates/biome_css_parser/src/syntax/css_modules.rs b/crates/biome_css_parser/src/syntax/css_modules.rs new file mode 100644 index 000000000000..47a67efe3f32 --- /dev/null +++ b/crates/biome_css_parser/src/syntax/css_modules.rs @@ -0,0 +1,27 @@ +use crate::parser::CssParser; +use biome_css_syntax::{CssSyntaxKind, TextRange, T}; +use biome_parser::diagnostic::{expect_one_of, ParseDiagnostic, ToDiagnostic}; +use biome_parser::{token_set, Parser, TokenSet}; + +/// A set of tokens representing the CSS Modules pseudo-classes `:local` and `:global`. +pub(crate) const CSS_MODULES_SCOPE_SET: TokenSet = token_set![T![global], T![local]]; + +/// Generates a parse diagnostic for when the `:local` or `:global` pseudo-classes are not allowed. +/// +/// This function returns an error diagnostic indicating that the `:local` or `:global` pseudo-classes +/// are not standard CSS features. It also provides a hint on how to enable +/// parsing of these pseudo-classes by setting the `css_modules` option to `true` +/// in the configuration file. +pub(crate) fn local_or_global_not_allowed(p: &CssParser, range: TextRange) -> ParseDiagnostic { + p.err_builder( + "`:local` and `:global` pseudo-classes are not standard CSS features.", + range, + ) + .with_hint( + "You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file.", + ) +} + +pub(crate) fn expected_any_css_module_scope(p: &CssParser, range: TextRange) -> ParseDiagnostic { + expect_one_of(&["global", "local"], range).into_diagnostic(p) +} diff --git a/crates/biome_css_parser/src/syntax/mod.rs b/crates/biome_css_parser/src/syntax/mod.rs index 2f471e6abe7c..071d50bb3952 100644 --- a/crates/biome_css_parser/src/syntax/mod.rs +++ b/crates/biome_css_parser/src/syntax/mod.rs @@ -1,5 +1,6 @@ mod at_rule; mod block; +mod css_modules; mod parse_error; mod property; mod selector; @@ -26,6 +27,7 @@ use value::dimension::{is_at_any_dimension, parse_any_dimension}; use value::function::{is_at_any_function, parse_any_function}; use self::parse_error::{expected_component_value, expected_declaration_item}; + pub(crate) fn parse_root(p: &mut CssParser) { let m = p.start(); p.eat(UNICODE_BOM); diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector.rs index 75ef3c23c8eb..215334707c57 100644 --- a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector.rs +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector.rs @@ -1,4 +1,5 @@ use crate::parser::CssParser; +use crate::syntax::css_modules::{local_or_global_not_allowed, CSS_MODULES_SCOPE_SET}; use crate::syntax::parse_error::expected_selector; use crate::syntax::selector::{ eat_or_recover_selector_function_close_token, parse_selector, @@ -6,28 +7,58 @@ use crate::syntax::selector::{ }; use biome_css_syntax::CssSyntaxKind::CSS_PSEUDO_CLASS_FUNCTION_SELECTOR; use biome_css_syntax::CssSyntaxKind::*; -use biome_css_syntax::{CssSyntaxKind, T}; +use biome_css_syntax::T; use biome_parser::parsed_syntax::ParsedSyntax; use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; -use biome_parser::{token_set, Parser, TokenSet}; - -const PSEUDO_CLASS_FUNCTION_SELECTOR_SET: TokenSet = - token_set![T![global], T![local]]; +use biome_parser::Parser; +/// Checks if the current parser position is at a pseudo-class function selector for CSS Modules. +/// +/// This function determines if the parser is currently positioned at the start of a `:local` or `:global` +/// pseudo-class function selector, which is part of the CSS Modules syntax. #[inline] pub(crate) fn is_at_pseudo_class_function_selector(p: &mut CssParser) -> bool { - p.at_ts(PSEUDO_CLASS_FUNCTION_SELECTOR_SET) && p.nth_at(1, T!['(']) + p.at_ts(CSS_MODULES_SCOPE_SET) && p.nth_at(1, T!['(']) } +/// Parses a pseudo-class function selector for CSS Modules. +/// +/// This function parses a pseudo-class function selector, specifically `:local` or `:global`, in CSS Modules. +/// If the `css_modules` option is not enabled, it generates a diagnostic error and skips the selector. +/// ```css +/// :local(.className) { +/// color: red; +/// } +/// :global(.globalClass) .nestedClass { +/// padding: 10px; +/// } +/// :local(.className) > :global(.globalClass) { +/// margin: 0; +/// } +/// ``` #[inline] pub(crate) fn parse_pseudo_class_function_selector(p: &mut CssParser) -> ParsedSyntax { if !is_at_pseudo_class_function_selector(p) { return Absent; } + if !p.options().css_modules { + // :local and :global are not standard CSS features + // provide a hint on how to enable parsing of these pseudo-classes + p.error(local_or_global_not_allowed(p, p.cur_range())); + + // Skip the entire pseudo-class function selector + // Skip until the next closing parenthesis + while !p.eat(T![')']) { + p.bump_any(); + } + + return Absent; + } + let m = p.start(); - p.bump_ts(PSEUDO_CLASS_FUNCTION_SELECTOR_SET); + p.bump_ts(CSS_MODULES_SCOPE_SET); p.bump(T!['(']); let kind = match parse_selector(p) { diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs index 48d9adaa15c8..4b53e1d5a885 100644 --- a/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs @@ -52,13 +52,15 @@ pub(crate) fn parse_pseudo_class_selector(p: &mut CssParser) -> ParsedSyntax { p.bump(T![:]); - let kind = if parse_pseudo_class(p) - .or_add_diagnostic(p, expected_any_pseudo_class) - .is_some() - { - CSS_PSEUDO_CLASS_SELECTOR - } else { - CSS_BOGUS_SUB_SELECTOR + // Show the error under the token next to the ':' + let range = p.cur_range(); + + let kind = match parse_pseudo_class(p) { + Present(_) => CSS_PSEUDO_CLASS_SELECTOR, + Absent => { + p.error(expected_any_pseudo_class(p, range)); + CSS_BOGUS_SUB_SELECTOR + } }; Present(m.complete(p, kind)) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes.css b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/at_rule_keyframes.css similarity index 100% rename from crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes.css rename to crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/at_rule_keyframes.css diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/at_rule_keyframes.css.snap similarity index 93% rename from crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes.css.snap rename to crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/at_rule_keyframes.css.snap index e6be519e8c50..f00fbda62b42 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/at_rule_keyframes.css.snap @@ -92,108 +92,107 @@ CssRoot { rules: CssRuleList [ CssAtRule { at_token: AT@0..1 "@" [] [], - rule: CssBogusAtRule { - items: [ - KEYFRAMES_KW@1..11 "keyframes" [] [Whitespace(" ")], - CssKeyframesBlock { - l_curly_token: L_CURLY@11..12 "{" [] [], - items: CssKeyframesItemList [ - CssKeyframesItem { - selectors: CssKeyframesSelectorList [ - CssKeyframesIdentSelector { - selector: FROM_KW@12..19 "from" [Newline("\n"), Whitespace("\t")] [Whitespace(" ")], - }, - ], - block: CssDeclarationBlock { - l_curly_token: L_CURLY@19..20 "{" [] [], - declarations: CssDeclarationList [ - CssDeclarationWithSemicolon { - declaration: CssDeclaration { - property: CssGenericProperty { - name: CssIdentifier { - value_token: IDENT@20..32 "transform" [Newline("\n"), Whitespace("\t\t")] [], - }, - colon_token: COLON@32..34 ":" [] [Whitespace(" ")], - value: CssGenericComponentValueList [ - CssFunction { - name: CssIdentifier { - value_token: IDENT@34..44 "translateX" [] [], - }, - l_paren_token: L_PAREN@44..45 "(" [] [], - items: CssParameterList [ - CssParameter { - any_css_expression: CssListOfComponentValuesExpression { - css_component_value_list: CssComponentValueList [ - CssPercentage { - value_token: CSS_NUMBER_LITERAL@45..46 "0" [] [], - percent_token: PERCENT@46..47 "%" [] [], - }, - ], - }, - }, - ], - r_paren_token: R_PAREN@47..48 ")" [] [], - }, - ], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@1..11 "keyframes" [] [Whitespace(" ")], + name: missing (required), + block: CssKeyframesBlock { + l_curly_token: L_CURLY@11..12 "{" [] [], + items: CssKeyframesItemList [ + CssKeyframesItem { + selectors: CssKeyframesSelectorList [ + CssKeyframesIdentSelector { + selector: FROM_KW@12..19 "from" [Newline("\n"), Whitespace("\t")] [Whitespace(" ")], + }, + ], + block: CssDeclarationBlock { + l_curly_token: L_CURLY@19..20 "{" [] [], + declarations: CssDeclarationList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@20..32 "transform" [Newline("\n"), Whitespace("\t\t")] [], }, - important: missing (optional), + colon_token: COLON@32..34 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssFunction { + name: CssIdentifier { + value_token: IDENT@34..44 "translateX" [] [], + }, + l_paren_token: L_PAREN@44..45 "(" [] [], + items: CssParameterList [ + CssParameter { + any_css_expression: CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssPercentage { + value_token: CSS_NUMBER_LITERAL@45..46 "0" [] [], + percent_token: PERCENT@46..47 "%" [] [], + }, + ], + }, + }, + ], + r_paren_token: R_PAREN@47..48 ")" [] [], + }, + ], }, - semicolon_token: SEMICOLON@48..49 ";" [] [], + important: missing (optional), }, - ], - r_curly_token: R_CURLY@49..52 "}" [Newline("\n"), Whitespace("\t")] [], - }, - }, - CssKeyframesItem { - selectors: CssKeyframesSelectorList [ - CssKeyframesIdentSelector { - selector: TO_KW@52..58 "to" [Newline("\n"), Newline("\n"), Whitespace("\t")] [Whitespace(" ")], + semicolon_token: SEMICOLON@48..49 ";" [] [], }, ], - block: CssDeclarationBlock { - l_curly_token: L_CURLY@58..59 "{" [] [], - declarations: CssDeclarationList [ - CssDeclarationWithSemicolon { - declaration: CssDeclaration { - property: CssGenericProperty { - name: CssIdentifier { - value_token: IDENT@59..71 "transform" [Newline("\n"), Whitespace("\t\t")] [], - }, - colon_token: COLON@71..73 ":" [] [Whitespace(" ")], - value: CssGenericComponentValueList [ - CssFunction { - name: CssIdentifier { - value_token: IDENT@73..83 "translateX" [] [], - }, - l_paren_token: L_PAREN@83..84 "(" [] [], - items: CssParameterList [ - CssParameter { - any_css_expression: CssListOfComponentValuesExpression { - css_component_value_list: CssComponentValueList [ - CssPercentage { - value_token: CSS_NUMBER_LITERAL@84..87 "100" [] [], - percent_token: PERCENT@87..88 "%" [] [], - }, - ], - }, - }, - ], - r_paren_token: R_PAREN@88..89 ")" [] [], - }, - ], + r_curly_token: R_CURLY@49..52 "}" [Newline("\n"), Whitespace("\t")] [], + }, + }, + CssKeyframesItem { + selectors: CssKeyframesSelectorList [ + CssKeyframesIdentSelector { + selector: TO_KW@52..58 "to" [Newline("\n"), Newline("\n"), Whitespace("\t")] [Whitespace(" ")], + }, + ], + block: CssDeclarationBlock { + l_curly_token: L_CURLY@58..59 "{" [] [], + declarations: CssDeclarationList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@59..71 "transform" [Newline("\n"), Whitespace("\t\t")] [], }, - important: missing (optional), + colon_token: COLON@71..73 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssFunction { + name: CssIdentifier { + value_token: IDENT@73..83 "translateX" [] [], + }, + l_paren_token: L_PAREN@83..84 "(" [] [], + items: CssParameterList [ + CssParameter { + any_css_expression: CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssPercentage { + value_token: CSS_NUMBER_LITERAL@84..87 "100" [] [], + percent_token: PERCENT@87..88 "%" [] [], + }, + ], + }, + }, + ], + r_paren_token: R_PAREN@88..89 ")" [] [], + }, + ], }, - semicolon_token: SEMICOLON@89..90 ";" [] [], + important: missing (optional), }, - ], - r_curly_token: R_CURLY@90..93 "}" [Newline("\n"), Whitespace("\t")] [], - }, + semicolon_token: SEMICOLON@89..90 ";" [] [], + }, + ], + r_curly_token: R_CURLY@90..93 "}" [Newline("\n"), Whitespace("\t")] [], }, - ], - r_curly_token: R_CURLY@93..95 "}" [Newline("\n")] [], - }, - ], + }, + ], + r_curly_token: R_CURLY@93..95 "}" [Newline("\n")] [], + }, }, }, CssAtRule { @@ -892,9 +891,10 @@ CssRoot { 1: CSS_RULE_LIST@0..839 0: CSS_AT_RULE@0..95 0: AT@0..1 "@" [] [] - 1: CSS_BOGUS_AT_RULE@1..95 + 1: CSS_KEYFRAMES_AT_RULE@1..95 0: KEYFRAMES_KW@1..11 "keyframes" [] [Whitespace(" ")] - 1: CSS_KEYFRAMES_BLOCK@11..95 + 1: (empty) + 2: CSS_KEYFRAMES_BLOCK@11..95 0: L_CURLY@11..12 "{" [] [] 1: CSS_KEYFRAMES_ITEM_LIST@12..93 0: CSS_KEYFRAMES_ITEM@12..52 diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes_1.css b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/at_rule_keyframes_1.css similarity index 100% rename from crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes_1.css rename to crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/at_rule_keyframes_1.css diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes_1.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/at_rule_keyframes_1.css.snap similarity index 100% rename from crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes_1.css.snap rename to crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/at_rule_keyframes_1.css.snap diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/disabled_css_module/at_rule_keyframe_disabled_css_modules.css b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/disabled_css_module/at_rule_keyframe_disabled_css_modules.css new file mode 100644 index 000000000000..f853dc7e22e9 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/disabled_css_module/at_rule_keyframe_disabled_css_modules.css @@ -0,0 +1,10 @@ +@keyframes test {} +@keyframes "test" {} +@keyframes :local(test) {} +@keyframes :local("test") {} +@keyframes :local test {} +@keyframes :local "test" {} +@keyframes :global(test) {} +@keyframes :global("test") {} +@keyframes :global test {} +@keyframes :global "test" {} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/disabled_css_module/at_rule_keyframe_disabled_css_modules.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/disabled_css_module/at_rule_keyframe_disabled_css_modules.css.snap new file mode 100644 index 000000000000..f9349a085ff2 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/disabled_css_module/at_rule_keyframe_disabled_css_modules.css.snap @@ -0,0 +1,453 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +@keyframes test {} +@keyframes "test" {} +@keyframes :local(test) {} +@keyframes :local("test") {} +@keyframes :local test {} +@keyframes :local "test" {} +@keyframes :global(test) {} +@keyframes :global("test") {} +@keyframes :global test {} +@keyframes :global "test" {} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@1..11 "keyframes" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@11..16 "test" [] [Whitespace(" ")], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@16..17 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@17..18 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@18..20 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@20..30 "keyframes" [] [Whitespace(" ")], + name: CssString { + value_token: CSS_STRING_LITERAL@30..37 "\"test\"" [] [Whitespace(" ")], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@37..38 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@38..39 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@39..41 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@41..51 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@51..52 ":" [] [], + LOCAL_KW@52..57 "local" [] [], + L_PAREN@57..58 "(" [] [], + IDENT@58..62 "test" [] [], + R_PAREN@62..64 ")" [] [Whitespace(" ")], + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@64..65 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@65..66 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@66..68 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@68..78 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@78..79 ":" [] [], + LOCAL_KW@79..84 "local" [] [], + L_PAREN@84..85 "(" [] [], + CSS_STRING_LITERAL@85..91 "\"test\"" [] [], + R_PAREN@91..93 ")" [] [Whitespace(" ")], + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@93..94 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@94..95 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@95..97 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@97..107 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@107..108 ":" [] [], + LOCAL_KW@108..114 "local" [] [Whitespace(" ")], + IDENT@114..119 "test" [] [Whitespace(" ")], + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@119..120 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@120..121 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@121..123 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@123..133 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@133..134 ":" [] [], + LOCAL_KW@134..140 "local" [] [Whitespace(" ")], + CSS_STRING_LITERAL@140..147 "\"test\"" [] [Whitespace(" ")], + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@147..148 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@148..149 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@149..151 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@151..161 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@161..162 ":" [] [], + GLOBAL_KW@162..168 "global" [] [], + L_PAREN@168..169 "(" [] [], + IDENT@169..173 "test" [] [], + R_PAREN@173..175 ")" [] [Whitespace(" ")], + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@175..176 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@176..177 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@177..179 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@179..189 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@189..190 ":" [] [], + GLOBAL_KW@190..196 "global" [] [], + L_PAREN@196..197 "(" [] [], + CSS_STRING_LITERAL@197..203 "\"test\"" [] [], + R_PAREN@203..205 ")" [] [Whitespace(" ")], + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@205..206 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@206..207 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@207..209 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@209..219 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@219..220 ":" [] [], + GLOBAL_KW@220..227 "global" [] [Whitespace(" ")], + IDENT@227..232 "test" [] [Whitespace(" ")], + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@232..233 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@233..234 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@234..236 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@236..246 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@246..247 ":" [] [], + GLOBAL_KW@247..254 "global" [] [Whitespace(" ")], + CSS_STRING_LITERAL@254..261 "\"test\"" [] [Whitespace(" ")], + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@261..262 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@262..263 "}" [] [], + }, + }, + }, + ], + eof_token: EOF@263..264 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..264 + 0: (empty) + 1: CSS_RULE_LIST@0..263 + 0: CSS_AT_RULE@0..18 + 0: AT@0..1 "@" [] [] + 1: CSS_KEYFRAMES_AT_RULE@1..18 + 0: KEYFRAMES_KW@1..11 "keyframes" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@11..16 + 0: IDENT@11..16 "test" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@16..18 + 0: L_CURLY@16..17 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@17..17 + 2: R_CURLY@17..18 "}" [] [] + 1: CSS_AT_RULE@18..39 + 0: AT@18..20 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@20..39 + 0: KEYFRAMES_KW@20..30 "keyframes" [] [Whitespace(" ")] + 1: CSS_STRING@30..37 + 0: CSS_STRING_LITERAL@30..37 "\"test\"" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@37..39 + 0: L_CURLY@37..38 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@38..38 + 2: R_CURLY@38..39 "}" [] [] + 2: CSS_AT_RULE@39..66 + 0: AT@39..41 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@41..66 + 0: KEYFRAMES_KW@41..51 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@51..64 + 0: COLON@51..52 ":" [] [] + 1: LOCAL_KW@52..57 "local" [] [] + 2: L_PAREN@57..58 "(" [] [] + 3: IDENT@58..62 "test" [] [] + 4: R_PAREN@62..64 ")" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@64..66 + 0: L_CURLY@64..65 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@65..65 + 2: R_CURLY@65..66 "}" [] [] + 3: CSS_AT_RULE@66..95 + 0: AT@66..68 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@68..95 + 0: KEYFRAMES_KW@68..78 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@78..93 + 0: COLON@78..79 ":" [] [] + 1: LOCAL_KW@79..84 "local" [] [] + 2: L_PAREN@84..85 "(" [] [] + 3: CSS_STRING_LITERAL@85..91 "\"test\"" [] [] + 4: R_PAREN@91..93 ")" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@93..95 + 0: L_CURLY@93..94 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@94..94 + 2: R_CURLY@94..95 "}" [] [] + 4: CSS_AT_RULE@95..121 + 0: AT@95..97 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@97..121 + 0: KEYFRAMES_KW@97..107 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@107..119 + 0: COLON@107..108 ":" [] [] + 1: LOCAL_KW@108..114 "local" [] [Whitespace(" ")] + 2: IDENT@114..119 "test" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@119..121 + 0: L_CURLY@119..120 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@120..120 + 2: R_CURLY@120..121 "}" [] [] + 5: CSS_AT_RULE@121..149 + 0: AT@121..123 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@123..149 + 0: KEYFRAMES_KW@123..133 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@133..147 + 0: COLON@133..134 ":" [] [] + 1: LOCAL_KW@134..140 "local" [] [Whitespace(" ")] + 2: CSS_STRING_LITERAL@140..147 "\"test\"" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@147..149 + 0: L_CURLY@147..148 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@148..148 + 2: R_CURLY@148..149 "}" [] [] + 6: CSS_AT_RULE@149..177 + 0: AT@149..151 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@151..177 + 0: KEYFRAMES_KW@151..161 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@161..175 + 0: COLON@161..162 ":" [] [] + 1: GLOBAL_KW@162..168 "global" [] [] + 2: L_PAREN@168..169 "(" [] [] + 3: IDENT@169..173 "test" [] [] + 4: R_PAREN@173..175 ")" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@175..177 + 0: L_CURLY@175..176 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@176..176 + 2: R_CURLY@176..177 "}" [] [] + 7: CSS_AT_RULE@177..207 + 0: AT@177..179 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@179..207 + 0: KEYFRAMES_KW@179..189 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@189..205 + 0: COLON@189..190 ":" [] [] + 1: GLOBAL_KW@190..196 "global" [] [] + 2: L_PAREN@196..197 "(" [] [] + 3: CSS_STRING_LITERAL@197..203 "\"test\"" [] [] + 4: R_PAREN@203..205 ")" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@205..207 + 0: L_CURLY@205..206 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@206..206 + 2: R_CURLY@206..207 "}" [] [] + 8: CSS_AT_RULE@207..234 + 0: AT@207..209 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@209..234 + 0: KEYFRAMES_KW@209..219 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@219..232 + 0: COLON@219..220 ":" [] [] + 1: GLOBAL_KW@220..227 "global" [] [Whitespace(" ")] + 2: IDENT@227..232 "test" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@232..234 + 0: L_CURLY@232..233 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@233..233 + 2: R_CURLY@233..234 "}" [] [] + 9: CSS_AT_RULE@234..263 + 0: AT@234..236 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@236..263 + 0: KEYFRAMES_KW@236..246 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@246..261 + 0: COLON@246..247 ":" [] [] + 1: GLOBAL_KW@247..254 "global" [] [Whitespace(" ")] + 2: CSS_STRING_LITERAL@254..261 "\"test\"" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@261..263 + 0: L_CURLY@261..262 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@262..262 + 2: R_CURLY@262..263 "}" [] [] + 2: EOF@263..264 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +at_rule_keyframe_disabled_css_modules.css:3:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + 1 │ @keyframes test {} + 2 │ @keyframes "test" {} + > 3 │ @keyframes :local(test) {} + │ ^^^^^ + 4 │ @keyframes :local("test") {} + 5 │ @keyframes :local test {} + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +at_rule_keyframe_disabled_css_modules.css:4:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + 2 │ @keyframes "test" {} + 3 │ @keyframes :local(test) {} + > 4 │ @keyframes :local("test") {} + │ ^^^^^ + 5 │ @keyframes :local test {} + 6 │ @keyframes :local "test" {} + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +at_rule_keyframe_disabled_css_modules.css:5:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + 3 │ @keyframes :local(test) {} + 4 │ @keyframes :local("test") {} + > 5 │ @keyframes :local test {} + │ ^^^^^ + 6 │ @keyframes :local "test" {} + 7 │ @keyframes :global(test) {} + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +at_rule_keyframe_disabled_css_modules.css:6:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + 4 │ @keyframes :local("test") {} + 5 │ @keyframes :local test {} + > 6 │ @keyframes :local "test" {} + │ ^^^^^ + 7 │ @keyframes :global(test) {} + 8 │ @keyframes :global("test") {} + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +at_rule_keyframe_disabled_css_modules.css:7:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + 5 │ @keyframes :local test {} + 6 │ @keyframes :local "test" {} + > 7 │ @keyframes :global(test) {} + │ ^^^^^^ + 8 │ @keyframes :global("test") {} + 9 │ @keyframes :global test {} + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +at_rule_keyframe_disabled_css_modules.css:8:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + 6 │ @keyframes :local "test" {} + 7 │ @keyframes :global(test) {} + > 8 │ @keyframes :global("test") {} + │ ^^^^^^ + 9 │ @keyframes :global test {} + 10 │ @keyframes :global "test" {} + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +at_rule_keyframe_disabled_css_modules.css:9:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + 7 │ @keyframes :global(test) {} + 8 │ @keyframes :global("test") {} + > 9 │ @keyframes :global test {} + │ ^^^^^^ + 10 │ @keyframes :global "test" {} + 11 │ + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +at_rule_keyframe_disabled_css_modules.css:10:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + 8 │ @keyframes :global("test") {} + 9 │ @keyframes :global test {} + > 10 │ @keyframes :global "test" {} + │ ^^^^^^ + 11 │ + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/enabled_css_module/at_rule_keyframe_enabled_css_modules.css b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/enabled_css_module/at_rule_keyframe_enabled_css_modules.css new file mode 100644 index 000000000000..68ac623c71da --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/enabled_css_module/at_rule_keyframe_enabled_css_modules.css @@ -0,0 +1,4 @@ +@keyframes :invalid(test) {} +@keyframes :invalid test {} +@keyframes :global(test {} +@keyframes :local() {} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/enabled_css_module/at_rule_keyframe_enabled_css_modules.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/enabled_css_module/at_rule_keyframe_enabled_css_modules.css.snap new file mode 100644 index 000000000000..893c0c82b7a2 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/enabled_css_module/at_rule_keyframe_enabled_css_modules.css.snap @@ -0,0 +1,255 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +@keyframes :invalid(test) {} +@keyframes :invalid test {} +@keyframes :global(test {} +@keyframes :local() {} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@1..11 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@11..12 ":" [] [], + CssBogus { + items: [ + IDENT@12..19 "invalid" [] [], + L_PAREN@19..20 "(" [] [], + CssCustomIdentifier { + value_token: IDENT@20..24 "test" [] [], + }, + R_PAREN@24..26 ")" [] [Whitespace(" ")], + ], + }, + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@26..27 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@27..28 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@28..30 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@30..40 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@40..41 ":" [] [], + CssBogus { + items: [ + IDENT@41..49 "invalid" [] [Whitespace(" ")], + CssCustomIdentifier { + value_token: IDENT@49..54 "test" [] [Whitespace(" ")], + }, + ], + }, + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@54..55 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@55..56 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@56..58 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@58..68 "keyframes" [] [Whitespace(" ")], + name: CssKeyframesScopedName { + colon_token: COLON@68..69 ":" [] [], + scope: CssKeyframesScopeFunction { + scope: GLOBAL_KW@69..75 "global" [] [], + l_paren_token: L_PAREN@75..76 "(" [] [], + name: CssCustomIdentifier { + value_token: IDENT@76..81 "test" [] [Whitespace(" ")], + }, + r_paren_token: missing (required), + }, + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@81..82 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@82..83 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@83..85 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@85..95 "keyframes" [] [Whitespace(" ")], + name: CssBogusKeyframesName { + items: [ + COLON@95..96 ":" [] [], + CssKeyframesScopeFunction { + scope: LOCAL_KW@96..101 "local" [] [], + l_paren_token: L_PAREN@101..102 "(" [] [], + name: missing (required), + r_paren_token: R_PAREN@102..104 ")" [] [Whitespace(" ")], + }, + ], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@104..105 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@105..106 "}" [] [], + }, + }, + }, + ], + eof_token: EOF@106..107 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..107 + 0: (empty) + 1: CSS_RULE_LIST@0..106 + 0: CSS_AT_RULE@0..28 + 0: AT@0..1 "@" [] [] + 1: CSS_KEYFRAMES_AT_RULE@1..28 + 0: KEYFRAMES_KW@1..11 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@11..26 + 0: COLON@11..12 ":" [] [] + 1: CSS_BOGUS@12..26 + 0: IDENT@12..19 "invalid" [] [] + 1: L_PAREN@19..20 "(" [] [] + 2: CSS_CUSTOM_IDENTIFIER@20..24 + 0: IDENT@20..24 "test" [] [] + 3: R_PAREN@24..26 ")" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@26..28 + 0: L_CURLY@26..27 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@27..27 + 2: R_CURLY@27..28 "}" [] [] + 1: CSS_AT_RULE@28..56 + 0: AT@28..30 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@30..56 + 0: KEYFRAMES_KW@30..40 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@40..54 + 0: COLON@40..41 ":" [] [] + 1: CSS_BOGUS@41..54 + 0: IDENT@41..49 "invalid" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@49..54 + 0: IDENT@49..54 "test" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@54..56 + 0: L_CURLY@54..55 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@55..55 + 2: R_CURLY@55..56 "}" [] [] + 2: CSS_AT_RULE@56..83 + 0: AT@56..58 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@58..83 + 0: KEYFRAMES_KW@58..68 "keyframes" [] [Whitespace(" ")] + 1: CSS_KEYFRAMES_SCOPED_NAME@68..81 + 0: COLON@68..69 ":" [] [] + 1: CSS_KEYFRAMES_SCOPE_FUNCTION@69..81 + 0: GLOBAL_KW@69..75 "global" [] [] + 1: L_PAREN@75..76 "(" [] [] + 2: CSS_CUSTOM_IDENTIFIER@76..81 + 0: IDENT@76..81 "test" [] [Whitespace(" ")] + 3: (empty) + 2: CSS_KEYFRAMES_BLOCK@81..83 + 0: L_CURLY@81..82 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@82..82 + 2: R_CURLY@82..83 "}" [] [] + 3: CSS_AT_RULE@83..106 + 0: AT@83..85 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@85..106 + 0: KEYFRAMES_KW@85..95 "keyframes" [] [Whitespace(" ")] + 1: CSS_BOGUS_KEYFRAMES_NAME@95..104 + 0: COLON@95..96 ":" [] [] + 1: CSS_KEYFRAMES_SCOPE_FUNCTION@96..104 + 0: LOCAL_KW@96..101 "local" [] [] + 1: L_PAREN@101..102 "(" [] [] + 2: (empty) + 3: R_PAREN@102..104 ")" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@104..106 + 0: L_CURLY@104..105 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@105..105 + 2: R_CURLY@105..106 "}" [] [] + 2: EOF@106..107 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +at_rule_keyframe_enabled_css_modules.css:1:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + > 1 │ @keyframes :invalid(test) {} + │ ^^^^^^^ + 2 │ @keyframes :invalid test {} + 3 │ @keyframes :global(test {} + + i Expected one of: + + - global + - local + +at_rule_keyframe_enabled_css_modules.css:2:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + 1 │ @keyframes :invalid(test) {} + > 2 │ @keyframes :invalid test {} + │ ^^^^^^^ + 3 │ @keyframes :global(test {} + 4 │ @keyframes :local() {} + + i Expected one of: + + - global + - local + +at_rule_keyframe_enabled_css_modules.css:3:25 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead found `{` + + 1 │ @keyframes :invalid(test) {} + 2 │ @keyframes :invalid test {} + > 3 │ @keyframes :global(test {} + │ ^ + 4 │ @keyframes :local() {} + 5 │ + + i Remove { + +at_rule_keyframe_enabled_css_modules.css:4:19 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an identifier but instead found ')'. + + 2 │ @keyframes :invalid test {} + 3 │ @keyframes :global(test {} + > 4 │ @keyframes :local() {} + │ ^ + 5 │ + + i Expected an identifier here. + + 2 │ @keyframes :invalid test {} + 3 │ @keyframes :global(test {} + > 4 │ @keyframes :local() {} + │ ^ + 5 │ + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/enabled_css_module/options.json b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/enabled_css_module/options.json new file mode 100644 index 000000000000..bb9ca9442bd6 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_keyframes/enabled_css_module/options.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../../../../../../../packages/@biomejs/biome/configuration_schema.json", + "css": { + "parser": { + "cssModules": true + } + } +} diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module.css b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/disabled/pseudo_class_function_selector_disabled.css similarity index 100% rename from crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module.css rename to crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/disabled/pseudo_class_function_selector_disabled.css diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/disabled/pseudo_class_function_selector_disabled.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/disabled/pseudo_class_function_selector_disabled.css.snap new file mode 100644 index 000000000000..459a55c41fa7 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/disabled/pseudo_class_function_selector_disabled.css.snap @@ -0,0 +1,222 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +:global(.class div) {} +:local(.class div + #id) {} +:global(.class div) .div {} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selector_token: missing (optional), + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssBogusSubSelector { + items: [ + COLON@0..1 ":" [] [], + GLOBAL_KW@1..7 "global" [] [], + L_PAREN@7..8 "(" [] [], + DOT@8..9 "." [] [], + IDENT@9..15 "class" [] [Whitespace(" ")], + IDENT@15..18 "div" [] [], + R_PAREN@18..20 ")" [] [Whitespace(" ")], + ], + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@20..21 "{" [] [], + items: CssDeclarationOrRuleList [], + r_curly_token: R_CURLY@21..22 "}" [] [], + }, + }, + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selector_token: missing (optional), + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssBogusSubSelector { + items: [ + COLON@22..24 ":" [Newline("\n")] [], + LOCAL_KW@24..29 "local" [] [], + L_PAREN@29..30 "(" [] [], + DOT@30..31 "." [] [], + IDENT@31..37 "class" [] [Whitespace(" ")], + IDENT@37..41 "div" [] [Whitespace(" ")], + PLUS@41..43 "+" [] [Whitespace(" ")], + HASH@43..44 "#" [] [], + IDENT@44..46 "id" [] [], + R_PAREN@46..48 ")" [] [Whitespace(" ")], + ], + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@48..49 "{" [] [], + items: CssDeclarationOrRuleList [], + r_curly_token: R_CURLY@49..50 "}" [] [], + }, + }, + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selector_token: missing (optional), + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssBogusSubSelector { + items: [ + COLON@50..52 ":" [Newline("\n")] [], + GLOBAL_KW@52..58 "global" [] [], + L_PAREN@58..59 "(" [] [], + DOT@59..60 "." [] [], + IDENT@60..66 "class" [] [Whitespace(" ")], + IDENT@66..69 "div" [] [], + R_PAREN@69..71 ")" [] [Whitespace(" ")], + ], + }, + CssClassSelector { + dot_token: DOT@71..72 "." [] [], + name: CssCustomIdentifier { + value_token: IDENT@72..76 "div" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@76..77 "{" [] [], + items: CssDeclarationOrRuleList [], + r_curly_token: R_CURLY@77..78 "}" [] [], + }, + }, + ], + eof_token: EOF@78..79 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..79 + 0: (empty) + 1: CSS_RULE_LIST@0..78 + 0: CSS_QUALIFIED_RULE@0..22 + 0: CSS_SELECTOR_LIST@0..20 + 0: CSS_COMPOUND_SELECTOR@0..20 + 0: (empty) + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@0..20 + 0: CSS_BOGUS_SUB_SELECTOR@0..20 + 0: COLON@0..1 ":" [] [] + 1: GLOBAL_KW@1..7 "global" [] [] + 2: L_PAREN@7..8 "(" [] [] + 3: DOT@8..9 "." [] [] + 4: IDENT@9..15 "class" [] [Whitespace(" ")] + 5: IDENT@15..18 "div" [] [] + 6: R_PAREN@18..20 ")" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@20..22 + 0: L_CURLY@20..21 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@21..21 + 2: R_CURLY@21..22 "}" [] [] + 1: CSS_QUALIFIED_RULE@22..50 + 0: CSS_SELECTOR_LIST@22..48 + 0: CSS_COMPOUND_SELECTOR@22..48 + 0: (empty) + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@22..48 + 0: CSS_BOGUS_SUB_SELECTOR@22..48 + 0: COLON@22..24 ":" [Newline("\n")] [] + 1: LOCAL_KW@24..29 "local" [] [] + 2: L_PAREN@29..30 "(" [] [] + 3: DOT@30..31 "." [] [] + 4: IDENT@31..37 "class" [] [Whitespace(" ")] + 5: IDENT@37..41 "div" [] [Whitespace(" ")] + 6: PLUS@41..43 "+" [] [Whitespace(" ")] + 7: HASH@43..44 "#" [] [] + 8: IDENT@44..46 "id" [] [] + 9: R_PAREN@46..48 ")" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@48..50 + 0: L_CURLY@48..49 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@49..49 + 2: R_CURLY@49..50 "}" [] [] + 2: CSS_QUALIFIED_RULE@50..78 + 0: CSS_SELECTOR_LIST@50..76 + 0: CSS_COMPOUND_SELECTOR@50..76 + 0: (empty) + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@50..76 + 0: CSS_BOGUS_SUB_SELECTOR@50..71 + 0: COLON@50..52 ":" [Newline("\n")] [] + 1: GLOBAL_KW@52..58 "global" [] [] + 2: L_PAREN@58..59 "(" [] [] + 3: DOT@59..60 "." [] [] + 4: IDENT@60..66 "class" [] [Whitespace(" ")] + 5: IDENT@66..69 "div" [] [] + 6: R_PAREN@69..71 ")" [] [Whitespace(" ")] + 1: CSS_CLASS_SELECTOR@71..76 + 0: DOT@71..72 "." [] [] + 1: CSS_CUSTOM_IDENTIFIER@72..76 + 0: IDENT@72..76 "div" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@76..78 + 0: L_CURLY@76..77 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@77..77 + 2: R_CURLY@77..78 "}" [] [] + 2: EOF@78..79 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +pseudo_class_function_selector_disabled.css:1:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + > 1 │ :global(.class div) {} + │ ^^^^^^ + 2 │ :local(.class div + #id) {} + 3 │ :global(.class div) .div {} + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +pseudo_class_function_selector_disabled.css:2:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + 1 │ :global(.class div) {} + > 2 │ :local(.class div + #id) {} + │ ^^^^^ + 3 │ :global(.class div) .div {} + 4 │ + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +pseudo_class_function_selector_disabled.css:3:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × `:local` and `:global` pseudo-classes are not standard CSS features. + + 1 │ :global(.class div) {} + 2 │ :local(.class div + #id) {} + > 3 │ :global(.class div) .div {} + │ ^^^^^^ + 4 │ + + i You can enable `:local` and `:global` pseudo-class parsing by setting the `css_modules` option to `true` in your configuration file. + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/enabled/options.json b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/enabled/options.json new file mode 100644 index 000000000000..0a0a644e7b99 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/enabled/options.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../../../../../../../../packages/@biomejs/biome/configuration_schema.json", + "css": { + "parser": { + "cssModules": true + } + } +} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector.css b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/enabled/pseudo_class_function_selector_enabled.css similarity index 100% rename from crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector.css rename to crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/enabled/pseudo_class_function_selector_enabled.css diff --git a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/enabled/pseudo_class_function_selector_enabled.css.snap similarity index 96% rename from crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector.css.snap rename to crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/enabled/pseudo_class_function_selector_enabled.css.snap index 70e878ada91e..bfe77998421a 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/selector/pseudo_class/pseudo_class_function_selector/enabled/pseudo_class_function_selector_enabled.css.snap @@ -2,7 +2,6 @@ source: crates/biome_css_parser/tests/spec_test.rs expression: snapshot --- - ## Input ```css @@ -425,7 +424,7 @@ CssRoot { ## Diagnostics ``` -pseudo_class_function_selector.css:1:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +pseudo_class_function_selector_enabled.css:1:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Expected a selector but instead found '{'. @@ -441,7 +440,7 @@ pseudo_class_function_selector.css:1:10 parse ━━━━━━━━━━━ 2 │ :global() {} 3 │ :global(.div, .class) {} -pseudo_class_function_selector.css:2:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +pseudo_class_function_selector_enabled.css:2:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Expected a selector but instead found ')'. @@ -459,7 +458,7 @@ pseudo_class_function_selector.css:2:9 parse ━━━━━━━━━━━ 3 │ :global(.div, .class) {} 4 │ :global(.div, .class {} -pseudo_class_function_selector.css:3:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +pseudo_class_function_selector_enabled.css:3:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Expected a selector but instead found '.div, .class'. @@ -479,7 +478,7 @@ pseudo_class_function_selector.css:3:9 parse ━━━━━━━━━━━ 4 │ :global(.div, .class {} 5 │ :global(.div .class {} -pseudo_class_function_selector.css:4:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +pseudo_class_function_selector_enabled.css:4:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Expected a selector but instead found '.div, .class'. @@ -499,7 +498,7 @@ pseudo_class_function_selector.css:4:9 parse ━━━━━━━━━━━ 5 │ :global(.div .class {} 6 │ :global(.div .class -pseudo_class_function_selector.css:4:22 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +pseudo_class_function_selector_enabled.css:4:22 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × expected `)` but instead found `{` @@ -512,7 +511,7 @@ pseudo_class_function_selector.css:4:22 parse ━━━━━━━━━━━ i Remove { -pseudo_class_function_selector.css:5:21 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +pseudo_class_function_selector_enabled.css:5:21 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × expected `)` but instead found `{` @@ -525,7 +524,7 @@ pseudo_class_function_selector.css:5:21 parse ━━━━━━━━━━━ i Remove { -pseudo_class_function_selector.css:7:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +pseudo_class_function_selector_enabled.css:7:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × expected `)` but instead the file ends @@ -542,5 +541,3 @@ pseudo_class_function_selector.css:7:1 parse ━━━━━━━━━━━ │ ``` - - diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/keyframes/at_rule_keyframe_css_modules.css b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/keyframes/at_rule_keyframe_css_modules.css new file mode 100644 index 000000000000..f853dc7e22e9 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/keyframes/at_rule_keyframe_css_modules.css @@ -0,0 +1,10 @@ +@keyframes test {} +@keyframes "test" {} +@keyframes :local(test) {} +@keyframes :local("test") {} +@keyframes :local test {} +@keyframes :local "test" {} +@keyframes :global(test) {} +@keyframes :global("test") {} +@keyframes :global test {} +@keyframes :global "test" {} diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/keyframes/at_rule_keyframe_css_modules.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/keyframes/at_rule_keyframe_css_modules.css.snap new file mode 100644 index 000000000000..79e67fa4765a --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/keyframes/at_rule_keyframe_css_modules.css.snap @@ -0,0 +1,377 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +@keyframes test {} +@keyframes "test" {} +@keyframes :local(test) {} +@keyframes :local("test") {} +@keyframes :local test {} +@keyframes :local "test" {} +@keyframes :global(test) {} +@keyframes :global("test") {} +@keyframes :global test {} +@keyframes :global "test" {} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@1..11 "keyframes" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@11..16 "test" [] [Whitespace(" ")], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@16..17 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@17..18 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@18..20 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@20..30 "keyframes" [] [Whitespace(" ")], + name: CssString { + value_token: CSS_STRING_LITERAL@30..37 "\"test\"" [] [Whitespace(" ")], + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@37..38 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@38..39 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@39..41 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@41..51 "keyframes" [] [Whitespace(" ")], + name: CssKeyframesScopedName { + colon_token: COLON@51..52 ":" [] [], + scope: CssKeyframesScopeFunction { + scope: LOCAL_KW@52..57 "local" [] [], + l_paren_token: L_PAREN@57..58 "(" [] [], + name: CssCustomIdentifier { + value_token: IDENT@58..62 "test" [] [], + }, + r_paren_token: R_PAREN@62..64 ")" [] [Whitespace(" ")], + }, + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@64..65 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@65..66 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@66..68 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@68..78 "keyframes" [] [Whitespace(" ")], + name: CssKeyframesScopedName { + colon_token: COLON@78..79 ":" [] [], + scope: CssKeyframesScopeFunction { + scope: LOCAL_KW@79..84 "local" [] [], + l_paren_token: L_PAREN@84..85 "(" [] [], + name: CssString { + value_token: CSS_STRING_LITERAL@85..91 "\"test\"" [] [], + }, + r_paren_token: R_PAREN@91..93 ")" [] [Whitespace(" ")], + }, + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@93..94 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@94..95 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@95..97 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@97..107 "keyframes" [] [Whitespace(" ")], + name: CssKeyframesScopedName { + colon_token: COLON@107..108 ":" [] [], + scope: CssKeyframesScopePrefix { + scope: LOCAL_KW@108..114 "local" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@114..119 "test" [] [Whitespace(" ")], + }, + }, + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@119..120 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@120..121 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@121..123 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@123..133 "keyframes" [] [Whitespace(" ")], + name: CssKeyframesScopedName { + colon_token: COLON@133..134 ":" [] [], + scope: CssKeyframesScopePrefix { + scope: LOCAL_KW@134..140 "local" [] [Whitespace(" ")], + name: CssString { + value_token: CSS_STRING_LITERAL@140..147 "\"test\"" [] [Whitespace(" ")], + }, + }, + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@147..148 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@148..149 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@149..151 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@151..161 "keyframes" [] [Whitespace(" ")], + name: CssKeyframesScopedName { + colon_token: COLON@161..162 ":" [] [], + scope: CssKeyframesScopeFunction { + scope: GLOBAL_KW@162..168 "global" [] [], + l_paren_token: L_PAREN@168..169 "(" [] [], + name: CssCustomIdentifier { + value_token: IDENT@169..173 "test" [] [], + }, + r_paren_token: R_PAREN@173..175 ")" [] [Whitespace(" ")], + }, + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@175..176 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@176..177 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@177..179 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@179..189 "keyframes" [] [Whitespace(" ")], + name: CssKeyframesScopedName { + colon_token: COLON@189..190 ":" [] [], + scope: CssKeyframesScopeFunction { + scope: GLOBAL_KW@190..196 "global" [] [], + l_paren_token: L_PAREN@196..197 "(" [] [], + name: CssString { + value_token: CSS_STRING_LITERAL@197..203 "\"test\"" [] [], + }, + r_paren_token: R_PAREN@203..205 ")" [] [Whitespace(" ")], + }, + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@205..206 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@206..207 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@207..209 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@209..219 "keyframes" [] [Whitespace(" ")], + name: CssKeyframesScopedName { + colon_token: COLON@219..220 ":" [] [], + scope: CssKeyframesScopePrefix { + scope: GLOBAL_KW@220..227 "global" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@227..232 "test" [] [Whitespace(" ")], + }, + }, + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@232..233 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@233..234 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@234..236 "@" [Newline("\n")] [], + rule: CssKeyframesAtRule { + keyframes_token: KEYFRAMES_KW@236..246 "keyframes" [] [Whitespace(" ")], + name: CssKeyframesScopedName { + colon_token: COLON@246..247 ":" [] [], + scope: CssKeyframesScopePrefix { + scope: GLOBAL_KW@247..254 "global" [] [Whitespace(" ")], + name: CssString { + value_token: CSS_STRING_LITERAL@254..261 "\"test\"" [] [Whitespace(" ")], + }, + }, + }, + block: CssKeyframesBlock { + l_curly_token: L_CURLY@261..262 "{" [] [], + items: CssKeyframesItemList [], + r_curly_token: R_CURLY@262..263 "}" [] [], + }, + }, + }, + ], + eof_token: EOF@263..264 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..264 + 0: (empty) + 1: CSS_RULE_LIST@0..263 + 0: CSS_AT_RULE@0..18 + 0: AT@0..1 "@" [] [] + 1: CSS_KEYFRAMES_AT_RULE@1..18 + 0: KEYFRAMES_KW@1..11 "keyframes" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@11..16 + 0: IDENT@11..16 "test" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@16..18 + 0: L_CURLY@16..17 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@17..17 + 2: R_CURLY@17..18 "}" [] [] + 1: CSS_AT_RULE@18..39 + 0: AT@18..20 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@20..39 + 0: KEYFRAMES_KW@20..30 "keyframes" [] [Whitespace(" ")] + 1: CSS_STRING@30..37 + 0: CSS_STRING_LITERAL@30..37 "\"test\"" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@37..39 + 0: L_CURLY@37..38 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@38..38 + 2: R_CURLY@38..39 "}" [] [] + 2: CSS_AT_RULE@39..66 + 0: AT@39..41 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@41..66 + 0: KEYFRAMES_KW@41..51 "keyframes" [] [Whitespace(" ")] + 1: CSS_KEYFRAMES_SCOPED_NAME@51..64 + 0: COLON@51..52 ":" [] [] + 1: CSS_KEYFRAMES_SCOPE_FUNCTION@52..64 + 0: LOCAL_KW@52..57 "local" [] [] + 1: L_PAREN@57..58 "(" [] [] + 2: CSS_CUSTOM_IDENTIFIER@58..62 + 0: IDENT@58..62 "test" [] [] + 3: R_PAREN@62..64 ")" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@64..66 + 0: L_CURLY@64..65 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@65..65 + 2: R_CURLY@65..66 "}" [] [] + 3: CSS_AT_RULE@66..95 + 0: AT@66..68 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@68..95 + 0: KEYFRAMES_KW@68..78 "keyframes" [] [Whitespace(" ")] + 1: CSS_KEYFRAMES_SCOPED_NAME@78..93 + 0: COLON@78..79 ":" [] [] + 1: CSS_KEYFRAMES_SCOPE_FUNCTION@79..93 + 0: LOCAL_KW@79..84 "local" [] [] + 1: L_PAREN@84..85 "(" [] [] + 2: CSS_STRING@85..91 + 0: CSS_STRING_LITERAL@85..91 "\"test\"" [] [] + 3: R_PAREN@91..93 ")" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@93..95 + 0: L_CURLY@93..94 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@94..94 + 2: R_CURLY@94..95 "}" [] [] + 4: CSS_AT_RULE@95..121 + 0: AT@95..97 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@97..121 + 0: KEYFRAMES_KW@97..107 "keyframes" [] [Whitespace(" ")] + 1: CSS_KEYFRAMES_SCOPED_NAME@107..119 + 0: COLON@107..108 ":" [] [] + 1: CSS_KEYFRAMES_SCOPE_PREFIX@108..119 + 0: LOCAL_KW@108..114 "local" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@114..119 + 0: IDENT@114..119 "test" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@119..121 + 0: L_CURLY@119..120 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@120..120 + 2: R_CURLY@120..121 "}" [] [] + 5: CSS_AT_RULE@121..149 + 0: AT@121..123 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@123..149 + 0: KEYFRAMES_KW@123..133 "keyframes" [] [Whitespace(" ")] + 1: CSS_KEYFRAMES_SCOPED_NAME@133..147 + 0: COLON@133..134 ":" [] [] + 1: CSS_KEYFRAMES_SCOPE_PREFIX@134..147 + 0: LOCAL_KW@134..140 "local" [] [Whitespace(" ")] + 1: CSS_STRING@140..147 + 0: CSS_STRING_LITERAL@140..147 "\"test\"" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@147..149 + 0: L_CURLY@147..148 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@148..148 + 2: R_CURLY@148..149 "}" [] [] + 6: CSS_AT_RULE@149..177 + 0: AT@149..151 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@151..177 + 0: KEYFRAMES_KW@151..161 "keyframes" [] [Whitespace(" ")] + 1: CSS_KEYFRAMES_SCOPED_NAME@161..175 + 0: COLON@161..162 ":" [] [] + 1: CSS_KEYFRAMES_SCOPE_FUNCTION@162..175 + 0: GLOBAL_KW@162..168 "global" [] [] + 1: L_PAREN@168..169 "(" [] [] + 2: CSS_CUSTOM_IDENTIFIER@169..173 + 0: IDENT@169..173 "test" [] [] + 3: R_PAREN@173..175 ")" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@175..177 + 0: L_CURLY@175..176 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@176..176 + 2: R_CURLY@176..177 "}" [] [] + 7: CSS_AT_RULE@177..207 + 0: AT@177..179 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@179..207 + 0: KEYFRAMES_KW@179..189 "keyframes" [] [Whitespace(" ")] + 1: CSS_KEYFRAMES_SCOPED_NAME@189..205 + 0: COLON@189..190 ":" [] [] + 1: CSS_KEYFRAMES_SCOPE_FUNCTION@190..205 + 0: GLOBAL_KW@190..196 "global" [] [] + 1: L_PAREN@196..197 "(" [] [] + 2: CSS_STRING@197..203 + 0: CSS_STRING_LITERAL@197..203 "\"test\"" [] [] + 3: R_PAREN@203..205 ")" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@205..207 + 0: L_CURLY@205..206 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@206..206 + 2: R_CURLY@206..207 "}" [] [] + 8: CSS_AT_RULE@207..234 + 0: AT@207..209 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@209..234 + 0: KEYFRAMES_KW@209..219 "keyframes" [] [Whitespace(" ")] + 1: CSS_KEYFRAMES_SCOPED_NAME@219..232 + 0: COLON@219..220 ":" [] [] + 1: CSS_KEYFRAMES_SCOPE_PREFIX@220..232 + 0: GLOBAL_KW@220..227 "global" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@227..232 + 0: IDENT@227..232 "test" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@232..234 + 0: L_CURLY@232..233 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@233..233 + 2: R_CURLY@233..234 "}" [] [] + 9: CSS_AT_RULE@234..263 + 0: AT@234..236 "@" [Newline("\n")] [] + 1: CSS_KEYFRAMES_AT_RULE@236..263 + 0: KEYFRAMES_KW@236..246 "keyframes" [] [Whitespace(" ")] + 1: CSS_KEYFRAMES_SCOPED_NAME@246..261 + 0: COLON@246..247 ":" [] [] + 1: CSS_KEYFRAMES_SCOPE_PREFIX@247..261 + 0: GLOBAL_KW@247..254 "global" [] [Whitespace(" ")] + 1: CSS_STRING@254..261 + 0: CSS_STRING_LITERAL@254..261 "\"test\"" [] [Whitespace(" ")] + 2: CSS_KEYFRAMES_BLOCK@261..263 + 0: L_CURLY@261..262 "{" [] [] + 1: CSS_KEYFRAMES_ITEM_LIST@262..262 + 2: R_CURLY@262..263 "}" [] [] + 2: EOF@263..264 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/keyframes/options.json b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/keyframes/options.json new file mode 100644 index 000000000000..f789463bbcbd --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/keyframes/options.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../../../../../../packages/@biomejs/biome/configuration_schema.json", + "css": { + "parser": { + "cssModules": true + } + } +} diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module/options.json b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module/options.json new file mode 100644 index 000000000000..bb9ca9442bd6 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module/options.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../../../../../../../packages/@biomejs/biome/configuration_schema.json", + "css": { + "parser": { + "cssModules": true + } + } +} diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module/pseudo_class_module.css b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module/pseudo_class_module.css new file mode 100644 index 000000000000..59d8330e1800 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module/pseudo_class_module.css @@ -0,0 +1,3 @@ +:global(.class div) {} +:local(.class div + #id) {} +:global(.class div) .div {} diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module/pseudo_class_module.css.snap similarity index 100% rename from crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module.css.snap rename to crates/biome_css_parser/tests/css_test_suite/ok/selector/pseudo_class/pseudo_class_module/pseudo_class_module.css.snap diff --git a/crates/biome_css_parser/tests/spec_test.rs b/crates/biome_css_parser/tests/spec_test.rs index caec24673d40..cabf229eaa8e 100644 --- a/crates/biome_css_parser/tests/spec_test.rs +++ b/crates/biome_css_parser/tests/spec_test.rs @@ -174,7 +174,7 @@ pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_ #[test] pub fn quick_test() { let code = r#" -@value large: (min-width: 960px), medium: (d) and (c); +@keyframes :local(a) {} "#; let root = parse_css( diff --git a/crates/biome_css_syntax/src/generated/kind.rs b/crates/biome_css_syntax/src/generated/kind.rs index 0d300237b92a..f95903d335a6 100644 --- a/crates/biome_css_syntax/src/generated/kind.rs +++ b/crates/biome_css_syntax/src/generated/kind.rs @@ -381,6 +381,9 @@ pub enum CssSyntaxKind { CSS_QUERY_FEATURE_RANGE_INTERVAL, CSS_QUERY_FEATURE_RANGE_COMPARISON, CSS_KEYFRAMES_BLOCK, + CSS_KEYFRAMES_SCOPED_NAME, + CSS_KEYFRAMES_SCOPE_FUNCTION, + CSS_KEYFRAMES_SCOPE_PREFIX, CSS_KEYFRAMES_ITEM_LIST, CSS_KEYFRAMES_ITEM, CSS_KEYFRAMES_IDENT_SELECTOR, @@ -452,6 +455,7 @@ pub enum CssSyntaxKind { CSS_BOGUS_FONT_FEATURE_VALUES_ITEM, CSS_BOGUS_FONT_FAMILY_NAME, CSS_BOGUS_CUSTOM_IDENTIFIER, + CSS_BOGUS_KEYFRAMES_NAME, #[doc(hidden)] __LAST, } diff --git a/crates/biome_css_syntax/src/generated/macros.rs b/crates/biome_css_syntax/src/generated/macros.rs index 8ef04f810a05..44cad32aee8d 100644 --- a/crates/biome_css_syntax/src/generated/macros.rs +++ b/crates/biome_css_syntax/src/generated/macros.rs @@ -246,6 +246,19 @@ macro_rules! map_syntax_node { unsafe { $crate::CssKeyframesPercentageSelector::new_unchecked(node) }; $body } + $crate::CssSyntaxKind::CSS_KEYFRAMES_SCOPE_FUNCTION => { + let $pattern = + unsafe { $crate::CssKeyframesScopeFunction::new_unchecked(node) }; + $body + } + $crate::CssSyntaxKind::CSS_KEYFRAMES_SCOPE_PREFIX => { + let $pattern = unsafe { $crate::CssKeyframesScopePrefix::new_unchecked(node) }; + $body + } + $crate::CssSyntaxKind::CSS_KEYFRAMES_SCOPED_NAME => { + let $pattern = unsafe { $crate::CssKeyframesScopedName::new_unchecked(node) }; + $body + } $crate::CssSyntaxKind::CSS_LAYER_AT_RULE => { let $pattern = unsafe { $crate::CssLayerAtRule::new_unchecked(node) }; $body @@ -659,6 +672,10 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::CssBogusKeyframesItem::new_unchecked(node) }; $body } + $crate::CssSyntaxKind::CSS_BOGUS_KEYFRAMES_NAME => { + let $pattern = unsafe { $crate::CssBogusKeyframesName::new_unchecked(node) }; + $body + } $crate::CssSyntaxKind::CSS_BOGUS_LAYER => { let $pattern = unsafe { $crate::CssBogusLayer::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 bb8dda32c913..a869ed6bdc15 100644 --- a/crates/biome_css_syntax/src/generated/nodes.rs +++ b/crates/biome_css_syntax/src/generated/nodes.rs @@ -2225,7 +2225,7 @@ impl CssKeyframesAtRule { pub fn keyframes_token(&self) -> SyntaxResult { support::required_token(&self.syntax, 0usize) } - pub fn name(&self) -> SyntaxResult { + pub fn name(&self) -> SyntaxResult { support::required_node(&self.syntax, 1usize) } pub fn block(&self) -> SyntaxResult { @@ -2244,7 +2244,7 @@ impl Serialize for CssKeyframesAtRule { #[cfg_attr(feature = "serde", derive(Serialize))] pub struct CssKeyframesAtRuleFields { pub keyframes_token: SyntaxResult, - pub name: SyntaxResult, + pub name: SyntaxResult, pub block: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] @@ -2407,6 +2407,139 @@ pub struct CssKeyframesPercentageSelectorFields { pub selector: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct CssKeyframesScopeFunction { + pub(crate) syntax: SyntaxNode, +} +impl CssKeyframesScopeFunction { + #[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) -> CssKeyframesScopeFunctionFields { + CssKeyframesScopeFunctionFields { + scope: self.scope(), + l_paren_token: self.l_paren_token(), + name: self.name(), + r_paren_token: self.r_paren_token(), + } + } + pub fn scope(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn l_paren_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } + pub fn r_paren_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for CssKeyframesScopeFunction { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct CssKeyframesScopeFunctionFields { + pub scope: SyntaxResult, + pub l_paren_token: SyntaxResult, + pub name: SyntaxResult, + pub r_paren_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct CssKeyframesScopePrefix { + pub(crate) syntax: SyntaxNode, +} +impl CssKeyframesScopePrefix { + #[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) -> CssKeyframesScopePrefixFields { + CssKeyframesScopePrefixFields { + scope: self.scope(), + name: self.name(), + } + } + pub fn scope(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn name(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for CssKeyframesScopePrefix { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct CssKeyframesScopePrefixFields { + pub scope: SyntaxResult, + pub name: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct CssKeyframesScopedName { + pub(crate) syntax: SyntaxNode, +} +impl CssKeyframesScopedName { + #[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) -> CssKeyframesScopedNameFields { + CssKeyframesScopedNameFields { + colon_token: self.colon_token(), + scope: self.scope(), + } + } + pub fn colon_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn scope(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for CssKeyframesScopedName { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct CssKeyframesScopedNameFields { + pub colon_token: SyntaxResult, + pub scope: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct CssLayerAtRule { pub(crate) syntax: SyntaxNode, } @@ -6933,40 +7066,40 @@ impl AnyCssImportUrl { } #[derive(Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize))] -pub enum AnyCssKeyframeName { - CssCustomIdentifier(CssCustomIdentifier), - CssString(CssString), +pub enum AnyCssKeyframesBlock { + CssBogusBlock(CssBogusBlock), + CssKeyframesBlock(CssKeyframesBlock), } -impl AnyCssKeyframeName { - pub fn as_css_custom_identifier(&self) -> Option<&CssCustomIdentifier> { +impl AnyCssKeyframesBlock { + pub fn as_css_bogus_block(&self) -> Option<&CssBogusBlock> { match &self { - AnyCssKeyframeName::CssCustomIdentifier(item) => Some(item), + AnyCssKeyframesBlock::CssBogusBlock(item) => Some(item), _ => None, } } - pub fn as_css_string(&self) -> Option<&CssString> { + pub fn as_css_keyframes_block(&self) -> Option<&CssKeyframesBlock> { match &self { - AnyCssKeyframeName::CssString(item) => Some(item), + AnyCssKeyframesBlock::CssKeyframesBlock(item) => Some(item), _ => None, } } } #[derive(Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize))] -pub enum AnyCssKeyframesBlock { - CssBogusBlock(CssBogusBlock), - CssKeyframesBlock(CssKeyframesBlock), +pub enum AnyCssKeyframesIdentifier { + CssCustomIdentifier(CssCustomIdentifier), + CssString(CssString), } -impl AnyCssKeyframesBlock { - pub fn as_css_bogus_block(&self) -> Option<&CssBogusBlock> { +impl AnyCssKeyframesIdentifier { + pub fn as_css_custom_identifier(&self) -> Option<&CssCustomIdentifier> { match &self { - AnyCssKeyframesBlock::CssBogusBlock(item) => Some(item), + AnyCssKeyframesIdentifier::CssCustomIdentifier(item) => Some(item), _ => None, } } - pub fn as_css_keyframes_block(&self) -> Option<&CssKeyframesBlock> { + pub fn as_css_string(&self) -> Option<&CssString> { match &self { - AnyCssKeyframesBlock::CssKeyframesBlock(item) => Some(item), + AnyCssKeyframesIdentifier::CssString(item) => Some(item), _ => None, } } @@ -6993,6 +7126,53 @@ impl AnyCssKeyframesItem { } #[derive(Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize))] +pub enum AnyCssKeyframesName { + AnyCssKeyframesIdentifier(AnyCssKeyframesIdentifier), + CssBogusKeyframesName(CssBogusKeyframesName), + CssKeyframesScopedName(CssKeyframesScopedName), +} +impl AnyCssKeyframesName { + pub fn as_any_css_keyframes_identifier(&self) -> Option<&AnyCssKeyframesIdentifier> { + match &self { + AnyCssKeyframesName::AnyCssKeyframesIdentifier(item) => Some(item), + _ => None, + } + } + pub fn as_css_bogus_keyframes_name(&self) -> Option<&CssBogusKeyframesName> { + match &self { + AnyCssKeyframesName::CssBogusKeyframesName(item) => Some(item), + _ => None, + } + } + pub fn as_css_keyframes_scoped_name(&self) -> Option<&CssKeyframesScopedName> { + match &self { + AnyCssKeyframesName::CssKeyframesScopedName(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize))] +pub enum AnyCssKeyframesScope { + CssKeyframesScopeFunction(CssKeyframesScopeFunction), + CssKeyframesScopePrefix(CssKeyframesScopePrefix), +} +impl AnyCssKeyframesScope { + pub fn as_css_keyframes_scope_function(&self) -> Option<&CssKeyframesScopeFunction> { + match &self { + AnyCssKeyframesScope::CssKeyframesScopeFunction(item) => Some(item), + _ => None, + } + } + pub fn as_css_keyframes_scope_prefix(&self) -> Option<&CssKeyframesScopePrefix> { + match &self { + AnyCssKeyframesScope::CssKeyframesScopePrefix(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize))] pub enum AnyCssKeyframesSelector { CssBogusSelector(CssBogusSelector), CssKeyframesIdentSelector(CssKeyframesIdentSelector), @@ -10493,6 +10673,134 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for CssKeyframesScopeFunction { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(CSS_KEYFRAMES_SCOPE_FUNCTION as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == CSS_KEYFRAMES_SCOPE_FUNCTION + } + 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 CssKeyframesScopeFunction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CssKeyframesScopeFunction") + .field("scope", &support::DebugSyntaxResult(self.scope())) + .field( + "l_paren_token", + &support::DebugSyntaxResult(self.l_paren_token()), + ) + .field("name", &support::DebugSyntaxResult(self.name())) + .field( + "r_paren_token", + &support::DebugSyntaxResult(self.r_paren_token()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: CssKeyframesScopeFunction) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: CssKeyframesScopeFunction) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for CssKeyframesScopePrefix { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(CSS_KEYFRAMES_SCOPE_PREFIX as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == CSS_KEYFRAMES_SCOPE_PREFIX + } + 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 CssKeyframesScopePrefix { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CssKeyframesScopePrefix") + .field("scope", &support::DebugSyntaxResult(self.scope())) + .field("name", &support::DebugSyntaxResult(self.name())) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: CssKeyframesScopePrefix) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: CssKeyframesScopePrefix) -> SyntaxElement { + n.syntax.into() + } +} +impl AstNode for CssKeyframesScopedName { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(CSS_KEYFRAMES_SCOPED_NAME as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == CSS_KEYFRAMES_SCOPED_NAME + } + 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 CssKeyframesScopedName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CssKeyframesScopedName") + .field( + "colon_token", + &support::DebugSyntaxResult(self.colon_token()), + ) + .field("scope", &support::DebugSyntaxResult(self.scope())) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: CssKeyframesScopedName) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: CssKeyframesScopedName) -> SyntaxElement { + n.syntax.into() + } +} impl AstNode for CssLayerAtRule { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -16476,126 +16784,126 @@ impl From for SyntaxElement { node.into() } } -impl From for AnyCssKeyframeName { - fn from(node: CssCustomIdentifier) -> AnyCssKeyframeName { - AnyCssKeyframeName::CssCustomIdentifier(node) +impl From for AnyCssKeyframesBlock { + fn from(node: CssBogusBlock) -> AnyCssKeyframesBlock { + AnyCssKeyframesBlock::CssBogusBlock(node) } } -impl From for AnyCssKeyframeName { - fn from(node: CssString) -> AnyCssKeyframeName { - AnyCssKeyframeName::CssString(node) +impl From for AnyCssKeyframesBlock { + fn from(node: CssKeyframesBlock) -> AnyCssKeyframesBlock { + AnyCssKeyframesBlock::CssKeyframesBlock(node) } } -impl AstNode for AnyCssKeyframeName { +impl AstNode for AnyCssKeyframesBlock { type Language = Language; const KIND_SET: SyntaxKindSet = - CssCustomIdentifier::KIND_SET.union(CssString::KIND_SET); + CssBogusBlock::KIND_SET.union(CssKeyframesBlock::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { - matches!(kind, CSS_CUSTOM_IDENTIFIER | CSS_STRING) + matches!(kind, CSS_BOGUS_BLOCK | CSS_KEYFRAMES_BLOCK) } fn cast(syntax: SyntaxNode) -> Option { let res = match syntax.kind() { - CSS_CUSTOM_IDENTIFIER => { - AnyCssKeyframeName::CssCustomIdentifier(CssCustomIdentifier { syntax }) + CSS_BOGUS_BLOCK => AnyCssKeyframesBlock::CssBogusBlock(CssBogusBlock { syntax }), + CSS_KEYFRAMES_BLOCK => { + AnyCssKeyframesBlock::CssKeyframesBlock(CssKeyframesBlock { syntax }) } - CSS_STRING => AnyCssKeyframeName::CssString(CssString { syntax }), _ => return None, }; Some(res) } fn syntax(&self) -> &SyntaxNode { match self { - AnyCssKeyframeName::CssCustomIdentifier(it) => &it.syntax, - AnyCssKeyframeName::CssString(it) => &it.syntax, + AnyCssKeyframesBlock::CssBogusBlock(it) => &it.syntax, + AnyCssKeyframesBlock::CssKeyframesBlock(it) => &it.syntax, } } fn into_syntax(self) -> SyntaxNode { match self { - AnyCssKeyframeName::CssCustomIdentifier(it) => it.syntax, - AnyCssKeyframeName::CssString(it) => it.syntax, + AnyCssKeyframesBlock::CssBogusBlock(it) => it.syntax, + AnyCssKeyframesBlock::CssKeyframesBlock(it) => it.syntax, } } } -impl std::fmt::Debug for AnyCssKeyframeName { +impl std::fmt::Debug for AnyCssKeyframesBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - AnyCssKeyframeName::CssCustomIdentifier(it) => std::fmt::Debug::fmt(it, f), - AnyCssKeyframeName::CssString(it) => std::fmt::Debug::fmt(it, f), + AnyCssKeyframesBlock::CssBogusBlock(it) => std::fmt::Debug::fmt(it, f), + AnyCssKeyframesBlock::CssKeyframesBlock(it) => std::fmt::Debug::fmt(it, f), } } } -impl From for SyntaxNode { - fn from(n: AnyCssKeyframeName) -> SyntaxNode { +impl From for SyntaxNode { + fn from(n: AnyCssKeyframesBlock) -> SyntaxNode { match n { - AnyCssKeyframeName::CssCustomIdentifier(it) => it.into(), - AnyCssKeyframeName::CssString(it) => it.into(), + AnyCssKeyframesBlock::CssBogusBlock(it) => it.into(), + AnyCssKeyframesBlock::CssKeyframesBlock(it) => it.into(), } } } -impl From for SyntaxElement { - fn from(n: AnyCssKeyframeName) -> SyntaxElement { +impl From for SyntaxElement { + fn from(n: AnyCssKeyframesBlock) -> SyntaxElement { let node: SyntaxNode = n.into(); node.into() } } -impl From for AnyCssKeyframesBlock { - fn from(node: CssBogusBlock) -> AnyCssKeyframesBlock { - AnyCssKeyframesBlock::CssBogusBlock(node) +impl From for AnyCssKeyframesIdentifier { + fn from(node: CssCustomIdentifier) -> AnyCssKeyframesIdentifier { + AnyCssKeyframesIdentifier::CssCustomIdentifier(node) } } -impl From for AnyCssKeyframesBlock { - fn from(node: CssKeyframesBlock) -> AnyCssKeyframesBlock { - AnyCssKeyframesBlock::CssKeyframesBlock(node) +impl From for AnyCssKeyframesIdentifier { + fn from(node: CssString) -> AnyCssKeyframesIdentifier { + AnyCssKeyframesIdentifier::CssString(node) } } -impl AstNode for AnyCssKeyframesBlock { +impl AstNode for AnyCssKeyframesIdentifier { type Language = Language; const KIND_SET: SyntaxKindSet = - CssBogusBlock::KIND_SET.union(CssKeyframesBlock::KIND_SET); + CssCustomIdentifier::KIND_SET.union(CssString::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { - matches!(kind, CSS_BOGUS_BLOCK | CSS_KEYFRAMES_BLOCK) + matches!(kind, CSS_CUSTOM_IDENTIFIER | CSS_STRING) } fn cast(syntax: SyntaxNode) -> Option { let res = match syntax.kind() { - CSS_BOGUS_BLOCK => AnyCssKeyframesBlock::CssBogusBlock(CssBogusBlock { syntax }), - CSS_KEYFRAMES_BLOCK => { - AnyCssKeyframesBlock::CssKeyframesBlock(CssKeyframesBlock { syntax }) + CSS_CUSTOM_IDENTIFIER => { + AnyCssKeyframesIdentifier::CssCustomIdentifier(CssCustomIdentifier { syntax }) } + CSS_STRING => AnyCssKeyframesIdentifier::CssString(CssString { syntax }), _ => return None, }; Some(res) } fn syntax(&self) -> &SyntaxNode { match self { - AnyCssKeyframesBlock::CssBogusBlock(it) => &it.syntax, - AnyCssKeyframesBlock::CssKeyframesBlock(it) => &it.syntax, + AnyCssKeyframesIdentifier::CssCustomIdentifier(it) => &it.syntax, + AnyCssKeyframesIdentifier::CssString(it) => &it.syntax, } } fn into_syntax(self) -> SyntaxNode { match self { - AnyCssKeyframesBlock::CssBogusBlock(it) => it.syntax, - AnyCssKeyframesBlock::CssKeyframesBlock(it) => it.syntax, + AnyCssKeyframesIdentifier::CssCustomIdentifier(it) => it.syntax, + AnyCssKeyframesIdentifier::CssString(it) => it.syntax, } } } -impl std::fmt::Debug for AnyCssKeyframesBlock { +impl std::fmt::Debug for AnyCssKeyframesIdentifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - AnyCssKeyframesBlock::CssBogusBlock(it) => std::fmt::Debug::fmt(it, f), - AnyCssKeyframesBlock::CssKeyframesBlock(it) => std::fmt::Debug::fmt(it, f), + AnyCssKeyframesIdentifier::CssCustomIdentifier(it) => std::fmt::Debug::fmt(it, f), + AnyCssKeyframesIdentifier::CssString(it) => std::fmt::Debug::fmt(it, f), } } } -impl From for SyntaxNode { - fn from(n: AnyCssKeyframesBlock) -> SyntaxNode { +impl From for SyntaxNode { + fn from(n: AnyCssKeyframesIdentifier) -> SyntaxNode { match n { - AnyCssKeyframesBlock::CssBogusBlock(it) => it.into(), - AnyCssKeyframesBlock::CssKeyframesBlock(it) => it.into(), + AnyCssKeyframesIdentifier::CssCustomIdentifier(it) => it.into(), + AnyCssKeyframesIdentifier::CssString(it) => it.into(), } } } -impl From for SyntaxElement { - fn from(n: AnyCssKeyframesBlock) -> SyntaxElement { +impl From for SyntaxElement { + fn from(n: AnyCssKeyframesIdentifier) -> SyntaxElement { let node: SyntaxNode = n.into(); node.into() } @@ -16664,6 +16972,156 @@ impl From for SyntaxElement { node.into() } } +impl From for AnyCssKeyframesName { + fn from(node: CssBogusKeyframesName) -> AnyCssKeyframesName { + AnyCssKeyframesName::CssBogusKeyframesName(node) + } +} +impl From for AnyCssKeyframesName { + fn from(node: CssKeyframesScopedName) -> AnyCssKeyframesName { + AnyCssKeyframesName::CssKeyframesScopedName(node) + } +} +impl AstNode for AnyCssKeyframesName { + type Language = Language; + const KIND_SET: SyntaxKindSet = AnyCssKeyframesIdentifier::KIND_SET + .union(CssBogusKeyframesName::KIND_SET) + .union(CssKeyframesScopedName::KIND_SET); + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + CSS_BOGUS_KEYFRAMES_NAME | CSS_KEYFRAMES_SCOPED_NAME => true, + k if AnyCssKeyframesIdentifier::can_cast(k) => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + CSS_BOGUS_KEYFRAMES_NAME => { + AnyCssKeyframesName::CssBogusKeyframesName(CssBogusKeyframesName { syntax }) + } + CSS_KEYFRAMES_SCOPED_NAME => { + AnyCssKeyframesName::CssKeyframesScopedName(CssKeyframesScopedName { syntax }) + } + _ => { + if let Some(any_css_keyframes_identifier) = AnyCssKeyframesIdentifier::cast(syntax) + { + return Some(AnyCssKeyframesName::AnyCssKeyframesIdentifier( + any_css_keyframes_identifier, + )); + } + return None; + } + }; + Some(res) + } + fn syntax(&self) -> &SyntaxNode { + match self { + AnyCssKeyframesName::CssBogusKeyframesName(it) => &it.syntax, + AnyCssKeyframesName::CssKeyframesScopedName(it) => &it.syntax, + AnyCssKeyframesName::AnyCssKeyframesIdentifier(it) => it.syntax(), + } + } + fn into_syntax(self) -> SyntaxNode { + match self { + AnyCssKeyframesName::CssBogusKeyframesName(it) => it.syntax, + AnyCssKeyframesName::CssKeyframesScopedName(it) => it.syntax, + AnyCssKeyframesName::AnyCssKeyframesIdentifier(it) => it.into_syntax(), + } + } +} +impl std::fmt::Debug for AnyCssKeyframesName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AnyCssKeyframesName::AnyCssKeyframesIdentifier(it) => std::fmt::Debug::fmt(it, f), + AnyCssKeyframesName::CssBogusKeyframesName(it) => std::fmt::Debug::fmt(it, f), + AnyCssKeyframesName::CssKeyframesScopedName(it) => std::fmt::Debug::fmt(it, f), + } + } +} +impl From for SyntaxNode { + fn from(n: AnyCssKeyframesName) -> SyntaxNode { + match n { + AnyCssKeyframesName::AnyCssKeyframesIdentifier(it) => it.into(), + AnyCssKeyframesName::CssBogusKeyframesName(it) => it.into(), + AnyCssKeyframesName::CssKeyframesScopedName(it) => it.into(), + } + } +} +impl From for SyntaxElement { + fn from(n: AnyCssKeyframesName) -> SyntaxElement { + let node: SyntaxNode = n.into(); + node.into() + } +} +impl From for AnyCssKeyframesScope { + fn from(node: CssKeyframesScopeFunction) -> AnyCssKeyframesScope { + AnyCssKeyframesScope::CssKeyframesScopeFunction(node) + } +} +impl From for AnyCssKeyframesScope { + fn from(node: CssKeyframesScopePrefix) -> AnyCssKeyframesScope { + AnyCssKeyframesScope::CssKeyframesScopePrefix(node) + } +} +impl AstNode for AnyCssKeyframesScope { + type Language = Language; + const KIND_SET: SyntaxKindSet = + CssKeyframesScopeFunction::KIND_SET.union(CssKeyframesScopePrefix::KIND_SET); + fn can_cast(kind: SyntaxKind) -> bool { + matches!( + kind, + CSS_KEYFRAMES_SCOPE_FUNCTION | CSS_KEYFRAMES_SCOPE_PREFIX + ) + } + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + CSS_KEYFRAMES_SCOPE_FUNCTION => { + AnyCssKeyframesScope::CssKeyframesScopeFunction(CssKeyframesScopeFunction { + syntax, + }) + } + CSS_KEYFRAMES_SCOPE_PREFIX => { + AnyCssKeyframesScope::CssKeyframesScopePrefix(CssKeyframesScopePrefix { syntax }) + } + _ => return None, + }; + Some(res) + } + fn syntax(&self) -> &SyntaxNode { + match self { + AnyCssKeyframesScope::CssKeyframesScopeFunction(it) => &it.syntax, + AnyCssKeyframesScope::CssKeyframesScopePrefix(it) => &it.syntax, + } + } + fn into_syntax(self) -> SyntaxNode { + match self { + AnyCssKeyframesScope::CssKeyframesScopeFunction(it) => it.syntax, + AnyCssKeyframesScope::CssKeyframesScopePrefix(it) => it.syntax, + } + } +} +impl std::fmt::Debug for AnyCssKeyframesScope { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AnyCssKeyframesScope::CssKeyframesScopeFunction(it) => std::fmt::Debug::fmt(it, f), + AnyCssKeyframesScope::CssKeyframesScopePrefix(it) => std::fmt::Debug::fmt(it, f), + } + } +} +impl From for SyntaxNode { + fn from(n: AnyCssKeyframesScope) -> SyntaxNode { + match n { + AnyCssKeyframesScope::CssKeyframesScopeFunction(it) => it.into(), + AnyCssKeyframesScope::CssKeyframesScopePrefix(it) => it.into(), + } + } +} +impl From for SyntaxElement { + fn from(n: AnyCssKeyframesScope) -> SyntaxElement { + let node: SyntaxNode = n.into(); + node.into() + } +} impl From for AnyCssKeyframesSelector { fn from(node: CssBogusSelector) -> AnyCssKeyframesSelector { AnyCssKeyframesSelector::CssBogusSelector(node) @@ -20241,12 +20699,12 @@ impl std::fmt::Display for AnyCssImportUrl { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for AnyCssKeyframeName { +impl std::fmt::Display for AnyCssKeyframesBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for AnyCssKeyframesBlock { +impl std::fmt::Display for AnyCssKeyframesIdentifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } @@ -20256,6 +20714,16 @@ impl std::fmt::Display for AnyCssKeyframesItem { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for AnyCssKeyframesName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for AnyCssKeyframesScope { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for AnyCssKeyframesSelector { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -20736,6 +21204,21 @@ impl std::fmt::Display for CssKeyframesPercentageSelector { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for CssKeyframesScopeFunction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for CssKeyframesScopePrefix { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for CssKeyframesScopedName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for CssLayerAtRule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -21681,6 +22164,63 @@ impl From for SyntaxElement { } #[derive(Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize))] +pub struct CssBogusKeyframesName { + syntax: SyntaxNode, +} +impl CssBogusKeyframesName { + #[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 items(&self) -> SyntaxElementChildren { + support::elements(&self.syntax) + } +} +impl AstNode for CssBogusKeyframesName { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(CSS_BOGUS_KEYFRAMES_NAME as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == CSS_BOGUS_KEYFRAMES_NAME + } + 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 CssBogusKeyframesName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CssBogusKeyframesName") + .field("items", &DebugSyntaxElementChildren(self.items())) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: CssBogusKeyframesName) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: CssBogusKeyframesName) -> SyntaxElement { + n.syntax.into() + } +} +#[derive(Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize))] pub struct CssBogusLayer { syntax: SyntaxNode, } diff --git a/crates/biome_css_syntax/src/generated/nodes_mut.rs b/crates/biome_css_syntax/src/generated/nodes_mut.rs index 1ab97245d471..19b59d6ecfdc 100644 --- a/crates/biome_css_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_css_syntax/src/generated/nodes_mut.rs @@ -900,7 +900,7 @@ impl CssKeyframesAtRule { .splice_slots(0usize..=0usize, once(Some(element.into()))), ) } - pub fn with_name(self, element: AnyCssKeyframeName) -> Self { + pub fn with_name(self, element: AnyCssKeyframesName) -> Self { Self::unwrap_cast( self.syntax .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), @@ -963,6 +963,60 @@ impl CssKeyframesPercentageSelector { ) } } +impl CssKeyframesScopeFunction { + pub fn with_scope_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_l_paren_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: AnyCssKeyframesIdentifier) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_paren_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } +} +impl CssKeyframesScopePrefix { + pub fn with_scope_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_name(self, element: AnyCssKeyframesIdentifier) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } +} +impl CssKeyframesScopedName { + pub fn with_colon_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_scope(self, element: AnyCssKeyframesScope) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } +} impl CssLayerAtRule { pub fn with_layer_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( diff --git a/crates/biome_css_syntax/src/lib.rs b/crates/biome_css_syntax/src/lib.rs index 155c2edb57ff..085dc56c94eb 100644 --- a/crates/biome_css_syntax/src/lib.rs +++ b/crates/biome_css_syntax/src/lib.rs @@ -93,6 +93,7 @@ impl biome_rowan::SyntaxKind for CssSyntaxKind { | CSS_BOGUS_PROPERTY | CSS_BOGUS_PROPERTY_VALUE | CSS_BOGUS_DOCUMENT_MATCHER + | CSS_BOGUS_KEYFRAMES_NAME ) } @@ -114,6 +115,7 @@ impl biome_rowan::SyntaxKind for CssSyntaxKind { kind if AnyCssKeyframesItem::can_cast(*kind) => CSS_BOGUS_KEYFRAMES_ITEM, kind if AnyCssProperty::can_cast(*kind) => CSS_BOGUS_PROPERTY, kind if AnyCssDocumentMatcher::can_cast(*kind) => CSS_BOGUS_DOCUMENT_MATCHER, + kind if AnyCssKeyframesName::can_cast(*kind) => CSS_BOGUS_KEYFRAMES_NAME, _ => CSS_BOGUS, } diff --git a/crates/biome_formatter_test/src/spec.rs b/crates/biome_formatter_test/src/spec.rs index a389314d3b17..0596f42a29eb 100644 --- a/crates/biome_formatter_test/src/spec.rs +++ b/crates/biome_formatter_test/src/spec.rs @@ -13,6 +13,7 @@ use biome_rowan::{TextRange, TextSize}; use biome_service::settings::{ServiceLanguage, Settings}; use biome_service::workspace::{ DocumentFileSource, FeaturesBuilder, RegisterProjectFolderParams, SupportsFeatureParams, + UpdateSettingsParams, }; use biome_service::App; use std::ops::Range; @@ -30,7 +31,11 @@ pub struct SpecTestFile<'a> { } impl<'a> SpecTestFile<'a> { - pub fn try_from_file(input_file: &'a str, root_path: &'a Path) -> Option> { + pub fn try_from_file( + input_file: &'a str, + root_path: &'a Path, + settings: Option, + ) -> Option> { let mut console = EnvConsole::default(); let app = App::with_console(&mut console); let file_path = &input_file; @@ -48,6 +53,10 @@ impl<'a> SpecTestFile<'a> { path: None, }) .unwrap(); + + if let Some(settings) = settings { + app.workspace.update_settings(settings).unwrap(); + } let mut input_file = BiomePath::new(file_path); let can_format = app .workspace diff --git a/crates/biome_js_formatter/tests/spec_test.rs b/crates/biome_js_formatter/tests/spec_test.rs index f03f59474800..e3009bae7c6f 100644 --- a/crates/biome_js_formatter/tests/spec_test.rs +++ b/crates/biome_js_formatter/tests/spec_test.rs @@ -27,7 +27,7 @@ mod language { pub fn run(spec_input_file: &str, _expected_file: &str, test_directory: &str, file_type: &str) { let root_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/specs/")); - let Some(test_file) = SpecTestFile::try_from_file(spec_input_file, root_path) else { + let Some(test_file) = SpecTestFile::try_from_file(spec_input_file, root_path, None) else { return; }; diff --git a/crates/biome_json_formatter/tests/spec_test.rs b/crates/biome_json_formatter/tests/spec_test.rs index 340c6dcc9bb6..8bd77f680a0c 100644 --- a/crates/biome_json_formatter/tests/spec_test.rs +++ b/crates/biome_json_formatter/tests/spec_test.rs @@ -26,7 +26,7 @@ mod language { pub fn run(spec_input_file: &str, _expected_file: &str, test_directory: &str, _file_type: &str) { let root_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/specs/")); - let Some(test_file) = SpecTestFile::try_from_file(spec_input_file, root_path) else { + let Some(test_file) = SpecTestFile::try_from_file(spec_input_file, root_path, None) else { return; }; diff --git a/xtask/codegen/css.ungram b/xtask/codegen/css.ungram index 13e735ac109b..6b07342889aa 100644 --- a/xtask/codegen/css.ungram +++ b/xtask/codegen/css.ungram @@ -58,6 +58,7 @@ CssBogusPropertyValue = SyntaxElement* CssBogusDocumentMatcher = SyntaxElement* CssBogusFontFamilyName = SyntaxElement* CssBogusCustomIdentifier = SyntaxElement* +CssBogusKeyframesName = SyntaxElement* CssRoot = bom: 'UNICODE_BOM'? @@ -817,6 +818,8 @@ AnyCssContainerStyleInParens = // https://drafts.csswg.org/css-animations/#keyframes // @keyframes = @keyframes { } // = | +// | :() | : // for CSS Modules +// = global | local // = # { } // = from | to | @@ -824,16 +827,49 @@ AnyCssContainerStyleInParens = // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ CssKeyframesAtRule = 'keyframes' - name: AnyCssKeyframeName + name: AnyCssKeyframesName block: AnyCssKeyframesBlock +AnyCssKeyframesName = + CssKeyframesScopedName + | AnyCssKeyframesIdentifier + | CssBogusKeyframesName + AnyCssKeyframesBlock = CssKeyframesBlock | CssBogusBlock +// This grammar is specific to CSS Modules and defines how scoped keyframes are parsed. +// In CSS Modules, keyframes can be scoped locally or globally using the `:local` and `:global` pseudo-classes. +// @keyframes :local("test") {} +// ^^^^^^^^^^^^^^^ +// @keyframes :global test {} +// ^^^^^^^^^^^^^ +CssKeyframesScopedName = + ':' + scope: AnyCssKeyframesScope + +AnyCssKeyframesScope = CssKeyframesScopeFunction | CssKeyframesScopePrefix + +// @keyframes :local("test") {} +// ^^^^^^^^^^^^^^^ +CssKeyframesScopeFunction = + scope: ('global' | 'local') + '(' + name: AnyCssKeyframesIdentifier + ')' + +// @keyframes :global test {} +// ^^^^^^^^^^^^^ +CssKeyframesScopePrefix = + scope: ('global' | 'local') + name: AnyCssKeyframesIdentifier + +// End of CSS Modules specific grammar + // @keyframes "something" { from {} to {} } // ^^^^^^^^^^^ -AnyCssKeyframeName = CssCustomIdentifier | CssString +AnyCssKeyframesIdentifier = CssCustomIdentifier | CssString // @keyframes "something" { from {} to {} } // ^^^^^^^^^^^^^^^^^ diff --git a/xtask/codegen/src/css_kinds_src.rs b/xtask/codegen/src/css_kinds_src.rs index ba8954e50814..5446d107af8c 100644 --- a/xtask/codegen/src/css_kinds_src.rs +++ b/xtask/codegen/src/css_kinds_src.rs @@ -409,6 +409,9 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "CSS_QUERY_FEATURE_RANGE_INTERVAL", "CSS_QUERY_FEATURE_RANGE_COMPARISON", "CSS_KEYFRAMES_BLOCK", + "CSS_KEYFRAMES_SCOPED_NAME", + "CSS_KEYFRAMES_SCOPE_FUNCTION", + "CSS_KEYFRAMES_SCOPE_PREFIX", "CSS_KEYFRAMES_ITEM_LIST", "CSS_KEYFRAMES_ITEM", "CSS_KEYFRAMES_IDENT_SELECTOR", @@ -481,5 +484,6 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "CSS_BOGUS_FONT_FEATURE_VALUES_ITEM", "CSS_BOGUS_FONT_FAMILY_NAME", "CSS_BOGUS_CUSTOM_IDENTIFIER", + "CSS_BOGUS_KEYFRAMES_NAME", ], };