From f5ccae3954ec67c5dfc98a3f51cfc69cbfc0a8fa Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Tue, 21 Nov 2023 00:05:55 +0200 Subject: [PATCH] feat(css_parser): CSS Parser pseudo element selector #268 (#812) - move functions to a module --- .../src/syntax/selector/pseudo_class.rs | 503 ------------------ .../function_compound_selector.rs | 34 ++ .../function_compound_selector_list.rs | 74 +++ .../pseudo_class/function_identifier.rs | 33 ++ .../selector/pseudo_class/function_nth.rs | 139 +++++ .../function_relative_selector_list.rs | 103 ++++ .../pseudo_class/function_selector.rs | 33 ++ .../pseudo_class/function_selector_list.rs | 36 ++ .../pseudo_class/function_value_list.rs | 85 +++ .../selector/pseudo_class/identifier.rs | 19 + .../src/syntax/selector/pseudo_class/mod.rs | 84 +++ .../src/syntax/selector/pseudo_element.rs | 2 +- crates/biome_parser/src/lib.rs | 41 ++ 13 files changed, 682 insertions(+), 504 deletions(-) delete mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class.rs create mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class/function_compound_selector.rs create mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class/function_compound_selector_list.rs create mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class/function_identifier.rs create mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class/function_nth.rs create mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class/function_relative_selector_list.rs create mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector.rs create mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector_list.rs create mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class/function_value_list.rs create mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class/identifier.rs create mode 100644 crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class.rs deleted file mode 100644 index 6d410a03fe1d..000000000000 --- a/crates/biome_css_parser/src/syntax/selector/pseudo_class.rs +++ /dev/null @@ -1,503 +0,0 @@ -use crate::lexer::CssLexContext; -use crate::parser::CssParser; -use crate::syntax::parse_error::{ - expect_any_pseudo_class, expect_any_selector, expected_identifier, expected_number, - expected_pseudo_class_nth, -}; -use crate::syntax::selector::{ - is_at_compound_selector, parse_compound_selector, parse_selector, - parse_selector_function_close_token, parse_selector_identifier, CssSelectorList, -}; -use crate::syntax::{ - is_at_identifier, parse_css_string, parse_number, parse_regular_identifier, - parse_regular_number, -}; -use biome_css_syntax::CssSyntaxKind::*; -use biome_css_syntax::{CssSyntaxKind, T}; -use biome_parser::parse_lists::ParseSeparatedList; -use biome_parser::parse_recovery::{ParseRecovery, RecoveryResult}; -use biome_parser::prelude::ParsedSyntax; -use biome_parser::prelude::ParsedSyntax::{Absent, Present}; -use biome_parser::{token_set, Parser, TokenSet}; - -#[inline] -pub(crate) fn parse_pseudo_class_selector(p: &mut CssParser) -> ParsedSyntax { - if !p.at(T![:]) { - return Absent; - } - - let m = p.start(); - - p.bump(T![:]); - parse_pseudo_class(p).or_add_diagnostic(p, expect_any_pseudo_class); - - Present(m.complete(p, CSS_PSEUDO_CLASS_SELECTOR)) -} - -#[inline] -fn parse_pseudo_class(p: &mut CssParser) -> ParsedSyntax { - if !is_at_identifier(p) { - return Absent; - } - - if is_at_pseudo_class_function_identifier(p) { - parse_pseudo_class_function_identifier(p) - } else if is_at_pseudo_class_function_selector(p) { - parse_pseudo_class_function_selector(p) - } else if is_at_pseudo_class_function_selector_list(p) { - parse_pseudo_class_function_selector_list(p) - } else if is_at_pseudo_class_function_compound_selector(p) { - parse_pseudo_class_function_compound_selector(p) - } else if is_at_pseudo_class_function_compound_selector_list(p) { - parse_pseudo_class_function_compound_selector_list(p) - } else if is_at_pseudo_class_function_relative_selector_list(p) { - parse_pseudo_class_function_relative_selector_list(p) - } else if is_at_pseudo_class_function_value_list(p) { - parse_pseudo_class_function_value_list(p) - } else if is_at_pseudo_class_function_nth(p) { - parse_pseudo_class_function_nth(p) - } else { - parse_pseudo_class_identifier(p) - } -} - -const PSEUDO_CLASS_FUNCTION_IDENTIFIER_SET: TokenSet = token_set![DIR_KW]; - -#[inline] -fn is_at_pseudo_class_function_identifier(p: &mut CssParser) -> bool { - p.at_ts(PSEUDO_CLASS_FUNCTION_IDENTIFIER_SET) && p.nth_at(1, T!['(']) -} - -#[inline] -fn parse_pseudo_class_function_identifier(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_class_function_identifier(p) { - return Absent; - } - - let m = p.start(); - - parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); - p.bump(T!['(']); - parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); - parse_selector_function_close_token(p); - - Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_IDENTIFIER)) -} - -const PSEUDO_CLASS_FUNCTION_SELECTOR_LIST_SET: TokenSet = - token_set![MATCHES_KW, NOT_KW, IS_KW, WHERE_KW]; - -#[inline] -fn is_at_pseudo_class_function_selector_list(p: &mut CssParser) -> bool { - p.at_ts(PSEUDO_CLASS_FUNCTION_SELECTOR_LIST_SET) && p.nth_at(1, T!['(']) -} - -#[inline] -fn parse_pseudo_class_function_selector_list(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_class_function_selector_list(p) { - return Absent; - } - - let m = p.start(); - - parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); - p.bump(T!['(']); - CssSelectorList::default() - .with_end_kind(T![')']) - .parse_list(p); - parse_selector_function_close_token(p); - - Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_SELECTOR_LIST)) -} - -const PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_LIST_SET: TokenSet = - token_set![MOZANY_KW, WEBKITANY_KW, PAST_KW, CURRENT_KW, FUTURE_KW]; - -#[inline] -fn is_at_pseudo_class_function_compound_selector_list(p: &mut CssParser) -> bool { - p.at_ts(PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_LIST_SET) && p.nth_at(1, T!['(']) -} - -#[inline] -fn parse_pseudo_class_function_compound_selector_list(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_class_function_compound_selector_list(p) { - return Absent; - } - - let m = p.start(); - - parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); - p.bump(T!['(']); - CssCompoundSelectorList.parse_list(p); - parse_selector_function_close_token(p); - - Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_LIST)) -} - -struct CssCompoundSelectorList; - -impl CssCompoundSelectorList { - const RECOVERY_SET: TokenSet = token_set![T!['{'], T![')']]; -} -impl ParseSeparatedList for CssCompoundSelectorList { - type Kind = CssSyntaxKind; - type Parser<'source> = CssParser<'source>; - - const LIST_KIND: CssSyntaxKind = CSS_COMPOUND_SELECTOR_LIST; - - fn parse_element(&mut self, p: &mut CssParser) -> ParsedSyntax { - parse_compound_selector(p) - } - - fn is_at_list_end(&self, p: &mut CssParser) -> bool { - p.at(T![')']) - } - - fn recover(&mut self, p: &mut CssParser, parsed_element: ParsedSyntax) -> RecoveryResult { - parsed_element.or_recover( - p, - &ParseRecovery::new(CSS_BOGUS_SELECTOR, Self::RECOVERY_SET), - expect_any_selector, - ) - } - - fn separating_element_kind(&mut self) -> CssSyntaxKind { - T![,] - } -} - -const PSEUDO_CLASS_FUNCTION_SELECTOR_SET: TokenSet = token_set![GLOBAL_KW, LOCAL_KW]; -#[inline] -fn is_at_pseudo_class_function_selector(p: &mut CssParser) -> bool { - p.at_ts(PSEUDO_CLASS_FUNCTION_SELECTOR_SET) && p.nth_at(1, T!['(']) -} - -#[inline] -fn parse_pseudo_class_function_selector(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_class_function_selector(p) { - return Absent; - } - - let m = p.start(); - - parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); - p.bump(T!['(']); - parse_selector(p).or_add_diagnostic(p, expect_any_selector); - parse_selector_function_close_token(p); - - Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_SELECTOR)) -} - -const PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_SET: TokenSet = - token_set![HOST_KW, HOSTCONTEXT_KW]; -#[inline] -fn is_at_pseudo_class_function_compound_selector(p: &mut CssParser) -> bool { - p.at_ts(PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_SET) && p.nth_at(1, T!['(']) -} - -#[inline] -fn parse_pseudo_class_function_compound_selector(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_class_function_compound_selector(p) { - return Absent; - } - - let m = p.start(); - - parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); - p.bump(T!['(']); - parse_compound_selector(p).or_add_diagnostic(p, expect_any_selector); - parse_selector_function_close_token(p); - - Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR)) -} - -const PSEUDO_CLASS_FUNCTION_RELATIVE_SELECTOR_LIST_SET: TokenSet = - token_set![HAS_KW]; - -#[inline] -fn is_at_pseudo_class_function_relative_selector_list(p: &mut CssParser) -> bool { - p.at_ts(PSEUDO_CLASS_FUNCTION_RELATIVE_SELECTOR_LIST_SET) && p.nth_at(1, T!['(']) -} - -#[inline] -fn parse_pseudo_class_function_relative_selector_list(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_class_function_relative_selector_list(p) { - return Absent; - } - - let m = p.start(); - - parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); - p.bump(T!['(']); - CssRelativeSelectorList.parse_list(p); - parse_selector_function_close_token(p); - - Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_RELATIVE_SELECTOR_LIST)) -} - -struct CssRelativeSelectorList; - -impl CssRelativeSelectorList { - const RECOVERY_SET: TokenSet = token_set![T!['{'], T![')']]; -} -impl ParseSeparatedList for CssRelativeSelectorList { - type Kind = CssSyntaxKind; - type Parser<'source> = CssParser<'source>; - - const LIST_KIND: CssSyntaxKind = CSS_RELATIVE_SELECTOR_LIST; - - fn parse_element(&mut self, p: &mut CssParser) -> ParsedSyntax { - parse_relative_selector(p) - } - - fn is_at_list_end(&self, p: &mut CssParser) -> bool { - p.at(T![')']) - } - - fn recover(&mut self, p: &mut CssParser, parsed_element: ParsedSyntax) -> RecoveryResult { - parsed_element.or_recover( - p, - &ParseRecovery::new(CSS_BOGUS_SELECTOR, Self::RECOVERY_SET), - expect_any_selector, - ) - } - - fn separating_element_kind(&mut self) -> CssSyntaxKind { - T![,] - } -} - -const RELATIVE_SELECTOR_COMBINATOR_SET: TokenSet = - token_set![T![>], T![+], T![~], T![||]]; - -#[inline] -fn is_at_relative_selector_combinator(p: &mut CssParser) -> bool { - p.at_ts(RELATIVE_SELECTOR_COMBINATOR_SET) -} - -#[inline] -fn is_at_relative_selector(p: &mut CssParser) -> bool { - is_at_relative_selector_combinator(p) || is_at_compound_selector(p) -} - -#[inline] -fn parse_relative_selector(p: &mut CssParser) -> ParsedSyntax { - if !is_at_relative_selector(p) { - return Absent; - } - - let m = p.start(); - - if is_at_relative_selector_combinator(p) { - p.bump_any(); - } - - parse_selector(p).or_add_diagnostic(p, expect_any_selector); - - Present(m.complete(p, CSS_RELATIVE_SELECTOR)) -} - -const PSEUDO_CLASS_FUNCTION_VALUE_LIST_SET: TokenSet = token_set![LANG_KW]; - -#[inline] -fn is_at_pseudo_class_function_value_list(p: &mut CssParser) -> bool { - p.at_ts(PSEUDO_CLASS_FUNCTION_VALUE_LIST_SET) && p.nth_at(1, T!['(']) -} - -#[inline] -fn parse_pseudo_class_function_value_list(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_class_function_value_list(p) { - return Absent; - } - - let m = p.start(); - - parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); - p.bump(T!['(']); - CssPseudoValueList.parse_list(p); - parse_selector_function_close_token(p); - - Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_VALUE_LIST)) -} - -struct CssPseudoValueList; - -impl CssPseudoValueList { - const RECOVERY_SET: TokenSet = token_set![T!['{'], T![')']]; -} -impl ParseSeparatedList for CssPseudoValueList { - type Kind = CssSyntaxKind; - type Parser<'source> = CssParser<'source>; - - const LIST_KIND: CssSyntaxKind = CSS_PSEUDO_VALUE_LIST; - - fn parse_element(&mut self, p: &mut CssParser) -> ParsedSyntax { - parse_pseudo_value(p) - } - - fn is_at_list_end(&self, p: &mut CssParser) -> bool { - p.at(T![')']) - } - - fn recover(&mut self, p: &mut CssParser, parsed_element: ParsedSyntax) -> RecoveryResult { - parsed_element.or_recover( - p, - &ParseRecovery::new(CSS_BOGUS, Self::RECOVERY_SET), - expected_identifier, - ) - } - - fn separating_element_kind(&mut self) -> CssSyntaxKind { - T![,] - } -} -#[inline] -fn is_at_pseudo_value(p: &mut CssParser) -> bool { - is_at_identifier(p) || p.at(CSS_STRING_LITERAL) -} - -#[inline] -fn parse_pseudo_value(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_value(p) { - return Absent; - } - - if p.at(CSS_STRING_LITERAL) { - parse_css_string(p) - } else { - parse_regular_identifier(p) - } -} - -const PSEUDO_CLASS_FUNCTION_NTH_SET: TokenSet = token_set![ - NTHCHILD_KW, - NTHLASTCHILD_KW, - NTHOFTYPE_KW, - NTHLASTOFTYPE_KW, - NTHCOL_KW, - NTHLASTCOL_KW -]; - -#[inline] -fn is_at_pseudo_class_function_nth(p: &mut CssParser) -> bool { - p.at_ts(PSEUDO_CLASS_FUNCTION_NTH_SET) && p.nth_at(1, T!['(']) -} - -#[inline] -fn parse_pseudo_class_function_nth(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_class_function_nth(p) { - return Absent; - } - - let m = p.start(); - - parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); - - p.bump_with_context(T!['('], CssLexContext::PseudoNthSelector); - parse_pseudo_class_nth_selector(p).or_add_diagnostic(p, expect_any_selector); - parse_selector_function_close_token(p); - - Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_NTH)) -} - -const PSEUDO_CLASS_FUNCTION_NTH_CLASS_SET: TokenSet = - token_set![T![odd], T![even], T![n], CSS_NUMBER_LITERAL, T![+], T![-],]; -#[inline] -fn is_at_pseudo_class_nth_selector(p: &mut CssParser) -> bool { - p.at_ts(PSEUDO_CLASS_FUNCTION_NTH_CLASS_SET) -} - -#[inline] -fn parse_pseudo_class_nth_selector(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_class_nth_selector(p) { - return Absent; - } - - let m = p.start(); - - parse_pseudo_class_nth(p).or_add_diagnostic(p, expected_pseudo_class_nth); - parse_pseudo_class_of_nth_selector(p).ok(); - - Present(m.complete(p, CSS_PSEUDO_CLASS_NTH_SELECTOR)) -} - -#[inline] -fn parse_pseudo_class_nth(p: &mut CssParser) -> ParsedSyntax { - if !is_at_pseudo_class_nth_selector(p) { - return Absent; - } - - let m = p.start(); - - let kind = if matches!(p.cur(), T![odd] | T![even]) { - p.bump_any(); - CSS_PSEUDO_CLASS_NTH_IDENTIFIER - } else { - // +123 - // +n + 3 - // -n + 3 - // n + 3 - // -2n + 1 - // +2n- 1 - // 123 - // 2n + 4 - // 2n- 4 - if matches!(p.cur(), T![+] | T![-]) { - p.bump_with_context(p.cur(), CssLexContext::PseudoNthSelector); - } - - parse_number(p, CssLexContext::PseudoNthSelector).ok(); - - if p.eat_with_context(T![n], CssLexContext::PseudoNthSelector) { - parse_nth_offset(p).ok(); - - CSS_PSEUDO_CLASS_NTH - } else { - CSS_PSEUDO_CLASS_NTH_NUMBER - } - }; - - Present(m.complete(p, kind)) -} - -#[inline] -fn parse_nth_offset(p: &mut CssParser) -> ParsedSyntax { - if !matches!(p.cur(), T![+] | T![-]) { - return Absent; - } - - let m = p.start(); - - p.bump_any(); - parse_regular_number(p).or_add_diagnostic(p, expected_number); - - Present(m.complete(p, CSS_NTH_OFFSET)) -} - -#[inline] -fn parse_pseudo_class_of_nth_selector(p: &mut CssParser) -> ParsedSyntax { - if !p.at(OF_KW) { - return Absent; - } - - let m = p.start(); - - p.bump(OF_KW); - - CssSelectorList::default() - .with_end_kind(T![')']) - .parse_list(p); - - Present(m.complete(p, CSS_PSEUDO_CLASS_OF_NTH_SELECTOR)) -} - -#[inline] -fn parse_pseudo_class_identifier(p: &mut CssParser) -> ParsedSyntax { - if !is_at_identifier(p) { - return Absent; - } - - let m = p.start(); - parse_selector_identifier(p).or_add_diagnostic(p, expected_identifier); - Present(m.complete(p, CSS_PSEUDO_CLASS_IDENTIFIER)) -} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_compound_selector.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_compound_selector.rs new file mode 100644 index 000000000000..6d86c83350b6 --- /dev/null +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_compound_selector.rs @@ -0,0 +1,34 @@ +use crate::parser::CssParser; +use crate::syntax::parse_error::{expect_any_selector, expected_identifier}; +use crate::syntax::parse_regular_identifier; +use crate::syntax::selector::{parse_compound_selector, parse_selector_function_close_token}; +use biome_css_syntax::CssSyntaxKind::CSS_PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::{CssSyntaxKind, T}; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; +use biome_parser::{token_set, Parser, TokenSet}; + +const PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_SET: TokenSet = + token_set![HOST_KW, HOSTCONTEXT_KW]; + +#[inline] +pub(crate) fn is_at_pseudo_class_function_compound_selector(p: &mut CssParser) -> bool { + p.at_ts(PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_SET) && p.nth_at(1, T!['(']) +} + +#[inline] +pub(crate) fn parse_pseudo_class_function_compound_selector(p: &mut CssParser) -> ParsedSyntax { + if !is_at_pseudo_class_function_compound_selector(p) { + return Absent; + } + + let m = p.start(); + + parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); + p.bump(T!['(']); + parse_compound_selector(p).or_add_diagnostic(p, expect_any_selector); + parse_selector_function_close_token(p); + + Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR)) +} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_compound_selector_list.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_compound_selector_list.rs new file mode 100644 index 000000000000..f147da4f63c7 --- /dev/null +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_compound_selector_list.rs @@ -0,0 +1,74 @@ +use crate::parser::CssParser; +use crate::syntax::parse_error::{expect_any_selector, expected_identifier}; +use crate::syntax::parse_regular_identifier; +use crate::syntax::selector::{parse_compound_selector, parse_selector_function_close_token}; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::CssSyntaxKind::{ + CSS_BOGUS_SELECTOR, CSS_COMPOUND_SELECTOR_LIST, + CSS_PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_LIST, +}; +use biome_css_syntax::{CssSyntaxKind, T}; +use biome_parser::parse_lists::ParseSeparatedList; +use biome_parser::parse_recovery::{ParseRecovery, RecoveryResult}; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; +use biome_parser::{token_set, Parser, TokenSet}; + +const PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_LIST_SET: TokenSet = + token_set![MOZANY_KW, WEBKITANY_KW, PAST_KW, CURRENT_KW, FUTURE_KW]; + +#[inline] +pub(crate) fn is_at_pseudo_class_function_compound_selector_list(p: &mut CssParser) -> bool { + p.at_ts(PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_LIST_SET) && p.nth_at(1, T!['(']) +} + +#[inline] +pub(crate) fn parse_pseudo_class_function_compound_selector_list( + p: &mut CssParser, +) -> ParsedSyntax { + if !is_at_pseudo_class_function_compound_selector_list(p) { + return Absent; + } + + let m = p.start(); + + parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); + p.bump(T!['(']); + CssCompoundSelectorList.parse_list(p); + parse_selector_function_close_token(p); + + Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_COMPOUND_SELECTOR_LIST)) +} + +struct CssCompoundSelectorList; + +impl CssCompoundSelectorList { + const RECOVERY_SET: TokenSet = token_set![T!['{'], T![')']]; +} + +impl ParseSeparatedList for CssCompoundSelectorList { + type Kind = CssSyntaxKind; + type Parser<'source> = CssParser<'source>; + + const LIST_KIND: CssSyntaxKind = CSS_COMPOUND_SELECTOR_LIST; + + fn parse_element(&mut self, p: &mut CssParser) -> ParsedSyntax { + parse_compound_selector(p) + } + + fn is_at_list_end(&self, p: &mut CssParser) -> bool { + p.at(T![')']) + } + + fn recover(&mut self, p: &mut CssParser, parsed_element: ParsedSyntax) -> RecoveryResult { + parsed_element.or_recover( + p, + &ParseRecovery::new(CSS_BOGUS_SELECTOR, Self::RECOVERY_SET), + expect_any_selector, + ) + } + + fn separating_element_kind(&mut self) -> CssSyntaxKind { + T![,] + } +} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_identifier.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_identifier.rs new file mode 100644 index 000000000000..4cbc2cad1a1d --- /dev/null +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_identifier.rs @@ -0,0 +1,33 @@ +use crate::parser::CssParser; +use crate::syntax::parse_error::expected_identifier; +use crate::syntax::parse_regular_identifier; +use crate::syntax::selector::parse_selector_function_close_token; +use biome_css_syntax::CssSyntaxKind::CSS_PSEUDO_CLASS_FUNCTION_IDENTIFIER; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::{CssSyntaxKind, T}; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; +use biome_parser::{token_set, Parser, TokenSet}; + +const PSEUDO_CLASS_FUNCTION_IDENTIFIER_SET: TokenSet = token_set![DIR_KW]; + +#[inline] +pub(crate) fn is_at_pseudo_class_function_identifier(p: &mut CssParser) -> bool { + p.at_ts(PSEUDO_CLASS_FUNCTION_IDENTIFIER_SET) && p.nth_at(1, T!['(']) +} + +#[inline] +pub(crate) fn parse_pseudo_class_function_identifier(p: &mut CssParser) -> ParsedSyntax { + if !is_at_pseudo_class_function_identifier(p) { + return Absent; + } + + let m = p.start(); + + parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); + p.bump(T!['(']); + parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); + parse_selector_function_close_token(p); + + Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_IDENTIFIER)) +} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_nth.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_nth.rs new file mode 100644 index 000000000000..b6d2c903acc0 --- /dev/null +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_nth.rs @@ -0,0 +1,139 @@ +use crate::lexer::CssLexContext; +use crate::parser::CssParser; +use crate::syntax::parse_error::{ + expect_any_selector, expected_identifier, expected_number, expected_pseudo_class_nth, +}; +use crate::syntax::selector::{parse_selector_function_close_token, CssSelectorList}; +use crate::syntax::{parse_number, parse_regular_identifier, parse_regular_number}; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::CssSyntaxKind::{ + CSS_NTH_OFFSET, CSS_PSEUDO_CLASS_FUNCTION_NTH, CSS_PSEUDO_CLASS_NTH, + CSS_PSEUDO_CLASS_NTH_IDENTIFIER, CSS_PSEUDO_CLASS_NTH_NUMBER, CSS_PSEUDO_CLASS_NTH_SELECTOR, + CSS_PSEUDO_CLASS_OF_NTH_SELECTOR, OF_KW, +}; +use biome_css_syntax::{CssSyntaxKind, T}; +use biome_parser::parse_lists::ParseSeparatedList; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; +use biome_parser::{token_set, Parser, TokenSet}; + +const PSEUDO_CLASS_FUNCTION_NTH_SET: TokenSet = token_set![ + NTHCHILD_KW, + NTHLASTCHILD_KW, + NTHOFTYPE_KW, + NTHLASTOFTYPE_KW, + NTHCOL_KW, + NTHLASTCOL_KW +]; + +#[inline] +pub(crate) fn is_at_pseudo_class_function_nth(p: &mut CssParser) -> bool { + p.at_ts(PSEUDO_CLASS_FUNCTION_NTH_SET) && p.nth_at(1, T!['(']) +} + +#[inline] +pub(crate) fn parse_pseudo_class_function_nth(p: &mut CssParser) -> ParsedSyntax { + if !is_at_pseudo_class_function_nth(p) { + return Absent; + } + + let m = p.start(); + + parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); + + p.bump_with_context(T!['('], CssLexContext::PseudoNthSelector); + parse_pseudo_class_nth_selector(p).or_add_diagnostic(p, expect_any_selector); + parse_selector_function_close_token(p); + + Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_NTH)) +} + +const PSEUDO_CLASS_FUNCTION_NTH_CLASS_IDENTIFIER_SET: TokenSet = + token_set![T![odd], T![even]]; + +const PSEUDO_CLASS_FUNCTION_NTH_CLASS_SIGN_SET: TokenSet = token_set![T![+], T![-]]; + +const PSEUDO_CLASS_FUNCTION_NTH_CLASS_SET: TokenSet = + PSEUDO_CLASS_FUNCTION_NTH_CLASS_IDENTIFIER_SET + .union(PSEUDO_CLASS_FUNCTION_NTH_CLASS_SIGN_SET) + .union(token_set![T![n], CSS_NUMBER_LITERAL,]); + +#[inline] +fn is_at_pseudo_class_nth_selector(p: &mut CssParser) -> bool { + p.at_ts(PSEUDO_CLASS_FUNCTION_NTH_CLASS_SET) +} + +#[inline] +fn parse_pseudo_class_nth_selector(p: &mut CssParser) -> ParsedSyntax { + if !is_at_pseudo_class_nth_selector(p) { + return Absent; + } + + let m = p.start(); + + parse_pseudo_class_nth(p).or_add_diagnostic(p, expected_pseudo_class_nth); + parse_pseudo_class_of_nth_selector(p).ok(); + + Present(m.complete(p, CSS_PSEUDO_CLASS_NTH_SELECTOR)) +} + +#[inline] +fn parse_pseudo_class_nth(p: &mut CssParser) -> ParsedSyntax { + if !is_at_pseudo_class_nth_selector(p) { + return Absent; + } + + let m = p.start(); + + let kind = if p.eat_ts(PSEUDO_CLASS_FUNCTION_NTH_CLASS_IDENTIFIER_SET) { + CSS_PSEUDO_CLASS_NTH_IDENTIFIER + } else { + p.eat_ts_with_context( + PSEUDO_CLASS_FUNCTION_NTH_CLASS_SIGN_SET, + CssLexContext::PseudoNthSelector, + ); + + parse_number(p, CssLexContext::PseudoNthSelector).ok(); + + if p.eat_with_context(T![n], CssLexContext::PseudoNthSelector) { + parse_nth_offset(p).ok(); + + CSS_PSEUDO_CLASS_NTH + } else { + CSS_PSEUDO_CLASS_NTH_NUMBER + } + }; + + Present(m.complete(p, kind)) +} + +#[inline] +fn parse_nth_offset(p: &mut CssParser) -> ParsedSyntax { + if !p.at_ts(PSEUDO_CLASS_FUNCTION_NTH_CLASS_SIGN_SET) { + return Absent; + } + + let m = p.start(); + + p.bump_ts(PSEUDO_CLASS_FUNCTION_NTH_CLASS_SIGN_SET); + parse_regular_number(p).or_add_diagnostic(p, expected_number); + + Present(m.complete(p, CSS_NTH_OFFSET)) +} + +#[inline] +fn parse_pseudo_class_of_nth_selector(p: &mut CssParser) -> ParsedSyntax { + if !p.at(OF_KW) { + return Absent; + } + + let m = p.start(); + + p.bump(OF_KW); + + CssSelectorList::default() + .with_end_kind(T![')']) + .parse_list(p); + + Present(m.complete(p, CSS_PSEUDO_CLASS_OF_NTH_SELECTOR)) +} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_relative_selector_list.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_relative_selector_list.rs new file mode 100644 index 000000000000..3c9f4403e8fc --- /dev/null +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_relative_selector_list.rs @@ -0,0 +1,103 @@ +use crate::parser::CssParser; +use crate::syntax::parse_error::{expect_any_selector, expected_identifier}; +use crate::syntax::parse_regular_identifier; +use crate::syntax::selector::{ + is_at_compound_selector, parse_selector, parse_selector_function_close_token, +}; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::CssSyntaxKind::{ + CSS_BOGUS_SELECTOR, CSS_PSEUDO_CLASS_FUNCTION_RELATIVE_SELECTOR_LIST, CSS_RELATIVE_SELECTOR, + CSS_RELATIVE_SELECTOR_LIST, +}; +use biome_css_syntax::{CssSyntaxKind, T}; +use biome_parser::parse_lists::ParseSeparatedList; +use biome_parser::parse_recovery::{ParseRecovery, RecoveryResult}; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; +use biome_parser::{token_set, Parser, TokenSet}; + +const PSEUDO_CLASS_FUNCTION_RELATIVE_SELECTOR_LIST_SET: TokenSet = + token_set![HAS_KW]; + +#[inline] +pub(crate) fn is_at_pseudo_class_function_relative_selector_list(p: &mut CssParser) -> bool { + p.at_ts(PSEUDO_CLASS_FUNCTION_RELATIVE_SELECTOR_LIST_SET) && p.nth_at(1, T!['(']) +} + +#[inline] +pub(crate) fn parse_pseudo_class_function_relative_selector_list( + p: &mut CssParser, +) -> ParsedSyntax { + if !is_at_pseudo_class_function_relative_selector_list(p) { + return Absent; + } + + let m = p.start(); + + parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); + p.bump(T!['(']); + CssRelativeSelectorList.parse_list(p); + parse_selector_function_close_token(p); + + Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_RELATIVE_SELECTOR_LIST)) +} + +struct CssRelativeSelectorList; + +impl CssRelativeSelectorList { + const RECOVERY_SET: TokenSet = token_set![T!['{'], T![')']]; +} + +impl ParseSeparatedList for CssRelativeSelectorList { + type Kind = CssSyntaxKind; + type Parser<'source> = CssParser<'source>; + + const LIST_KIND: CssSyntaxKind = CSS_RELATIVE_SELECTOR_LIST; + + fn parse_element(&mut self, p: &mut CssParser) -> ParsedSyntax { + parse_relative_selector(p) + } + + fn is_at_list_end(&self, p: &mut CssParser) -> bool { + p.at(T![')']) + } + + fn recover(&mut self, p: &mut CssParser, parsed_element: ParsedSyntax) -> RecoveryResult { + parsed_element.or_recover( + p, + &ParseRecovery::new(CSS_BOGUS_SELECTOR, Self::RECOVERY_SET), + expect_any_selector, + ) + } + + fn separating_element_kind(&mut self) -> CssSyntaxKind { + T![,] + } +} + +const RELATIVE_SELECTOR_COMBINATOR_SET: TokenSet = + token_set![T![>], T![+], T![~], T![||]]; + +#[inline] +fn is_at_relative_selector_combinator(p: &mut CssParser) -> bool { + p.at_ts(RELATIVE_SELECTOR_COMBINATOR_SET) +} + +#[inline] +fn is_at_relative_selector(p: &mut CssParser) -> bool { + is_at_relative_selector_combinator(p) || is_at_compound_selector(p) +} + +#[inline] +fn parse_relative_selector(p: &mut CssParser) -> ParsedSyntax { + if !is_at_relative_selector(p) { + return Absent; + } + + let m = p.start(); + + p.eat_ts(RELATIVE_SELECTOR_COMBINATOR_SET); + parse_selector(p).or_add_diagnostic(p, expect_any_selector); + + Present(m.complete(p, CSS_RELATIVE_SELECTOR)) +} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector.rs new file mode 100644 index 000000000000..45eeaead3b0f --- /dev/null +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector.rs @@ -0,0 +1,33 @@ +use crate::parser::CssParser; +use crate::syntax::parse_error::{expect_any_selector, expected_identifier}; +use crate::syntax::parse_regular_identifier; +use crate::syntax::selector::{parse_selector, parse_selector_function_close_token}; +use biome_css_syntax::CssSyntaxKind::CSS_PSEUDO_CLASS_FUNCTION_SELECTOR; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::{CssSyntaxKind, T}; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; +use biome_parser::{token_set, Parser, TokenSet}; + +const PSEUDO_CLASS_FUNCTION_SELECTOR_SET: TokenSet = token_set![GLOBAL_KW, LOCAL_KW]; + +#[inline] +pub(crate) fn is_at_pseudo_class_function_selector(p: &mut CssParser) -> bool { + p.at_ts(PSEUDO_CLASS_FUNCTION_SELECTOR_SET) && p.nth_at(1, T!['(']) +} + +#[inline] +pub(crate) fn parse_pseudo_class_function_selector(p: &mut CssParser) -> ParsedSyntax { + if !is_at_pseudo_class_function_selector(p) { + return Absent; + } + + let m = p.start(); + + parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); + p.bump(T!['(']); + parse_selector(p).or_add_diagnostic(p, expect_any_selector); + parse_selector_function_close_token(p); + + Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_SELECTOR)) +} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector_list.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector_list.rs new file mode 100644 index 000000000000..9a5df6c4b76d --- /dev/null +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_selector_list.rs @@ -0,0 +1,36 @@ +use crate::parser::CssParser; +use crate::syntax::parse_error::expected_identifier; +use crate::syntax::parse_regular_identifier; +use crate::syntax::selector::{parse_selector_function_close_token, CssSelectorList}; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::{CssSyntaxKind, T}; +use biome_parser::parse_lists::ParseSeparatedList; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; +use biome_parser::{token_set, Parser, TokenSet}; + +const PSEUDO_CLASS_FUNCTION_SELECTOR_LIST_SET: TokenSet = + token_set![MATCHES_KW, NOT_KW, IS_KW, WHERE_KW]; + +#[inline] +pub(crate) fn is_at_pseudo_class_function_selector_list(p: &mut CssParser) -> bool { + p.at_ts(PSEUDO_CLASS_FUNCTION_SELECTOR_LIST_SET) && p.nth_at(1, T!['(']) +} + +#[inline] +pub(crate) fn parse_pseudo_class_function_selector_list(p: &mut CssParser) -> ParsedSyntax { + if !is_at_pseudo_class_function_selector_list(p) { + return Absent; + } + + let m = p.start(); + + parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); + p.bump(T!['(']); + CssSelectorList::default() + .with_end_kind(T![')']) + .parse_list(p); + parse_selector_function_close_token(p); + + Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_SELECTOR_LIST)) +} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_value_list.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_value_list.rs new file mode 100644 index 000000000000..d8e3623b1831 --- /dev/null +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_value_list.rs @@ -0,0 +1,85 @@ +use crate::parser::CssParser; +use crate::syntax::parse_error::expected_identifier; +use crate::syntax::selector::parse_selector_function_close_token; +use crate::syntax::{is_at_identifier, parse_css_string, parse_regular_identifier}; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::{CssSyntaxKind, T}; +use biome_parser::parse_lists::ParseSeparatedList; +use biome_parser::parse_recovery::{ParseRecovery, RecoveryResult}; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; +use biome_parser::{token_set, Parser, TokenSet}; + +const PSEUDO_CLASS_FUNCTION_VALUE_LIST_SET: TokenSet = token_set![LANG_KW]; + +#[inline] +pub(crate) fn is_at_pseudo_class_function_value_list(p: &mut CssParser) -> bool { + p.at_ts(PSEUDO_CLASS_FUNCTION_VALUE_LIST_SET) && p.nth_at(1, T!['(']) +} + +#[inline] +pub(crate) fn parse_pseudo_class_function_value_list(p: &mut CssParser) -> ParsedSyntax { + if !is_at_pseudo_class_function_value_list(p) { + return Absent; + } + + let m = p.start(); + + parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier); + p.bump(T!['(']); + CssPseudoValueList.parse_list(p); + parse_selector_function_close_token(p); + + Present(m.complete(p, CSS_PSEUDO_CLASS_FUNCTION_VALUE_LIST)) +} + +struct CssPseudoValueList; + +impl CssPseudoValueList { + const RECOVERY_SET: TokenSet = token_set![T!['{'], T![')']]; +} + +impl ParseSeparatedList for CssPseudoValueList { + type Kind = CssSyntaxKind; + type Parser<'source> = CssParser<'source>; + + const LIST_KIND: CssSyntaxKind = CSS_PSEUDO_VALUE_LIST; + + fn parse_element(&mut self, p: &mut CssParser) -> ParsedSyntax { + parse_pseudo_value(p) + } + + fn is_at_list_end(&self, p: &mut CssParser) -> bool { + p.at(T![')']) + } + + fn recover(&mut self, p: &mut CssParser, parsed_element: ParsedSyntax) -> RecoveryResult { + parsed_element.or_recover( + p, + &ParseRecovery::new(CSS_BOGUS, Self::RECOVERY_SET), + expected_identifier, + ) + } + + fn separating_element_kind(&mut self) -> CssSyntaxKind { + T![,] + } +} + +#[inline] +fn is_at_pseudo_value(p: &mut CssParser) -> bool { + is_at_identifier(p) || p.at(CSS_STRING_LITERAL) +} + +#[inline] +fn parse_pseudo_value(p: &mut CssParser) -> ParsedSyntax { + if !is_at_pseudo_value(p) { + return Absent; + } + + if p.at(CSS_STRING_LITERAL) { + parse_css_string(p) + } else { + parse_regular_identifier(p) + } +} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/identifier.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/identifier.rs new file mode 100644 index 000000000000..350bea84e3ad --- /dev/null +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/identifier.rs @@ -0,0 +1,19 @@ +use crate::parser::CssParser; +use crate::syntax::is_at_identifier; +use crate::syntax::parse_error::expected_identifier; +use crate::syntax::selector::parse_selector_identifier; +use biome_css_syntax::CssSyntaxKind::*; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; +use biome_parser::Parser; + +#[inline] +pub(crate) fn parse_pseudo_class_identifier(p: &mut CssParser) -> ParsedSyntax { + if !is_at_identifier(p) { + return Absent; + } + + let m = p.start(); + parse_selector_identifier(p).or_add_diagnostic(p, expected_identifier); + Present(m.complete(p, CSS_PSEUDO_CLASS_IDENTIFIER)) +} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs new file mode 100644 index 000000000000..9a38c92664f4 --- /dev/null +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs @@ -0,0 +1,84 @@ +mod function_compound_selector; +mod function_compound_selector_list; +mod function_identifier; +mod function_nth; +mod function_relative_selector_list; +mod function_selector; +mod function_selector_list; +mod function_value_list; +mod identifier; + +use self::function_compound_selector_list::{ + is_at_pseudo_class_function_compound_selector_list, + parse_pseudo_class_function_compound_selector_list, +}; +use self::function_identifier::{ + is_at_pseudo_class_function_identifier, parse_pseudo_class_function_identifier, +}; +use self::function_nth::{is_at_pseudo_class_function_nth, parse_pseudo_class_function_nth}; +use self::function_relative_selector_list::{ + is_at_pseudo_class_function_relative_selector_list, + parse_pseudo_class_function_relative_selector_list, +}; +use self::function_selector::{ + is_at_pseudo_class_function_selector, parse_pseudo_class_function_selector, +}; +use self::function_selector_list::{ + is_at_pseudo_class_function_selector_list, parse_pseudo_class_function_selector_list, +}; +use self::function_value_list::{ + is_at_pseudo_class_function_value_list, parse_pseudo_class_function_value_list, +}; +use self::identifier::parse_pseudo_class_identifier; +use crate::parser::CssParser; +use crate::syntax::is_at_identifier; +use crate::syntax::parse_error::expect_any_pseudo_class; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::T; +use biome_parser::prelude::ParsedSyntax; +use biome_parser::prelude::ParsedSyntax::{Absent, Present}; +use biome_parser::Parser; +use function_compound_selector::{ + is_at_pseudo_class_function_compound_selector, parse_pseudo_class_function_compound_selector, +}; + +#[inline] +pub(crate) fn parse_pseudo_class_selector(p: &mut CssParser) -> ParsedSyntax { + if !p.at(T![:]) { + return Absent; + } + + let m = p.start(); + + p.bump(T![:]); + parse_pseudo_class(p).or_add_diagnostic(p, expect_any_pseudo_class); + + Present(m.complete(p, CSS_PSEUDO_CLASS_SELECTOR)) +} + +#[inline] +fn parse_pseudo_class(p: &mut CssParser) -> ParsedSyntax { + if !is_at_identifier(p) { + return Absent; + } + + if is_at_pseudo_class_function_identifier(p) { + parse_pseudo_class_function_identifier(p) + } else if is_at_pseudo_class_function_selector(p) { + parse_pseudo_class_function_selector(p) + } else if is_at_pseudo_class_function_selector_list(p) { + parse_pseudo_class_function_selector_list(p) + } else if is_at_pseudo_class_function_compound_selector(p) { + parse_pseudo_class_function_compound_selector(p) + } else if is_at_pseudo_class_function_compound_selector_list(p) { + parse_pseudo_class_function_compound_selector_list(p) + } else if is_at_pseudo_class_function_relative_selector_list(p) { + parse_pseudo_class_function_relative_selector_list(p) + } else if is_at_pseudo_class_function_value_list(p) { + parse_pseudo_class_function_value_list(p) + } else if is_at_pseudo_class_function_nth(p) { + parse_pseudo_class_function_nth(p) + } else { + parse_pseudo_class_identifier(p) + } +} diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_element.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_element.rs index 878140bb6926..15cd42e32991 100644 --- a/crates/biome_css_parser/src/syntax/selector/pseudo_element.rs +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_element.rs @@ -58,7 +58,7 @@ pub(crate) fn parse_pseudo_element_function_identifier(p: &mut CssParser) -> Par let m = p.start(); - p.bump_any(); + p.bump_ts(PSEUDO_ELEMENT_FUNCTION_IDENTIFIER_SET); p.bump(T!['(']); let start_identifier = p.cur_range().start(); diff --git a/crates/biome_parser/src/lib.rs b/crates/biome_parser/src/lib.rs index 0b4d4a7599bb..3038d07507fa 100644 --- a/crates/biome_parser/src/lib.rs +++ b/crates/biome_parser/src/lib.rs @@ -483,6 +483,18 @@ pub trait Parser: Sized { self.do_bump(kind) } + /// Consume the current token if `kind` matches. + fn bump_ts(&mut self, kinds: TokenSet) { + assert!( + kinds.contains(self.cur()), + "expected {:?} but at {:?}", + kinds, + self.cur() + ); + + self.bump_any() + } + /// Consume any token but cast it as a different kind using the specified `context. fn bump_remap_with_context( &mut self, @@ -586,6 +598,35 @@ pub trait Parser: Sized { true } + /// Consume the next token if token set matches. + fn eat_ts(&mut self, kinds: TokenSet) -> bool { + if !self.at_ts(kinds) { + return false; + } + + self.do_bump(self.cur()); + + true + } + + /// Consume the next token if token set matches using the specified `context. + fn eat_ts_with_context( + &mut self, + kinds: TokenSet, + context: ::Context, + ) -> bool + where + Self::Source: BumpWithContext, + { + if !self.at_ts(kinds) { + return false; + } + + self.do_bump_with_context(self.cur(), context); + + true + } + /// Try to eat a specific token kind, if the kind is not there then adds an error to the events stack /// using the specified `context. fn expect_with_context(