use crate::lexer::CssLexContext;
use crate::parser::CssParser;
use crate::syntax::at_rule::parse_error::{
expected_keyframes_item, expected_keyframes_item_selector,
};
use crate::syntax::block::{parse_declaration_block, ParseBlockBody};
use crate::syntax::css_modules::{
expected_any_css_module_scope, local_or_global_not_allowed, CSS_MODULES_SCOPE_SET,
};
use crate::syntax::parse_error::expected_non_css_wide_keyword_identifier;
use crate::syntax::value::dimension::{is_at_percentage_dimension, parse_percentage_dimension};
use crate::syntax::{
is_at_declaration, is_at_identifier, is_at_string, parse_custom_identifier, parse_string,
};
use biome_css_syntax::CssSyntaxKind::*;
use biome_css_syntax::{CssSyntaxKind, T};
use biome_parser::parse_lists::{ParseNodeList, ParseSeparatedList};
use biome_parser::parse_recovery::{ParseRecovery, RecoveryResult};
use biome_parser::parsed_syntax::ParsedSyntax::Present;
use biome_parser::prelude::ParsedSyntax::Absent;
use biome_parser::prelude::*;
#[inline]
pub(crate) fn is_at_keyframes_at_rule(p: &mut CssParser) -> bool {
p.at(T![keyframes])
}
#[inline]
pub(crate) fn parse_keyframes_at_rule(p: &mut CssParser) -> ParsedSyntax {
if !is_at_keyframes_at_rule(p) {
return Absent;
}
let m = p.start();
p.bump(T![keyframes]);
if is_at_keyframes_scoped_name(p) {
parse_keyframes_scoped_name(p).ok();
} else {
parse_keyframes_identifier(p)
.or_add_diagnostic(p, expected_non_css_wide_keyword_identifier);
};
KeyframesBlock.parse_block_body(p);
Present(m.complete(p, CSS_KEYFRAMES_AT_RULE))
}
fn is_at_keyframes_scoped_name(p: &mut CssParser) -> bool {
p.at(T![:])
}
fn parse_keyframes_scoped_name(p: &mut CssParser) -> ParsedSyntax {
if !is_at_keyframes_scoped_name(p) {
return Absent;
}
let m = p.start();
p.bump(T![:]);
if p.options().is_css_modules_disabled() {
p.error(local_or_global_not_allowed(p, p.cur_range()));
while !p.at(T!['{']) {
p.bump_any();
}
return Present(m.complete(p, CSS_BOGUS_KEYFRAMES_NAME));
}
let kind = {
let m = p.start();
if !p.eat_ts(CSS_MODULES_SCOPE_SET) {
p.error(expected_any_css_module_scope(p, p.cur_range()));
p.bump_any();
}
let kind = if p.eat(T!['(']) {
CSS_KEYFRAMES_SCOPE_FUNCTION
} else {
CSS_KEYFRAMES_SCOPE_PREFIX
};
let name = parse_keyframes_identifier(p)
.or_add_diagnostic(p, expected_non_css_wide_keyword_identifier);
if kind == CSS_KEYFRAMES_SCOPE_FUNCTION {
p.expect(T![')']);
}
m.complete(p, kind);
if name.is_some() {
CSS_KEYFRAMES_SCOPED_NAME
} else {
CSS_BOGUS_KEYFRAMES_NAME
}
};
Present(m.complete(p, kind))
}
fn is_at_keyframes_identifier(p: &mut CssParser) -> bool {
is_at_identifier(p) || is_at_string(p)
}
fn parse_keyframes_identifier(p: &mut CssParser) -> ParsedSyntax {
if !is_at_keyframes_identifier(p) {
return Absent;
}
if is_at_identifier(p) {
parse_custom_identifier(p, CssLexContext::Regular)
} else {
parse_string(p)
}
}
struct KeyframesBlock;
impl ParseBlockBody for KeyframesBlock {
const BLOCK_KIND: CssSyntaxKind = CSS_KEYFRAMES_BLOCK;
fn is_at_element(&self, p: &mut CssParser) -> bool {
is_at_keyframes_item_selector(p)
}
fn parse_list(&mut self, p: &mut CssParser) {
KeyframesItemList.parse_list(p);
}
}
struct KeyframesItemListParseRecovery;
impl ParseRecovery for KeyframesItemListParseRecovery {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const RECOVERED_KIND: Self::Kind = CSS_BOGUS_KEYFRAMES_ITEM;
fn is_at_recovered(&self, p: &mut Self::Parser<'_>) -> bool {
p.at(T!['}']) || is_at_keyframes_item_selector(p)
}
}
struct KeyframesItemList;
impl ParseNodeList for KeyframesItemList {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const LIST_KIND: Self::Kind = CSS_KEYFRAMES_ITEM_LIST;
fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax {
parse_keyframes_item(p)
}
fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool {
p.at(T!['}'])
}
fn recover(
&mut self,
p: &mut Self::Parser<'_>,
parsed_element: ParsedSyntax,
) -> RecoveryResult {
parsed_element.or_recover(p, &KeyframesItemListParseRecovery, expected_keyframes_item)
}
}
struct KeyframesItemBlockParseRecovery;
impl ParseRecovery for KeyframesItemBlockParseRecovery {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const RECOVERED_KIND: Self::Kind = CSS_BOGUS_BLOCK;
fn is_at_recovered(&self, p: &mut Self::Parser<'_>) -> bool {
p.at(T!['}']) || is_at_keyframes_item_selector(p)
}
}
#[inline]
fn parse_keyframes_item(p: &mut CssParser) -> ParsedSyntax {
let m = p.start();
KeyframesSelectorList.parse_list(p);
if p.cur_range().start() == m.start() {
p.error(expected_keyframes_item_selector(p, p.cur_range()))
}
parse_declaration_block(p);
Present(m.complete(p, CSS_KEYFRAMES_ITEM))
}
struct KeyframesSelectorListParseRecovery;
impl ParseRecovery for KeyframesSelectorListParseRecovery {
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 {
is_at_keyframes_item_selector(p) || is_at_keyframes_selector_list_end(p)
}
}
struct KeyframesSelectorList;
impl ParseSeparatedList for KeyframesSelectorList {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const LIST_KIND: Self::Kind = CSS_KEYFRAMES_SELECTOR_LIST;
fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax {
parse_keyframes_item_selector(p)
}
fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool {
p.at(T!['{'])
}
fn recover(
&mut self,
p: &mut Self::Parser<'_>,
parsed_element: ParsedSyntax,
) -> RecoveryResult {
parsed_element.or_recover(
p,
&KeyframesSelectorListParseRecovery,
expected_keyframes_item_selector,
)
}
fn separating_element_kind(&mut self) -> Self::Kind {
T![,]
}
}
fn is_at_keyframes_selector_list_end(p: &mut CssParser) -> bool {
p.at(T!['{']) || is_at_declaration(p) || p.at(T!['}'])
}
const KEYFRAMES_ITEM_SELECTOR_IDENT_SET: TokenSet<CssSyntaxKind> = token_set!(T![from], T![to]);
fn is_at_keyframes_item_selector(p: &mut CssParser) -> bool {
p.at_ts(KEYFRAMES_ITEM_SELECTOR_IDENT_SET) || is_at_percentage_dimension(p)
}
#[inline]
fn parse_keyframes_item_selector(p: &mut CssParser) -> ParsedSyntax {
if !is_at_keyframes_item_selector(p) {
return Absent;
}
let m = p.start();
let kind = if is_at_percentage_dimension(p) {
parse_percentage_dimension(p).ok();
CSS_KEYFRAMES_PERCENTAGE_SELECTOR
} else {
p.bump_ts(KEYFRAMES_ITEM_SELECTOR_IDENT_SET);
CSS_KEYFRAMES_IDENT_SELECTOR
};
Present(m.complete(p, kind))
}