From b7ea069c52acca36761abdb29afa20dc2f32795a Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:05:26 +0200 Subject: [PATCH 1/2] fix(css): parse scroll-state and general-enclosed queries --- ...se-positive-container-query-diagnostics.md | 10 + .../at-rule/container-general-enclosed.scss | 17 + .../container-general-enclosed.scss.snap | 70 ++ .../at-rule/supports-general-enclosed.scss | 11 + .../supports-general-enclosed.scss.snap | 59 ++ .../src/syntax/at_rule/container/mod.rs | 31 +- .../src/syntax/at_rule/supports/mod.rs | 4 +- crates/biome_css_parser/src/syntax/mod.rs | 157 +++-- .../src/syntax/value/function.rs | 254 +++++--- .../biome_css_parser/src/syntax/value/url.rs | 97 ++- ..._rule_container_general_enclosed_error.css | 11 + ..._container_general_enclosed_error.css.snap | 568 +++++++++++++++++ .../at_rule_container_scroll_state_error.css | 5 + ...rule_container_scroll_state_error.css.snap | 294 +++++++++ ...t_rule_supports_general_enclosed_error.css | 11 + ...e_supports_general_enclosed_error.css.snap | 541 ++++++++++++++++ .../at-rule/container-general-enclosed.scss | 11 + .../container-general-enclosed.scss.snap | 597 ++++++++++++++++++ .../at-rule/supports-general-enclosed.scss | 11 + .../supports-general-enclosed.scss.snap | 570 +++++++++++++++++ .../at_rule_container_general_enclosed.css | 5 + ...t_rule_container_general_enclosed.css.snap | 234 +++++++ .../at_rule_container_scroll_state.css | 11 + .../at_rule_container_scroll_state.css.snap | 443 +++++++++++++ .../at-rule/container-general-enclosed.scss | 5 + .../container-general-enclosed.scss.snap | 235 +++++++ .../scss/at-rule/container-scroll-state.scss | 6 + .../at-rule/container-scroll-state.scss.snap | 526 ++++++++++----- 28 files changed, 4493 insertions(+), 301 deletions(-) create mode 100644 .changeset/fix-false-positive-container-query-diagnostics.md create mode 100644 crates/biome_css_formatter/tests/specs/css/scss/at-rule/container-general-enclosed.scss create mode 100644 crates/biome_css_formatter/tests/specs/css/scss/at-rule/container-general-enclosed.scss.snap create mode 100644 crates/biome_css_formatter/tests/specs/css/scss/at-rule/supports-general-enclosed.scss create mode 100644 crates/biome_css_formatter/tests/specs/css/scss/at-rule/supports-general-enclosed.scss.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_general_enclosed_error.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_general_enclosed_error.css.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_scroll_state_error.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_scroll_state_error.css.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_supports/at_rule_supports_general_enclosed_error.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_supports/at_rule_supports_general_enclosed_error.css.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/container-general-enclosed.scss create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/container-general-enclosed.scss.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-general-enclosed.scss create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-general-enclosed.scss.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_general_enclosed.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_general_enclosed.css.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_scroll_state.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_scroll_state.css.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-general-enclosed.scss create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-general-enclosed.scss.snap diff --git a/.changeset/fix-false-positive-container-query-diagnostics.md b/.changeset/fix-false-positive-container-query-diagnostics.md new file mode 100644 index 000000000000..1d7751ced310 --- /dev/null +++ b/.changeset/fix-false-positive-container-query-diagnostics.md @@ -0,0 +1,10 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9253](https://github.com/biomejs/biome/issues/9253): removed false-positive diagnostics for valid `@container`/`@supports` general-enclosed queries. + +```css +@container scroll-state(scrolled: bottom) { } +@supports foo(bar: baz) { } +``` diff --git a/crates/biome_css_formatter/tests/specs/css/scss/at-rule/container-general-enclosed.scss b/crates/biome_css_formatter/tests/specs/css/scss/at-rule/container-general-enclosed.scss new file mode 100644 index 000000000000..1c7198dce578 --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/scss/at-rule/container-general-enclosed.scss @@ -0,0 +1,17 @@ +@container (foo(bar: baz)) { + .a { color: red; } +} + +@container (foo(bar: baz)) and (width > 100px) { + .b { color: blue; } +} + +@container (foo(url("a.svg" namespace.fn(test)))) { + .c { color: green; } +} + +@container main-layout and( test ) { } + +@container main-layout or( test ) { } + +@container main-layout foo( url("a.svg" fn(test) ) ) { } diff --git a/crates/biome_css_formatter/tests/specs/css/scss/at-rule/container-general-enclosed.scss.snap b/crates/biome_css_formatter/tests/specs/css/scss/at-rule/container-general-enclosed.scss.snap new file mode 100644 index 000000000000..e004e188b50d --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/scss/at-rule/container-general-enclosed.scss.snap @@ -0,0 +1,70 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +assertion_line: 212 +info: css/scss/at-rule/container-general-enclosed.scss +--- + +# Input + +```scss +@container (foo(bar: baz)) { + .a { color: red; } +} + +@container (foo(bar: baz)) and (width > 100px) { + .b { color: blue; } +} + +@container (foo(url("a.svg" namespace.fn(test)))) { + .c { color: green; } +} + +@container main-layout and( test ) { } + +@container main-layout or( test ) { } + +@container main-layout foo( url("a.svg" fn(test) ) ) { } + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Quote style: Double Quotes +Trailing newline: true +----- + +```scss +@container (foo(bar: baz)) { + .a { color: red; } +} + +@container (foo(bar: baz)) and (width > 100px) { + .b { color: blue; } +} + +@container (foo(url("a.svg"namespace.fn(test)))) { + .c { + color: green; + } +} + +@container main-layout and(test) { +} + +@container main-layout or(test) { +} + +@container main-layout foo(url("a.svg"fn(test))) { +} + +``` diff --git a/crates/biome_css_formatter/tests/specs/css/scss/at-rule/supports-general-enclosed.scss b/crates/biome_css_formatter/tests/specs/css/scss/at-rule/supports-general-enclosed.scss new file mode 100644 index 000000000000..deed5815d867 --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/scss/at-rule/supports-general-enclosed.scss @@ -0,0 +1,11 @@ +@supports foo(bar: baz) { + .a { color: red; } +} + +@supports (foo(bar: baz)) and (display: grid) { + .b { display: grid; } +} + +@supports foo(url("a.svg" namespace.fn(test))) { + .c { color: green; } +} diff --git a/crates/biome_css_formatter/tests/specs/css/scss/at-rule/supports-general-enclosed.scss.snap b/crates/biome_css_formatter/tests/specs/css/scss/at-rule/supports-general-enclosed.scss.snap new file mode 100644 index 000000000000..ce03abe2873b --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/scss/at-rule/supports-general-enclosed.scss.snap @@ -0,0 +1,59 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +assertion_line: 212 +info: css/scss/at-rule/supports-general-enclosed.scss +--- + +# Input + +```scss +@supports foo(bar: baz) { + .a { color: red; } +} + +@supports (foo(bar: baz)) and (display: grid) { + .b { display: grid; } +} + +@supports foo(url("a.svg" namespace.fn(test))) { + .c { color: green; } +} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Quote style: Double Quotes +Trailing newline: true +----- + +```scss +@supports foo(bar: baz) { + .a { + color: red; + } +} + +@supports (foo(bar: baz)) and (display: grid) { + .b { + display: grid; + } +} + +@supports foo(url("a.svg"namespace.fn(test))) { + .c { + color: green; + } +} + +``` 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 01f96fd1208d..279ce82b5a56 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 @@ -12,9 +12,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::value::function::is_nth_at_function; +use crate::syntax::value::function::{is_at_any_css_function, is_nth_at_css_function}; use crate::syntax::{ - is_at_declaration, parse_custom_identifier, parse_declaration, parse_regular_identifier, + is_at_declaration, parse_any_css_value, parse_custom_identifier, parse_declaration, + parse_regular_identifier, }; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; @@ -116,7 +117,7 @@ pub(crate) fn parse_any_container_query(p: &mut CssParser) -> ParsedSyntax { if is_at_container_not_query(p) { parse_container_not_query(p) } else { - parse_any_container_query_in_parens(p).map(|lhs| match p.cur() { + parse_any_container_query_in_parens(p, None).map(|lhs| match p.cur() { T![and] => parse_container_and_query(p, lhs), T![or] => parse_container_or_query(p, lhs), _ => lhs, @@ -141,7 +142,7 @@ fn parse_container_and_query(p: &mut CssParser, lhs: CompletedMarker) -> Complet let m = lhs.precede(p); p.bump(T![and]); - let recovery_result = parse_any_container_query_in_parens(p) + let recovery_result = parse_any_container_query_in_parens(p, Some(T![and])) .or_recover( p, &AnyInParensChainParseRecovery::new(T![and]), @@ -177,7 +178,7 @@ fn parse_container_or_query(p: &mut CssParser, lhs: CompletedMarker) -> Complete let m = lhs.precede(p); p.bump(T![or]); - let recovery_result = parse_any_container_query_in_parens(p) + let recovery_result = parse_any_container_query_in_parens(p, Some(T![or])) .or_recover( p, &AnyInParensChainParseRecovery::new(T![or]), @@ -205,7 +206,7 @@ fn is_at_container_not_query(p: &mut CssParser) -> bool { #[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") + is_nth_at_css_function(p, 0) && p.cur_text().eq_ignore_ascii_case("scroll-state") } #[inline] @@ -404,7 +405,7 @@ fn parse_container_not_query(p: &mut CssParser) -> ParsedSyntax { let m = p.start(); p.bump(T![not]); - parse_any_container_query_in_parens(p) + parse_any_container_query_in_parens(p, None) .or_recover( p, &AnyQueryParseRecovery, @@ -416,7 +417,10 @@ fn parse_container_not_query(p: &mut CssParser) -> ParsedSyntax { } #[inline] -pub(crate) fn parse_any_container_query_in_parens(p: &mut CssParser) -> ParsedSyntax { +pub(crate) fn parse_any_container_query_in_parens( + p: &mut CssParser, + chain_token: Option, +) -> ParsedSyntax { if is_at_container_style_query_in_parens(p) { parse_container_style_query_in_parens(p) } else if is_at_container_query_in_parens(p) { @@ -425,6 +429,17 @@ pub(crate) fn parse_any_container_query_in_parens(p: &mut CssParser) -> ParsedSy parse_container_size_feature_in_parens(p) } else if is_at_container_scroll_state_query(p) { parse_container_scroll_state_query(p) + } else if is_at_any_css_function(p) { + // Here we're inside a branch, + // which means that the parser is at unknown syntax. + // + // If we're inside a chain, we can try to recover over a chain token. + if let Some(chain_token) = chain_token + && p.at(chain_token) + { + return Absent; + } + parse_any_css_value(p) } else { Absent } diff --git a/crates/biome_css_parser/src/syntax/at_rule/supports/mod.rs b/crates/biome_css_parser/src/syntax/at_rule/supports/mod.rs index 1012e04ae683..5a51f837119f 100644 --- a/crates/biome_css_parser/src/syntax/at_rule/supports/mod.rs +++ b/crates/biome_css_parser/src/syntax/at_rule/supports/mod.rs @@ -8,7 +8,7 @@ use crate::syntax::at_rule::supports::error::{ use crate::syntax::block::parse_conditional_block; use crate::syntax::parse_error::{expected_declaration, expected_selector}; use crate::syntax::selector::parse_selector; -use crate::syntax::{is_nth_at_identifier, parse_any_value, parse_declaration}; +use crate::syntax::{is_nth_at_identifier, parse_any_css_value, parse_declaration}; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; use biome_parser::parse_recovery::ParseRecovery; @@ -230,7 +230,7 @@ fn parse_any_supports_condition_in_parens( { return Absent; } - parse_any_value(p) + parse_any_css_value(p) } } diff --git a/crates/biome_css_parser/src/syntax/mod.rs b/crates/biome_css_parser/src/syntax/mod.rs index 6027981b3bd2..4750ab08f28e 100644 --- a/crates/biome_css_parser/src/syntax/mod.rs +++ b/crates/biome_css_parser/src/syntax/mod.rs @@ -14,7 +14,7 @@ use crate::parser::CssParser; use crate::syntax::at_rule::{is_at_at_rule, parse_at_rule}; use crate::syntax::block::{DeclarationOrRuleList, parse_declaration_or_rule_list_block}; use crate::syntax::parse_error::{ - expected_any_rule, expected_component_value, expected_non_css_wide_keyword_identifier, + expected_any_rule, expected_non_css_wide_keyword_identifier, inconsistent_scss_bracketed_list_separators, scss_only_syntax_error, tailwind_disabled, }; use crate::syntax::property::color::{is_at_color, parse_color}; @@ -28,17 +28,17 @@ use crate::syntax::selector::SelectorList; use crate::syntax::selector::is_nth_at_selector; use crate::syntax::selector::relative_selector::{RelativeSelectorList, is_at_relative_selector}; use crate::syntax::value::function::{ - BINARY_OPERATION_TOKEN, parse_tailwind_value_theme_reference, + is_at_any_function_with_context, parse_any_function_with_context, + parse_tailwind_value_theme_reference, }; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, EmbeddingKind, 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::prelude::ParsedSyntax; use biome_parser::prelude::ParsedSyntax::{Absent, Present}; -use biome_parser::{Parser, SyntaxFeature, token_set}; +use biome_parser::{Parser, SyntaxFeature}; use value::dimension::{is_at_any_dimension, parse_any_dimension}; -use value::function::{is_at_any_function, parse_any_function}; pub(crate) enum CssSyntaxFeatures { /// Enable support for SCSS-specific syntax. @@ -331,11 +331,31 @@ fn parse_metavariable(p: &mut CssParser) -> ParsedSyntax { #[inline] pub(crate) fn is_at_any_value(p: &mut CssParser) -> bool { - is_at_any_function(p) - || is_at_scss_identifier(p) - || is_at_scss_qualified_name(p) - || is_at_scss_parent_selector_value(p) - || is_at_identifier(p) + is_at_any_value_with_mode(p, ValueParsingMode::ScssAware) +} + +/// Checks if the parser is at the start of any value for the provided parsing mode. +#[inline] +pub(crate) fn is_at_any_value_with_mode(p: &mut CssParser, mode: ValueParsingMode) -> bool { + is_at_any_value_with_context(p, ValueParsingContext::new(p, mode)) +} + +#[inline] +pub(crate) fn is_at_any_value_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> bool { + is_at_any_function_with_context(p, context) + || (context.is_scss_syntax_allowed() + && (is_at_scss_identifier(p) + || is_at_scss_qualified_name(p) + || is_at_scss_parent_selector_value(p))) + || is_at_any_non_function_css_value(p) +} + +#[inline] +fn is_at_any_non_function_css_value(p: &mut CssParser) -> bool { + is_at_identifier(p) || p.at(CSS_STRING_LITERAL) || is_at_any_dimension(p) || p.at(CSS_NUMBER_LITERAL) @@ -348,19 +368,97 @@ pub(crate) fn is_at_any_value(p: &mut CssParser) -> bool { #[inline] pub(crate) fn parse_any_value(p: &mut CssParser) -> ParsedSyntax { - if is_at_any_function(p) { - parse_any_function(p) - } else if is_at_scss_identifier(p) { + parse_any_value_with_mode(p, ValueParsingMode::ScssAware) +} + +/// Parses any value using CSS-only branches. +/// +/// This intentionally skips SCSS-only constructs and is used by +/// `` fallback branches. +#[inline] +pub(crate) fn parse_any_css_value(p: &mut CssParser) -> ParsedSyntax { + parse_any_value_with_mode(p, ValueParsingMode::CssOnly) +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) enum ValueParsingMode { + /// Enables SCSS-specific branches where available. + ScssAware, + /// Restricts parsing to CSS syntax branches only. + /// + /// Used by `` fallbacks to avoid SCSS-only diagnostics + /// for unknown-but-valid CSS constructs. + CssOnly, +} + +impl ValueParsingMode { + #[inline] + pub(crate) const fn is_scss_syntax_allowed(self) -> bool { + matches!(self, Self::ScssAware) + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) struct ValueParsingContext { + mode: ValueParsingMode, + scss_feature_supported: bool, +} + +impl ValueParsingContext { + #[inline] + pub(crate) fn new(p: &CssParser, mode: ValueParsingMode) -> Self { + Self { + mode, + scss_feature_supported: CssSyntaxFeatures::Scss.is_supported(p), + } + } + + /// Returns whether grammar branches that recognize SCSS-only syntax are enabled. + #[inline] + pub(crate) const fn is_scss_syntax_allowed(self) -> bool { + self.mode.is_scss_syntax_allowed() + } + + /// Returns whether SCSS parsing is fully enabled in this context. + #[inline] + pub(crate) const fn is_scss_parsing_allowed(self) -> bool { + self.mode.is_scss_syntax_allowed() && self.scss_feature_supported + } +} + +/// Parses any value while explicitly controlling whether SCSS-only branches are allowed. +#[inline] +pub(crate) fn parse_any_value_with_mode(p: &mut CssParser, mode: ValueParsingMode) -> ParsedSyntax { + parse_any_value_with_context(p, ValueParsingContext::new(p, mode)) +} + +#[inline] +pub(crate) fn parse_any_value_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> ParsedSyntax { + if is_at_any_function_with_context(p, context) { + // Functions must win over SCSS name-like branches (`namespace.fn(...)`), + // otherwise we can parse only the name and leave `(...)` behind. + parse_any_function_with_context(p, context) + } else if context.is_scss_syntax_allowed() && is_at_scss_identifier(p) { CssSyntaxFeatures::Scss.parse_exclusive_syntax(p, parse_scss_identifier, |p, m| { scss_only_syntax_error(p, "SCSS variables", m.range(p)) }) - } else if is_at_scss_qualified_name(p) { + } else if context.is_scss_syntax_allowed() && is_at_scss_qualified_name(p) { CssSyntaxFeatures::Scss.parse_exclusive_syntax(p, parse_scss_qualified_name, |p, m| { scss_only_syntax_error(p, "SCSS qualified names", m.range(p)) }) - } else if is_at_scss_parent_selector_value(p) { + } else if context.is_scss_syntax_allowed() && is_at_scss_parent_selector_value(p) { parse_scss_parent_selector_value(p) - } else if is_at_dashed_identifier(p) { + } else { + parse_any_non_function_css_value(p) + } +} + +#[inline] +fn parse_any_non_function_css_value(p: &mut CssParser) -> ParsedSyntax { + if is_at_dashed_identifier(p) { if p.nth_at(1, T![-]) && p.nth_at(2, T![*]) { CssSyntaxFeatures::Tailwind.parse_exclusive_syntax( p, @@ -393,33 +491,6 @@ pub(crate) fn parse_any_value(p: &mut CssParser) -> ParsedSyntax { } } -struct CssComponentValueList; -impl ParseNodeList for CssComponentValueList { - type Kind = CssSyntaxKind; - type Parser<'source> = CssParser<'source>; - const LIST_KIND: Self::Kind = CSS_COMPONENT_VALUE_LIST; - - fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax { - parse_any_value(p) - } - - fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { - p.at(T![,]) || p.at(T![')']) || p.at_ts(BINARY_OPERATION_TOKEN) - } - - fn recover( - &mut self, - p: &mut Self::Parser<'_>, - parsed_element: ParsedSyntax, - ) -> RecoveryResult { - parsed_element.or_recover_with_token_set( - p, - &ParseRecoveryTokenSet::new(CSS_BOGUS, token_set!(T![')'], T![;])), - expected_component_value, - ) - } -} - #[inline] pub(crate) fn is_at_ratio(p: &mut CssParser) -> bool { p.at(CSS_NUMBER_LITERAL) && p.nth_at(1, T![/]) && p.nth_at(2, CSS_NUMBER_LITERAL) diff --git a/crates/biome_css_parser/src/syntax/value/function.rs b/crates/biome_css_parser/src/syntax/value/function.rs index f0c1fda65ac8..a7bdf86cc6a5 100644 --- a/crates/biome_css_parser/src/syntax/value/function.rs +++ b/crates/biome_css_parser/src/syntax/value/function.rs @@ -1,6 +1,6 @@ use super::r#if::is_at_if_function; use super::parse_error::expected_expression; -use super::url::{is_at_url_function, parse_url_function}; +use super::url::{is_at_url_function, parse_url_function_with_context}; use crate::parser::CssParser; use crate::syntax::css_modules::v_bind_not_allowed; use crate::syntax::parse_error::{ @@ -15,8 +15,9 @@ use crate::syntax::scss::{ use crate::syntax::value::attr::{is_at_attr_function, parse_attr_function}; use crate::syntax::value::r#if::parse_if_function; use crate::syntax::{ - CssComponentValueList, CssSyntaxFeatures, is_at_any_value, is_at_dashed_identifier, - is_nth_at_identifier, parse_dashed_identifier, parse_regular_identifier, + CssSyntaxFeatures, ValueParsingContext, ValueParsingMode, is_at_any_value_with_context, + is_at_dashed_identifier, is_nth_at_identifier, parse_any_value_with_context, + parse_dashed_identifier, parse_regular_identifier, }; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; @@ -26,32 +27,35 @@ use biome_parser::parsed_syntax::ParsedSyntax; use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; use biome_parser::{Parser, SyntaxFeature, TokenSet, token_set}; -/// Checks if the current position in the `CssParser` is at the start of any recognized CSS function. -/// -/// This function combines checks for specific CSS functions like `url()` and simple functions. -/// It's used to quickly determine if the parser is positioned at a relevant function. +/// Checks if the current position is at any function recognized by CSS-only value parsing. +#[inline] +pub(crate) fn is_at_any_css_function(p: &mut CssParser) -> bool { + is_at_any_function_with_context(p, ValueParsingContext::new(p, ValueParsingMode::CssOnly)) +} + #[inline] -pub(crate) fn is_at_any_function(p: &mut CssParser) -> bool { +pub(crate) fn is_at_any_function_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> bool { is_at_url_function(p) || is_at_if_function(p) || is_at_attr_function(p) || is_at_vue_v_bind_function(p) - || is_at_function(p) + || is_at_function_with_context(p, context) } -/// Parses any recognized CSS function at the current position in the `CssParser`. -/// -/// This function first checks if the parser is positioned at a valid function. -/// If it is, the function will parse either a URL function or a simple function, -/// based on what is detected. #[inline] -pub(crate) fn parse_any_function(p: &mut CssParser) -> ParsedSyntax { - if !is_at_any_function(p) { +pub(crate) fn parse_any_function_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> ParsedSyntax { + if !is_at_any_function_with_context(p, context) { return Absent; } if is_at_url_function(p) { - parse_url_function(p) + parse_url_function_with_context(p, context) } else if is_at_if_function(p) { parse_if_function(p) } else if is_at_attr_function(p) { @@ -59,11 +63,11 @@ pub(crate) fn parse_any_function(p: &mut CssParser) -> ParsedSyntax { } else if is_at_vue_v_bind_function(p) { CssSyntaxFeatures::CssModulesWithVue.parse_exclusive_syntax( p, - parse_function, + |p| parse_function_with_context(p, context), |p, marker| v_bind_not_allowed(p, marker.range(p)), ) } else { - parse_function(p) + parse_function_with_context(p, context) } } @@ -73,18 +77,50 @@ pub(crate) fn parse_any_function(p: &mut CssParser) -> ParsedSyntax { /// excluding URL functions (since URL functions are also considered simple functions but are handled separately). #[inline] pub(crate) fn is_at_function(p: &mut CssParser) -> bool { - is_nth_at_function(p, 0) && !is_at_url_function(p) + is_at_function_with_context(p, ValueParsingContext::new(p, ValueParsingMode::ScssAware)) +} + +/// Checks if the current position is at a simple function head in CSS-only mode. +#[inline] +pub(crate) fn is_at_css_function(p: &mut CssParser) -> bool { + is_at_function_with_context(p, ValueParsingContext::new(p, ValueParsingMode::CssOnly)) +} + +#[inline] +fn is_at_function_with_context(p: &mut CssParser, context: ValueParsingContext) -> bool { + is_nth_at_function_with_context(p, 0, context) && !is_at_url_function(p) } #[inline] pub(crate) fn is_nth_at_function(p: &mut CssParser, n: usize) -> bool { + is_nth_at_function_with_context( + p, + n, + ValueParsingContext::new(p, ValueParsingMode::ScssAware), + ) +} + +/// Checks if the `n`th token starts a simple function head in CSS-only mode. +#[inline] +pub(crate) fn is_nth_at_css_function(p: &mut CssParser, n: usize) -> bool { + is_nth_at_function_with_context(p, n, ValueParsingContext::new(p, ValueParsingMode::CssOnly)) +} + +#[inline] +fn is_nth_at_function_with_context( + p: &mut CssParser, + n: usize, + context: ValueParsingContext, +) -> bool { (is_nth_at_identifier(p, n) && p.nth_at(n + 1, T!['('])) - || (is_nth_at_scss_qualified_name(p, n) && p.nth_at(n + 3, T!['('])) + || (context.is_scss_syntax_allowed() + && is_nth_at_scss_qualified_name(p, n) + && p.nth_at(n + 3, T!['('])) } #[inline] fn is_at_vue_v_bind_function(p: &mut CssParser) -> bool { - if !is_nth_at_function(p, 0) { + if !is_nth_at_css_function(p, 0) { return false; } @@ -106,13 +142,24 @@ fn is_at_vue_v_bind_function(p: &mut CssParser) -> bool { /// #[inline] pub(crate) fn parse_function(p: &mut CssParser) -> ParsedSyntax { - if !is_at_function(p) { + parse_function_with_context(p, ValueParsingContext::new(p, ValueParsingMode::ScssAware)) +} + +/// Parses a simple function using CSS-only branches. +#[inline] +pub(crate) fn parse_css_function(p: &mut CssParser) -> ParsedSyntax { + parse_function_with_context(p, ValueParsingContext::new(p, ValueParsingMode::CssOnly)) +} + +#[inline] +fn parse_function_with_context(p: &mut CssParser, context: ValueParsingContext) -> ParsedSyntax { + if !is_at_function_with_context(p, context) { return Absent; } let m = p.start(); - if is_at_scss_qualified_name(p) { + if context.is_scss_syntax_allowed() && is_at_scss_qualified_name(p) { CssSyntaxFeatures::Scss .parse_exclusive_syntax(p, parse_scss_function_name, |p, marker| { scss_only_syntax_error(p, "SCSS qualified function names", marker.range(p)) @@ -122,13 +169,16 @@ pub(crate) fn parse_function(p: &mut CssParser) -> ParsedSyntax { parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); } p.bump(T!['(']); - ParameterList.parse_list(p); + ParameterList::new(context).parse_list(p); p.expect(T![')']); Present(m.complete(p, CSS_FUNCTION)) } -struct ParameterListParseRecovery; +#[derive(Debug, Copy, Clone)] +struct ParameterListParseRecovery { + context: ValueParsingContext, +} impl ParseRecovery for ParameterListParseRecovery { type Kind = CssSyntaxKind; @@ -151,11 +201,20 @@ impl ParseRecovery for ParameterListParseRecovery { /// transform: rotate(30deg,, /* Error in parameter, recover here */) /// ``` fn is_at_recovered(&self, p: &mut Self::Parser<'_>) -> bool { - p.at_ts(token_set!(T![,], T![')'], T![;])) || is_at_parameter(p) + p.at_ts(token_set!(T![,], T![')'], T![;])) || is_at_parameter_with_context(p, self.context) } } -pub(crate) struct ParameterList; +pub(crate) struct ParameterList { + context: ValueParsingContext, +} + +impl ParameterList { + #[inline] + fn new(context: ValueParsingContext) -> Self { + Self { context } + } +} impl ParseSeparatedList for ParameterList { type Kind = CssSyntaxKind; @@ -163,7 +222,7 @@ impl ParseSeparatedList for ParameterList { const LIST_KIND: Self::Kind = CSS_PARAMETER_LIST; fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax { - parse_parameter(p) + parse_parameter_with_context(p, self.context) } fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { @@ -175,7 +234,13 @@ impl ParseSeparatedList for ParameterList { p: &mut Self::Parser<'_>, parsed_element: ParsedSyntax, ) -> RecoveryResult { - parsed_element.or_recover(p, &ParameterListParseRecovery, expected_declaration_item) + parsed_element.or_recover( + p, + &ParameterListParseRecovery { + context: self.context, + }, + expected_declaration_item, + ) } fn separating_element_kind(&mut self) -> Self::Kind { @@ -187,66 +252,56 @@ impl ParseSeparatedList for ParameterList { } } -/// The function first checks whether the current position in the parser is at -/// the start of a valid parameter #[inline] -pub(crate) fn is_at_parameter(p: &mut CssParser) -> bool { - is_at_any_expression(p) +fn is_at_parameter_with_context(p: &mut CssParser, context: ValueParsingContext) -> bool { + is_at_any_expression_with_context(p, context) } -/// Parses a single CSS parameter. -/// -/// This function attempts to parse a single parameter from the current position -/// in the CSS parser. -/// -/// # Examples -/// -/// Imagine parsing a CSS transform function like `rotate(45deg)`. When the parser -/// reaches `45deg`, `parse_parameter` would be invoked to parse and capture this -/// value as a parameter of the `rotate` function. -/// #[inline] -pub(crate) fn parse_parameter(p: &mut CssParser) -> ParsedSyntax { - if !is_at_parameter(p) { +fn parse_parameter_with_context(p: &mut CssParser, context: ValueParsingContext) -> ParsedSyntax { + if !is_at_parameter_with_context(p, context) { return Absent; } - if CssSyntaxFeatures::Scss.is_supported(p) { + if context.is_scss_parsing_allowed() { parse_scss_expression_in_args_until(p, token_set![T![,], T![')'], T![;], T!['}']]) } else { - parse_any_expression(p) + parse_any_expression_with_context(p, context) } } -/// Determines if the current position in the CSS parser is at the start of any CSS expression. #[inline] -pub(crate) fn is_at_any_expression(p: &mut CssParser) -> bool { +fn is_at_any_expression_with_context(p: &mut CssParser, context: ValueParsingContext) -> bool { is_at_unary_operator(p) || is_at_parenthesized(p) - || is_at_any_value(p) + || is_at_any_value_with_context(p, context) || is_at_comma_separated_value(p) } -/// Parses any CSS expression from the current position in the CSS parser. #[inline] -pub(crate) fn parse_any_expression(p: &mut CssParser) -> ParsedSyntax { - if !is_at_any_expression(p) { +fn parse_any_expression_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> ParsedSyntax { + if !is_at_any_expression_with_context(p, context) { return Absent; } - if CssSyntaxFeatures::Scss.is_supported(p) - && (is_at_parenthesized(p) || is_at_any_value(p) || p.at_ts(SCSS_UNARY_OPERATOR_TOKEN_SET)) + if context.is_scss_parsing_allowed() + && (is_at_parenthesized(p) + || is_at_any_value_with_context(p, context) + || p.at_ts(SCSS_UNARY_OPERATOR_TOKEN_SET)) { return parse_scss_expression(p); } - let param = parse_unary_expression_operand(p); + let param = parse_unary_expression_operand_with_context(p, context); if is_at_binary_operator(p) { let binary_expression = param.precede(p); p.bump_ts(BINARY_OPERATION_TOKEN); - parse_any_expression(p).or_add_diagnostic(p, expected_expression); + parse_any_expression_with_context(p, context).or_add_diagnostic(p, expected_expression); Present(binary_expression.complete(p, CSS_BINARY_EXPRESSION)) } else { @@ -274,27 +329,34 @@ pub(crate) fn is_at_unary_operator(p: &mut CssParser) -> bool { } #[inline] -pub(crate) fn parse_unary_expression(p: &mut CssParser) -> ParsedSyntax { +fn parse_unary_expression_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> ParsedSyntax { if !is_at_unary_operator(p) { return Absent; } let m = p.start(); p.bump_ts(UNARY_OPERATION_TOKEN); - parse_unary_expression_operand(p).or_add_diagnostic(p, expected_expression); + parse_unary_expression_operand_with_context(p, context) + .or_add_diagnostic(p, expected_expression); Present(m.complete(p, CSS_UNARY_EXPRESSION)) } #[inline] -fn parse_unary_expression_operand(p: &mut CssParser) -> ParsedSyntax { +fn parse_unary_expression_operand_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> ParsedSyntax { if is_at_unary_operator(p) { - parse_unary_expression(p) + parse_unary_expression_with_context(p, context) } else if is_at_parenthesized(p) { - parse_parenthesized_expression(p) + parse_parenthesized_expression_with_context(p, context) } else if is_at_comma_separated_value(p) { parse_comma_separated_value(p) } else { - parse_list_of_component_values_expression(p) + parse_list_of_component_values_expression_with_context(p, context) } } @@ -307,38 +369,74 @@ pub(crate) fn is_at_parenthesized(p: &mut CssParser) -> bool { p.at(T!['(']) } -/// Parses a parenthesized expression from the current position in the CSS parser. -/// -/// This function is invoked when a parenthesized expression is identified. It handles -/// the parsing of the entire expression enclosed within the parentheses. #[inline] -pub(crate) fn parse_parenthesized_expression(p: &mut CssParser) -> ParsedSyntax { +fn parse_parenthesized_expression_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> ParsedSyntax { if !is_at_parenthesized(p) { return Absent; } let m = p.start(); p.expect(T!['(']); - parse_any_expression(p).ok(); + parse_any_expression_with_context(p, context).ok(); p.expect(T![')']); Present(m.complete(p, CSS_PARENTHESIZED_EXPRESSION)) } -/// Parses a list of component values from the current position in the CSS parser. -/// -/// This function is used to parse a sequence of CSS component values, typically found -/// in various CSS properties. It is called when the parser is at the start of such a list. #[inline] -pub(crate) fn parse_list_of_component_values_expression(p: &mut CssParser) -> ParsedSyntax { - if !is_at_any_value(p) { +fn parse_list_of_component_values_expression_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> ParsedSyntax { + if !is_at_any_value_with_context(p, context) { return Absent; } let m = p.start(); - CssComponentValueList.parse_list(p); + ComponentValueExpressionList::new(context).parse_list(p); Present(m.complete(p, CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION)) } +#[derive(Debug, Copy, Clone)] +struct ComponentValueExpressionList { + context: ValueParsingContext, +} + +impl ComponentValueExpressionList { + #[inline] + fn new(context: ValueParsingContext) -> Self { + Self { context } + } +} + +impl ParseNodeList for ComponentValueExpressionList { + type Kind = CssSyntaxKind; + type Parser<'source> = CssParser<'source>; + const LIST_KIND: Self::Kind = CSS_COMPONENT_VALUE_LIST; + + fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax { + parse_any_value_with_context(p, self.context) + } + + fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { + p.at(T![,]) || p.at(T![')']) || p.at_ts(BINARY_OPERATION_TOKEN) + } + + fn recover( + &mut self, + p: &mut Self::Parser<'_>, + parsed_element: ParsedSyntax, + ) -> RecoveryResult { + parsed_element.or_recover_with_token_set( + p, + &ParseRecoveryTokenSet::new(CSS_BOGUS, token_set!(T![')'], T![;])), + expected_component_value, + ) + } +} + /// Parses theme references: --tab-size-* pub(crate) fn parse_tailwind_value_theme_reference(p: &mut CssParser) -> ParsedSyntax { if !is_at_dashed_identifier(p) { diff --git a/crates/biome_css_parser/src/syntax/value/url.rs b/crates/biome_css_parser/src/syntax/value/url.rs index 7220866f3fa4..3250c43f3511 100644 --- a/crates/biome_css_parser/src/syntax/value/url.rs +++ b/crates/biome_css_parser/src/syntax/value/url.rs @@ -1,8 +1,12 @@ use crate::lexer::CssLexContext; use crate::parser::CssParser; -use crate::syntax::value::function::{is_at_function, is_nth_at_function, parse_function}; +use crate::syntax::value::function::{ + is_at_css_function, is_at_function, is_nth_at_css_function, is_nth_at_function, + parse_css_function, parse_function, +}; use crate::syntax::value::parse_error::expected_url_modifier; +use crate::syntax::{ValueParsingContext, ValueParsingMode}; use crate::syntax::{is_at_identifier, is_at_string, parse_regular_identifier, parse_string}; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; @@ -55,6 +59,14 @@ pub(crate) fn is_at_url_function(p: &mut CssParser) -> bool { /// Here, `` represents any modifiers that might be applied to the URL, such as /// resolution-based adjustments or format hints. pub(crate) fn parse_url_function(p: &mut CssParser) -> ParsedSyntax { + parse_url_function_with_context(p, ValueParsingContext::new(p, ValueParsingMode::ScssAware)) +} + +/// Parses a URL function while honoring the provided parsing context. +pub(crate) fn parse_url_function_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> ParsedSyntax { if !is_at_url_function(p) { return Absent; } @@ -62,7 +74,7 @@ pub(crate) fn parse_url_function(p: &mut CssParser) -> ParsedSyntax { p.bump_ts(URL_SET); - if is_nth_at_function(p, 1) { + if is_at_nth_url_function(p, 1, context) { // we need to check if the next token is a function or not // to cover the case of `src(var(--foo));` p.bump(T!['(']); @@ -71,12 +83,45 @@ pub(crate) fn parse_url_function(p: &mut CssParser) -> ParsedSyntax { parse_url_value(p).ok(); } - UrlModifierList.parse_list(p); + UrlModifierList::new(context).parse_list(p); p.expect(T![')']); Present(m.complete(p, CSS_URL_FUNCTION)) } +#[inline] +fn is_at_nth_url_function(p: &mut CssParser, n: usize, context: ValueParsingContext) -> bool { + if context.is_scss_syntax_allowed() { + is_nth_at_function(p, n) + } else { + is_nth_at_css_function(p, n) + } +} + +#[inline] +fn is_at_url_modifier_function_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> bool { + if context.is_scss_syntax_allowed() { + is_at_function(p) + } else { + is_at_css_function(p) + } +} + +#[inline] +fn parse_url_modifier_function_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> ParsedSyntax { + if context.is_scss_syntax_allowed() { + parse_function(p) + } else { + parse_css_function(p) + } +} + /// Determines if the current position of the parser is at a URL value. /// /// This function checks if the parser's current position is at the beginning of either a raw URL value @@ -121,7 +166,10 @@ pub(crate) fn parse_url_value_raw(p: &mut CssParser) -> ParsedSyntax { Present(m.complete(p, CSS_URL_VALUE_RAW)) } -struct UrlModifierListParseRecovery; +#[derive(Debug, Copy, Clone)] +struct UrlModifierListParseRecovery { + context: ValueParsingContext, +} impl ParseRecovery for UrlModifierListParseRecovery { type Kind = CssSyntaxKind; @@ -136,11 +184,20 @@ impl ParseRecovery for UrlModifierListParseRecovery { // url("//aa.com/img.svg" foo "bar" ); // ^ ^ // "bar" is an invalid modifier |____| ')' is the recovery point - p.at(T![')']) || is_at_url_modifier(p) + p.at(T![')']) || is_at_url_modifier_with_context(p, self.context) } } -struct UrlModifierList; +struct UrlModifierList { + context: ValueParsingContext, +} + +impl UrlModifierList { + #[inline] + fn new(context: ValueParsingContext) -> Self { + Self { context } + } +} impl ParseNodeList for UrlModifierList { type Kind = CssSyntaxKind; @@ -148,7 +205,7 @@ impl ParseNodeList for UrlModifierList { const LIST_KIND: Self::Kind = CSS_URL_MODIFIER_LIST; fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax { - parse_url_modifier(p) + parse_url_modifier_with_context(p, self.context) } fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { @@ -160,26 +217,32 @@ impl ParseNodeList for UrlModifierList { p: &mut Self::Parser<'_>, parsed_element: ParsedSyntax, ) -> RecoveryResult { - parsed_element.or_recover(p, &UrlModifierListParseRecovery, expected_url_modifier) + parsed_element.or_recover( + p, + &UrlModifierListParseRecovery { + context: self.context, + }, + expected_url_modifier, + ) } } -/// This function determines if the current token is either an identifier or any function, -/// indicating a potential modifier for a URL in CSS. #[inline] -pub(crate) fn is_at_url_modifier(p: &mut CssParser) -> bool { - is_at_identifier(p) || is_at_function(p) +fn is_at_url_modifier_with_context(p: &mut CssParser, context: ValueParsingContext) -> bool { + is_at_identifier(p) || is_at_url_modifier_function_with_context(p, context) } -/// Parses a URL modifier, which can be either a simple function or a regular identifier. #[inline] -pub(crate) fn parse_url_modifier(p: &mut CssParser) -> ParsedSyntax { - if !is_at_url_modifier(p) { +fn parse_url_modifier_with_context( + p: &mut CssParser, + context: ValueParsingContext, +) -> ParsedSyntax { + if !is_at_url_modifier_with_context(p, context) { return Absent; } - if is_at_function(p) { - parse_function(p) + if is_at_url_modifier_function_with_context(p, context) { + parse_url_modifier_function_with_context(p, context) } else { parse_regular_identifier(p) } diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_general_enclosed_error.css b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_general_enclosed_error.css new file mode 100644 index 000000000000..feaf5d118ea8 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_general_enclosed_error.css @@ -0,0 +1,11 @@ +@container (foo(bar: baz)) { + .a { color: red; } +} + +@container (foo(bar: baz)) and (width > 100px) { + .b { color: blue; } +} + +@container (foo(url("a.svg" fn(test)))) { + .c { color: green; } +} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_general_enclosed_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_general_enclosed_error.css.snap new file mode 100644 index 000000000000..2f7e0dda7d4b --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_general_enclosed_error.css.snap @@ -0,0 +1,568 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css +@container (foo(bar: baz)) { + .a { color: red; } +} + +@container (foo(bar: baz)) and (width > 100px) { + .b { color: blue; } +} + +@container (foo(url("a.svg" fn(test)))) { + .c { color: green; } +} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssBogusAtRule { + items: [ + CssBogus { + items: [ + CONTAINER_KW@1..11 "container" [] [Whitespace(" ")], + CssBogus { + items: [ + L_PAREN@11..12 "(" [] [], + CssBogusSupportsCondition { + items: [ + CssIdentifier { + value_token: IDENT@12..15 "foo" [] [], + }, + L_PAREN@15..16 "(" [] [], + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssIdentifier { + value_token: IDENT@16..19 "bar" [] [], + }, + CssBogus { + items: [ + COLON@19..21 ":" [] [Whitespace(" ")], + IDENT@21..24 "baz" [] [], + ], + }, + ], + }, + ], + }, + ], + }, + R_PAREN@24..25 ")" [] [], + ], + }, + R_PAREN@25..27 ")" [] [Whitespace(" ")], + ], + }, + ], + }, + CssRuleBlock { + l_curly_token: L_CURLY@27..28 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@28..32 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@32..34 "a" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@34..36 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@36..41 "color" [] [], + }, + colon_token: COLON@41..43 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@43..46 "red" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@46..48 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@48..49 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@49..51 "}" [Newline("\n")] [], + }, + ], + }, + }, + CssAtRule { + at_token: AT@51..54 "@" [Newline("\n"), Newline("\n")] [], + rule: CssBogusAtRule { + items: [ + CssBogus { + items: [ + CONTAINER_KW@54..64 "container" [] [Whitespace(" ")], + CssBogus { + items: [ + CssBogus { + items: [ + L_PAREN@64..65 "(" [] [], + CssBogusSupportsCondition { + items: [ + CssIdentifier { + value_token: IDENT@65..68 "foo" [] [], + }, + L_PAREN@68..69 "(" [] [], + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssIdentifier { + value_token: IDENT@69..72 "bar" [] [], + }, + CssBogus { + items: [ + COLON@72..74 ":" [] [Whitespace(" ")], + IDENT@74..77 "baz" [] [], + ], + }, + ], + }, + ], + }, + ], + }, + R_PAREN@77..78 ")" [] [], + ], + }, + R_PAREN@78..80 ")" [] [Whitespace(" ")], + ], + }, + AND_KW@80..84 "and" [] [Whitespace(" ")], + CssContainerSizeFeatureInParens { + l_paren_token: L_PAREN@84..85 "(" [] [], + feature: CssQueryFeatureRange { + left: CssIdentifier { + value_token: IDENT@85..91 "width" [] [Whitespace(" ")], + }, + comparison: CssQueryFeatureRangeComparison { + operator: R_ANGLE@91..93 ">" [] [Whitespace(" ")], + }, + right: CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@93..96 "100" [] [], + unit_token: IDENT@96..98 "px" [] [], + }, + }, + r_paren_token: R_PAREN@98..100 ")" [] [Whitespace(" ")], + }, + ], + }, + ], + }, + CssRuleBlock { + l_curly_token: L_CURLY@100..101 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@101..105 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@105..107 "b" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@107..109 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@109..114 "color" [] [], + }, + colon_token: COLON@114..116 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@116..120 "blue" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@120..122 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@122..123 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@123..125 "}" [Newline("\n")] [], + }, + ], + }, + }, + CssAtRule { + at_token: AT@125..128 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@128..138 "container" [] [Whitespace(" ")], + name: missing (optional), + query: CssContainerQueryInParens { + l_paren_token: L_PAREN@138..139 "(" [] [], + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@139..142 "foo" [] [], + }, + l_paren_token: L_PAREN@142..143 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssUrlFunction { + name: URL_KW@143..146 "url" [] [], + l_paren_token: L_PAREN@146..147 "(" [] [], + value: CssString { + value_token: CSS_STRING_LITERAL@147..155 "\"a.svg\"" [] [Whitespace(" ")], + }, + modifiers: CssUrlModifierList [ + CssFunction { + name: CssIdentifier { + value_token: IDENT@155..157 "fn" [] [], + }, + l_paren_token: L_PAREN@157..158 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@158..162 "test" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@162..163 ")" [] [], + }, + ], + r_paren_token: R_PAREN@163..164 ")" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@164..165 ")" [] [], + }, + r_paren_token: R_PAREN@165..167 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@167..168 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@168..172 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@172..174 "c" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@174..176 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@176..181 "color" [] [], + }, + colon_token: COLON@181..183 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@183..188 "green" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@188..190 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@190..191 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@191..193 "}" [Newline("\n")] [], + }, + }, + }, + ], + eof_token: EOF@193..194 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..194 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..193 + 0: CSS_AT_RULE@0..51 + 0: AT@0..1 "@" [] [] + 1: CSS_BOGUS_AT_RULE@1..51 + 0: CSS_BOGUS@1..27 + 0: CONTAINER_KW@1..11 "container" [] [Whitespace(" ")] + 1: CSS_BOGUS@11..27 + 0: L_PAREN@11..12 "(" [] [] + 1: CSS_BOGUS_SUPPORTS_CONDITION@12..25 + 0: CSS_IDENTIFIER@12..15 + 0: IDENT@12..15 "foo" [] [] + 1: L_PAREN@15..16 "(" [] [] + 2: CSS_BOGUS@16..24 + 0: CSS_BOGUS@16..24 + 0: CSS_BOGUS@16..24 + 0: CSS_IDENTIFIER@16..19 + 0: IDENT@16..19 "bar" [] [] + 1: CSS_BOGUS@19..24 + 0: COLON@19..21 ":" [] [Whitespace(" ")] + 1: IDENT@21..24 "baz" [] [] + 3: R_PAREN@24..25 ")" [] [] + 2: R_PAREN@25..27 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@27..51 + 0: L_CURLY@27..28 "{" [] [] + 1: CSS_RULE_LIST@28..49 + 0: CSS_QUALIFIED_RULE@28..49 + 0: CSS_SELECTOR_LIST@28..34 + 0: CSS_COMPOUND_SELECTOR@28..34 + 0: CSS_NESTED_SELECTOR_LIST@28..28 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@28..34 + 0: CSS_CLASS_SELECTOR@28..34 + 0: DOT@28..32 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@32..34 + 0: IDENT@32..34 "a" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@34..49 + 0: L_CURLY@34..36 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@36..48 + 0: CSS_DECLARATION_WITH_SEMICOLON@36..48 + 0: CSS_DECLARATION@36..46 + 0: CSS_GENERIC_PROPERTY@36..46 + 0: CSS_IDENTIFIER@36..41 + 0: IDENT@36..41 "color" [] [] + 1: COLON@41..43 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@43..46 + 0: CSS_IDENTIFIER@43..46 + 0: IDENT@43..46 "red" [] [] + 1: (empty) + 1: SEMICOLON@46..48 ";" [] [Whitespace(" ")] + 2: R_CURLY@48..49 "}" [] [] + 2: R_CURLY@49..51 "}" [Newline("\n")] [] + 1: CSS_AT_RULE@51..125 + 0: AT@51..54 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_BOGUS_AT_RULE@54..125 + 0: CSS_BOGUS@54..100 + 0: CONTAINER_KW@54..64 "container" [] [Whitespace(" ")] + 1: CSS_BOGUS@64..100 + 0: CSS_BOGUS@64..80 + 0: L_PAREN@64..65 "(" [] [] + 1: CSS_BOGUS_SUPPORTS_CONDITION@65..78 + 0: CSS_IDENTIFIER@65..68 + 0: IDENT@65..68 "foo" [] [] + 1: L_PAREN@68..69 "(" [] [] + 2: CSS_BOGUS@69..77 + 0: CSS_BOGUS@69..77 + 0: CSS_BOGUS@69..77 + 0: CSS_IDENTIFIER@69..72 + 0: IDENT@69..72 "bar" [] [] + 1: CSS_BOGUS@72..77 + 0: COLON@72..74 ":" [] [Whitespace(" ")] + 1: IDENT@74..77 "baz" [] [] + 3: R_PAREN@77..78 ")" [] [] + 2: R_PAREN@78..80 ")" [] [Whitespace(" ")] + 1: AND_KW@80..84 "and" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SIZE_FEATURE_IN_PARENS@84..100 + 0: L_PAREN@84..85 "(" [] [] + 1: CSS_QUERY_FEATURE_RANGE@85..98 + 0: CSS_IDENTIFIER@85..91 + 0: IDENT@85..91 "width" [] [Whitespace(" ")] + 1: CSS_QUERY_FEATURE_RANGE_COMPARISON@91..93 + 0: R_ANGLE@91..93 ">" [] [Whitespace(" ")] + 2: CSS_REGULAR_DIMENSION@93..98 + 0: CSS_NUMBER_LITERAL@93..96 "100" [] [] + 1: IDENT@96..98 "px" [] [] + 2: R_PAREN@98..100 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@100..125 + 0: L_CURLY@100..101 "{" [] [] + 1: CSS_RULE_LIST@101..123 + 0: CSS_QUALIFIED_RULE@101..123 + 0: CSS_SELECTOR_LIST@101..107 + 0: CSS_COMPOUND_SELECTOR@101..107 + 0: CSS_NESTED_SELECTOR_LIST@101..101 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@101..107 + 0: CSS_CLASS_SELECTOR@101..107 + 0: DOT@101..105 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@105..107 + 0: IDENT@105..107 "b" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@107..123 + 0: L_CURLY@107..109 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@109..122 + 0: CSS_DECLARATION_WITH_SEMICOLON@109..122 + 0: CSS_DECLARATION@109..120 + 0: CSS_GENERIC_PROPERTY@109..120 + 0: CSS_IDENTIFIER@109..114 + 0: IDENT@109..114 "color" [] [] + 1: COLON@114..116 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@116..120 + 0: CSS_IDENTIFIER@116..120 + 0: IDENT@116..120 "blue" [] [] + 1: (empty) + 1: SEMICOLON@120..122 ";" [] [Whitespace(" ")] + 2: R_CURLY@122..123 "}" [] [] + 2: R_CURLY@123..125 "}" [Newline("\n")] [] + 2: CSS_AT_RULE@125..193 + 0: AT@125..128 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@128..193 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@128..167 + 0: CONTAINER_KW@128..138 "container" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_CONTAINER_QUERY_IN_PARENS@138..167 + 0: L_PAREN@138..139 "(" [] [] + 1: CSS_FUNCTION@139..165 + 0: CSS_IDENTIFIER@139..142 + 0: IDENT@139..142 "foo" [] [] + 1: L_PAREN@142..143 "(" [] [] + 2: CSS_PARAMETER_LIST@143..164 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@143..164 + 0: CSS_COMPONENT_VALUE_LIST@143..164 + 0: CSS_URL_FUNCTION@143..164 + 0: URL_KW@143..146 "url" [] [] + 1: L_PAREN@146..147 "(" [] [] + 2: CSS_STRING@147..155 + 0: CSS_STRING_LITERAL@147..155 "\"a.svg\"" [] [Whitespace(" ")] + 3: CSS_URL_MODIFIER_LIST@155..163 + 0: CSS_FUNCTION@155..163 + 0: CSS_IDENTIFIER@155..157 + 0: IDENT@155..157 "fn" [] [] + 1: L_PAREN@157..158 "(" [] [] + 2: CSS_PARAMETER_LIST@158..162 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@158..162 + 0: CSS_COMPONENT_VALUE_LIST@158..162 + 0: CSS_IDENTIFIER@158..162 + 0: IDENT@158..162 "test" [] [] + 3: R_PAREN@162..163 ")" [] [] + 4: R_PAREN@163..164 ")" [] [] + 3: R_PAREN@164..165 ")" [] [] + 2: R_PAREN@165..167 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@167..193 + 0: L_CURLY@167..168 "{" [] [] + 1: CSS_RULE_LIST@168..191 + 0: CSS_QUALIFIED_RULE@168..191 + 0: CSS_SELECTOR_LIST@168..174 + 0: CSS_COMPOUND_SELECTOR@168..174 + 0: CSS_NESTED_SELECTOR_LIST@168..168 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@168..174 + 0: CSS_CLASS_SELECTOR@168..174 + 0: DOT@168..172 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@172..174 + 0: IDENT@172..174 "c" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@174..191 + 0: L_CURLY@174..176 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@176..190 + 0: CSS_DECLARATION_WITH_SEMICOLON@176..190 + 0: CSS_DECLARATION@176..188 + 0: CSS_GENERIC_PROPERTY@176..188 + 0: CSS_IDENTIFIER@176..181 + 0: IDENT@176..181 "color" [] [] + 1: COLON@181..183 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@183..188 + 0: CSS_IDENTIFIER@183..188 + 0: IDENT@183..188 "green" [] [] + 1: (empty) + 1: SEMICOLON@188..190 ";" [] [Whitespace(" ")] + 2: R_CURLY@190..191 "}" [] [] + 2: R_CURLY@191..193 "}" [Newline("\n")] [] + 2: EOF@193..194 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +at_rule_container_general_enclosed_error.css:1:20 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + > 1 │ @container (foo(bar: baz)) { + │ ^^^^^ + 2 │ .a { color: red; } + 3 │ } + + i Expected one of: + + - identifier + - string + - number + - dimension + - ratio + - custom property + - function + +at_rule_container_general_enclosed_error.css:5:20 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + 3 │ } + 4 │ + > 5 │ @container (foo(bar: baz)) and (width > 100px) { + │ ^^^^^ + 6 │ .b { color: blue; } + 7 │ } + + i Expected one of: + + - identifier + - string + - number + - dimension + - ratio + - custom property + - function + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_scroll_state_error.css b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_scroll_state_error.css new file mode 100644 index 000000000000..d98aeb396136 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_scroll_state_error.css @@ -0,0 +1,5 @@ +@container scroll-state(scrolled: bottom and ) { } + +@container scroll-state(not stuck) { } + +@container scroll-state((stuck) or scrolled: bottom) { } diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_scroll_state_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_scroll_state_error.css.snap new file mode 100644 index 000000000000..1b02fe1f179b --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_container/at_rule_container_scroll_state_error.css.snap @@ -0,0 +1,294 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css +@container scroll-state(scrolled: bottom and ) { } + +@container scroll-state(not stuck) { } + +@container scroll-state((stuck) or scrolled: bottom) { } + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssBogusAtRule { + items: [ + CssBogus { + items: [ + CONTAINER_KW@1..11 "container" [] [Whitespace(" ")], + CssBogus { + items: [ + CssContainerScrollStateQueryInParens { + name: CssIdentifier { + value_token: IDENT@11..23 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@23..24 "(" [] [], + query: CssQueryFeaturePlain { + name: CssIdentifier { + value_token: IDENT@24..32 "scrolled" [] [], + }, + colon_token: COLON@32..34 ":" [] [Whitespace(" ")], + value: CssIdentifier { + value_token: IDENT@34..41 "bottom" [] [Whitespace(" ")], + }, + }, + r_paren_token: missing (required), + }, + AND_KW@41..45 "and" [] [Whitespace(" ")], + CssBogus { + items: [ + R_PAREN@45..47 ")" [] [Whitespace(" ")], + ], + }, + ], + }, + ], + }, + CssRuleBlock { + l_curly_token: L_CURLY@47..49 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@49..50 "}" [] [], + }, + ], + }, + }, + CssAtRule { + at_token: AT@50..53 "@" [Newline("\n"), Newline("\n")] [], + rule: CssBogusAtRule { + items: [ + CssBogus { + items: [ + CONTAINER_KW@53..63 "container" [] [Whitespace(" ")], + CssBogus { + items: [ + CssIdentifier { + value_token: IDENT@63..75 "scroll-state" [] [], + }, + L_PAREN@75..76 "(" [] [], + CssBogus { + items: [ + NOT_KW@76..80 "not" [] [Whitespace(" ")], + CssBogus { + items: [ + IDENT@80..85 "stuck" [] [], + ], + }, + ], + }, + R_PAREN@85..87 ")" [] [Whitespace(" ")], + ], + }, + ], + }, + CssRuleBlock { + l_curly_token: L_CURLY@87..89 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@89..90 "}" [] [], + }, + ], + }, + }, + CssAtRule { + at_token: AT@90..93 "@" [Newline("\n"), Newline("\n")] [], + rule: CssBogusAtRule { + items: [ + CssBogus { + items: [ + CONTAINER_KW@93..103 "container" [] [Whitespace(" ")], + CssBogus { + items: [ + CssIdentifier { + value_token: IDENT@103..115 "scroll-state" [] [], + }, + L_PAREN@115..116 "(" [] [], + CssBogus { + items: [ + CssContainerScrollStateInParens { + l_paren_token: L_PAREN@116..117 "(" [] [], + query: CssQueryFeatureBoolean { + name: CssIdentifier { + value_token: IDENT@117..122 "stuck" [] [], + }, + }, + r_paren_token: R_PAREN@122..124 ")" [] [Whitespace(" ")], + }, + OR_KW@124..127 "or" [] [Whitespace(" ")], + CssBogus { + items: [ + IDENT@127..135 "scrolled" [] [], + COLON@135..137 ":" [] [Whitespace(" ")], + IDENT@137..143 "bottom" [] [], + ], + }, + ], + }, + R_PAREN@143..145 ")" [] [Whitespace(" ")], + ], + }, + ], + }, + CssRuleBlock { + l_curly_token: L_CURLY@145..147 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@147..148 "}" [] [], + }, + ], + }, + }, + ], + eof_token: EOF@148..149 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..149 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..148 + 0: CSS_AT_RULE@0..50 + 0: AT@0..1 "@" [] [] + 1: CSS_BOGUS_AT_RULE@1..50 + 0: CSS_BOGUS@1..47 + 0: CONTAINER_KW@1..11 "container" [] [Whitespace(" ")] + 1: CSS_BOGUS@11..47 + 0: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@11..41 + 0: CSS_IDENTIFIER@11..23 + 0: IDENT@11..23 "scroll-state" [] [] + 1: L_PAREN@23..24 "(" [] [] + 2: CSS_QUERY_FEATURE_PLAIN@24..41 + 0: CSS_IDENTIFIER@24..32 + 0: IDENT@24..32 "scrolled" [] [] + 1: COLON@32..34 ":" [] [Whitespace(" ")] + 2: CSS_IDENTIFIER@34..41 + 0: IDENT@34..41 "bottom" [] [Whitespace(" ")] + 3: (empty) + 1: AND_KW@41..45 "and" [] [Whitespace(" ")] + 2: CSS_BOGUS@45..47 + 0: R_PAREN@45..47 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@47..50 + 0: L_CURLY@47..49 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@49..49 + 2: R_CURLY@49..50 "}" [] [] + 1: CSS_AT_RULE@50..90 + 0: AT@50..53 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_BOGUS_AT_RULE@53..90 + 0: CSS_BOGUS@53..87 + 0: CONTAINER_KW@53..63 "container" [] [Whitespace(" ")] + 1: CSS_BOGUS@63..87 + 0: CSS_IDENTIFIER@63..75 + 0: IDENT@63..75 "scroll-state" [] [] + 1: L_PAREN@75..76 "(" [] [] + 2: CSS_BOGUS@76..85 + 0: NOT_KW@76..80 "not" [] [Whitespace(" ")] + 1: CSS_BOGUS@80..85 + 0: IDENT@80..85 "stuck" [] [] + 3: R_PAREN@85..87 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@87..90 + 0: L_CURLY@87..89 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@89..89 + 2: R_CURLY@89..90 "}" [] [] + 2: CSS_AT_RULE@90..148 + 0: AT@90..93 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_BOGUS_AT_RULE@93..148 + 0: CSS_BOGUS@93..145 + 0: CONTAINER_KW@93..103 "container" [] [Whitespace(" ")] + 1: CSS_BOGUS@103..145 + 0: CSS_IDENTIFIER@103..115 + 0: IDENT@103..115 "scroll-state" [] [] + 1: L_PAREN@115..116 "(" [] [] + 2: CSS_BOGUS@116..143 + 0: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@116..124 + 0: L_PAREN@116..117 "(" [] [] + 1: CSS_QUERY_FEATURE_BOOLEAN@117..122 + 0: CSS_IDENTIFIER@117..122 + 0: IDENT@117..122 "stuck" [] [] + 2: R_PAREN@122..124 ")" [] [Whitespace(" ")] + 1: OR_KW@124..127 "or" [] [Whitespace(" ")] + 2: CSS_BOGUS@127..143 + 0: IDENT@127..135 "scrolled" [] [] + 1: COLON@135..137 ":" [] [Whitespace(" ")] + 2: IDENT@137..143 "bottom" [] [] + 3: R_PAREN@143..145 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@145..148 + 0: L_CURLY@145..147 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@147..147 + 2: R_CURLY@147..148 "}" [] [] + 2: EOF@148..149 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +at_rule_container_scroll_state_error.css:1:42 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead found `and` + + > 1 │ @container scroll-state(scrolled: bottom and ) { } + │ ^^^ + 2 │ + 3 │ @container scroll-state(not stuck) { } + + i Remove and + +at_rule_container_scroll_state_error.css:1:46 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + > 1 │ @container scroll-state(scrolled: bottom and ) { } + │ ^ + 2 │ + 3 │ @container scroll-state(not stuck) { } + + i Expected one of: + + - ( ) + - ( ) + - scroll-state( ) + - style( ) + +at_rule_container_scroll_state_error.css:3:29 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + 1 │ @container scroll-state(scrolled: bottom and ) { } + 2 │ + > 3 │ @container scroll-state(not stuck) { } + │ ^^^^^ + 4 │ + 5 │ @container scroll-state((stuck) or scrolled: bottom) { } + + i Expected one of: + + - ( ) + - ( ) + +at_rule_container_scroll_state_error.css:5:36 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + 3 │ @container scroll-state(not stuck) { } + 4 │ + > 5 │ @container scroll-state((stuck) or scrolled: bottom) { } + │ ^^^^^^^^^^^^^^^^ + 6 │ + + i Expected one of: + + - ( ) + - ( ) + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_supports/at_rule_supports_general_enclosed_error.css b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_supports/at_rule_supports_general_enclosed_error.css new file mode 100644 index 000000000000..4d22698730bf --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_supports/at_rule_supports_general_enclosed_error.css @@ -0,0 +1,11 @@ +@supports foo(bar: baz) { + .a { color: red; } +} + +@supports (foo(bar: baz)) and (display: grid) { + .b { display: grid; } +} + +@supports foo(url("a.svg" fn(test))) { + .c { color: green; } +} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_supports/at_rule_supports_general_enclosed_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_supports/at_rule_supports_general_enclosed_error.css.snap new file mode 100644 index 000000000000..db4fde8109f3 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_supports/at_rule_supports_general_enclosed_error.css.snap @@ -0,0 +1,541 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css +@supports foo(bar: baz) { + .a { color: red; } +} + +@supports (foo(bar: baz)) and (display: grid) { + .b { display: grid; } +} + +@supports foo(url("a.svg" fn(test))) { + .c { color: green; } +} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssSupportsAtRule { + declarator: CssSupportsAtRuleDeclarator { + supports_token: SUPPORTS_KW@1..10 "supports" [] [Whitespace(" ")], + condition: CssBogusSupportsCondition { + items: [ + CssIdentifier { + value_token: IDENT@10..13 "foo" [] [], + }, + L_PAREN@13..14 "(" [] [], + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssIdentifier { + value_token: IDENT@14..17 "bar" [] [], + }, + CssBogus { + items: [ + COLON@17..19 ":" [] [Whitespace(" ")], + IDENT@19..22 "baz" [] [], + ], + }, + ], + }, + ], + }, + ], + }, + R_PAREN@22..24 ")" [] [Whitespace(" ")], + ], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@24..25 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@25..29 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@29..31 "a" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@31..33 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@33..38 "color" [] [], + }, + colon_token: COLON@38..40 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@40..43 "red" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@43..45 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@45..46 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@46..48 "}" [Newline("\n")] [], + }, + }, + }, + CssAtRule { + at_token: AT@48..51 "@" [Newline("\n"), Newline("\n")] [], + rule: CssSupportsAtRule { + declarator: CssSupportsAtRuleDeclarator { + supports_token: SUPPORTS_KW@51..60 "supports" [] [Whitespace(" ")], + condition: CssSupportsAndCondition { + left: CssSupportsConditionInParens { + l_paren_token: L_PAREN@60..61 "(" [] [], + condition: CssBogusSupportsCondition { + items: [ + CssIdentifier { + value_token: IDENT@61..64 "foo" [] [], + }, + L_PAREN@64..65 "(" [] [], + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssIdentifier { + value_token: IDENT@65..68 "bar" [] [], + }, + CssBogus { + items: [ + COLON@68..70 ":" [] [Whitespace(" ")], + IDENT@70..73 "baz" [] [], + ], + }, + ], + }, + ], + }, + ], + }, + R_PAREN@73..74 ")" [] [], + ], + }, + r_paren_token: R_PAREN@74..76 ")" [] [Whitespace(" ")], + }, + and_token: AND_KW@76..80 "and" [] [Whitespace(" ")], + right: CssSupportsFeatureDeclaration { + l_paren_token: L_PAREN@80..81 "(" [] [], + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@81..88 "display" [] [], + }, + colon_token: COLON@88..90 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@90..94 "grid" [] [], + }, + ], + }, + important: missing (optional), + }, + r_paren_token: R_PAREN@94..96 ")" [] [Whitespace(" ")], + }, + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@96..97 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@97..101 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@101..103 "b" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@103..105 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@105..112 "display" [] [], + }, + colon_token: COLON@112..114 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@114..118 "grid" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@118..120 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@120..121 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@121..123 "}" [Newline("\n")] [], + }, + }, + }, + CssAtRule { + at_token: AT@123..126 "@" [Newline("\n"), Newline("\n")] [], + rule: CssSupportsAtRule { + declarator: CssSupportsAtRuleDeclarator { + supports_token: SUPPORTS_KW@126..135 "supports" [] [Whitespace(" ")], + condition: CssFunction { + name: CssIdentifier { + value_token: IDENT@135..138 "foo" [] [], + }, + l_paren_token: L_PAREN@138..139 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssUrlFunction { + name: URL_KW@139..142 "url" [] [], + l_paren_token: L_PAREN@142..143 "(" [] [], + value: CssString { + value_token: CSS_STRING_LITERAL@143..151 "\"a.svg\"" [] [Whitespace(" ")], + }, + modifiers: CssUrlModifierList [ + CssFunction { + name: CssIdentifier { + value_token: IDENT@151..153 "fn" [] [], + }, + l_paren_token: L_PAREN@153..154 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@154..158 "test" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@158..159 ")" [] [], + }, + ], + r_paren_token: R_PAREN@159..160 ")" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@160..162 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@162..163 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@163..167 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@167..169 "c" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@169..171 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@171..176 "color" [] [], + }, + colon_token: COLON@176..178 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@178..183 "green" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@183..185 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@185..186 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@186..188 "}" [Newline("\n")] [], + }, + }, + }, + ], + eof_token: EOF@188..189 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..189 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..188 + 0: CSS_AT_RULE@0..48 + 0: AT@0..1 "@" [] [] + 1: CSS_SUPPORTS_AT_RULE@1..48 + 0: CSS_SUPPORTS_AT_RULE_DECLARATOR@1..24 + 0: SUPPORTS_KW@1..10 "supports" [] [Whitespace(" ")] + 1: CSS_BOGUS_SUPPORTS_CONDITION@10..24 + 0: CSS_IDENTIFIER@10..13 + 0: IDENT@10..13 "foo" [] [] + 1: L_PAREN@13..14 "(" [] [] + 2: CSS_BOGUS@14..22 + 0: CSS_BOGUS@14..22 + 0: CSS_BOGUS@14..22 + 0: CSS_IDENTIFIER@14..17 + 0: IDENT@14..17 "bar" [] [] + 1: CSS_BOGUS@17..22 + 0: COLON@17..19 ":" [] [Whitespace(" ")] + 1: IDENT@19..22 "baz" [] [] + 3: R_PAREN@22..24 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@24..48 + 0: L_CURLY@24..25 "{" [] [] + 1: CSS_RULE_LIST@25..46 + 0: CSS_QUALIFIED_RULE@25..46 + 0: CSS_SELECTOR_LIST@25..31 + 0: CSS_COMPOUND_SELECTOR@25..31 + 0: CSS_NESTED_SELECTOR_LIST@25..25 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@25..31 + 0: CSS_CLASS_SELECTOR@25..31 + 0: DOT@25..29 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@29..31 + 0: IDENT@29..31 "a" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@31..46 + 0: L_CURLY@31..33 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@33..45 + 0: CSS_DECLARATION_WITH_SEMICOLON@33..45 + 0: CSS_DECLARATION@33..43 + 0: CSS_GENERIC_PROPERTY@33..43 + 0: CSS_IDENTIFIER@33..38 + 0: IDENT@33..38 "color" [] [] + 1: COLON@38..40 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@40..43 + 0: CSS_IDENTIFIER@40..43 + 0: IDENT@40..43 "red" [] [] + 1: (empty) + 1: SEMICOLON@43..45 ";" [] [Whitespace(" ")] + 2: R_CURLY@45..46 "}" [] [] + 2: R_CURLY@46..48 "}" [Newline("\n")] [] + 1: CSS_AT_RULE@48..123 + 0: AT@48..51 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_SUPPORTS_AT_RULE@51..123 + 0: CSS_SUPPORTS_AT_RULE_DECLARATOR@51..96 + 0: SUPPORTS_KW@51..60 "supports" [] [Whitespace(" ")] + 1: CSS_SUPPORTS_AND_CONDITION@60..96 + 0: CSS_SUPPORTS_CONDITION_IN_PARENS@60..76 + 0: L_PAREN@60..61 "(" [] [] + 1: CSS_BOGUS_SUPPORTS_CONDITION@61..74 + 0: CSS_IDENTIFIER@61..64 + 0: IDENT@61..64 "foo" [] [] + 1: L_PAREN@64..65 "(" [] [] + 2: CSS_BOGUS@65..73 + 0: CSS_BOGUS@65..73 + 0: CSS_BOGUS@65..73 + 0: CSS_IDENTIFIER@65..68 + 0: IDENT@65..68 "bar" [] [] + 1: CSS_BOGUS@68..73 + 0: COLON@68..70 ":" [] [Whitespace(" ")] + 1: IDENT@70..73 "baz" [] [] + 3: R_PAREN@73..74 ")" [] [] + 2: R_PAREN@74..76 ")" [] [Whitespace(" ")] + 1: AND_KW@76..80 "and" [] [Whitespace(" ")] + 2: CSS_SUPPORTS_FEATURE_DECLARATION@80..96 + 0: L_PAREN@80..81 "(" [] [] + 1: CSS_DECLARATION@81..94 + 0: CSS_GENERIC_PROPERTY@81..94 + 0: CSS_IDENTIFIER@81..88 + 0: IDENT@81..88 "display" [] [] + 1: COLON@88..90 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@90..94 + 0: CSS_IDENTIFIER@90..94 + 0: IDENT@90..94 "grid" [] [] + 1: (empty) + 2: R_PAREN@94..96 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@96..123 + 0: L_CURLY@96..97 "{" [] [] + 1: CSS_RULE_LIST@97..121 + 0: CSS_QUALIFIED_RULE@97..121 + 0: CSS_SELECTOR_LIST@97..103 + 0: CSS_COMPOUND_SELECTOR@97..103 + 0: CSS_NESTED_SELECTOR_LIST@97..97 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@97..103 + 0: CSS_CLASS_SELECTOR@97..103 + 0: DOT@97..101 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@101..103 + 0: IDENT@101..103 "b" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@103..121 + 0: L_CURLY@103..105 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@105..120 + 0: CSS_DECLARATION_WITH_SEMICOLON@105..120 + 0: CSS_DECLARATION@105..118 + 0: CSS_GENERIC_PROPERTY@105..118 + 0: CSS_IDENTIFIER@105..112 + 0: IDENT@105..112 "display" [] [] + 1: COLON@112..114 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@114..118 + 0: CSS_IDENTIFIER@114..118 + 0: IDENT@114..118 "grid" [] [] + 1: (empty) + 1: SEMICOLON@118..120 ";" [] [Whitespace(" ")] + 2: R_CURLY@120..121 "}" [] [] + 2: R_CURLY@121..123 "}" [Newline("\n")] [] + 2: CSS_AT_RULE@123..188 + 0: AT@123..126 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_SUPPORTS_AT_RULE@126..188 + 0: CSS_SUPPORTS_AT_RULE_DECLARATOR@126..162 + 0: SUPPORTS_KW@126..135 "supports" [] [Whitespace(" ")] + 1: CSS_FUNCTION@135..162 + 0: CSS_IDENTIFIER@135..138 + 0: IDENT@135..138 "foo" [] [] + 1: L_PAREN@138..139 "(" [] [] + 2: CSS_PARAMETER_LIST@139..160 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@139..160 + 0: CSS_COMPONENT_VALUE_LIST@139..160 + 0: CSS_URL_FUNCTION@139..160 + 0: URL_KW@139..142 "url" [] [] + 1: L_PAREN@142..143 "(" [] [] + 2: CSS_STRING@143..151 + 0: CSS_STRING_LITERAL@143..151 "\"a.svg\"" [] [Whitespace(" ")] + 3: CSS_URL_MODIFIER_LIST@151..159 + 0: CSS_FUNCTION@151..159 + 0: CSS_IDENTIFIER@151..153 + 0: IDENT@151..153 "fn" [] [] + 1: L_PAREN@153..154 "(" [] [] + 2: CSS_PARAMETER_LIST@154..158 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@154..158 + 0: CSS_COMPONENT_VALUE_LIST@154..158 + 0: CSS_IDENTIFIER@154..158 + 0: IDENT@154..158 "test" [] [] + 3: R_PAREN@158..159 ")" [] [] + 4: R_PAREN@159..160 ")" [] [] + 3: R_PAREN@160..162 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@162..188 + 0: L_CURLY@162..163 "{" [] [] + 1: CSS_RULE_LIST@163..186 + 0: CSS_QUALIFIED_RULE@163..186 + 0: CSS_SELECTOR_LIST@163..169 + 0: CSS_COMPOUND_SELECTOR@163..169 + 0: CSS_NESTED_SELECTOR_LIST@163..163 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@163..169 + 0: CSS_CLASS_SELECTOR@163..169 + 0: DOT@163..167 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@167..169 + 0: IDENT@167..169 "c" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@169..186 + 0: L_CURLY@169..171 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@171..185 + 0: CSS_DECLARATION_WITH_SEMICOLON@171..185 + 0: CSS_DECLARATION@171..183 + 0: CSS_GENERIC_PROPERTY@171..183 + 0: CSS_IDENTIFIER@171..176 + 0: IDENT@171..176 "color" [] [] + 1: COLON@176..178 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@178..183 + 0: CSS_IDENTIFIER@178..183 + 0: IDENT@178..183 "green" [] [] + 1: (empty) + 1: SEMICOLON@183..185 ";" [] [Whitespace(" ")] + 2: R_CURLY@185..186 "}" [] [] + 2: R_CURLY@186..188 "}" [Newline("\n")] [] + 2: EOF@188..189 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +at_rule_supports_general_enclosed_error.css:1:18 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + > 1 │ @supports foo(bar: baz) { + │ ^^^^^ + 2 │ .a { color: red; } + 3 │ } + + i Expected one of: + + - identifier + - string + - number + - dimension + - ratio + - custom property + - function + +at_rule_supports_general_enclosed_error.css:5:19 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + 3 │ } + 4 │ + > 5 │ @supports (foo(bar: baz)) and (display: grid) { + │ ^^^^^ + 6 │ .b { display: grid; } + 7 │ } + + i Expected one of: + + - identifier + - string + - number + - dimension + - ratio + - custom property + - function + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/container-general-enclosed.scss b/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/container-general-enclosed.scss new file mode 100644 index 000000000000..915fdb70c210 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/container-general-enclosed.scss @@ -0,0 +1,11 @@ +@container (foo(bar: baz)) { + .a { color: red; } +} + +@container (foo(bar: baz)) and (width > 100px) { + .b { color: blue; } +} + +@container (foo(url("a.svg" namespace.fn(test)))) { + .c { color: green; } +} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/container-general-enclosed.scss.snap b/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/container-general-enclosed.scss.snap new file mode 100644 index 000000000000..95c612d67eca --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/container-general-enclosed.scss.snap @@ -0,0 +1,597 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +assertion_line: 208 +expression: snapshot +--- + +## Input + +```css +@container (foo(bar: baz)) { + .a { color: red; } +} + +@container (foo(bar: baz)) and (width > 100px) { + .b { color: blue; } +} + +@container (foo(url("a.svg" namespace.fn(test)))) { + .c { color: green; } +} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssBogusAtRule { + items: [ + CssBogus { + items: [ + CONTAINER_KW@1..11 "container" [] [Whitespace(" ")], + CssBogus { + items: [ + L_PAREN@11..12 "(" [] [], + CssBogusSupportsCondition { + items: [ + CssIdentifier { + value_token: IDENT@12..15 "foo" [] [], + }, + L_PAREN@15..16 "(" [] [], + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssIdentifier { + value_token: IDENT@16..19 "bar" [] [], + }, + CssBogus { + items: [ + COLON@19..21 ":" [] [Whitespace(" ")], + IDENT@21..24 "baz" [] [], + ], + }, + ], + }, + ], + }, + ], + }, + R_PAREN@24..25 ")" [] [], + ], + }, + R_PAREN@25..27 ")" [] [Whitespace(" ")], + ], + }, + ], + }, + CssRuleBlock { + l_curly_token: L_CURLY@27..28 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@28..32 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@32..34 "a" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@34..36 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@36..41 "color" [] [], + }, + colon_token: COLON@41..43 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@43..46 "red" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@46..48 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@48..49 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@49..51 "}" [Newline("\n")] [], + }, + ], + }, + }, + CssAtRule { + at_token: AT@51..54 "@" [Newline("\n"), Newline("\n")] [], + rule: CssBogusAtRule { + items: [ + CssBogus { + items: [ + CONTAINER_KW@54..64 "container" [] [Whitespace(" ")], + CssBogus { + items: [ + CssBogus { + items: [ + L_PAREN@64..65 "(" [] [], + CssBogusSupportsCondition { + items: [ + CssIdentifier { + value_token: IDENT@65..68 "foo" [] [], + }, + L_PAREN@68..69 "(" [] [], + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssIdentifier { + value_token: IDENT@69..72 "bar" [] [], + }, + CssBogus { + items: [ + COLON@72..74 ":" [] [Whitespace(" ")], + IDENT@74..77 "baz" [] [], + ], + }, + ], + }, + ], + }, + ], + }, + R_PAREN@77..78 ")" [] [], + ], + }, + R_PAREN@78..80 ")" [] [Whitespace(" ")], + ], + }, + AND_KW@80..84 "and" [] [Whitespace(" ")], + CssContainerSizeFeatureInParens { + l_paren_token: L_PAREN@84..85 "(" [] [], + feature: CssQueryFeatureRange { + left: CssIdentifier { + value_token: IDENT@85..91 "width" [] [Whitespace(" ")], + }, + comparison: CssQueryFeatureRangeComparison { + operator: R_ANGLE@91..93 ">" [] [Whitespace(" ")], + }, + right: CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@93..96 "100" [] [], + unit_token: IDENT@96..98 "px" [] [], + }, + }, + r_paren_token: R_PAREN@98..100 ")" [] [Whitespace(" ")], + }, + ], + }, + ], + }, + CssRuleBlock { + l_curly_token: L_CURLY@100..101 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@101..105 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@105..107 "b" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@107..109 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@109..114 "color" [] [], + }, + colon_token: COLON@114..116 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@116..120 "blue" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@120..122 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@122..123 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@123..125 "}" [Newline("\n")] [], + }, + ], + }, + }, + CssAtRule { + at_token: AT@125..128 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@128..138 "container" [] [Whitespace(" ")], + name: missing (optional), + query: CssContainerQueryInParens { + l_paren_token: L_PAREN@138..139 "(" [] [], + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@139..142 "foo" [] [], + }, + l_paren_token: L_PAREN@142..143 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssUrlFunction { + name: URL_KW@143..146 "url" [] [], + l_paren_token: L_PAREN@146..147 "(" [] [], + value: CssString { + value_token: CSS_STRING_LITERAL@147..155 "\"a.svg\"" [] [Whitespace(" ")], + }, + modifiers: CssUrlModifierList [ + CssIdentifier { + value_token: IDENT@155..164 "namespace" [] [], + }, + CssBogusUrlModifier { + items: [ + DOT@164..165 "." [] [], + ], + }, + CssFunction { + name: CssIdentifier { + value_token: IDENT@165..167 "fn" [] [], + }, + l_paren_token: L_PAREN@167..168 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@168..172 "test" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@172..173 ")" [] [], + }, + ], + r_paren_token: R_PAREN@173..174 ")" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@174..175 ")" [] [], + }, + r_paren_token: R_PAREN@175..177 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@177..178 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@178..182 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@182..184 "c" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@184..186 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@186..191 "color" [] [], + }, + colon_token: COLON@191..193 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@193..198 "green" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@198..200 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@200..201 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@201..203 "}" [Newline("\n")] [], + }, + }, + }, + ], + eof_token: EOF@203..204 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..204 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..203 + 0: CSS_AT_RULE@0..51 + 0: AT@0..1 "@" [] [] + 1: CSS_BOGUS_AT_RULE@1..51 + 0: CSS_BOGUS@1..27 + 0: CONTAINER_KW@1..11 "container" [] [Whitespace(" ")] + 1: CSS_BOGUS@11..27 + 0: L_PAREN@11..12 "(" [] [] + 1: CSS_BOGUS_SUPPORTS_CONDITION@12..25 + 0: CSS_IDENTIFIER@12..15 + 0: IDENT@12..15 "foo" [] [] + 1: L_PAREN@15..16 "(" [] [] + 2: CSS_BOGUS@16..24 + 0: CSS_BOGUS@16..24 + 0: CSS_BOGUS@16..24 + 0: CSS_IDENTIFIER@16..19 + 0: IDENT@16..19 "bar" [] [] + 1: CSS_BOGUS@19..24 + 0: COLON@19..21 ":" [] [Whitespace(" ")] + 1: IDENT@21..24 "baz" [] [] + 3: R_PAREN@24..25 ")" [] [] + 2: R_PAREN@25..27 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@27..51 + 0: L_CURLY@27..28 "{" [] [] + 1: CSS_RULE_LIST@28..49 + 0: CSS_QUALIFIED_RULE@28..49 + 0: CSS_SELECTOR_LIST@28..34 + 0: CSS_COMPOUND_SELECTOR@28..34 + 0: CSS_NESTED_SELECTOR_LIST@28..28 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@28..34 + 0: CSS_CLASS_SELECTOR@28..34 + 0: DOT@28..32 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@32..34 + 0: IDENT@32..34 "a" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@34..49 + 0: L_CURLY@34..36 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@36..48 + 0: CSS_DECLARATION_WITH_SEMICOLON@36..48 + 0: CSS_DECLARATION@36..46 + 0: CSS_GENERIC_PROPERTY@36..46 + 0: CSS_IDENTIFIER@36..41 + 0: IDENT@36..41 "color" [] [] + 1: COLON@41..43 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@43..46 + 0: CSS_IDENTIFIER@43..46 + 0: IDENT@43..46 "red" [] [] + 1: (empty) + 1: SEMICOLON@46..48 ";" [] [Whitespace(" ")] + 2: R_CURLY@48..49 "}" [] [] + 2: R_CURLY@49..51 "}" [Newline("\n")] [] + 1: CSS_AT_RULE@51..125 + 0: AT@51..54 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_BOGUS_AT_RULE@54..125 + 0: CSS_BOGUS@54..100 + 0: CONTAINER_KW@54..64 "container" [] [Whitespace(" ")] + 1: CSS_BOGUS@64..100 + 0: CSS_BOGUS@64..80 + 0: L_PAREN@64..65 "(" [] [] + 1: CSS_BOGUS_SUPPORTS_CONDITION@65..78 + 0: CSS_IDENTIFIER@65..68 + 0: IDENT@65..68 "foo" [] [] + 1: L_PAREN@68..69 "(" [] [] + 2: CSS_BOGUS@69..77 + 0: CSS_BOGUS@69..77 + 0: CSS_BOGUS@69..77 + 0: CSS_IDENTIFIER@69..72 + 0: IDENT@69..72 "bar" [] [] + 1: CSS_BOGUS@72..77 + 0: COLON@72..74 ":" [] [Whitespace(" ")] + 1: IDENT@74..77 "baz" [] [] + 3: R_PAREN@77..78 ")" [] [] + 2: R_PAREN@78..80 ")" [] [Whitespace(" ")] + 1: AND_KW@80..84 "and" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SIZE_FEATURE_IN_PARENS@84..100 + 0: L_PAREN@84..85 "(" [] [] + 1: CSS_QUERY_FEATURE_RANGE@85..98 + 0: CSS_IDENTIFIER@85..91 + 0: IDENT@85..91 "width" [] [Whitespace(" ")] + 1: CSS_QUERY_FEATURE_RANGE_COMPARISON@91..93 + 0: R_ANGLE@91..93 ">" [] [Whitespace(" ")] + 2: CSS_REGULAR_DIMENSION@93..98 + 0: CSS_NUMBER_LITERAL@93..96 "100" [] [] + 1: IDENT@96..98 "px" [] [] + 2: R_PAREN@98..100 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@100..125 + 0: L_CURLY@100..101 "{" [] [] + 1: CSS_RULE_LIST@101..123 + 0: CSS_QUALIFIED_RULE@101..123 + 0: CSS_SELECTOR_LIST@101..107 + 0: CSS_COMPOUND_SELECTOR@101..107 + 0: CSS_NESTED_SELECTOR_LIST@101..101 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@101..107 + 0: CSS_CLASS_SELECTOR@101..107 + 0: DOT@101..105 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@105..107 + 0: IDENT@105..107 "b" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@107..123 + 0: L_CURLY@107..109 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@109..122 + 0: CSS_DECLARATION_WITH_SEMICOLON@109..122 + 0: CSS_DECLARATION@109..120 + 0: CSS_GENERIC_PROPERTY@109..120 + 0: CSS_IDENTIFIER@109..114 + 0: IDENT@109..114 "color" [] [] + 1: COLON@114..116 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@116..120 + 0: CSS_IDENTIFIER@116..120 + 0: IDENT@116..120 "blue" [] [] + 1: (empty) + 1: SEMICOLON@120..122 ";" [] [Whitespace(" ")] + 2: R_CURLY@122..123 "}" [] [] + 2: R_CURLY@123..125 "}" [Newline("\n")] [] + 2: CSS_AT_RULE@125..203 + 0: AT@125..128 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@128..203 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@128..177 + 0: CONTAINER_KW@128..138 "container" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_CONTAINER_QUERY_IN_PARENS@138..177 + 0: L_PAREN@138..139 "(" [] [] + 1: CSS_FUNCTION@139..175 + 0: CSS_IDENTIFIER@139..142 + 0: IDENT@139..142 "foo" [] [] + 1: L_PAREN@142..143 "(" [] [] + 2: CSS_PARAMETER_LIST@143..174 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@143..174 + 0: CSS_COMPONENT_VALUE_LIST@143..174 + 0: CSS_URL_FUNCTION@143..174 + 0: URL_KW@143..146 "url" [] [] + 1: L_PAREN@146..147 "(" [] [] + 2: CSS_STRING@147..155 + 0: CSS_STRING_LITERAL@147..155 "\"a.svg\"" [] [Whitespace(" ")] + 3: CSS_URL_MODIFIER_LIST@155..173 + 0: CSS_IDENTIFIER@155..164 + 0: IDENT@155..164 "namespace" [] [] + 1: CSS_BOGUS_URL_MODIFIER@164..165 + 0: DOT@164..165 "." [] [] + 2: CSS_FUNCTION@165..173 + 0: CSS_IDENTIFIER@165..167 + 0: IDENT@165..167 "fn" [] [] + 1: L_PAREN@167..168 "(" [] [] + 2: CSS_PARAMETER_LIST@168..172 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@168..172 + 0: CSS_COMPONENT_VALUE_LIST@168..172 + 0: CSS_IDENTIFIER@168..172 + 0: IDENT@168..172 "test" [] [] + 3: R_PAREN@172..173 ")" [] [] + 4: R_PAREN@173..174 ")" [] [] + 3: R_PAREN@174..175 ")" [] [] + 2: R_PAREN@175..177 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@177..203 + 0: L_CURLY@177..178 "{" [] [] + 1: CSS_RULE_LIST@178..201 + 0: CSS_QUALIFIED_RULE@178..201 + 0: CSS_SELECTOR_LIST@178..184 + 0: CSS_COMPOUND_SELECTOR@178..184 + 0: CSS_NESTED_SELECTOR_LIST@178..178 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@178..184 + 0: CSS_CLASS_SELECTOR@178..184 + 0: DOT@178..182 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@182..184 + 0: IDENT@182..184 "c" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@184..201 + 0: L_CURLY@184..186 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@186..200 + 0: CSS_DECLARATION_WITH_SEMICOLON@186..200 + 0: CSS_DECLARATION@186..198 + 0: CSS_GENERIC_PROPERTY@186..198 + 0: CSS_IDENTIFIER@186..191 + 0: IDENT@186..191 "color" [] [] + 1: COLON@191..193 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@193..198 + 0: CSS_IDENTIFIER@193..198 + 0: IDENT@193..198 "green" [] [] + 1: (empty) + 1: SEMICOLON@198..200 ";" [] [Whitespace(" ")] + 2: R_CURLY@200..201 "}" [] [] + 2: R_CURLY@201..203 "}" [Newline("\n")] [] + 2: EOF@203..204 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +container-general-enclosed.scss:1:20 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + > 1 │ @container (foo(bar: baz)) { + │ ^^^^^ + 2 │ .a { color: red; } + 3 │ } + + i Expected one of: + + - identifier + - string + - number + - dimension + - ratio + - custom property + - function + +container-general-enclosed.scss:5:20 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + 3 │ } + 4 │ + > 5 │ @container (foo(bar: baz)) and (width > 100px) { + │ ^^^^^ + 6 │ .b { color: blue; } + 7 │ } + + i Expected one of: + + - identifier + - string + - number + - dimension + - ratio + - custom property + - function + +container-general-enclosed.scss:9:38 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + 7 │ } + 8 │ + > 9 │ @container (foo(url("a.svg" namespace.fn(test)))) { + │ ^ + 10 │ .c { color: green; } + 11 │ } + + i Expected one of: + + - function + - identifier + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-general-enclosed.scss b/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-general-enclosed.scss new file mode 100644 index 000000000000..deed5815d867 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-general-enclosed.scss @@ -0,0 +1,11 @@ +@supports foo(bar: baz) { + .a { color: red; } +} + +@supports (foo(bar: baz)) and (display: grid) { + .b { display: grid; } +} + +@supports foo(url("a.svg" namespace.fn(test))) { + .c { color: green; } +} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-general-enclosed.scss.snap b/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-general-enclosed.scss.snap new file mode 100644 index 000000000000..18402a1da179 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/scss/at-rule/supports-general-enclosed.scss.snap @@ -0,0 +1,570 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +assertion_line: 208 +expression: snapshot +--- + +## Input + +```css +@supports foo(bar: baz) { + .a { color: red; } +} + +@supports (foo(bar: baz)) and (display: grid) { + .b { display: grid; } +} + +@supports foo(url("a.svg" namespace.fn(test))) { + .c { color: green; } +} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssSupportsAtRule { + declarator: CssSupportsAtRuleDeclarator { + supports_token: SUPPORTS_KW@1..10 "supports" [] [Whitespace(" ")], + condition: CssBogusSupportsCondition { + items: [ + CssIdentifier { + value_token: IDENT@10..13 "foo" [] [], + }, + L_PAREN@13..14 "(" [] [], + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssIdentifier { + value_token: IDENT@14..17 "bar" [] [], + }, + CssBogus { + items: [ + COLON@17..19 ":" [] [Whitespace(" ")], + IDENT@19..22 "baz" [] [], + ], + }, + ], + }, + ], + }, + ], + }, + R_PAREN@22..24 ")" [] [Whitespace(" ")], + ], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@24..25 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@25..29 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@29..31 "a" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@31..33 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@33..38 "color" [] [], + }, + colon_token: COLON@38..40 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@40..43 "red" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@43..45 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@45..46 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@46..48 "}" [Newline("\n")] [], + }, + }, + }, + CssAtRule { + at_token: AT@48..51 "@" [Newline("\n"), Newline("\n")] [], + rule: CssSupportsAtRule { + declarator: CssSupportsAtRuleDeclarator { + supports_token: SUPPORTS_KW@51..60 "supports" [] [Whitespace(" ")], + condition: CssSupportsAndCondition { + left: CssSupportsConditionInParens { + l_paren_token: L_PAREN@60..61 "(" [] [], + condition: CssBogusSupportsCondition { + items: [ + CssIdentifier { + value_token: IDENT@61..64 "foo" [] [], + }, + L_PAREN@64..65 "(" [] [], + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssIdentifier { + value_token: IDENT@65..68 "bar" [] [], + }, + CssBogus { + items: [ + COLON@68..70 ":" [] [Whitespace(" ")], + IDENT@70..73 "baz" [] [], + ], + }, + ], + }, + ], + }, + ], + }, + R_PAREN@73..74 ")" [] [], + ], + }, + r_paren_token: R_PAREN@74..76 ")" [] [Whitespace(" ")], + }, + and_token: AND_KW@76..80 "and" [] [Whitespace(" ")], + right: CssSupportsFeatureDeclaration { + l_paren_token: L_PAREN@80..81 "(" [] [], + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@81..88 "display" [] [], + }, + colon_token: COLON@88..90 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@90..94 "grid" [] [], + }, + ], + }, + important: missing (optional), + }, + r_paren_token: R_PAREN@94..96 ")" [] [Whitespace(" ")], + }, + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@96..97 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@97..101 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@101..103 "b" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@103..105 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@105..112 "display" [] [], + }, + colon_token: COLON@112..114 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@114..118 "grid" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@118..120 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@120..121 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@121..123 "}" [Newline("\n")] [], + }, + }, + }, + CssAtRule { + at_token: AT@123..126 "@" [Newline("\n"), Newline("\n")] [], + rule: CssSupportsAtRule { + declarator: CssSupportsAtRuleDeclarator { + supports_token: SUPPORTS_KW@126..135 "supports" [] [Whitespace(" ")], + condition: CssFunction { + name: CssIdentifier { + value_token: IDENT@135..138 "foo" [] [], + }, + l_paren_token: L_PAREN@138..139 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssUrlFunction { + name: URL_KW@139..142 "url" [] [], + l_paren_token: L_PAREN@142..143 "(" [] [], + value: CssString { + value_token: CSS_STRING_LITERAL@143..151 "\"a.svg\"" [] [Whitespace(" ")], + }, + modifiers: CssUrlModifierList [ + CssIdentifier { + value_token: IDENT@151..160 "namespace" [] [], + }, + CssBogusUrlModifier { + items: [ + DOT@160..161 "." [] [], + ], + }, + CssFunction { + name: CssIdentifier { + value_token: IDENT@161..163 "fn" [] [], + }, + l_paren_token: L_PAREN@163..164 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@164..168 "test" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@168..169 ")" [] [], + }, + ], + r_paren_token: R_PAREN@169..170 ")" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@170..172 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@172..173 "{" [] [], + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@173..177 "." [Newline("\n"), Whitespace(" ")] [], + name: CssCustomIdentifier { + value_token: IDENT@177..179 "c" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@179..181 "{" [] [Whitespace(" ")], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@181..186 "color" [] [], + }, + colon_token: COLON@186..188 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@188..193 "green" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@193..195 ";" [] [Whitespace(" ")], + }, + ], + r_curly_token: R_CURLY@195..196 "}" [] [], + }, + }, + ], + r_curly_token: R_CURLY@196..198 "}" [Newline("\n")] [], + }, + }, + }, + ], + eof_token: EOF@198..199 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..199 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..198 + 0: CSS_AT_RULE@0..48 + 0: AT@0..1 "@" [] [] + 1: CSS_SUPPORTS_AT_RULE@1..48 + 0: CSS_SUPPORTS_AT_RULE_DECLARATOR@1..24 + 0: SUPPORTS_KW@1..10 "supports" [] [Whitespace(" ")] + 1: CSS_BOGUS_SUPPORTS_CONDITION@10..24 + 0: CSS_IDENTIFIER@10..13 + 0: IDENT@10..13 "foo" [] [] + 1: L_PAREN@13..14 "(" [] [] + 2: CSS_BOGUS@14..22 + 0: CSS_BOGUS@14..22 + 0: CSS_BOGUS@14..22 + 0: CSS_IDENTIFIER@14..17 + 0: IDENT@14..17 "bar" [] [] + 1: CSS_BOGUS@17..22 + 0: COLON@17..19 ":" [] [Whitespace(" ")] + 1: IDENT@19..22 "baz" [] [] + 3: R_PAREN@22..24 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@24..48 + 0: L_CURLY@24..25 "{" [] [] + 1: CSS_RULE_LIST@25..46 + 0: CSS_QUALIFIED_RULE@25..46 + 0: CSS_SELECTOR_LIST@25..31 + 0: CSS_COMPOUND_SELECTOR@25..31 + 0: CSS_NESTED_SELECTOR_LIST@25..25 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@25..31 + 0: CSS_CLASS_SELECTOR@25..31 + 0: DOT@25..29 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@29..31 + 0: IDENT@29..31 "a" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@31..46 + 0: L_CURLY@31..33 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@33..45 + 0: CSS_DECLARATION_WITH_SEMICOLON@33..45 + 0: CSS_DECLARATION@33..43 + 0: CSS_GENERIC_PROPERTY@33..43 + 0: CSS_IDENTIFIER@33..38 + 0: IDENT@33..38 "color" [] [] + 1: COLON@38..40 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@40..43 + 0: CSS_IDENTIFIER@40..43 + 0: IDENT@40..43 "red" [] [] + 1: (empty) + 1: SEMICOLON@43..45 ";" [] [Whitespace(" ")] + 2: R_CURLY@45..46 "}" [] [] + 2: R_CURLY@46..48 "}" [Newline("\n")] [] + 1: CSS_AT_RULE@48..123 + 0: AT@48..51 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_SUPPORTS_AT_RULE@51..123 + 0: CSS_SUPPORTS_AT_RULE_DECLARATOR@51..96 + 0: SUPPORTS_KW@51..60 "supports" [] [Whitespace(" ")] + 1: CSS_SUPPORTS_AND_CONDITION@60..96 + 0: CSS_SUPPORTS_CONDITION_IN_PARENS@60..76 + 0: L_PAREN@60..61 "(" [] [] + 1: CSS_BOGUS_SUPPORTS_CONDITION@61..74 + 0: CSS_IDENTIFIER@61..64 + 0: IDENT@61..64 "foo" [] [] + 1: L_PAREN@64..65 "(" [] [] + 2: CSS_BOGUS@65..73 + 0: CSS_BOGUS@65..73 + 0: CSS_BOGUS@65..73 + 0: CSS_IDENTIFIER@65..68 + 0: IDENT@65..68 "bar" [] [] + 1: CSS_BOGUS@68..73 + 0: COLON@68..70 ":" [] [Whitespace(" ")] + 1: IDENT@70..73 "baz" [] [] + 3: R_PAREN@73..74 ")" [] [] + 2: R_PAREN@74..76 ")" [] [Whitespace(" ")] + 1: AND_KW@76..80 "and" [] [Whitespace(" ")] + 2: CSS_SUPPORTS_FEATURE_DECLARATION@80..96 + 0: L_PAREN@80..81 "(" [] [] + 1: CSS_DECLARATION@81..94 + 0: CSS_GENERIC_PROPERTY@81..94 + 0: CSS_IDENTIFIER@81..88 + 0: IDENT@81..88 "display" [] [] + 1: COLON@88..90 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@90..94 + 0: CSS_IDENTIFIER@90..94 + 0: IDENT@90..94 "grid" [] [] + 1: (empty) + 2: R_PAREN@94..96 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@96..123 + 0: L_CURLY@96..97 "{" [] [] + 1: CSS_RULE_LIST@97..121 + 0: CSS_QUALIFIED_RULE@97..121 + 0: CSS_SELECTOR_LIST@97..103 + 0: CSS_COMPOUND_SELECTOR@97..103 + 0: CSS_NESTED_SELECTOR_LIST@97..97 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@97..103 + 0: CSS_CLASS_SELECTOR@97..103 + 0: DOT@97..101 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@101..103 + 0: IDENT@101..103 "b" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@103..121 + 0: L_CURLY@103..105 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@105..120 + 0: CSS_DECLARATION_WITH_SEMICOLON@105..120 + 0: CSS_DECLARATION@105..118 + 0: CSS_GENERIC_PROPERTY@105..118 + 0: CSS_IDENTIFIER@105..112 + 0: IDENT@105..112 "display" [] [] + 1: COLON@112..114 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@114..118 + 0: CSS_IDENTIFIER@114..118 + 0: IDENT@114..118 "grid" [] [] + 1: (empty) + 1: SEMICOLON@118..120 ";" [] [Whitespace(" ")] + 2: R_CURLY@120..121 "}" [] [] + 2: R_CURLY@121..123 "}" [Newline("\n")] [] + 2: CSS_AT_RULE@123..198 + 0: AT@123..126 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_SUPPORTS_AT_RULE@126..198 + 0: CSS_SUPPORTS_AT_RULE_DECLARATOR@126..172 + 0: SUPPORTS_KW@126..135 "supports" [] [Whitespace(" ")] + 1: CSS_FUNCTION@135..172 + 0: CSS_IDENTIFIER@135..138 + 0: IDENT@135..138 "foo" [] [] + 1: L_PAREN@138..139 "(" [] [] + 2: CSS_PARAMETER_LIST@139..170 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@139..170 + 0: CSS_COMPONENT_VALUE_LIST@139..170 + 0: CSS_URL_FUNCTION@139..170 + 0: URL_KW@139..142 "url" [] [] + 1: L_PAREN@142..143 "(" [] [] + 2: CSS_STRING@143..151 + 0: CSS_STRING_LITERAL@143..151 "\"a.svg\"" [] [Whitespace(" ")] + 3: CSS_URL_MODIFIER_LIST@151..169 + 0: CSS_IDENTIFIER@151..160 + 0: IDENT@151..160 "namespace" [] [] + 1: CSS_BOGUS_URL_MODIFIER@160..161 + 0: DOT@160..161 "." [] [] + 2: CSS_FUNCTION@161..169 + 0: CSS_IDENTIFIER@161..163 + 0: IDENT@161..163 "fn" [] [] + 1: L_PAREN@163..164 "(" [] [] + 2: CSS_PARAMETER_LIST@164..168 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@164..168 + 0: CSS_COMPONENT_VALUE_LIST@164..168 + 0: CSS_IDENTIFIER@164..168 + 0: IDENT@164..168 "test" [] [] + 3: R_PAREN@168..169 ")" [] [] + 4: R_PAREN@169..170 ")" [] [] + 3: R_PAREN@170..172 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@172..198 + 0: L_CURLY@172..173 "{" [] [] + 1: CSS_RULE_LIST@173..196 + 0: CSS_QUALIFIED_RULE@173..196 + 0: CSS_SELECTOR_LIST@173..179 + 0: CSS_COMPOUND_SELECTOR@173..179 + 0: CSS_NESTED_SELECTOR_LIST@173..173 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@173..179 + 0: CSS_CLASS_SELECTOR@173..179 + 0: DOT@173..177 "." [Newline("\n"), Whitespace(" ")] [] + 1: CSS_CUSTOM_IDENTIFIER@177..179 + 0: IDENT@177..179 "c" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@179..196 + 0: L_CURLY@179..181 "{" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_LIST@181..195 + 0: CSS_DECLARATION_WITH_SEMICOLON@181..195 + 0: CSS_DECLARATION@181..193 + 0: CSS_GENERIC_PROPERTY@181..193 + 0: CSS_IDENTIFIER@181..186 + 0: IDENT@181..186 "color" [] [] + 1: COLON@186..188 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@188..193 + 0: CSS_IDENTIFIER@188..193 + 0: IDENT@188..193 "green" [] [] + 1: (empty) + 1: SEMICOLON@193..195 ";" [] [Whitespace(" ")] + 2: R_CURLY@195..196 "}" [] [] + 2: R_CURLY@196..198 "}" [Newline("\n")] [] + 2: EOF@198..199 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +supports-general-enclosed.scss:1:18 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + > 1 │ @supports foo(bar: baz) { + │ ^^^^^ + 2 │ .a { color: red; } + 3 │ } + + i Expected one of: + + - identifier + - string + - number + - dimension + - ratio + - custom property + - function + +supports-general-enclosed.scss:5:19 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + 3 │ } + 4 │ + > 5 │ @supports (foo(bar: baz)) and (display: grid) { + │ ^^^^^ + 6 │ .b { display: grid; } + 7 │ } + + i Expected one of: + + - identifier + - string + - number + - dimension + - ratio + - custom property + - function + +supports-general-enclosed.scss:9:36 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + 7 │ } + 8 │ + > 9 │ @supports foo(url("a.svg" namespace.fn(test))) { + │ ^ + 10 │ .c { color: green; } + 11 │ } + + i Expected one of: + + - function + - identifier + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_general_enclosed.css b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_general_enclosed.css new file mode 100644 index 000000000000..befd333d9863 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_general_enclosed.css @@ -0,0 +1,5 @@ +@container main-layout and(test) { } + +@container main-layout or(test) { } + +@container main-layout foo(url("a.svg" fn(test))) { } diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_general_enclosed.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_general_enclosed.css.snap new file mode 100644 index 000000000000..73dfcea5f75e --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_general_enclosed.css.snap @@ -0,0 +1,234 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css +@container main-layout and(test) { } + +@container main-layout or(test) { } + +@container main-layout foo(url("a.svg" fn(test))) { } + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@1..11 "container" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@11..23 "main-layout" [] [Whitespace(" ")], + }, + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@23..26 "and" [] [], + }, + l_paren_token: L_PAREN@26..27 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@27..31 "test" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@31..33 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@33..35 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@35..36 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@36..39 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@39..49 "container" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@49..61 "main-layout" [] [Whitespace(" ")], + }, + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@61..63 "or" [] [], + }, + l_paren_token: L_PAREN@63..64 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@64..68 "test" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@68..70 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@70..72 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@72..73 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@73..76 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@76..86 "container" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@86..98 "main-layout" [] [Whitespace(" ")], + }, + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@98..101 "foo" [] [], + }, + l_paren_token: L_PAREN@101..102 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssUrlFunction { + name: URL_KW@102..105 "url" [] [], + l_paren_token: L_PAREN@105..106 "(" [] [], + value: CssString { + value_token: CSS_STRING_LITERAL@106..114 "\"a.svg\"" [] [Whitespace(" ")], + }, + modifiers: CssUrlModifierList [ + CssFunction { + name: CssIdentifier { + value_token: IDENT@114..116 "fn" [] [], + }, + l_paren_token: L_PAREN@116..117 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@117..121 "test" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@121..122 ")" [] [], + }, + ], + r_paren_token: R_PAREN@122..123 ")" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@123..125 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@125..127 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@127..128 "}" [] [], + }, + }, + }, + ], + eof_token: EOF@128..129 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..129 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..128 + 0: CSS_AT_RULE@0..36 + 0: AT@0..1 "@" [] [] + 1: CSS_CONTAINER_AT_RULE@1..36 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@1..33 + 0: CONTAINER_KW@1..11 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@11..23 + 0: IDENT@11..23 "main-layout" [] [Whitespace(" ")] + 2: CSS_FUNCTION@23..33 + 0: CSS_IDENTIFIER@23..26 + 0: IDENT@23..26 "and" [] [] + 1: L_PAREN@26..27 "(" [] [] + 2: CSS_PARAMETER_LIST@27..31 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@27..31 + 0: CSS_COMPONENT_VALUE_LIST@27..31 + 0: CSS_IDENTIFIER@27..31 + 0: IDENT@27..31 "test" [] [] + 3: R_PAREN@31..33 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@33..36 + 0: L_CURLY@33..35 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@35..35 + 2: R_CURLY@35..36 "}" [] [] + 1: CSS_AT_RULE@36..73 + 0: AT@36..39 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@39..73 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@39..70 + 0: CONTAINER_KW@39..49 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@49..61 + 0: IDENT@49..61 "main-layout" [] [Whitespace(" ")] + 2: CSS_FUNCTION@61..70 + 0: CSS_IDENTIFIER@61..63 + 0: IDENT@61..63 "or" [] [] + 1: L_PAREN@63..64 "(" [] [] + 2: CSS_PARAMETER_LIST@64..68 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@64..68 + 0: CSS_COMPONENT_VALUE_LIST@64..68 + 0: CSS_IDENTIFIER@64..68 + 0: IDENT@64..68 "test" [] [] + 3: R_PAREN@68..70 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@70..73 + 0: L_CURLY@70..72 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@72..72 + 2: R_CURLY@72..73 "}" [] [] + 2: CSS_AT_RULE@73..128 + 0: AT@73..76 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@76..128 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@76..125 + 0: CONTAINER_KW@76..86 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@86..98 + 0: IDENT@86..98 "main-layout" [] [Whitespace(" ")] + 2: CSS_FUNCTION@98..125 + 0: CSS_IDENTIFIER@98..101 + 0: IDENT@98..101 "foo" [] [] + 1: L_PAREN@101..102 "(" [] [] + 2: CSS_PARAMETER_LIST@102..123 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@102..123 + 0: CSS_COMPONENT_VALUE_LIST@102..123 + 0: CSS_URL_FUNCTION@102..123 + 0: URL_KW@102..105 "url" [] [] + 1: L_PAREN@105..106 "(" [] [] + 2: CSS_STRING@106..114 + 0: CSS_STRING_LITERAL@106..114 "\"a.svg\"" [] [Whitespace(" ")] + 3: CSS_URL_MODIFIER_LIST@114..122 + 0: CSS_FUNCTION@114..122 + 0: CSS_IDENTIFIER@114..116 + 0: IDENT@114..116 "fn" [] [] + 1: L_PAREN@116..117 "(" [] [] + 2: CSS_PARAMETER_LIST@117..121 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@117..121 + 0: CSS_COMPONENT_VALUE_LIST@117..121 + 0: CSS_IDENTIFIER@117..121 + 0: IDENT@117..121 "test" [] [] + 3: R_PAREN@121..122 ")" [] [] + 4: R_PAREN@122..123 ")" [] [] + 3: R_PAREN@123..125 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@125..128 + 0: L_CURLY@125..127 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@127..127 + 2: R_CURLY@127..128 "}" [] [] + 2: EOF@128..129 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_scroll_state.css b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_scroll_state.css new file mode 100644 index 000000000000..b4d26a6f662a --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_scroll_state.css @@ -0,0 +1,11 @@ +@container scroll-state( scrolled: bottom ) { } + +@container scroll-state(stuck) { } + +@container scroll-state(not (stuck)) { } + +@container scroll-state((stuck) and (scrolled: bottom)) { } + +@container scroll-state((stuck) or (snapped: x)) { } + +@container main-layout scroll-state(not ((stuck) and (scrolled: bottom))) { } diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_scroll_state.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_scroll_state.css.snap new file mode 100644 index 000000000000..50fa6509b4b6 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_container_scroll_state.css.snap @@ -0,0 +1,443 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css +@container scroll-state( scrolled: bottom ) { } + +@container scroll-state(stuck) { } + +@container scroll-state(not (stuck)) { } + +@container scroll-state((stuck) and (scrolled: bottom)) { } + +@container scroll-state((stuck) or (snapped: x)) { } + +@container main-layout scroll-state(not ((stuck) and (scrolled: bottom))) { } + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@1..13 "container" [] [Whitespace(" ")], + name: missing (optional), + query: CssContainerScrollStateQueryInParens { + name: CssIdentifier { + value_token: IDENT@13..25 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@25..28 "(" [] [Whitespace(" ")], + query: CssQueryFeaturePlain { + name: CssIdentifier { + value_token: IDENT@28..36 "scrolled" [] [], + }, + colon_token: COLON@36..40 ":" [] [Whitespace(" ")], + value: CssIdentifier { + value_token: IDENT@40..48 "bottom" [] [Whitespace(" ")], + }, + }, + r_paren_token: R_PAREN@48..52 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@52..55 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@55..56 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@56..59 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@59..69 "container" [] [Whitespace(" ")], + name: missing (optional), + query: CssContainerScrollStateQueryInParens { + name: CssIdentifier { + value_token: IDENT@69..81 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@81..82 "(" [] [], + query: CssQueryFeatureBoolean { + name: CssIdentifier { + value_token: IDENT@82..87 "stuck" [] [], + }, + }, + r_paren_token: R_PAREN@87..89 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@89..92 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@92..93 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@93..96 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@96..106 "container" [] [Whitespace(" ")], + name: missing (optional), + query: CssContainerScrollStateQueryInParens { + name: CssIdentifier { + value_token: IDENT@106..118 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@118..119 "(" [] [], + query: CssContainerScrollStateNotQuery { + not_token: NOT_KW@119..123 "not" [] [Whitespace(" ")], + query: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@123..124 "(" [] [], + query: CssQueryFeatureBoolean { + name: CssIdentifier { + value_token: IDENT@124..129 "stuck" [] [], + }, + }, + r_paren_token: R_PAREN@129..130 ")" [] [], + }, + }, + r_paren_token: R_PAREN@130..132 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@132..134 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@134..135 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@135..138 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@138..148 "container" [] [Whitespace(" ")], + name: missing (optional), + query: CssContainerScrollStateQueryInParens { + name: CssIdentifier { + value_token: IDENT@148..160 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@160..161 "(" [] [], + query: CssContainerScrollStateAndQuery { + left: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@161..162 "(" [] [], + query: CssQueryFeatureBoolean { + name: CssIdentifier { + value_token: IDENT@162..167 "stuck" [] [], + }, + }, + r_paren_token: R_PAREN@167..169 ")" [] [Whitespace(" ")], + }, + and_token: AND_KW@169..173 "and" [] [Whitespace(" ")], + right: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@173..174 "(" [] [], + query: CssQueryFeaturePlain { + name: CssIdentifier { + value_token: IDENT@174..182 "scrolled" [] [], + }, + colon_token: COLON@182..184 ":" [] [Whitespace(" ")], + value: CssIdentifier { + value_token: IDENT@184..190 "bottom" [] [], + }, + }, + r_paren_token: R_PAREN@190..191 ")" [] [], + }, + }, + r_paren_token: R_PAREN@191..193 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@193..195 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@195..196 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@196..199 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@199..209 "container" [] [Whitespace(" ")], + name: missing (optional), + query: CssContainerScrollStateQueryInParens { + name: CssIdentifier { + value_token: IDENT@209..221 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@221..222 "(" [] [], + query: CssContainerScrollStateOrQuery { + left: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@222..223 "(" [] [], + query: CssQueryFeatureBoolean { + name: CssIdentifier { + value_token: IDENT@223..228 "stuck" [] [], + }, + }, + r_paren_token: R_PAREN@228..230 ")" [] [Whitespace(" ")], + }, + or_token: OR_KW@230..233 "or" [] [Whitespace(" ")], + right: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@233..234 "(" [] [], + query: CssQueryFeaturePlain { + name: CssIdentifier { + value_token: IDENT@234..241 "snapped" [] [], + }, + colon_token: COLON@241..243 ":" [] [Whitespace(" ")], + value: CssIdentifier { + value_token: IDENT@243..244 "x" [] [], + }, + }, + r_paren_token: R_PAREN@244..245 ")" [] [], + }, + }, + r_paren_token: R_PAREN@245..247 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@247..249 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@249..250 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@250..253 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@253..263 "container" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@263..275 "main-layout" [] [Whitespace(" ")], + }, + query: CssContainerScrollStateQueryInParens { + name: CssIdentifier { + value_token: IDENT@275..287 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@287..288 "(" [] [], + query: CssContainerScrollStateNotQuery { + not_token: NOT_KW@288..292 "not" [] [Whitespace(" ")], + query: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@292..293 "(" [] [], + query: CssContainerScrollStateAndQuery { + left: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@293..294 "(" [] [], + query: CssQueryFeatureBoolean { + name: CssIdentifier { + value_token: IDENT@294..299 "stuck" [] [], + }, + }, + r_paren_token: R_PAREN@299..301 ")" [] [Whitespace(" ")], + }, + and_token: AND_KW@301..305 "and" [] [Whitespace(" ")], + right: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@305..306 "(" [] [], + query: CssQueryFeaturePlain { + name: CssIdentifier { + value_token: IDENT@306..314 "scrolled" [] [], + }, + colon_token: COLON@314..316 ":" [] [Whitespace(" ")], + value: CssIdentifier { + value_token: IDENT@316..322 "bottom" [] [], + }, + }, + r_paren_token: R_PAREN@322..323 ")" [] [], + }, + }, + r_paren_token: R_PAREN@323..324 ")" [] [], + }, + }, + r_paren_token: R_PAREN@324..326 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@326..329 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@329..330 "}" [] [], + }, + }, + }, + ], + eof_token: EOF@330..331 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..331 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..330 + 0: CSS_AT_RULE@0..56 + 0: AT@0..1 "@" [] [] + 1: CSS_CONTAINER_AT_RULE@1..56 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@1..52 + 0: CONTAINER_KW@1..13 "container" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@13..52 + 0: CSS_IDENTIFIER@13..25 + 0: IDENT@13..25 "scroll-state" [] [] + 1: L_PAREN@25..28 "(" [] [Whitespace(" ")] + 2: CSS_QUERY_FEATURE_PLAIN@28..48 + 0: CSS_IDENTIFIER@28..36 + 0: IDENT@28..36 "scrolled" [] [] + 1: COLON@36..40 ":" [] [Whitespace(" ")] + 2: CSS_IDENTIFIER@40..48 + 0: IDENT@40..48 "bottom" [] [Whitespace(" ")] + 3: R_PAREN@48..52 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@52..56 + 0: L_CURLY@52..55 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@55..55 + 2: R_CURLY@55..56 "}" [] [] + 1: CSS_AT_RULE@56..93 + 0: AT@56..59 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@59..93 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@59..89 + 0: CONTAINER_KW@59..69 "container" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@69..89 + 0: CSS_IDENTIFIER@69..81 + 0: IDENT@69..81 "scroll-state" [] [] + 1: L_PAREN@81..82 "(" [] [] + 2: CSS_QUERY_FEATURE_BOOLEAN@82..87 + 0: CSS_IDENTIFIER@82..87 + 0: IDENT@82..87 "stuck" [] [] + 3: R_PAREN@87..89 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@89..93 + 0: L_CURLY@89..92 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@92..92 + 2: R_CURLY@92..93 "}" [] [] + 2: CSS_AT_RULE@93..135 + 0: AT@93..96 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@96..135 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@96..132 + 0: CONTAINER_KW@96..106 "container" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@106..132 + 0: CSS_IDENTIFIER@106..118 + 0: IDENT@106..118 "scroll-state" [] [] + 1: L_PAREN@118..119 "(" [] [] + 2: CSS_CONTAINER_SCROLL_STATE_NOT_QUERY@119..130 + 0: NOT_KW@119..123 "not" [] [Whitespace(" ")] + 1: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@123..130 + 0: L_PAREN@123..124 "(" [] [] + 1: CSS_QUERY_FEATURE_BOOLEAN@124..129 + 0: CSS_IDENTIFIER@124..129 + 0: IDENT@124..129 "stuck" [] [] + 2: R_PAREN@129..130 ")" [] [] + 3: R_PAREN@130..132 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@132..135 + 0: L_CURLY@132..134 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@134..134 + 2: R_CURLY@134..135 "}" [] [] + 3: CSS_AT_RULE@135..196 + 0: AT@135..138 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@138..196 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@138..193 + 0: CONTAINER_KW@138..148 "container" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@148..193 + 0: CSS_IDENTIFIER@148..160 + 0: IDENT@148..160 "scroll-state" [] [] + 1: L_PAREN@160..161 "(" [] [] + 2: CSS_CONTAINER_SCROLL_STATE_AND_QUERY@161..191 + 0: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@161..169 + 0: L_PAREN@161..162 "(" [] [] + 1: CSS_QUERY_FEATURE_BOOLEAN@162..167 + 0: CSS_IDENTIFIER@162..167 + 0: IDENT@162..167 "stuck" [] [] + 2: R_PAREN@167..169 ")" [] [Whitespace(" ")] + 1: AND_KW@169..173 "and" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@173..191 + 0: L_PAREN@173..174 "(" [] [] + 1: CSS_QUERY_FEATURE_PLAIN@174..190 + 0: CSS_IDENTIFIER@174..182 + 0: IDENT@174..182 "scrolled" [] [] + 1: COLON@182..184 ":" [] [Whitespace(" ")] + 2: CSS_IDENTIFIER@184..190 + 0: IDENT@184..190 "bottom" [] [] + 2: R_PAREN@190..191 ")" [] [] + 3: R_PAREN@191..193 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@193..196 + 0: L_CURLY@193..195 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@195..195 + 2: R_CURLY@195..196 "}" [] [] + 4: CSS_AT_RULE@196..250 + 0: AT@196..199 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@199..250 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@199..247 + 0: CONTAINER_KW@199..209 "container" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@209..247 + 0: CSS_IDENTIFIER@209..221 + 0: IDENT@209..221 "scroll-state" [] [] + 1: L_PAREN@221..222 "(" [] [] + 2: CSS_CONTAINER_SCROLL_STATE_OR_QUERY@222..245 + 0: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@222..230 + 0: L_PAREN@222..223 "(" [] [] + 1: CSS_QUERY_FEATURE_BOOLEAN@223..228 + 0: CSS_IDENTIFIER@223..228 + 0: IDENT@223..228 "stuck" [] [] + 2: R_PAREN@228..230 ")" [] [Whitespace(" ")] + 1: OR_KW@230..233 "or" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@233..245 + 0: L_PAREN@233..234 "(" [] [] + 1: CSS_QUERY_FEATURE_PLAIN@234..244 + 0: CSS_IDENTIFIER@234..241 + 0: IDENT@234..241 "snapped" [] [] + 1: COLON@241..243 ":" [] [Whitespace(" ")] + 2: CSS_IDENTIFIER@243..244 + 0: IDENT@243..244 "x" [] [] + 2: R_PAREN@244..245 ")" [] [] + 3: R_PAREN@245..247 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@247..250 + 0: L_CURLY@247..249 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@249..249 + 2: R_CURLY@249..250 "}" [] [] + 5: CSS_AT_RULE@250..330 + 0: AT@250..253 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@253..330 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@253..326 + 0: CONTAINER_KW@253..263 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@263..275 + 0: IDENT@263..275 "main-layout" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@275..326 + 0: CSS_IDENTIFIER@275..287 + 0: IDENT@275..287 "scroll-state" [] [] + 1: L_PAREN@287..288 "(" [] [] + 2: CSS_CONTAINER_SCROLL_STATE_NOT_QUERY@288..324 + 0: NOT_KW@288..292 "not" [] [Whitespace(" ")] + 1: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@292..324 + 0: L_PAREN@292..293 "(" [] [] + 1: CSS_CONTAINER_SCROLL_STATE_AND_QUERY@293..323 + 0: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@293..301 + 0: L_PAREN@293..294 "(" [] [] + 1: CSS_QUERY_FEATURE_BOOLEAN@294..299 + 0: CSS_IDENTIFIER@294..299 + 0: IDENT@294..299 "stuck" [] [] + 2: R_PAREN@299..301 ")" [] [Whitespace(" ")] + 1: AND_KW@301..305 "and" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@305..323 + 0: L_PAREN@305..306 "(" [] [] + 1: CSS_QUERY_FEATURE_PLAIN@306..322 + 0: CSS_IDENTIFIER@306..314 + 0: IDENT@306..314 "scrolled" [] [] + 1: COLON@314..316 ":" [] [Whitespace(" ")] + 2: CSS_IDENTIFIER@316..322 + 0: IDENT@316..322 "bottom" [] [] + 2: R_PAREN@322..323 ")" [] [] + 2: R_PAREN@323..324 ")" [] [] + 3: R_PAREN@324..326 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@326..330 + 0: L_CURLY@326..329 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@329..329 + 2: R_CURLY@329..330 "}" [] [] + 2: EOF@330..331 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-general-enclosed.scss b/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-general-enclosed.scss new file mode 100644 index 000000000000..befd333d9863 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-general-enclosed.scss @@ -0,0 +1,5 @@ +@container main-layout and(test) { } + +@container main-layout or(test) { } + +@container main-layout foo(url("a.svg" fn(test))) { } diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-general-enclosed.scss.snap b/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-general-enclosed.scss.snap new file mode 100644 index 000000000000..a1077f9aef28 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-general-enclosed.scss.snap @@ -0,0 +1,235 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +assertion_line: 208 +expression: snapshot +--- + +## Input + +```css +@container main-layout and(test) { } + +@container main-layout or(test) { } + +@container main-layout foo(url("a.svg" fn(test))) { } + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@1..11 "container" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@11..23 "main-layout" [] [Whitespace(" ")], + }, + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@23..26 "and" [] [], + }, + l_paren_token: L_PAREN@26..27 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@27..31 "test" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@31..33 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@33..35 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@35..36 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@36..39 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@39..49 "container" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@49..61 "main-layout" [] [Whitespace(" ")], + }, + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@61..63 "or" [] [], + }, + l_paren_token: L_PAREN@63..64 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@64..68 "test" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@68..70 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@70..72 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@72..73 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@73..76 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@76..86 "container" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@86..98 "main-layout" [] [Whitespace(" ")], + }, + query: CssFunction { + name: CssIdentifier { + value_token: IDENT@98..101 "foo" [] [], + }, + l_paren_token: L_PAREN@101..102 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssUrlFunction { + name: URL_KW@102..105 "url" [] [], + l_paren_token: L_PAREN@105..106 "(" [] [], + value: CssString { + value_token: CSS_STRING_LITERAL@106..114 "\"a.svg\"" [] [Whitespace(" ")], + }, + modifiers: CssUrlModifierList [ + CssFunction { + name: CssIdentifier { + value_token: IDENT@114..116 "fn" [] [], + }, + l_paren_token: L_PAREN@116..117 "(" [] [], + items: CssParameterList [ + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssIdentifier { + value_token: IDENT@117..121 "test" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@121..122 ")" [] [], + }, + ], + r_paren_token: R_PAREN@122..123 ")" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@123..125 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@125..127 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@127..128 "}" [] [], + }, + }, + }, + ], + eof_token: EOF@128..129 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..129 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..128 + 0: CSS_AT_RULE@0..36 + 0: AT@0..1 "@" [] [] + 1: CSS_CONTAINER_AT_RULE@1..36 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@1..33 + 0: CONTAINER_KW@1..11 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@11..23 + 0: IDENT@11..23 "main-layout" [] [Whitespace(" ")] + 2: CSS_FUNCTION@23..33 + 0: CSS_IDENTIFIER@23..26 + 0: IDENT@23..26 "and" [] [] + 1: L_PAREN@26..27 "(" [] [] + 2: CSS_PARAMETER_LIST@27..31 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@27..31 + 0: CSS_COMPONENT_VALUE_LIST@27..31 + 0: CSS_IDENTIFIER@27..31 + 0: IDENT@27..31 "test" [] [] + 3: R_PAREN@31..33 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@33..36 + 0: L_CURLY@33..35 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@35..35 + 2: R_CURLY@35..36 "}" [] [] + 1: CSS_AT_RULE@36..73 + 0: AT@36..39 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@39..73 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@39..70 + 0: CONTAINER_KW@39..49 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@49..61 + 0: IDENT@49..61 "main-layout" [] [Whitespace(" ")] + 2: CSS_FUNCTION@61..70 + 0: CSS_IDENTIFIER@61..63 + 0: IDENT@61..63 "or" [] [] + 1: L_PAREN@63..64 "(" [] [] + 2: CSS_PARAMETER_LIST@64..68 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@64..68 + 0: CSS_COMPONENT_VALUE_LIST@64..68 + 0: CSS_IDENTIFIER@64..68 + 0: IDENT@64..68 "test" [] [] + 3: R_PAREN@68..70 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@70..73 + 0: L_CURLY@70..72 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@72..72 + 2: R_CURLY@72..73 "}" [] [] + 2: CSS_AT_RULE@73..128 + 0: AT@73..76 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@76..128 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@76..125 + 0: CONTAINER_KW@76..86 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@86..98 + 0: IDENT@86..98 "main-layout" [] [Whitespace(" ")] + 2: CSS_FUNCTION@98..125 + 0: CSS_IDENTIFIER@98..101 + 0: IDENT@98..101 "foo" [] [] + 1: L_PAREN@101..102 "(" [] [] + 2: CSS_PARAMETER_LIST@102..123 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@102..123 + 0: CSS_COMPONENT_VALUE_LIST@102..123 + 0: CSS_URL_FUNCTION@102..123 + 0: URL_KW@102..105 "url" [] [] + 1: L_PAREN@105..106 "(" [] [] + 2: CSS_STRING@106..114 + 0: CSS_STRING_LITERAL@106..114 "\"a.svg\"" [] [Whitespace(" ")] + 3: CSS_URL_MODIFIER_LIST@114..122 + 0: CSS_FUNCTION@114..122 + 0: CSS_IDENTIFIER@114..116 + 0: IDENT@114..116 "fn" [] [] + 1: L_PAREN@116..117 "(" [] [] + 2: CSS_PARAMETER_LIST@117..121 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@117..121 + 0: CSS_COMPONENT_VALUE_LIST@117..121 + 0: CSS_IDENTIFIER@117..121 + 0: IDENT@117..121 "test" [] [] + 3: R_PAREN@121..122 ")" [] [] + 4: R_PAREN@122..123 ")" [] [] + 3: R_PAREN@123..125 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@125..128 + 0: L_CURLY@125..127 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@127..127 + 2: R_CURLY@127..128 "}" [] [] + 2: EOF@128..129 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-scroll-state.scss b/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-scroll-state.scss index 768cfbe82e30..512e78b274d3 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-scroll-state.scss +++ b/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-scroll-state.scss @@ -1,5 +1,7 @@ @container scroll-state( scrolled: bottom ) { } +@container scroll-state(stuck) { } + @container not scroll-state(stuck) { } @container scroll-state(not (stuck)) { } @@ -7,3 +9,7 @@ @container scroll-state((stuck) and (scrolled: bottom)) { } @container scroll-state((stuck) or (scrolled: bottom)) { } + +@container scroll-state((stuck) or (snapped: x)) { } + +@container main-layout scroll-state(not ((stuck) and (scrolled: bottom))) { } diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-scroll-state.scss.snap b/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-scroll-state.scss.snap index fadc93221b74..1e3c52cc90a1 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-scroll-state.scss.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/scss/at-rule/container-scroll-state.scss.snap @@ -1,6 +1,5 @@ --- source: crates/biome_css_parser/tests/spec_test.rs -assertion_line: 208 expression: snapshot --- @@ -9,6 +8,8 @@ expression: snapshot ```css @container scroll-state( scrolled: bottom ) { } +@container scroll-state(stuck) { } + @container not scroll-state(stuck) { } @container scroll-state(not (stuck)) { } @@ -17,6 +18,10 @@ expression: snapshot @container scroll-state((stuck) or (scrolled: bottom)) { } +@container scroll-state((stuck) or (snapped: x)) { } + +@container main-layout scroll-state(not ((stuck) and (scrolled: bottom))) { } + ``` @@ -61,165 +66,292 @@ CssRoot { rule: CssContainerAtRule { declarator: CssContainerAtRuleDeclarator { container_token: CONTAINER_KW@59..69 "container" [] [Whitespace(" ")], + name: missing (optional), + query: CssContainerScrollStateQueryInParens { + name: CssIdentifier { + value_token: IDENT@69..81 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@81..82 "(" [] [], + query: CssQueryFeatureBoolean { + name: CssIdentifier { + value_token: IDENT@82..87 "stuck" [] [], + }, + }, + r_paren_token: R_PAREN@87..89 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@89..92 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@92..93 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@93..96 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@96..106 "container" [] [Whitespace(" ")], name: CssCustomIdentifier { - value_token: IDENT@69..73 "not" [] [Whitespace(" ")], + value_token: IDENT@106..110 "not" [] [Whitespace(" ")], }, query: CssContainerScrollStateQueryInParens { name: CssIdentifier { - value_token: IDENT@73..85 "scroll-state" [] [], + value_token: IDENT@110..122 "scroll-state" [] [], }, - l_paren_token: L_PAREN@85..86 "(" [] [], + l_paren_token: L_PAREN@122..123 "(" [] [], query: CssQueryFeatureBoolean { name: CssIdentifier { - value_token: IDENT@86..91 "stuck" [] [], + value_token: IDENT@123..128 "stuck" [] [], }, }, - r_paren_token: R_PAREN@91..93 ")" [] [Whitespace(" ")], + r_paren_token: R_PAREN@128..130 ")" [] [Whitespace(" ")], }, }, block: CssRuleBlock { - l_curly_token: L_CURLY@93..96 "{" [] [Whitespace(" ")], + l_curly_token: L_CURLY@130..133 "{" [] [Whitespace(" ")], rules: CssRuleList [], - r_curly_token: R_CURLY@96..97 "}" [] [], + r_curly_token: R_CURLY@133..134 "}" [] [], }, }, }, CssAtRule { - at_token: AT@97..100 "@" [Newline("\n"), Newline("\n")] [], + at_token: AT@134..137 "@" [Newline("\n"), Newline("\n")] [], rule: CssContainerAtRule { declarator: CssContainerAtRuleDeclarator { - container_token: CONTAINER_KW@100..110 "container" [] [Whitespace(" ")], + container_token: CONTAINER_KW@137..147 "container" [] [Whitespace(" ")], name: missing (optional), query: CssContainerScrollStateQueryInParens { name: CssIdentifier { - value_token: IDENT@110..122 "scroll-state" [] [], + value_token: IDENT@147..159 "scroll-state" [] [], }, - l_paren_token: L_PAREN@122..123 "(" [] [], + l_paren_token: L_PAREN@159..160 "(" [] [], query: CssContainerScrollStateNotQuery { - not_token: NOT_KW@123..127 "not" [] [Whitespace(" ")], + not_token: NOT_KW@160..164 "not" [] [Whitespace(" ")], query: CssContainerScrollStateInParens { - l_paren_token: L_PAREN@127..128 "(" [] [], + l_paren_token: L_PAREN@164..165 "(" [] [], query: CssQueryFeatureBoolean { name: CssIdentifier { - value_token: IDENT@128..133 "stuck" [] [], + value_token: IDENT@165..170 "stuck" [] [], }, }, - r_paren_token: R_PAREN@133..134 ")" [] [], + r_paren_token: R_PAREN@170..171 ")" [] [], }, }, - r_paren_token: R_PAREN@134..136 ")" [] [Whitespace(" ")], + r_paren_token: R_PAREN@171..173 ")" [] [Whitespace(" ")], }, }, block: CssRuleBlock { - l_curly_token: L_CURLY@136..138 "{" [] [Whitespace(" ")], + l_curly_token: L_CURLY@173..175 "{" [] [Whitespace(" ")], rules: CssRuleList [], - r_curly_token: R_CURLY@138..139 "}" [] [], + r_curly_token: R_CURLY@175..176 "}" [] [], }, }, }, CssAtRule { - at_token: AT@139..142 "@" [Newline("\n"), Newline("\n")] [], + at_token: AT@176..179 "@" [Newline("\n"), Newline("\n")] [], rule: CssContainerAtRule { declarator: CssContainerAtRuleDeclarator { - container_token: CONTAINER_KW@142..152 "container" [] [Whitespace(" ")], + container_token: CONTAINER_KW@179..189 "container" [] [Whitespace(" ")], name: missing (optional), query: CssContainerScrollStateQueryInParens { name: CssIdentifier { - value_token: IDENT@152..164 "scroll-state" [] [], + value_token: IDENT@189..201 "scroll-state" [] [], }, - l_paren_token: L_PAREN@164..165 "(" [] [], + l_paren_token: L_PAREN@201..202 "(" [] [], query: CssContainerScrollStateAndQuery { left: CssContainerScrollStateInParens { - l_paren_token: L_PAREN@165..166 "(" [] [], + l_paren_token: L_PAREN@202..203 "(" [] [], query: CssQueryFeatureBoolean { name: CssIdentifier { - value_token: IDENT@166..171 "stuck" [] [], + value_token: IDENT@203..208 "stuck" [] [], }, }, - r_paren_token: R_PAREN@171..173 ")" [] [Whitespace(" ")], + r_paren_token: R_PAREN@208..210 ")" [] [Whitespace(" ")], }, - and_token: AND_KW@173..177 "and" [] [Whitespace(" ")], + and_token: AND_KW@210..214 "and" [] [Whitespace(" ")], right: CssContainerScrollStateInParens { - l_paren_token: L_PAREN@177..178 "(" [] [], + l_paren_token: L_PAREN@214..215 "(" [] [], query: CssQueryFeaturePlain { name: CssIdentifier { - value_token: IDENT@178..186 "scrolled" [] [], + value_token: IDENT@215..223 "scrolled" [] [], }, - colon_token: COLON@186..188 ":" [] [Whitespace(" ")], + colon_token: COLON@223..225 ":" [] [Whitespace(" ")], value: CssIdentifier { - value_token: IDENT@188..194 "bottom" [] [], + value_token: IDENT@225..231 "bottom" [] [], }, }, - r_paren_token: R_PAREN@194..195 ")" [] [], + r_paren_token: R_PAREN@231..232 ")" [] [], }, }, - r_paren_token: R_PAREN@195..197 ")" [] [Whitespace(" ")], + r_paren_token: R_PAREN@232..234 ")" [] [Whitespace(" ")], }, }, block: CssRuleBlock { - l_curly_token: L_CURLY@197..199 "{" [] [Whitespace(" ")], + l_curly_token: L_CURLY@234..236 "{" [] [Whitespace(" ")], rules: CssRuleList [], - r_curly_token: R_CURLY@199..200 "}" [] [], + r_curly_token: R_CURLY@236..237 "}" [] [], }, }, }, CssAtRule { - at_token: AT@200..203 "@" [Newline("\n"), Newline("\n")] [], + at_token: AT@237..240 "@" [Newline("\n"), Newline("\n")] [], rule: CssContainerAtRule { declarator: CssContainerAtRuleDeclarator { - container_token: CONTAINER_KW@203..213 "container" [] [Whitespace(" ")], + container_token: CONTAINER_KW@240..250 "container" [] [Whitespace(" ")], name: missing (optional), query: CssContainerScrollStateQueryInParens { name: CssIdentifier { - value_token: IDENT@213..225 "scroll-state" [] [], + value_token: IDENT@250..262 "scroll-state" [] [], }, - l_paren_token: L_PAREN@225..226 "(" [] [], + l_paren_token: L_PAREN@262..263 "(" [] [], query: CssContainerScrollStateOrQuery { left: CssContainerScrollStateInParens { - l_paren_token: L_PAREN@226..227 "(" [] [], + l_paren_token: L_PAREN@263..264 "(" [] [], query: CssQueryFeatureBoolean { name: CssIdentifier { - value_token: IDENT@227..232 "stuck" [] [], + value_token: IDENT@264..269 "stuck" [] [], }, }, - r_paren_token: R_PAREN@232..234 ")" [] [Whitespace(" ")], + r_paren_token: R_PAREN@269..271 ")" [] [Whitespace(" ")], }, - or_token: OR_KW@234..237 "or" [] [Whitespace(" ")], + or_token: OR_KW@271..274 "or" [] [Whitespace(" ")], right: CssContainerScrollStateInParens { - l_paren_token: L_PAREN@237..238 "(" [] [], + l_paren_token: L_PAREN@274..275 "(" [] [], query: CssQueryFeaturePlain { name: CssIdentifier { - value_token: IDENT@238..246 "scrolled" [] [], + value_token: IDENT@275..283 "scrolled" [] [], }, - colon_token: COLON@246..248 ":" [] [Whitespace(" ")], + colon_token: COLON@283..285 ":" [] [Whitespace(" ")], value: CssIdentifier { - value_token: IDENT@248..254 "bottom" [] [], + value_token: IDENT@285..291 "bottom" [] [], }, }, - r_paren_token: R_PAREN@254..255 ")" [] [], + r_paren_token: R_PAREN@291..292 ")" [] [], }, }, - r_paren_token: R_PAREN@255..257 ")" [] [Whitespace(" ")], + r_paren_token: R_PAREN@292..294 ")" [] [Whitespace(" ")], }, }, block: CssRuleBlock { - l_curly_token: L_CURLY@257..259 "{" [] [Whitespace(" ")], + l_curly_token: L_CURLY@294..296 "{" [] [Whitespace(" ")], rules: CssRuleList [], - r_curly_token: R_CURLY@259..260 "}" [] [], + r_curly_token: R_CURLY@296..297 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@297..300 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@300..310 "container" [] [Whitespace(" ")], + name: missing (optional), + query: CssContainerScrollStateQueryInParens { + name: CssIdentifier { + value_token: IDENT@310..322 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@322..323 "(" [] [], + query: CssContainerScrollStateOrQuery { + left: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@323..324 "(" [] [], + query: CssQueryFeatureBoolean { + name: CssIdentifier { + value_token: IDENT@324..329 "stuck" [] [], + }, + }, + r_paren_token: R_PAREN@329..331 ")" [] [Whitespace(" ")], + }, + or_token: OR_KW@331..334 "or" [] [Whitespace(" ")], + right: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@334..335 "(" [] [], + query: CssQueryFeaturePlain { + name: CssIdentifier { + value_token: IDENT@335..342 "snapped" [] [], + }, + colon_token: COLON@342..344 ":" [] [Whitespace(" ")], + value: CssIdentifier { + value_token: IDENT@344..345 "x" [] [], + }, + }, + r_paren_token: R_PAREN@345..346 ")" [] [], + }, + }, + r_paren_token: R_PAREN@346..348 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@348..350 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@350..351 "}" [] [], + }, + }, + }, + CssAtRule { + at_token: AT@351..354 "@" [Newline("\n"), Newline("\n")] [], + rule: CssContainerAtRule { + declarator: CssContainerAtRuleDeclarator { + container_token: CONTAINER_KW@354..364 "container" [] [Whitespace(" ")], + name: CssCustomIdentifier { + value_token: IDENT@364..376 "main-layout" [] [Whitespace(" ")], + }, + query: CssContainerScrollStateQueryInParens { + name: CssIdentifier { + value_token: IDENT@376..388 "scroll-state" [] [], + }, + l_paren_token: L_PAREN@388..389 "(" [] [], + query: CssContainerScrollStateNotQuery { + not_token: NOT_KW@389..393 "not" [] [Whitespace(" ")], + query: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@393..394 "(" [] [], + query: CssContainerScrollStateAndQuery { + left: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@394..395 "(" [] [], + query: CssQueryFeatureBoolean { + name: CssIdentifier { + value_token: IDENT@395..400 "stuck" [] [], + }, + }, + r_paren_token: R_PAREN@400..402 ")" [] [Whitespace(" ")], + }, + and_token: AND_KW@402..406 "and" [] [Whitespace(" ")], + right: CssContainerScrollStateInParens { + l_paren_token: L_PAREN@406..407 "(" [] [], + query: CssQueryFeaturePlain { + name: CssIdentifier { + value_token: IDENT@407..415 "scrolled" [] [], + }, + colon_token: COLON@415..417 ":" [] [Whitespace(" ")], + value: CssIdentifier { + value_token: IDENT@417..423 "bottom" [] [], + }, + }, + r_paren_token: R_PAREN@423..424 ")" [] [], + }, + }, + r_paren_token: R_PAREN@424..425 ")" [] [], + }, + }, + r_paren_token: R_PAREN@425..427 ")" [] [Whitespace(" ")], + }, + }, + block: CssRuleBlock { + l_curly_token: L_CURLY@427..430 "{" [] [Whitespace(" ")], + rules: CssRuleList [], + r_curly_token: R_CURLY@430..431 "}" [] [], }, }, }, ], - eof_token: EOF@260..261 "" [Newline("\n")] [], + eof_token: EOF@431..432 "" [Newline("\n")] [], } ``` ## CST ``` -0: CSS_ROOT@0..261 +0: CSS_ROOT@0..432 0: (empty) - 1: CSS_ROOT_ITEM_LIST@0..260 + 1: CSS_ROOT_ITEM_LIST@0..431 0: CSS_AT_RULE@0..56 0: AT@0..1 "@" [] [] 1: CSS_CONTAINER_AT_RULE@1..56 @@ -241,112 +373,200 @@ CssRoot { 0: L_CURLY@52..55 "{" [] [Whitespace(" ")] 1: CSS_RULE_LIST@55..55 2: R_CURLY@55..56 "}" [] [] - 1: CSS_AT_RULE@56..97 + 1: CSS_AT_RULE@56..93 0: AT@56..59 "@" [Newline("\n"), Newline("\n")] [] - 1: CSS_CONTAINER_AT_RULE@59..97 - 0: CSS_CONTAINER_AT_RULE_DECLARATOR@59..93 + 1: CSS_CONTAINER_AT_RULE@59..93 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@59..89 0: CONTAINER_KW@59..69 "container" [] [Whitespace(" ")] - 1: CSS_CUSTOM_IDENTIFIER@69..73 - 0: IDENT@69..73 "not" [] [Whitespace(" ")] - 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@73..93 - 0: CSS_IDENTIFIER@73..85 - 0: IDENT@73..85 "scroll-state" [] [] - 1: L_PAREN@85..86 "(" [] [] - 2: CSS_QUERY_FEATURE_BOOLEAN@86..91 - 0: CSS_IDENTIFIER@86..91 - 0: IDENT@86..91 "stuck" [] [] - 3: R_PAREN@91..93 ")" [] [Whitespace(" ")] - 1: CSS_RULE_BLOCK@93..97 - 0: L_CURLY@93..96 "{" [] [Whitespace(" ")] - 1: CSS_RULE_LIST@96..96 - 2: R_CURLY@96..97 "}" [] [] - 2: CSS_AT_RULE@97..139 - 0: AT@97..100 "@" [Newline("\n"), Newline("\n")] [] - 1: CSS_CONTAINER_AT_RULE@100..139 - 0: CSS_CONTAINER_AT_RULE_DECLARATOR@100..136 - 0: CONTAINER_KW@100..110 "container" [] [Whitespace(" ")] 1: (empty) - 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@110..136 + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@69..89 + 0: CSS_IDENTIFIER@69..81 + 0: IDENT@69..81 "scroll-state" [] [] + 1: L_PAREN@81..82 "(" [] [] + 2: CSS_QUERY_FEATURE_BOOLEAN@82..87 + 0: CSS_IDENTIFIER@82..87 + 0: IDENT@82..87 "stuck" [] [] + 3: R_PAREN@87..89 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@89..93 + 0: L_CURLY@89..92 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@92..92 + 2: R_CURLY@92..93 "}" [] [] + 2: CSS_AT_RULE@93..134 + 0: AT@93..96 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@96..134 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@96..130 + 0: CONTAINER_KW@96..106 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@106..110 + 0: IDENT@106..110 "not" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@110..130 0: CSS_IDENTIFIER@110..122 0: IDENT@110..122 "scroll-state" [] [] 1: L_PAREN@122..123 "(" [] [] - 2: CSS_CONTAINER_SCROLL_STATE_NOT_QUERY@123..134 - 0: NOT_KW@123..127 "not" [] [Whitespace(" ")] - 1: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@127..134 - 0: L_PAREN@127..128 "(" [] [] - 1: CSS_QUERY_FEATURE_BOOLEAN@128..133 - 0: CSS_IDENTIFIER@128..133 - 0: IDENT@128..133 "stuck" [] [] - 2: R_PAREN@133..134 ")" [] [] - 3: R_PAREN@134..136 ")" [] [Whitespace(" ")] - 1: CSS_RULE_BLOCK@136..139 - 0: L_CURLY@136..138 "{" [] [Whitespace(" ")] - 1: CSS_RULE_LIST@138..138 - 2: R_CURLY@138..139 "}" [] [] - 3: CSS_AT_RULE@139..200 - 0: AT@139..142 "@" [Newline("\n"), Newline("\n")] [] - 1: CSS_CONTAINER_AT_RULE@142..200 - 0: CSS_CONTAINER_AT_RULE_DECLARATOR@142..197 - 0: CONTAINER_KW@142..152 "container" [] [Whitespace(" ")] + 2: CSS_QUERY_FEATURE_BOOLEAN@123..128 + 0: CSS_IDENTIFIER@123..128 + 0: IDENT@123..128 "stuck" [] [] + 3: R_PAREN@128..130 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@130..134 + 0: L_CURLY@130..133 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@133..133 + 2: R_CURLY@133..134 "}" [] [] + 3: CSS_AT_RULE@134..176 + 0: AT@134..137 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@137..176 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@137..173 + 0: CONTAINER_KW@137..147 "container" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@147..173 + 0: CSS_IDENTIFIER@147..159 + 0: IDENT@147..159 "scroll-state" [] [] + 1: L_PAREN@159..160 "(" [] [] + 2: CSS_CONTAINER_SCROLL_STATE_NOT_QUERY@160..171 + 0: NOT_KW@160..164 "not" [] [Whitespace(" ")] + 1: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@164..171 + 0: L_PAREN@164..165 "(" [] [] + 1: CSS_QUERY_FEATURE_BOOLEAN@165..170 + 0: CSS_IDENTIFIER@165..170 + 0: IDENT@165..170 "stuck" [] [] + 2: R_PAREN@170..171 ")" [] [] + 3: R_PAREN@171..173 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@173..176 + 0: L_CURLY@173..175 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@175..175 + 2: R_CURLY@175..176 "}" [] [] + 4: CSS_AT_RULE@176..237 + 0: AT@176..179 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@179..237 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@179..234 + 0: CONTAINER_KW@179..189 "container" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@189..234 + 0: CSS_IDENTIFIER@189..201 + 0: IDENT@189..201 "scroll-state" [] [] + 1: L_PAREN@201..202 "(" [] [] + 2: CSS_CONTAINER_SCROLL_STATE_AND_QUERY@202..232 + 0: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@202..210 + 0: L_PAREN@202..203 "(" [] [] + 1: CSS_QUERY_FEATURE_BOOLEAN@203..208 + 0: CSS_IDENTIFIER@203..208 + 0: IDENT@203..208 "stuck" [] [] + 2: R_PAREN@208..210 ")" [] [Whitespace(" ")] + 1: AND_KW@210..214 "and" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@214..232 + 0: L_PAREN@214..215 "(" [] [] + 1: CSS_QUERY_FEATURE_PLAIN@215..231 + 0: CSS_IDENTIFIER@215..223 + 0: IDENT@215..223 "scrolled" [] [] + 1: COLON@223..225 ":" [] [Whitespace(" ")] + 2: CSS_IDENTIFIER@225..231 + 0: IDENT@225..231 "bottom" [] [] + 2: R_PAREN@231..232 ")" [] [] + 3: R_PAREN@232..234 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@234..237 + 0: L_CURLY@234..236 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@236..236 + 2: R_CURLY@236..237 "}" [] [] + 5: CSS_AT_RULE@237..297 + 0: AT@237..240 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@240..297 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@240..294 + 0: CONTAINER_KW@240..250 "container" [] [Whitespace(" ")] 1: (empty) - 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@152..197 - 0: CSS_IDENTIFIER@152..164 - 0: IDENT@152..164 "scroll-state" [] [] - 1: L_PAREN@164..165 "(" [] [] - 2: CSS_CONTAINER_SCROLL_STATE_AND_QUERY@165..195 - 0: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@165..173 - 0: L_PAREN@165..166 "(" [] [] - 1: CSS_QUERY_FEATURE_BOOLEAN@166..171 - 0: CSS_IDENTIFIER@166..171 - 0: IDENT@166..171 "stuck" [] [] - 2: R_PAREN@171..173 ")" [] [Whitespace(" ")] - 1: AND_KW@173..177 "and" [] [Whitespace(" ")] - 2: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@177..195 - 0: L_PAREN@177..178 "(" [] [] - 1: CSS_QUERY_FEATURE_PLAIN@178..194 - 0: CSS_IDENTIFIER@178..186 - 0: IDENT@178..186 "scrolled" [] [] - 1: COLON@186..188 ":" [] [Whitespace(" ")] - 2: CSS_IDENTIFIER@188..194 - 0: IDENT@188..194 "bottom" [] [] - 2: R_PAREN@194..195 ")" [] [] - 3: R_PAREN@195..197 ")" [] [Whitespace(" ")] - 1: CSS_RULE_BLOCK@197..200 - 0: L_CURLY@197..199 "{" [] [Whitespace(" ")] - 1: CSS_RULE_LIST@199..199 - 2: R_CURLY@199..200 "}" [] [] - 4: CSS_AT_RULE@200..260 - 0: AT@200..203 "@" [Newline("\n"), Newline("\n")] [] - 1: CSS_CONTAINER_AT_RULE@203..260 - 0: CSS_CONTAINER_AT_RULE_DECLARATOR@203..257 - 0: CONTAINER_KW@203..213 "container" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@250..294 + 0: CSS_IDENTIFIER@250..262 + 0: IDENT@250..262 "scroll-state" [] [] + 1: L_PAREN@262..263 "(" [] [] + 2: CSS_CONTAINER_SCROLL_STATE_OR_QUERY@263..292 + 0: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@263..271 + 0: L_PAREN@263..264 "(" [] [] + 1: CSS_QUERY_FEATURE_BOOLEAN@264..269 + 0: CSS_IDENTIFIER@264..269 + 0: IDENT@264..269 "stuck" [] [] + 2: R_PAREN@269..271 ")" [] [Whitespace(" ")] + 1: OR_KW@271..274 "or" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@274..292 + 0: L_PAREN@274..275 "(" [] [] + 1: CSS_QUERY_FEATURE_PLAIN@275..291 + 0: CSS_IDENTIFIER@275..283 + 0: IDENT@275..283 "scrolled" [] [] + 1: COLON@283..285 ":" [] [Whitespace(" ")] + 2: CSS_IDENTIFIER@285..291 + 0: IDENT@285..291 "bottom" [] [] + 2: R_PAREN@291..292 ")" [] [] + 3: R_PAREN@292..294 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@294..297 + 0: L_CURLY@294..296 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@296..296 + 2: R_CURLY@296..297 "}" [] [] + 6: CSS_AT_RULE@297..351 + 0: AT@297..300 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@300..351 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@300..348 + 0: CONTAINER_KW@300..310 "container" [] [Whitespace(" ")] 1: (empty) - 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@213..257 - 0: CSS_IDENTIFIER@213..225 - 0: IDENT@213..225 "scroll-state" [] [] - 1: L_PAREN@225..226 "(" [] [] - 2: CSS_CONTAINER_SCROLL_STATE_OR_QUERY@226..255 - 0: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@226..234 - 0: L_PAREN@226..227 "(" [] [] - 1: CSS_QUERY_FEATURE_BOOLEAN@227..232 - 0: CSS_IDENTIFIER@227..232 - 0: IDENT@227..232 "stuck" [] [] - 2: R_PAREN@232..234 ")" [] [Whitespace(" ")] - 1: OR_KW@234..237 "or" [] [Whitespace(" ")] - 2: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@237..255 - 0: L_PAREN@237..238 "(" [] [] - 1: CSS_QUERY_FEATURE_PLAIN@238..254 - 0: CSS_IDENTIFIER@238..246 - 0: IDENT@238..246 "scrolled" [] [] - 1: COLON@246..248 ":" [] [Whitespace(" ")] - 2: CSS_IDENTIFIER@248..254 - 0: IDENT@248..254 "bottom" [] [] - 2: R_PAREN@254..255 ")" [] [] - 3: R_PAREN@255..257 ")" [] [Whitespace(" ")] - 1: CSS_RULE_BLOCK@257..260 - 0: L_CURLY@257..259 "{" [] [Whitespace(" ")] - 1: CSS_RULE_LIST@259..259 - 2: R_CURLY@259..260 "}" [] [] - 2: EOF@260..261 "" [Newline("\n")] [] + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@310..348 + 0: CSS_IDENTIFIER@310..322 + 0: IDENT@310..322 "scroll-state" [] [] + 1: L_PAREN@322..323 "(" [] [] + 2: CSS_CONTAINER_SCROLL_STATE_OR_QUERY@323..346 + 0: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@323..331 + 0: L_PAREN@323..324 "(" [] [] + 1: CSS_QUERY_FEATURE_BOOLEAN@324..329 + 0: CSS_IDENTIFIER@324..329 + 0: IDENT@324..329 "stuck" [] [] + 2: R_PAREN@329..331 ")" [] [Whitespace(" ")] + 1: OR_KW@331..334 "or" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@334..346 + 0: L_PAREN@334..335 "(" [] [] + 1: CSS_QUERY_FEATURE_PLAIN@335..345 + 0: CSS_IDENTIFIER@335..342 + 0: IDENT@335..342 "snapped" [] [] + 1: COLON@342..344 ":" [] [Whitespace(" ")] + 2: CSS_IDENTIFIER@344..345 + 0: IDENT@344..345 "x" [] [] + 2: R_PAREN@345..346 ")" [] [] + 3: R_PAREN@346..348 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@348..351 + 0: L_CURLY@348..350 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@350..350 + 2: R_CURLY@350..351 "}" [] [] + 7: CSS_AT_RULE@351..431 + 0: AT@351..354 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_CONTAINER_AT_RULE@354..431 + 0: CSS_CONTAINER_AT_RULE_DECLARATOR@354..427 + 0: CONTAINER_KW@354..364 "container" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@364..376 + 0: IDENT@364..376 "main-layout" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_QUERY_IN_PARENS@376..427 + 0: CSS_IDENTIFIER@376..388 + 0: IDENT@376..388 "scroll-state" [] [] + 1: L_PAREN@388..389 "(" [] [] + 2: CSS_CONTAINER_SCROLL_STATE_NOT_QUERY@389..425 + 0: NOT_KW@389..393 "not" [] [Whitespace(" ")] + 1: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@393..425 + 0: L_PAREN@393..394 "(" [] [] + 1: CSS_CONTAINER_SCROLL_STATE_AND_QUERY@394..424 + 0: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@394..402 + 0: L_PAREN@394..395 "(" [] [] + 1: CSS_QUERY_FEATURE_BOOLEAN@395..400 + 0: CSS_IDENTIFIER@395..400 + 0: IDENT@395..400 "stuck" [] [] + 2: R_PAREN@400..402 ")" [] [Whitespace(" ")] + 1: AND_KW@402..406 "and" [] [Whitespace(" ")] + 2: CSS_CONTAINER_SCROLL_STATE_IN_PARENS@406..424 + 0: L_PAREN@406..407 "(" [] [] + 1: CSS_QUERY_FEATURE_PLAIN@407..423 + 0: CSS_IDENTIFIER@407..415 + 0: IDENT@407..415 "scrolled" [] [] + 1: COLON@415..417 ":" [] [Whitespace(" ")] + 2: CSS_IDENTIFIER@417..423 + 0: IDENT@417..423 "bottom" [] [] + 2: R_PAREN@423..424 ")" [] [] + 2: R_PAREN@424..425 ")" [] [] + 3: R_PAREN@425..427 ")" [] [Whitespace(" ")] + 1: CSS_RULE_BLOCK@427..431 + 0: L_CURLY@427..430 "{" [] [Whitespace(" ")] + 1: CSS_RULE_LIST@430..430 + 2: R_CURLY@430..431 "}" [] [] + 2: EOF@431..432 "" [Newline("\n")] [] ``` From 8f24677ae7dd62e4a096f69035df9e54de4cf731 Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Sun, 1 Mar 2026 23:11:27 +0200 Subject: [PATCH 2/2] fix(css): improve recovery from invalid component value expressions --- .../src/syntax/value/function.rs | 4 +- .../component_value_expression_recovery.css | 7 + ...mponent_value_expression_recovery.css.snap | 319 ++++++++++++++++++ 3 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/function/component_value_expression_recovery.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/function/component_value_expression_recovery.css.snap diff --git a/crates/biome_css_parser/src/syntax/value/function.rs b/crates/biome_css_parser/src/syntax/value/function.rs index a7bdf86cc6a5..340cdecc030d 100644 --- a/crates/biome_css_parser/src/syntax/value/function.rs +++ b/crates/biome_css_parser/src/syntax/value/function.rs @@ -312,6 +312,8 @@ fn parse_any_expression_with_context( pub(crate) const BINARY_OPERATION_TOKEN: TokenSet = token_set![T![+], T![-], T![*], T![/]]; const UNARY_OPERATION_TOKEN: TokenSet = token_set![T![+], T![-], T![*]]; +const COMPONENT_VALUE_EXPRESSION_RECOVERY_SET: TokenSet = + token_set!(T![')'], T![;], T![,]).union(BINARY_OPERATION_TOKEN); /// Checks if the current position in the CSS parser is at a binary operator. /// @@ -431,7 +433,7 @@ impl ParseNodeList for ComponentValueExpressionList { ) -> RecoveryResult { parsed_element.or_recover_with_token_set( p, - &ParseRecoveryTokenSet::new(CSS_BOGUS, token_set!(T![')'], T![;])), + &ParseRecoveryTokenSet::new(CSS_BOGUS, COMPONENT_VALUE_EXPRESSION_RECOVERY_SET), expected_component_value, ) } diff --git a/crates/biome_css_parser/tests/css_test_suite/error/function/component_value_expression_recovery.css b/crates/biome_css_parser/tests/css_test_suite/error/function/component_value_expression_recovery.css new file mode 100644 index 000000000000..7db4e64dedac --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/function/component_value_expression_recovery.css @@ -0,0 +1,7 @@ +.comma-recovery { + color: fn(1px ? 2px, 3px); +} + +.operator-recovery { + color: fn(1px ? 2px + 3px); +} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/function/component_value_expression_recovery.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/function/component_value_expression_recovery.css.snap new file mode 100644 index 000000000000..590495e2a3cc --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/function/component_value_expression_recovery.css.snap @@ -0,0 +1,319 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css +.comma-recovery { + color: fn(1px ? 2px, 3px); +} + +.operator-recovery { + color: fn(1px ? 2px + 3px); +} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@0..1 "." [] [], + name: CssCustomIdentifier { + value_token: IDENT@1..16 "comma-recovery" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@16..17 "{" [] [], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssBogusProperty { + items: [ + CssIdentifier { + value_token: IDENT@17..24 "color" [Newline("\n"), Whitespace("\t")] [], + }, + COLON@24..26 ":" [] [Whitespace(" ")], + CssBogus { + items: [ + CssBogusSupportsCondition { + items: [ + CssIdentifier { + value_token: IDENT@26..28 "fn" [] [], + }, + L_PAREN@28..29 "(" [] [], + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@29..30 "1" [] [], + unit_token: IDENT@30..33 "px" [] [Whitespace(" ")], + }, + CssBogus { + items: [ + ERROR_TOKEN@33..35 "?" [] [Whitespace(" ")], + CSS_DIMENSION_VALUE@35..36 "2" [] [], + PX_KW@36..38 "px" [] [], + ], + }, + ], + }, + ], + }, + COMMA@38..40 "," [] [Whitespace(" ")], + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@40..41 "3" [] [], + unit_token: IDENT@41..43 "px" [] [], + }, + ], + }, + ], + }, + R_PAREN@43..44 ")" [] [], + ], + }, + ], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@44..45 ";" [] [], + }, + ], + r_curly_token: R_CURLY@45..47 "}" [Newline("\n")] [], + }, + }, + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selectors: CssNestedSelectorList [], + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@47..50 "." [Newline("\n"), Newline("\n")] [], + name: CssCustomIdentifier { + value_token: IDENT@50..68 "operator-recovery" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@68..69 "{" [] [], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssBogusProperty { + items: [ + CssIdentifier { + value_token: IDENT@69..76 "color" [Newline("\n"), Whitespace("\t")] [], + }, + COLON@76..78 ":" [] [Whitespace(" ")], + CssBogus { + items: [ + CssBogusSupportsCondition { + items: [ + CssIdentifier { + value_token: IDENT@78..80 "fn" [] [], + }, + L_PAREN@80..81 "(" [] [], + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssBogus { + items: [ + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@81..82 "1" [] [], + unit_token: IDENT@82..85 "px" [] [Whitespace(" ")], + }, + CssBogus { + items: [ + ERROR_TOKEN@85..87 "?" [] [Whitespace(" ")], + CSS_DIMENSION_VALUE@87..88 "2" [] [], + PX_KW@88..91 "px" [] [Whitespace(" ")], + ], + }, + ], + }, + ], + }, + PLUS@91..93 "+" [] [Whitespace(" ")], + CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@93..94 "3" [] [], + unit_token: IDENT@94..96 "px" [] [], + }, + ], + }, + ], + }, + ], + }, + R_PAREN@96..97 ")" [] [], + ], + }, + ], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@97..98 ";" [] [], + }, + ], + r_curly_token: R_CURLY@98..100 "}" [Newline("\n")] [], + }, + }, + ], + eof_token: EOF@100..101 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..101 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..100 + 0: CSS_QUALIFIED_RULE@0..47 + 0: CSS_SELECTOR_LIST@0..16 + 0: CSS_COMPOUND_SELECTOR@0..16 + 0: CSS_NESTED_SELECTOR_LIST@0..0 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@0..16 + 0: CSS_CLASS_SELECTOR@0..16 + 0: DOT@0..1 "." [] [] + 1: CSS_CUSTOM_IDENTIFIER@1..16 + 0: IDENT@1..16 "comma-recovery" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@16..47 + 0: L_CURLY@16..17 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@17..45 + 0: CSS_DECLARATION_WITH_SEMICOLON@17..45 + 0: CSS_DECLARATION@17..44 + 0: CSS_BOGUS_PROPERTY@17..44 + 0: CSS_IDENTIFIER@17..24 + 0: IDENT@17..24 "color" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@24..26 ":" [] [Whitespace(" ")] + 2: CSS_BOGUS@26..44 + 0: CSS_BOGUS_SUPPORTS_CONDITION@26..44 + 0: CSS_IDENTIFIER@26..28 + 0: IDENT@26..28 "fn" [] [] + 1: L_PAREN@28..29 "(" [] [] + 2: CSS_BOGUS@29..43 + 0: CSS_BOGUS@29..38 + 0: CSS_BOGUS@29..38 + 0: CSS_REGULAR_DIMENSION@29..33 + 0: CSS_NUMBER_LITERAL@29..30 "1" [] [] + 1: IDENT@30..33 "px" [] [Whitespace(" ")] + 1: CSS_BOGUS@33..38 + 0: ERROR_TOKEN@33..35 "?" [] [Whitespace(" ")] + 1: CSS_DIMENSION_VALUE@35..36 "2" [] [] + 2: PX_KW@36..38 "px" [] [] + 1: COMMA@38..40 "," [] [Whitespace(" ")] + 2: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@40..43 + 0: CSS_COMPONENT_VALUE_LIST@40..43 + 0: CSS_REGULAR_DIMENSION@40..43 + 0: CSS_NUMBER_LITERAL@40..41 "3" [] [] + 1: IDENT@41..43 "px" [] [] + 3: R_PAREN@43..44 ")" [] [] + 1: (empty) + 1: SEMICOLON@44..45 ";" [] [] + 2: R_CURLY@45..47 "}" [Newline("\n")] [] + 1: CSS_QUALIFIED_RULE@47..100 + 0: CSS_SELECTOR_LIST@47..68 + 0: CSS_COMPOUND_SELECTOR@47..68 + 0: CSS_NESTED_SELECTOR_LIST@47..47 + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@47..68 + 0: CSS_CLASS_SELECTOR@47..68 + 0: DOT@47..50 "." [Newline("\n"), Newline("\n")] [] + 1: CSS_CUSTOM_IDENTIFIER@50..68 + 0: IDENT@50..68 "operator-recovery" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@68..100 + 0: L_CURLY@68..69 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@69..98 + 0: CSS_DECLARATION_WITH_SEMICOLON@69..98 + 0: CSS_DECLARATION@69..97 + 0: CSS_BOGUS_PROPERTY@69..97 + 0: CSS_IDENTIFIER@69..76 + 0: IDENT@69..76 "color" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@76..78 ":" [] [Whitespace(" ")] + 2: CSS_BOGUS@78..97 + 0: CSS_BOGUS_SUPPORTS_CONDITION@78..97 + 0: CSS_IDENTIFIER@78..80 + 0: IDENT@78..80 "fn" [] [] + 1: L_PAREN@80..81 "(" [] [] + 2: CSS_BOGUS@81..96 + 0: CSS_BOGUS@81..96 + 0: CSS_BOGUS@81..91 + 0: CSS_BOGUS@81..91 + 0: CSS_REGULAR_DIMENSION@81..85 + 0: CSS_NUMBER_LITERAL@81..82 "1" [] [] + 1: IDENT@82..85 "px" [] [Whitespace(" ")] + 1: CSS_BOGUS@85..91 + 0: ERROR_TOKEN@85..87 "?" [] [Whitespace(" ")] + 1: CSS_DIMENSION_VALUE@87..88 "2" [] [] + 2: PX_KW@88..91 "px" [] [Whitespace(" ")] + 1: PLUS@91..93 "+" [] [Whitespace(" ")] + 2: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@93..96 + 0: CSS_COMPONENT_VALUE_LIST@93..96 + 0: CSS_REGULAR_DIMENSION@93..96 + 0: CSS_NUMBER_LITERAL@93..94 "3" [] [] + 1: IDENT@94..96 "px" [] [] + 3: R_PAREN@96..97 ")" [] [] + 1: (empty) + 1: SEMICOLON@97..98 ";" [] [] + 2: R_CURLY@98..100 "}" [Newline("\n")] [] + 2: EOF@100..101 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +component_value_expression_recovery.css:2:16 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × unexpected character `?` + + 1 │ .comma-recovery { + > 2 │ color: fn(1px ? 2px, 3px); + │ ^ + 3 │ } + 4 │ + +component_value_expression_recovery.css:6:16 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × unexpected character `?` + + 5 │ .operator-recovery { + > 6 │ color: fn(1px ? 2px + 3px); + │ ^ + 7 │ } + 8 │ + +```