oxc_css_parser/parser/at_rule/
keyframes.rs1use 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
12impl<'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
38fn is_timeline_range_name(name: &str) -> bool {
40 name.eq_ignore_ascii_case("cover")
41 || name.eq_ignore_ascii_case("contain")
42 || name.eq_ignore_ascii_case("entry")
43 || name.eq_ignore_ascii_case("exit")
44 || name.eq_ignore_ascii_case("entry-crossing")
45 || name.eq_ignore_ascii_case("exit-crossing")
46}
47
48impl<'a> Parse<'a> for KeyframeSelector<'a> {
51 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
52 match &input.cursor.peek()?.token {
53 Token::Percentage(..) => Ok(KeyframeSelector::Percentage(input.parse()?)),
54 _ => {
55 let ident = input.parse::<InterpolableIdent>()?;
56 if let Token::Percentage(..) = &input.cursor.peek()?.token {
59 if let InterpolableIdent::Literal(ident) = &ident
60 && !is_timeline_range_name(ident.name)
61 {
62 input.recoverable_errors.push(Error {
63 kind: ErrorKind::UnknownKeyframeSelectorIdent,
64 span: ident.span,
65 });
66 }
67 let percentage = input.parse::<Percentage>()?;
68 let span = Span { start: ident.span().start, end: percentage.span.end };
69 return Ok(KeyframeSelector::TimelineRange(KeyframeTimelineRange {
70 name: ident,
71 percentage,
72 span,
73 }));
74 }
75 if let InterpolableIdent::Literal(ident) = &ident
76 && !ident.name.eq_ignore_ascii_case("from")
77 && !ident.name.eq_ignore_ascii_case("to")
78 {
79 input.recoverable_errors.push(Error {
80 kind: ErrorKind::UnknownKeyframeSelectorIdent,
81 span: ident.span,
82 });
83 }
84 Ok(KeyframeSelector::Ident(ident))
85 }
86 }
87 }
88}
89
90impl<'a> Parse<'a> for KeyframesName<'a> {
95 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
96 match &input.cursor.peek()?.token {
97 Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..) => {
98 let ident = input.parse()?;
99 if let InterpolableIdent::Literal(ident) = &ident
100 && (util::is_css_wide_keyword(ident.name)
101 || ident.name.eq_ignore_ascii_case("default"))
102 {
103 input.recoverable_errors.push(Error {
104 kind: ErrorKind::CSSWideKeywordDisallowed,
105 span: ident.span,
106 });
107 }
108 Ok(KeyframesName::Ident(ident))
109 }
110 Token::AtKeyword(..) => input.parse().map(KeyframesName::LessVariable),
111 Token::Tilde(..) => input.parse().map(KeyframesName::LessEscapedStr),
112 _ => input.parse().map(KeyframesName::Str),
113 }
114 }
115}