Skip to main content

oxc_css_parser/parser/at_rule/
supports.rs

1use super::Parser;
2use crate::{
3    Parse,
4    ast::*,
5    error::{Error, ErrorKind, PResult},
6    pos::Span,
7    tokenizer::{Token, TokenWithSpan},
8};
9
10// https://drafts.csswg.org/css-conditional-3/#at-supports
11impl<'a> Parse<'a> for SupportsCondition<'a> {
12    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
13        match &input.cursor.peek()?.token {
14            Token::Ident(token) if token.name().eq_ignore_ascii_case("not") => {
15                let keyword = input.parse::<Ident>()?;
16                let condition = input.parse::<SupportsInParens>()?;
17                let span = Span { start: keyword.span.start, end: condition.span().end };
18                Ok(SupportsCondition {
19                    conditions: input.vec1(SupportsConditionKind::Not(SupportsNot {
20                        keyword,
21                        condition,
22                        span: span.clone(),
23                    })),
24                    span,
25                })
26            }
27            _ => {
28                let first = input.parse::<SupportsInParens>()?;
29                let mut span = first.span().clone();
30                let mut conditions = input.vec1(SupportsConditionKind::SupportsInParens(first));
31                while let Token::Ident(ident) = &input.cursor.peek()?.token {
32                    let name = ident.name();
33                    if name.eq_ignore_ascii_case("and") {
34                        let ident = input.parse::<Ident>()?;
35                        let condition = input.parse::<SupportsInParens>()?;
36                        let span = Span { start: ident.span.start, end: condition.span().end };
37                        conditions.push(SupportsConditionKind::And(SupportsAnd {
38                            keyword: ident,
39                            condition,
40                            span,
41                        }));
42                    } else if name.eq_ignore_ascii_case("or") {
43                        let ident = input.parse::<Ident>()?;
44                        let condition = input.parse::<SupportsInParens>()?;
45                        let span = Span { start: ident.span.start, end: condition.span().end };
46                        conditions.push(SupportsConditionKind::Or(SupportsOr {
47                            keyword: ident,
48                            condition,
49                            span,
50                        }));
51                    } else {
52                        break;
53                    }
54                }
55                if let Some(last) = conditions.last() {
56                    span.end = last.span().end;
57                }
58                Ok(SupportsCondition { conditions, span })
59            }
60        }
61    }
62}
63
64impl<'a> Parse<'a> for SupportsInParens<'a> {
65    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
66        match input.cursor.peek()? {
67            TokenWithSpan { token: Token::LParen(..), .. } => input
68                .try_parse(|parser| {
69                    parser.parse::<SupportsDecl>().map(|supports_decl| {
70                        let span = supports_decl.span.clone();
71                        SupportsInParens {
72                            kind: SupportsInParensKind::Feature(parser.alloc(supports_decl)),
73                            span,
74                        }
75                    })
76                })
77                .or_else(|_| {
78                    input.try_parse(|parser| {
79                        let (_, Span { start, .. }) = parser.cursor.expect_l_paren()?;
80                        let condition = parser.parse::<SupportsCondition>()?;
81                        let (_, Span { end, .. }) = parser.cursor.expect_r_paren()?;
82                        Ok(SupportsInParens {
83                            kind: SupportsInParensKind::SupportsCondition(condition),
84                            span: Span { start, end },
85                        })
86                    })
87                })
88                .or_else(|_| {
89                    // <general-enclosed>: MQ L4 catch-all (referenced from <supports-condition>),
90                    // evaluates false at runtime.
91                    let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
92                    let tokens = input.parse_tokens_in_parens()?;
93                    let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
94                    Ok(SupportsInParens {
95                        kind: SupportsInParensKind::GeneralEnclosed(tokens),
96                        span: Span { start, end },
97                    })
98                }),
99            // Sass: an interpolation may stand for a whole condition operand
100            // (`@supports #{"(a: b)"} and (c: d)`) or splice into a function
101            // name (`@supports a#{"b"}c(d)`).
102            TokenWithSpan { token: Token::Ident(..) | Token::HashLBrace(..), .. } => {
103                let name = input.parse::<InterpolableIdent>()?;
104                let name_end = name.span().end;
105                match name {
106                    InterpolableIdent::Literal(function_ident)
107                        if function_ident.name.eq_ignore_ascii_case("selector") =>
108                    {
109                        input.cursor.expect_l_paren()?;
110                        let selector_list = input.parse::<SelectorList>()?;
111                        input.cursor.expect_r_paren()?;
112                        let span = selector_list.span.clone();
113                        Ok(SupportsInParens {
114                            kind: SupportsInParensKind::Selector(selector_list),
115                            span,
116                        })
117                    }
118                    name => {
119                        let glued_lparen = matches!(
120                            input.cursor.peek()?,
121                            TokenWithSpan { token: Token::LParen(..), span }
122                                if span.start == name_end
123                        );
124                        // Only a pure interpolation may stand alone
125                        // (`#{"(a: b)"}`); a mixed ident like `a#{b}` still
126                        // needs parens or a function call, as in dart-sass.
127                        let pure_interpolation = matches!(
128                            &name,
129                            InterpolableIdent::SassInterpolated(interpolation)
130                                if matches!(
131                                    interpolation.elements.as_slice(),
132                                    [SassInterpolatedIdentElement::Expression(..)]
133                                )
134                        );
135                        if glued_lparen {
136                            // An unknown function here is `<general-enclosed>`
137                            // (css-conditional): its contents are raw tokens.
138                            let function = input.parse_raw_function(name)?;
139                            let span = function.span.clone();
140                            Ok(SupportsInParens {
141                                kind: SupportsInParensKind::Function(function),
142                                span,
143                            })
144                        } else if pure_interpolation {
145                            let span = name.span().clone();
146                            Ok(SupportsInParens {
147                                kind: SupportsInParensKind::Interpolation(name),
148                                span,
149                            })
150                        } else {
151                            let TokenWithSpan { token, span } = input.cursor.peek()?;
152                            Err(Error {
153                                kind: ErrorKind::Unexpected("'('", token.symbol()),
154                                span: span.clone(),
155                            })
156                        }
157                    }
158                }
159            }
160            TokenWithSpan { token, span } => Err(Error {
161                kind: ErrorKind::Unexpected("'('", token.symbol()),
162                span: span.clone(),
163            }),
164        }
165    }
166}
167
168impl<'a> Parse<'a> for SupportsDecl<'a> {
169    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
170        let start = input.cursor.expect_l_paren()?.1.start;
171        let decl = input.parse()?;
172        let end = input.cursor.expect_r_paren()?.1.end;
173        Ok(SupportsDecl { decl, span: Span { start, end } })
174    }
175}