mod attribute;
mod nested_selector;
mod pseudo_class;
mod pseudo_element;
pub(crate) mod relative_selector;
use crate::lexer::CssLexContext;
use crate::parser::CssParser;
use crate::syntax::parse_error::{
expected_any_sub_selector, expected_compound_selector, expected_identifier, expected_selector,
};
use crate::syntax::selector::attribute::parse_attribute_selector;
use crate::syntax::selector::nested_selector::NestedSelectorList;
use crate::syntax::selector::pseudo_class::parse_pseudo_class_selector;
use crate::syntax::selector::pseudo_element::parse_pseudo_element_selector;
use crate::syntax::{
is_at_identifier, is_nth_at_identifier, parse_custom_identifier_with_keywords,
parse_identifier, parse_regular_identifier,
};
use biome_css_syntax::CssSyntaxKind::*;
use biome_css_syntax::{CssSyntaxKind, TextRange, T};
use biome_parser::diagnostic::ToDiagnostic;
use biome_parser::parse_lists::{ParseNodeList, ParseSeparatedList};
use biome_parser::parse_recovery::{
ParseRecovery, ParseRecoveryTokenSet, RecoveryError, RecoveryResult,
};
use biome_parser::prelude::ParsedSyntax;
use biome_parser::prelude::ParsedSyntax::{Absent, Present};
use biome_parser::{token_set, CompletedMarker, Parser, ParserProgress, TokenSet};
use super::{is_nth_at_metavariable, parse_metavariable};
const SELECTOR_LEX_SET: TokenSet<CssSyntaxKind> =
COMPLEX_SELECTOR_COMBINATOR_SET.union(token_set![T!['{'], T![,], T![')']]);
#[inline]
fn selector_lex_context(p: &mut CssParser) -> CssLexContext {
if p.nth_at_ts(1, SELECTOR_LEX_SET) {
CssLexContext::Regular
} else {
CssLexContext::Selector
}
}
pub(crate) struct SelectorList {
end_kind_ts: TokenSet<CssSyntaxKind>,
recovery_ts: TokenSet<CssSyntaxKind>,
is_recovery_disabled: bool,
}
impl Default for SelectorList {
fn default() -> Self {
SelectorList {
end_kind_ts: token_set!(T!['{']),
recovery_ts: token_set![T!['{']],
is_recovery_disabled: false,
}
}
}
impl SelectorList {
pub(crate) fn with_end_kind_ts(mut self, end_kind_ts: TokenSet<CssSyntaxKind>) -> Self {
self.end_kind_ts = end_kind_ts;
self
}
pub(crate) fn with_recovery_ts(mut self, recovery_ts: TokenSet<CssSyntaxKind>) -> Self {
self.recovery_ts = recovery_ts;
self
}
pub(crate) fn disable_recovery(mut self) -> Self {
self.is_recovery_disabled = true;
self
}
}
impl ParseSeparatedList for SelectorList {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const LIST_KIND: Self::Kind = CSS_SELECTOR_LIST;
fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax {
parse_selector(p)
}
fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool {
p.at_ts(self.end_kind_ts)
}
fn recover(
&mut self,
p: &mut Self::Parser<'_>,
parsed_element: ParsedSyntax,
) -> RecoveryResult {
if parsed_element.is_absent() && self.is_recovery_disabled {
p.error(expected_selector(p, p.cur_range()));
Err(RecoveryError::RecoveryDisabled)
} else {
parsed_element.or_recover(
p,
&SelectorListParseRecovery::new(self.recovery_ts),
expected_selector,
)
}
}
fn separating_element_kind(&mut self) -> Self::Kind {
T![,]
}
}
struct SelectorListParseRecovery {
recovery_ts: TokenSet<CssSyntaxKind>,
}
impl SelectorListParseRecovery {
fn new(recovery_ts: TokenSet<CssSyntaxKind>) -> Self {
SelectorListParseRecovery { recovery_ts }
}
}
impl ParseRecovery for SelectorListParseRecovery {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const RECOVERED_KIND: Self::Kind = CSS_BOGUS_SELECTOR;
fn is_at_recovered(&self, p: &mut Self::Parser<'_>) -> bool {
p.at_ts(self.recovery_ts)
|| p.at(T![,])
|| is_nth_at_selector(p, 0)
|| p.has_nth_preceding_line_break(1)
}
}
#[inline]
pub(crate) fn is_nth_at_selector(p: &mut CssParser, n: usize) -> bool {
is_nth_at_compound_selector(p, n) || is_nth_at_metavariable(p, n)
}
#[inline]
pub(crate) fn parse_selector(p: &mut CssParser) -> ParsedSyntax {
if !is_nth_at_selector(p, 0) {
return Absent;
}
if is_nth_at_metavariable(p, 0) {
parse_metavariable(p)
} else {
parse_compound_selector(p).and_then(|selector| parse_complex_selector(p, selector))
}
}
const COMPLEX_SELECTOR_COMBINATOR_SET: TokenSet<CssSyntaxKind> =
token_set![T![>], T![+], T![~], T![||], CSS_SPACE_LITERAL];
#[inline]
fn is_nth_at_complex_selector_combinator(p: &mut CssParser, n: usize) -> bool {
p.nth_at_ts(n, COMPLEX_SELECTOR_COMBINATOR_SET)
}
#[inline]
fn parse_complex_selector(p: &mut CssParser, mut left: CompletedMarker) -> ParsedSyntax {
let mut progress = ParserProgress::default();
loop {
progress.assert_progressing(p);
if is_nth_at_complex_selector_combinator(p, 0) {
let complex_selector = left.precede(p);
p.bump_ts(COMPLEX_SELECTOR_COMBINATOR_SET);
parse_compound_selector(p).or_add_diagnostic(p, expected_compound_selector);
left = complex_selector.complete(p, CSS_COMPLEX_SELECTOR)
} else {
return Present(left);
}
}
}
#[inline]
fn is_nth_at_compound_selector(p: &mut CssParser, n: usize) -> bool {
p.nth_at(n, T![&])
|| is_nth_at_simple_selector(p, n)
|| p.nth_at_ts(n, SubSelectorList::START_SET)
}
#[inline]
fn parse_compound_selector(p: &mut CssParser) -> ParsedSyntax {
if !is_nth_at_compound_selector(p, 0) {
return Absent;
}
let m = p.start();
NestedSelectorList.parse_list(p);
parse_simple_selector(p).ok(); SubSelectorList.parse_list(p);
Present(m.complete(p, CSS_COMPOUND_SELECTOR))
}
#[inline]
fn is_nth_at_simple_selector(p: &mut CssParser, n: usize) -> bool {
is_nth_at_namespace(p, n) || p.nth_at(n, T![*]) || is_nth_at_identifier(p, n)
}
#[inline]
fn parse_simple_selector(p: &mut CssParser) -> ParsedSyntax {
if !is_nth_at_simple_selector(p, 0) {
return Absent;
}
let namespace = parse_namespace(p);
if p.at(T![*]) {
parse_universal_selector(p, namespace)
} else {
parse_type_selector(p, namespace)
}
}
#[inline]
fn is_nth_at_namespace(p: &mut CssParser, n: usize) -> bool {
p.nth_at(n, T![|]) || is_nth_at_namespace_prefix(p, n) && p.nth_at(n + 1, T![|])
}
#[inline]
fn parse_namespace(p: &mut CssParser) -> ParsedSyntax {
if !is_nth_at_namespace(p, 0) {
return Absent;
}
let m = p.start();
parse_namespace_prefix(p).ok();
p.bump(T![|]);
Present(m.complete(p, CSS_NAMESPACE))
}
#[inline]
fn is_nth_at_namespace_prefix(p: &mut CssParser, n: usize) -> bool {
p.nth_at(n, T![*]) || is_nth_at_identifier(p, n)
}
#[inline]
fn parse_namespace_prefix(p: &mut CssParser) -> ParsedSyntax {
if !is_nth_at_namespace_prefix(p, 0) {
return Absent;
}
let m = p.start();
let kind = if p.eat(T![*]) {
CSS_UNIVERSAL_NAMESPACE_PREFIX
} else {
parse_regular_identifier(p).ok();
CSS_NAMED_NAMESPACE_PREFIX
};
Present(m.complete(p, kind))
}
pub(crate) struct SubSelectorList;
impl SubSelectorList {
pub(crate) const START_SET: TokenSet<CssSyntaxKind> =
token_set![T![#], T![.], T![:], T![::], T!['[']];
}
impl ParseNodeList for SubSelectorList {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const LIST_KIND: CssSyntaxKind = CSS_SUB_SELECTOR_LIST;
fn parse_element(&mut self, p: &mut CssParser) -> ParsedSyntax {
parse_sub_selector(p)
}
fn is_at_list_end(&self, p: &mut CssParser) -> bool {
!p.at_ts(Self::START_SET)
}
fn recover(&mut self, p: &mut CssParser, parsed_element: ParsedSyntax) -> RecoveryResult {
parsed_element.or_recover_with_token_set(
p,
&ParseRecoveryTokenSet::new(CSS_BOGUS_SUB_SELECTOR, Self::START_SET),
expected_any_sub_selector,
)
}
}
#[inline]
fn parse_sub_selector(p: &mut CssParser) -> ParsedSyntax {
match p.cur() {
T![.] => parse_class_selector(p),
T![#] => parse_id_selector(p),
T!['['] => parse_attribute_selector(p),
T![:] => parse_pseudo_class_selector(p),
T![::] => parse_pseudo_element_selector(p),
_ => Absent,
}
}
#[inline]
pub(crate) fn parse_class_selector(p: &mut CssParser) -> ParsedSyntax {
if !p.at(T![.]) {
return Absent;
}
let m = p.start();
p.bump(T![.]);
parse_selector_custom_identifier(p).or_add_diagnostic(p, expected_identifier);
Present(m.complete(p, CSS_CLASS_SELECTOR))
}
#[inline]
pub(crate) fn parse_id_selector(p: &mut CssParser) -> ParsedSyntax {
if !p.at(T![#]) {
return Absent;
}
let m = p.start();
p.bump(T![#]);
parse_selector_custom_identifier(p).or_add_diagnostic(p, expected_identifier);
Present(m.complete(p, CSS_ID_SELECTOR))
}
#[inline]
pub(crate) fn parse_universal_selector(p: &mut CssParser, namespace: ParsedSyntax) -> ParsedSyntax {
if !p.at(T![*]) {
return Absent;
}
let m = namespace.precede(p);
let context = selector_lex_context(p);
p.eat_with_context(T![*], context);
Present(m.complete(p, CSS_UNIVERSAL_SELECTOR))
}
#[inline]
fn parse_type_selector(p: &mut CssParser, namespace: ParsedSyntax) -> ParsedSyntax {
if !is_at_identifier(p) {
return Absent;
}
let m = namespace.precede(p);
parse_selector_identifier(p).or_add_diagnostic(p, expected_identifier);
Present(m.complete(p, CSS_TYPE_SELECTOR))
}
#[inline]
fn parse_selector_identifier(p: &mut CssParser) -> ParsedSyntax {
let context = selector_lex_context(p);
parse_identifier(p, context)
}
#[inline]
fn parse_selector_custom_identifier(p: &mut CssParser) -> ParsedSyntax {
let context = selector_lex_context(p);
parse_custom_identifier_with_keywords(p, context, true)
}
const SELECTOR_FUNCTION_RECOVERY_SET: TokenSet<CssSyntaxKind> = token_set![T![')'], T!['{']];
#[inline]
pub(crate) fn eat_or_recover_selector_function_close_token<'a, E, D>(
p: &mut CssParser<'a>,
parameter: CompletedMarker,
error_builder: E,
) -> bool
where
E: FnOnce(&CssParser, TextRange) -> D,
D: ToDiagnostic<CssParser<'a>>,
{
let context = selector_lex_context(p);
if p.eat_with_context(T![')'], context) {
true
} else {
if let Ok(m) = ParseRecoveryTokenSet::new(CSS_BOGUS, SELECTOR_FUNCTION_RECOVERY_SET)
.enable_recovery_on_line_break()
.recover(p)
{
let diagnostic = error_builder(
p,
TextRange::new(parameter.range(p).start(), m.range(p).end()),
);
p.error(diagnostic);
}
let context = selector_lex_context(p);
p.expect_with_context(T![')'], context);
false
}
}
#[inline]
pub(crate) fn recover_selector_function_parameter<'a, E, D>(p: &mut CssParser<'a>, error_builder: E)
where
E: FnOnce(&CssParser, TextRange) -> D,
D: ToDiagnostic<CssParser<'a>>,
{
let start = p.cur_range().start();
let range = ParseRecoveryTokenSet::new(CSS_BOGUS, SELECTOR_FUNCTION_RECOVERY_SET)
.enable_recovery_on_line_break()
.recover(p)
.map_or_else(|_| p.cur_range(), |m| m.range(p));
let diagnostic = error_builder(p, TextRange::new(start, range.end()));
p.error(diagnostic);
}