Skip to main content

oxc_css_parser/parser/at_rule/
container.rs

1use super::Parser;
2use crate::{
3    Parse,
4    ast::*,
5    eat,
6    error::{Error, ErrorKind, PResult},
7    expect, expect_without_ws_or_comments, peek,
8    pos::{Span, Spanned},
9    tokenizer::{Token, TokenWithSpan},
10};
11
12impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for ContainerCondition<'s> {
13    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
14        match &peek!(input).token {
15            Token::Ident(ident) if ident.name().eq_ignore_ascii_case("not") => {
16                let container_condition_not = input.parse::<ContainerConditionNot>()?;
17                let span = container_condition_not.span.clone();
18                Ok(ContainerCondition {
19                    conditions: vec![ContainerConditionKind::Not(container_condition_not)],
20                    span,
21                })
22            }
23            _ => {
24                let first = input.parse::<QueryInParens>()?;
25                let mut span = first.span.clone();
26                let mut conditions = vec![ContainerConditionKind::QueryInParens(first)];
27                if let Token::Ident(ident) = &peek!(input).token {
28                    let name = ident.name();
29                    if name.eq_ignore_ascii_case("and") {
30                        loop {
31                            conditions.push(ContainerConditionKind::And(input.parse()?));
32                            match &peek!(input).token {
33                                Token::Ident(ident) if ident.name().eq_ignore_ascii_case("and") => {
34                                }
35                                _ => break,
36                            }
37                        }
38                    } else if name.eq_ignore_ascii_case("or") {
39                        loop {
40                            conditions.push(ContainerConditionKind::Or(input.parse()?));
41                            match &peek!(input).token {
42                                Token::Ident(ident) if ident.name().eq_ignore_ascii_case("or") => {}
43                                _ => break,
44                            }
45                        }
46                    }
47                }
48
49                if let Some(last) = conditions.last() {
50                    span.end = last.span().end;
51                }
52                Ok(ContainerCondition { conditions, span })
53            }
54        }
55    }
56}
57
58impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for ContainerConditionAnd<'s> {
59    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
60        let keyword = input.parse::<Ident>()?;
61        if keyword.name.eq_ignore_ascii_case("and") {
62            let query_in_parens = input.parse::<QueryInParens>()?;
63            let span = Span {
64                start: keyword.span.start,
65                end: query_in_parens.span.end,
66            };
67            Ok(ContainerConditionAnd {
68                keyword,
69                query_in_parens,
70                span,
71            })
72        } else {
73            Err(Error {
74                kind: ErrorKind::ExpectContainerConditionAnd,
75                span: keyword.span,
76            })
77        }
78    }
79}
80
81impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for ContainerConditionNot<'s> {
82    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
83        let keyword = input.parse::<Ident>()?;
84        if keyword.name.eq_ignore_ascii_case("not") {
85            let query_in_parens = input.parse::<QueryInParens>()?;
86            let span = Span {
87                start: keyword.span.start,
88                end: query_in_parens.span.end,
89            };
90            Ok(ContainerConditionNot {
91                keyword,
92                query_in_parens,
93                span,
94            })
95        } else {
96            Err(Error {
97                kind: ErrorKind::ExpectContainerConditionNot,
98                span: keyword.span,
99            })
100        }
101    }
102}
103
104impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for ContainerConditionOr<'s> {
105    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
106        let keyword = input.parse::<Ident>()?;
107        if keyword.name.eq_ignore_ascii_case("or") {
108            let query_in_parens = input.parse::<QueryInParens>()?;
109            let span = Span {
110                start: keyword.span.start,
111                end: query_in_parens.span.end,
112            };
113            Ok(ContainerConditionOr {
114                keyword,
115                query_in_parens,
116                span,
117            })
118        } else {
119            Err(Error {
120                kind: ErrorKind::ExpectContainerConditionOr,
121                span: keyword.span,
122            })
123        }
124    }
125}
126
127impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for QueryInParens<'s> {
128    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
129        if let Some((_, Span { start, .. })) = eat!(input, LParen) {
130            let kind = if let Ok(container_condition) = input.try_parse(ContainerCondition::parse) {
131                QueryInParensKind::ContainerCondition(container_condition)
132            } else {
133                QueryInParensKind::SizeFeature(Box::new(input.parse()?))
134            };
135            let (_, Span { end, .. }) = expect!(input, RParen);
136            Ok(QueryInParens {
137                kind,
138                span: Span { start, end },
139            })
140        } else {
141            let (style_keyword, ident_span) = expect!(input, Ident);
142            let keyword = style_keyword.name();
143            if keyword.eq_ignore_ascii_case("style") {
144                expect_without_ws_or_comments!(input, LParen);
145                let kind = input.parse().map(QueryInParensKind::StyleQuery)?;
146                let (_, Span { end, .. }) = expect!(input, RParen);
147                Ok(QueryInParens {
148                    kind,
149                    span: Span {
150                        start: ident_span.start,
151                        end,
152                    },
153                })
154            } else if keyword.eq_ignore_ascii_case("scroll-state") {
155                // https://drafts.csswg.org/css-conditional-5/#scroll-state-container
156                expect_without_ws_or_comments!(input, LParen);
157                let kind = input
158                    .parse()
159                    .map(|media| QueryInParensKind::ScrollState(Box::new(media)))?;
160                let (_, Span { end, .. }) = expect!(input, RParen);
161                Ok(QueryInParens {
162                    kind,
163                    span: Span {
164                        start: ident_span.start,
165                        end,
166                    },
167                })
168            } else {
169                Err(Error {
170                    kind: ErrorKind::ExpectStyleQuery,
171                    span: ident_span,
172                })
173            }
174        }
175    }
176}
177
178impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for StyleCondition<'s> {
179    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
180        match &peek!(input).token {
181            Token::Ident(ident) if ident.name().eq_ignore_ascii_case("not") => {
182                let style_condition_not = input.parse::<StyleConditionNot>()?;
183                let span = style_condition_not.span.clone();
184                Ok(StyleCondition {
185                    conditions: vec![StyleConditionKind::Not(style_condition_not)],
186                    span,
187                })
188            }
189            _ => {
190                let first = input.parse::<StyleInParens>()?;
191                let mut span = first.span.clone();
192                let mut conditions = vec![StyleConditionKind::StyleInParens(first)];
193                if let Token::Ident(ident) = &peek!(input).token {
194                    let name = ident.name();
195                    if name.eq_ignore_ascii_case("and") {
196                        loop {
197                            conditions.push(StyleConditionKind::And(input.parse()?));
198                            match &peek!(input).token {
199                                Token::Ident(ident) if ident.name().eq_ignore_ascii_case("and") => {
200                                }
201                                _ => break,
202                            }
203                        }
204                    } else if name.eq_ignore_ascii_case("or") {
205                        loop {
206                            conditions.push(StyleConditionKind::Or(input.parse()?));
207                            match &peek!(input).token {
208                                Token::Ident(ident) if ident.name().eq_ignore_ascii_case("or") => {}
209                                _ => break,
210                            }
211                        }
212                    }
213                }
214
215                if let Some(last) = conditions.last() {
216                    span.end = last.span().end;
217                }
218                Ok(StyleCondition { conditions, span })
219            }
220        }
221    }
222}
223
224impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for StyleConditionAnd<'s> {
225    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
226        let ident = input.parse::<Ident>()?;
227        if ident.name.eq_ignore_ascii_case("and") {
228            let style_in_parens = input.parse::<StyleInParens>()?;
229            let span = Span {
230                start: ident.span.start,
231                end: style_in_parens.span.end,
232            };
233            Ok(StyleConditionAnd {
234                keyword: ident,
235                style_in_parens,
236                span,
237            })
238        } else {
239            Err(Error {
240                kind: ErrorKind::ExpectStyleConditionAnd,
241                span: ident.span,
242            })
243        }
244    }
245}
246
247impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for StyleConditionNot<'s> {
248    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
249        let keyword = input.parse::<Ident>()?;
250        if keyword.name.eq_ignore_ascii_case("not") {
251            let style_in_parens = input.parse::<StyleInParens>()?;
252            let span = Span {
253                start: keyword.span.start,
254                end: style_in_parens.span.end,
255            };
256            Ok(StyleConditionNot {
257                keyword,
258                style_in_parens,
259                span,
260            })
261        } else {
262            Err(Error {
263                kind: ErrorKind::ExpectStyleConditionNot,
264                span: keyword.span,
265            })
266        }
267    }
268}
269
270impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for StyleConditionOr<'s> {
271    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
272        let keyword = input.parse::<Ident>()?;
273        if keyword.name.eq_ignore_ascii_case("or") {
274            let style_in_parens = input.parse::<StyleInParens>()?;
275            let span = Span {
276                start: keyword.span.start,
277                end: style_in_parens.span.end,
278            };
279            Ok(StyleConditionOr {
280                keyword,
281                style_in_parens,
282                span,
283            })
284        } else {
285            Err(Error {
286                kind: ErrorKind::ExpectStyleConditionOr,
287                span: keyword.span,
288            })
289        }
290    }
291}
292
293impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for StyleInParens<'s> {
294    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
295        let (_, Span { start, .. }) = expect!(input, LParen);
296        let kind = input.parse()?;
297        let (_, Span { end, .. }) = expect!(input, RParen);
298        Ok(StyleInParens {
299            kind,
300            span: Span { start, end },
301        })
302    }
303}
304
305impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for StyleInParensKind<'s> {
306    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
307        if let Ok(style_condition) = input.try_parse(StyleCondition::parse) {
308            Ok(StyleInParensKind::Condition(style_condition))
309        } else {
310            input.parse().map(StyleInParensKind::Feature)
311        }
312    }
313}
314
315impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for StyleQuery<'s> {
316    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
317        if let Ok(condition) = input.try_parse(StyleCondition::parse) {
318            Ok(StyleQuery::Condition(condition))
319        } else {
320            let feature = input.parse().map(StyleQuery::Feature);
321            eat!(input, Semicolon);
322            feature
323        }
324    }
325}
326
327// https://drafts.csswg.org/css-contain-3/#container-rule
328impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for ContainerPrelude<'s> {
329    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
330        let name = input.try_parse(|parser| match parser.parse()? {
331            InterpolableIdent::Literal(ident)
332                if ident.name.eq_ignore_ascii_case("not")
333                    || ident.name.eq_ignore_ascii_case("scroll-state") =>
334            {
335                Err(Error {
336                    kind: ErrorKind::TryParseError,
337                    span: ident.span,
338                })
339            }
340            InterpolableIdent::Literal(ident) if ident.name.eq_ignore_ascii_case("style") => {
341                match peek!(parser) {
342                    TokenWithSpan {
343                        token: Token::LParen(..),
344                        span,
345                    } if span.start == ident.span.end => Err(Error {
346                        kind: ErrorKind::TryParseError,
347                        span: ident.span,
348                    }),
349                    _ => Ok(InterpolableIdent::Literal(ident)),
350                }
351            }
352            ident => Ok(ident),
353        });
354        let condition = input.parse::<ContainerCondition>()?;
355        let mut span = condition.span().clone();
356        if let Ok(name) = &name {
357            span.start = name.span().start;
358        }
359        Ok(ContainerPrelude {
360            name: name.ok(),
361            condition,
362            span,
363        })
364    }
365}