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