Skip to main content

oxc_css_parser/parser/at_rule/
container.rs

1use super::Parser;
2use crate::{
3    Parse,
4    ast::*,
5    error::{Error, ErrorKind, PResult},
6    pos::Span,
7    tokenizer::{Token, TokenWithSpan},
8};
9
10impl<'a> Parse<'a> for ContainerCondition<'a> {
11    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
12        match &input.cursor.peek()?.token {
13            Token::Ident(ident) if ident.name().eq_ignore_ascii_case("not") => {
14                let container_condition_not = input.parse::<ContainerConditionNot>()?;
15                let span = container_condition_not.span.clone();
16                Ok(ContainerCondition {
17                    conditions: input.vec1(ContainerConditionKind::Not(container_condition_not)),
18                    span,
19                })
20            }
21            _ => {
22                let first = input.parse::<QueryInParens>()?;
23                let mut span = first.span.clone();
24                let mut conditions = input.vec1(ContainerConditionKind::QueryInParens(first));
25                // formally `and`/`or` may not mix without parens and `not`
26                // is leading-only, but real-world code (less.js) chains them
27                // freely: `(a) or (b) and (c)`, `(a) not (b)`.
28                loop {
29                    let kind = match &input.cursor.peek()?.token {
30                        Token::Ident(ident) if ident.name().eq_ignore_ascii_case("and") => {
31                            ContainerConditionKind::And(input.parse()?)
32                        }
33                        Token::Ident(ident) if ident.name().eq_ignore_ascii_case("or") => {
34                            ContainerConditionKind::Or(input.parse()?)
35                        }
36                        Token::Ident(ident) if ident.name().eq_ignore_ascii_case("not") => {
37                            ContainerConditionKind::Not(input.parse()?)
38                        }
39                        _ => break,
40                    };
41                    conditions.push(kind);
42                }
43
44                if let Some(last) = conditions.last() {
45                    span.end = last.span().end;
46                }
47                Ok(ContainerCondition { conditions, span })
48            }
49        }
50    }
51}
52
53impl<'a> Parse<'a> for ContainerConditionAnd<'a> {
54    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
55        let keyword = input.parse::<Ident>()?;
56        if keyword.name.eq_ignore_ascii_case("and") {
57            let query_in_parens = input.parse::<QueryInParens>()?;
58            let span = Span { start: keyword.span.start, end: query_in_parens.span.end };
59            Ok(ContainerConditionAnd { keyword, query_in_parens, span })
60        } else {
61            Err(Error { kind: ErrorKind::ExpectContainerConditionAnd, span: keyword.span })
62        }
63    }
64}
65
66impl<'a> Parse<'a> for ContainerConditionNot<'a> {
67    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
68        let keyword = input.parse::<Ident>()?;
69        if keyword.name.eq_ignore_ascii_case("not") {
70            let query_in_parens = input.parse::<QueryInParens>()?;
71            let span = Span { start: keyword.span.start, end: query_in_parens.span.end };
72            Ok(ContainerConditionNot { keyword, query_in_parens, span })
73        } else {
74            Err(Error { kind: ErrorKind::ExpectContainerConditionNot, span: keyword.span })
75        }
76    }
77}
78
79impl<'a> Parse<'a> for ContainerConditionOr<'a> {
80    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
81        let keyword = input.parse::<Ident>()?;
82        if keyword.name.eq_ignore_ascii_case("or") {
83            let query_in_parens = input.parse::<QueryInParens>()?;
84            let span = Span { start: keyword.span.start, end: query_in_parens.span.end };
85            Ok(ContainerConditionOr { keyword, query_in_parens, span })
86        } else {
87            Err(Error { kind: ErrorKind::ExpectContainerConditionOr, span: keyword.span })
88        }
89    }
90}
91
92impl<'a> Parse<'a> for QueryInParens<'a> {
93    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
94        if let Some((_, Span { start, .. })) = input.cursor.eat_l_paren()? {
95            let kind = if let Ok(container_condition) = input.try_parse(ContainerCondition::parse) {
96                QueryInParensKind::ContainerCondition(container_condition)
97            } else {
98                let size_feature = input.parse()?;
99                QueryInParensKind::SizeFeature(input.alloc(size_feature))
100            };
101            let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
102            Ok(QueryInParens { kind, span: Span { start, end } })
103        } else {
104            let (style_keyword, ident_span) = input.cursor.expect_ident()?;
105            let keyword = style_keyword.name();
106            if keyword.eq_ignore_ascii_case("style") {
107                input.cursor.expect_l_paren_without_ws_or_comments()?;
108                let kind = input.parse().map(QueryInParensKind::StyleQuery)?;
109                let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
110                Ok(QueryInParens { kind, span: Span { start: ident_span.start, end } })
111            } else if keyword.eq_ignore_ascii_case("scroll-state") {
112                // https://drafts.csswg.org/css-conditional-5/#scroll-state-container
113                input.cursor.expect_l_paren_without_ws_or_comments()?;
114                let media = input.parse()?;
115                let kind = QueryInParensKind::ScrollState(input.alloc(media));
116                let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
117                Ok(QueryInParens { kind, span: Span { start: ident_span.start, end } })
118            } else {
119                Err(Error { kind: ErrorKind::ExpectStyleQuery, span: ident_span })
120            }
121        }
122    }
123}
124
125impl<'a> Parse<'a> for StyleCondition<'a> {
126    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
127        match &input.cursor.peek()?.token {
128            Token::Ident(ident) if ident.name().eq_ignore_ascii_case("not") => {
129                let style_condition_not = input.parse::<StyleConditionNot>()?;
130                let span = style_condition_not.span.clone();
131                Ok(StyleCondition {
132                    conditions: input.vec1(StyleConditionKind::Not(style_condition_not)),
133                    span,
134                })
135            }
136            _ => {
137                let first = input.parse::<StyleInParens>()?;
138                let mut span = first.span.clone();
139                let mut conditions = input.vec1(StyleConditionKind::StyleInParens(first));
140                if let Token::Ident(ident) = &input.cursor.peek()?.token {
141                    let name = ident.name();
142                    if name.eq_ignore_ascii_case("and") {
143                        loop {
144                            conditions.push(StyleConditionKind::And(input.parse()?));
145                            match &input.cursor.peek()?.token {
146                                Token::Ident(ident) if ident.name().eq_ignore_ascii_case("and") => {
147                                }
148                                _ => break,
149                            }
150                        }
151                    } else if name.eq_ignore_ascii_case("or") {
152                        loop {
153                            conditions.push(StyleConditionKind::Or(input.parse()?));
154                            match &input.cursor.peek()?.token {
155                                Token::Ident(ident) if ident.name().eq_ignore_ascii_case("or") => {}
156                                _ => break,
157                            }
158                        }
159                    }
160                }
161
162                if let Some(last) = conditions.last() {
163                    span.end = last.span().end;
164                }
165                Ok(StyleCondition { conditions, span })
166            }
167        }
168    }
169}
170
171impl<'a> Parse<'a> for StyleConditionAnd<'a> {
172    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
173        let ident = input.parse::<Ident>()?;
174        if ident.name.eq_ignore_ascii_case("and") {
175            let style_in_parens = input.parse::<StyleInParens>()?;
176            let span = Span { start: ident.span.start, end: style_in_parens.span.end };
177            Ok(StyleConditionAnd { keyword: ident, style_in_parens, span })
178        } else {
179            Err(Error { kind: ErrorKind::ExpectStyleConditionAnd, span: ident.span })
180        }
181    }
182}
183
184impl<'a> Parse<'a> for StyleConditionNot<'a> {
185    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
186        let keyword = input.parse::<Ident>()?;
187        if keyword.name.eq_ignore_ascii_case("not") {
188            let style_in_parens = input.parse::<StyleInParens>()?;
189            let span = Span { start: keyword.span.start, end: style_in_parens.span.end };
190            Ok(StyleConditionNot { keyword, style_in_parens, span })
191        } else {
192            Err(Error { kind: ErrorKind::ExpectStyleConditionNot, span: keyword.span })
193        }
194    }
195}
196
197impl<'a> Parse<'a> for StyleConditionOr<'a> {
198    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
199        let keyword = input.parse::<Ident>()?;
200        if keyword.name.eq_ignore_ascii_case("or") {
201            let style_in_parens = input.parse::<StyleInParens>()?;
202            let span = Span { start: keyword.span.start, end: style_in_parens.span.end };
203            Ok(StyleConditionOr { keyword, style_in_parens, span })
204        } else {
205            Err(Error { kind: ErrorKind::ExpectStyleConditionOr, span: keyword.span })
206        }
207    }
208}
209
210impl<'a> Parse<'a> for StyleInParens<'a> {
211    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
212        let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
213        let kind = input.parse()?;
214        let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
215        Ok(StyleInParens { kind, span: Span { start, end } })
216    }
217}
218
219impl<'a> Parse<'a> for StyleInParensKind<'a> {
220    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
221        if let Ok(style_condition) = input.try_parse(StyleCondition::parse) {
222            Ok(StyleInParensKind::Condition(style_condition))
223        } else {
224            input.parse().map(StyleInParensKind::Feature)
225        }
226    }
227}
228
229impl<'a> Parse<'a> for StyleQuery<'a> {
230    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
231        if let Ok(condition) = input.try_parse(StyleCondition::parse) {
232            Ok(StyleQuery::Condition(condition))
233        } else if let Ok(name) = input.try_parse(|p| {
234            // a bare custom-property existence test: `style(--theme)`
235            let name = p.parse::<InterpolableIdent>()?;
236            match (&name, &p.cursor.peek()?.token) {
237                (InterpolableIdent::Literal(ident), Token::RParen(..))
238                    if ident.name.starts_with("--") =>
239                {
240                    Ok(name)
241                }
242                _ => {
243                    let span = p.cursor.peek()?.span.clone();
244                    Err(Error { kind: ErrorKind::TryParseError, span })
245                }
246            }
247        }) {
248            Ok(StyleQuery::FeatureName(name))
249        } else {
250            let feature = input.parse().map(StyleQuery::Feature);
251            input.cursor.eat_semicolon()?;
252            feature
253        }
254    }
255}
256
257// https://drafts.csswg.org/css-contain-3/#container-rule
258impl<'a> Parse<'a> for ContainerPrelude<'a> {
259    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
260        let name = input.try_parse(|parser| match parser.parse()? {
261            InterpolableIdent::Literal(ident)
262                if ident.name.eq_ignore_ascii_case("not")
263                    || ident.name.eq_ignore_ascii_case("scroll-state") =>
264            {
265                Err(Error { kind: ErrorKind::TryParseError, span: ident.span })
266            }
267            InterpolableIdent::Literal(ident) if ident.name.eq_ignore_ascii_case("style") => {
268                match parser.cursor.peek()? {
269                    TokenWithSpan { token: Token::LParen(..), span }
270                        if span.start == ident.span.end =>
271                    {
272                        Err(Error { kind: ErrorKind::TryParseError, span: ident.span })
273                    }
274                    _ => Ok(InterpolableIdent::Literal(ident)),
275                }
276            }
277            ident => Ok(ident),
278        });
279        let condition = input.parse::<ContainerCondition>()?;
280        let mut span = condition.span().clone();
281        if let Ok(name) = &name {
282            span.start = name.span().start;
283        }
284        Ok(ContainerPrelude { name: name.ok(), condition, span })
285    }
286}