Skip to main content

oxc_css_parser/parser/at_rule/
keyframes.rs

1use super::Parser;
2use crate::{
3    Parse,
4    ast::*,
5    error::{Error, ErrorKind, PResult},
6    parser::state::ParserState,
7    pos::Span,
8    tokenizer::Token,
9    util,
10};
11
12// https://drafts.csswg.org/css-animations/#keyframes
13//
14// <keyframe-block> = <keyframe-selector># { <declaration-list> }
15impl<'a> Parse<'a> for KeyframeBlock<'a> {
16    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
17        let first_selector = input.parse::<KeyframeSelector>()?;
18        let start = first_selector.span().start;
19
20        let mut selectors = input.vec_with_capacity(2);
21        selectors.push(first_selector);
22        let mut comma_spans = input.vec();
23        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
24            comma_spans.push(comma_span);
25            selectors.push(input.parse()?);
26        }
27        debug_assert_eq!(comma_spans.len() + 1, selectors.len());
28
29        let block = input
30            .with_state(ParserState { in_keyframes_at_rule: false, ..input.state.clone() })
31            .parse::<SimpleBlock>()?;
32
33        let span = Span { start, end: block.span.end };
34        Ok(KeyframeBlock { selectors, comma_spans, block, span })
35    }
36}
37
38// <keyframe-selector> = from | to | <percentage [0,100]>
39impl<'a> Parse<'a> for KeyframeSelector<'a> {
40    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
41        match &input.cursor.peek()?.token {
42            Token::Percentage(..) => Ok(KeyframeSelector::Percentage(input.parse()?)),
43            _ => {
44                let ident = input.parse()?;
45                match &ident {
46                    InterpolableIdent::Literal(ident)
47                        if !ident.name.eq_ignore_ascii_case("from")
48                            && !ident.name.eq_ignore_ascii_case("to") =>
49                    {
50                        input.recoverable_errors.push(Error {
51                            kind: ErrorKind::UnknownKeyframeSelectorIdent,
52                            span: ident.span.clone(),
53                        });
54                    }
55                    _ => {}
56                }
57                Ok(KeyframeSelector::Ident(ident))
58            }
59        }
60    }
61}
62
63// https://drafts.csswg.org/css-animations/#keyframes
64//
65// @keyframes <keyframes-name> { <rule-list> }
66// <keyframes-name> = <custom-ident> | <string>   (not a CSS-wide keyword)
67impl<'a> Parse<'a> for KeyframesName<'a> {
68    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
69        match &input.cursor.peek()?.token {
70            Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..) => {
71                let ident = input.parse()?;
72                match &ident {
73                    InterpolableIdent::Literal(ident)
74                        if util::is_css_wide_keyword(ident.name)
75                            || ident.name.eq_ignore_ascii_case("default") =>
76                    {
77                        input.recoverable_errors.push(Error {
78                            kind: ErrorKind::CSSWideKeywordDisallowed,
79                            span: ident.span.clone(),
80                        });
81                    }
82                    _ => {}
83                }
84                Ok(KeyframesName::Ident(ident))
85            }
86            Token::AtKeyword(..) => input.parse().map(KeyframesName::LessVariable),
87            Token::Tilde(..) => input.parse().map(KeyframesName::LessEscapedStr),
88            _ => input.parse().map(KeyframesName::Str),
89        }
90    }
91}