diff --git a/.changeset/loose-eyes-roll.md b/.changeset/loose-eyes-roll.md new file mode 100644 index 000000000000..e4be73d46290 --- /dev/null +++ b/.changeset/loose-eyes-roll.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#8840](https://github.com/biomejs/biome/issues/8840). Now the Biome CSS parser correctly parses `not + scroll-state` inside `@container` queries. diff --git a/crates/biome_css_formatter/src/css/any/container_query_in_parens.rs b/crates/biome_css_formatter/src/css/any/container_query_in_parens.rs index b43037b22002..87b972a57f8e 100644 --- a/crates/biome_css_formatter/src/css/any/container_query_in_parens.rs +++ b/crates/biome_css_formatter/src/css/any/container_query_in_parens.rs @@ -8,6 +8,7 @@ impl FormatRule for FormatAnyCssContainerQueryInPa type Context = CssFormatContext; fn fmt(&self, node: &AnyCssContainerQueryInParens, f: &mut CssFormatter) -> FormatResult<()> { match node { + AnyCssContainerQueryInParens::AnyCssValue(node) => node.format().fmt(f), AnyCssContainerQueryInParens::CssContainerQueryInParens(node) => node.format().fmt(f), AnyCssContainerQueryInParens::CssContainerSizeFeatureInParens(node) => { node.format().fmt(f) diff --git a/crates/biome_css_parser/src/syntax/at_rule/container/mod.rs b/crates/biome_css_parser/src/syntax/at_rule/container/mod.rs index a5488380724b..cd5e54c602ac 100644 --- a/crates/biome_css_parser/src/syntax/at_rule/container/mod.rs +++ b/crates/biome_css_parser/src/syntax/at_rule/container/mod.rs @@ -11,7 +11,10 @@ use crate::syntax::at_rule::error::{ use crate::syntax::at_rule::feature::{expected_any_query_feature, parse_any_query_feature}; use crate::syntax::block::parse_conditional_block; use crate::syntax::parse_error::expected_non_css_wide_keyword_identifier; -use crate::syntax::{is_at_declaration, parse_custom_identifier, parse_declaration}; +use crate::syntax::value::function::is_nth_at_function; +use crate::syntax::{ + is_at_declaration, parse_any_value, parse_custom_identifier, parse_declaration, +}; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; use biome_parser::parse_recovery::ParseRecovery; @@ -199,6 +202,20 @@ fn is_at_container_not_query(p: &mut CssParser) -> bool { p.at(T![not]) } +#[inline] +fn is_at_container_scroll_state_query(p: &mut CssParser) -> bool { + is_nth_at_function(p, 0) && p.cur_text().eq_ignore_ascii_case("scroll-state") +} + +#[inline] +fn parse_container_scroll_state_query(p: &mut CssParser) -> ParsedSyntax { + if !is_at_container_scroll_state_query(p) { + return Absent; + } + + parse_any_value(p) +} + /// Parses a negated container query using the `not(...)` syntax. /// /// # Example @@ -233,6 +250,8 @@ pub(crate) fn parse_any_container_query_in_parens(p: &mut CssParser) -> ParsedSy parse_container_query_in_parens(p) } else if is_at_container_size_feature_in_parens(p) { parse_container_size_feature_in_parens(p) + } else if is_at_container_scroll_state_query(p) { + parse_container_scroll_state_query(p) } else { Absent } @@ -240,7 +259,10 @@ pub(crate) fn parse_any_container_query_in_parens(p: &mut CssParser) -> ParsedSy #[inline] fn is_at_container_query_in_parens(p: &mut CssParser) -> bool { - p.at(T!['(']) && (p.nth_at(1, T![not]) || p.nth_at(1, T!['('])) + p.at(T!['(']) + && (p.nth_at(1, T![not]) + || p.nth_at(1, T!['(']) + || (p.nth_at(1, T![ident]) && p.nth_at(2, T!['(']))) } /// Parses a parenthesized container query. diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_and_query_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_and_query_error.css.snap index 56820c04280a..54b1218fde3a 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_and_query_error.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_and_query_error.css.snap @@ -1,5 +1,6 @@ --- source: crates/biome_css_parser/tests/spec_test.rs +assertion_line: 179 expression: snapshot --- diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_or_query_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_or_query_error.css.snap index 802a8a7c18d2..bc601d063270 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_or_query_error.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_or_query_error.css.snap @@ -1,5 +1,6 @@ --- source: crates/biome_css_parser/tests/spec_test.rs +assertion_line: 179 expression: snapshot --- diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container.css b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container.css index 6f3df57a56bb..ab02397f6737 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container.css +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container.css @@ -36,3 +36,9 @@ @container name not style(color: red) {} @container style (--responsive: true) { } + +@container not scroll-state(stuck) { } + +@container not (scroll-state(stuck)) { } + +@container (not scroll-state(stuck)) { } diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container.css.snap index 4e8186e1aedb..5f1188ecef36 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container.css.snap @@ -45,6 +45,12 @@ expression: snapshot @container style (--responsive: true) { } +@container not scroll-state(stuck) { } + +@container not (scroll-state(stuck)) { } + +@container (not scroll-state(stuck)) { } + ``` @@ -766,17 +772,128 @@ CssRoot { }, }, }, + CssAtRule { + at_token: AT@891..894 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@894..904 "container" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@904..908 "not" [] [Whitespace(" ")], + }, + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@908..920 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@920..921 "(" [] [], + items: CssParameterList [ + CssParameter { + any_css_expression: CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@921..926 "stuck" [] [], + }, + ], + }, + }, + ], + r_paren_token: R_PAREN@926..928 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@928..931 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@931..932 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@932..935 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@935..945 "container" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@945..949 "not" [] [Whitespace(" ")], + }, + query: CssContainerQueryInParens { + l_paren_token: L_PAREN@949..950 "(" [] [], + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@950..962 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@962..963 "(" [] [], + items: CssParameterList [ + CssParameter { + any_css_expression: CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@963..968 "stuck" [] [], + }, + ], + }, + }, + ], + r_paren_token: R_PAREN@968..969 ")" [] [], + }, + r_paren_token: R_PAREN@969..971 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@971..974 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@974..975 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@975..978 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@978..988 "container" [] [Whitespace(" ")], + name: missing (optional), + query: CssContainerQueryInParens { + l_paren_token: L_PAREN@988..989 "(" [] [], + query: CssContainerNotQuery { + not_token: NOT_KW@989..993 "not" [] [Whitespace(" ")], + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@993..1005 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@1005..1006 "(" [] [], + items: CssParameterList [ + CssParameter { + any_css_expression: CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@1006..1011 "stuck" [] [], + }, + ], + }, + }, + ], + r_paren_token: R_PAREN@1011..1012 ")" [] [], + }, + }, + r_paren_token: R_PAREN@1012..1014 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@1014..1017 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@1017..1018 "}" [] [], + }, + }, + }, ], - eof_token: EOF@891..892 "" [Newline("\n")] [], + eof_token: EOF@1018..1019 "" [Newline("\n")] [], } ``` ## CST ``` -0: CSS_ROOT@0..892 +0: CSS_ROOT@0..1019 0: (empty) - 1: CSS_ROOT_ITEM_LIST@0..891 + 1: CSS_ROOT_ITEM_LIST@0..1018 0: CSS_AT_RULE@0..41 0: AT@0..1 "@" [] [] 1: CSS_CONTAINER_AT_RULE@1..41 @@ -1270,6 +1387,79 @@ CssRoot { 0: L_CURLY@887..890 "{" [] [Whitespace(" ")] 1: CSS_RULE_LIST@890..890 2: R_CURLY@890..891 "}" [] [] - 2: EOF@891..892 "" [Newline("\n")] [] + 20: CSS_AT_RULE@891..932 + 0: AT@891..894 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@894..932 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@894..928 + 0: CONTAINER_KW@894..904 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@904..908 + 0: IDENT@904..908 "not" [] [Whitespace(" ")] + 2: CSS_FUNCTION@908..928 + 0: CSS_IDENTIFIER@908..920 + 0: IDENT@908..920 "scroll-state" [] [] + 1: L_PAREN@920..921 "(" [] [] + 2: CSS_PARAMETER_LIST@921..926 + 0: CSS_PARAMETER@921..926 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@921..926 + 0: CSS_COMPONENT_VALUE_LIST@921..926 + 0: CSS_IDENTIFIER@921..926 + 0: IDENT@921..926 "stuck" [] [] + 3: R_PAREN@926..928 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@928..932 + 0: L_CURLY@928..931 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@931..931 + 2: R_CURLY@931..932 "}" [] [] + 21: CSS_AT_RULE@932..975 + 0: AT@932..935 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@935..975 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@935..971 + 0: CONTAINER_KW@935..945 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@945..949 + 0: IDENT@945..949 "not" [] [Whitespace(" ")] + 2: CSS_CONTAINER_QUERY_IN_PARENS@949..971 + 0: L_PAREN@949..950 "(" [] [] + 1: CSS_FUNCTION@950..969 + 0: CSS_IDENTIFIER@950..962 + 0: IDENT@950..962 "scroll-state" [] [] + 1: L_PAREN@962..963 "(" [] [] + 2: CSS_PARAMETER_LIST@963..968 + 0: CSS_PARAMETER@963..968 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@963..968 + 0: CSS_COMPONENT_VALUE_LIST@963..968 + 0: CSS_IDENTIFIER@963..968 + 0: IDENT@963..968 "stuck" [] [] + 3: R_PAREN@968..969 ")" [] [] + 2: R_PAREN@969..971 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@971..975 + 0: L_CURLY@971..974 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@974..974 + 2: R_CURLY@974..975 "}" [] [] + 22: CSS_AT_RULE@975..1018 + 0: AT@975..978 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@978..1018 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@978..1014 + 0: CONTAINER_KW@978..988 "container" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_CONTAINER_QUERY_IN_PARENS@988..1014 + 0: L_PAREN@988..989 "(" [] [] + 1: CSS_CONTAINER_NOT_QUERY@989..1012 + 0: NOT_KW@989..993 "not" [] [Whitespace(" ")] + 1: CSS_FUNCTION@993..1012 + 0: CSS_IDENTIFIER@993..1005 + 0: IDENT@993..1005 "scroll-state" [] [] + 1: L_PAREN@1005..1006 "(" [] [] + 2: CSS_PARAMETER_LIST@1006..1011 + 0: CSS_PARAMETER@1006..1011 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@1006..1011 + 0: CSS_COMPONENT_VALUE_LIST@1006..1011 + 0: CSS_IDENTIFIER@1006..1011 + 0: IDENT@1006..1011 "stuck" [] [] + 3: R_PAREN@1011..1012 ")" [] [] + 2: R_PAREN@1012..1014 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@1014..1018 + 0: L_CURLY@1014..1017 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@1017..1017 + 2: R_CURLY@1017..1018 "}" [] [] + 2: EOF@1018..1019 "" [Newline("\n")] [] ``` diff --git a/crates/biome_css_syntax/src/generated/nodes.rs b/crates/biome_css_syntax/src/generated/nodes.rs index ea2de285e2e1..da296f6dbbb2 100644 --- a/crates/biome_css_syntax/src/generated/nodes.rs +++ b/crates/biome_css_syntax/src/generated/nodes.rs @@ -10122,11 +10122,18 @@ impl AnyCssContainerQuery { } #[derive(Clone, PartialEq, Eq, Hash, Serialize)] pub enum AnyCssContainerQueryInParens { + AnyCssValue(AnyCssValue), CssContainerQueryInParens(CssContainerQueryInParens), CssContainerSizeFeatureInParens(CssContainerSizeFeatureInParens), CssContainerStyleQueryInParens(CssContainerStyleQueryInParens), } impl AnyCssContainerQueryInParens { + pub fn as_any_css_value(&self) -> Option<&AnyCssValue> { + match &self { + Self::AnyCssValue(item) => Some(item), + _ => None, + } + } pub fn as_css_container_query_in_parens(&self) -> Option<&CssContainerQueryInParens> { match &self { Self::CssContainerQueryInParens(item) => Some(item), @@ -25516,16 +25523,18 @@ impl From for AnyCssContainerQueryInParens { } impl AstNode for AnyCssContainerQueryInParens { type Language = Language; - const KIND_SET: SyntaxKindSet = CssContainerQueryInParens::KIND_SET + const KIND_SET: SyntaxKindSet = AnyCssValue::KIND_SET + .union(CssContainerQueryInParens::KIND_SET) .union(CssContainerSizeFeatureInParens::KIND_SET) .union(CssContainerStyleQueryInParens::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { - matches!( - kind, + match kind { CSS_CONTAINER_QUERY_IN_PARENS - | CSS_CONTAINER_SIZE_FEATURE_IN_PARENS - | CSS_CONTAINER_STYLE_QUERY_IN_PARENS - ) + | CSS_CONTAINER_SIZE_FEATURE_IN_PARENS + | CSS_CONTAINER_STYLE_QUERY_IN_PARENS => true, + k if AnyCssValue::can_cast(k) => true, + _ => false, + } } fn cast(syntax: SyntaxNode) -> Option { let res = match syntax.kind() { @@ -25538,7 +25547,12 @@ impl AstNode for AnyCssContainerQueryInParens { CSS_CONTAINER_STYLE_QUERY_IN_PARENS => { Self::CssContainerStyleQueryInParens(CssContainerStyleQueryInParens { syntax }) } - _ => return None, + _ => { + if let Some(any_css_value) = AnyCssValue::cast(syntax) { + return Some(Self::AnyCssValue(any_css_value)); + } + return None; + } }; Some(res) } @@ -25547,6 +25561,7 @@ impl AstNode for AnyCssContainerQueryInParens { Self::CssContainerQueryInParens(it) => it.syntax(), Self::CssContainerSizeFeatureInParens(it) => it.syntax(), Self::CssContainerStyleQueryInParens(it) => it.syntax(), + Self::AnyCssValue(it) => it.syntax(), } } fn into_syntax(self) -> SyntaxNode { @@ -25554,12 +25569,14 @@ impl AstNode for AnyCssContainerQueryInParens { Self::CssContainerQueryInParens(it) => it.into_syntax(), Self::CssContainerSizeFeatureInParens(it) => it.into_syntax(), Self::CssContainerStyleQueryInParens(it) => it.into_syntax(), + Self::AnyCssValue(it) => it.into_syntax(), } } } impl std::fmt::Debug for AnyCssContainerQueryInParens { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::AnyCssValue(it) => std::fmt::Debug::fmt(it, f), Self::CssContainerQueryInParens(it) => std::fmt::Debug::fmt(it, f), Self::CssContainerSizeFeatureInParens(it) => std::fmt::Debug::fmt(it, f), Self::CssContainerStyleQueryInParens(it) => std::fmt::Debug::fmt(it, f), @@ -25569,6 +25586,7 @@ impl std::fmt::Debug for AnyCssContainerQueryInParens { impl From for SyntaxNode { fn from(n: AnyCssContainerQueryInParens) -> Self { match n { + AnyCssContainerQueryInParens::AnyCssValue(it) => it.into_syntax(), AnyCssContainerQueryInParens::CssContainerQueryInParens(it) => it.into_syntax(), AnyCssContainerQueryInParens::CssContainerSizeFeatureInParens(it) => it.into_syntax(), AnyCssContainerQueryInParens::CssContainerStyleQueryInParens(it) => it.into_syntax(), diff --git a/xtask/codegen/css.ungram b/xtask/codegen/css.ungram index efe2972958d5..907f7ac14dba 100644 --- a/xtask/codegen/css.ungram +++ b/xtask/codegen/css.ungram @@ -947,6 +947,7 @@ AnyCssContainerQueryInParens = CssContainerQueryInParens | CssContainerSizeFeatureInParens | CssContainerStyleQueryInParens + | AnyCssValue // general-enclosed // ( ) // @container name (width <= 500px) and ((width <= 500px) or (width <= 500px)) { }