Skip to main content

oxc_css_parser/parser/at_rule/
custom_selector.rs

1use super::Parser;
2use crate::{Parse, ast::*, error::PResult, pos::Span, tokenizer::Token, util};
3
4// The custom-selector name token `:<ident>` (e.g. `:--heading`), extended here
5// with this parser's optional `$arg` prefix and `(args)` list (PostCSS/Sass).
6impl<'a> Parse<'a> for CustomSelector<'a> {
7    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
8        let prefix_arg = if matches!(input.cursor.peek()?.token, Token::DollarVar(..)) {
9            Some(input.parse::<CustomSelectorArg>()?)
10        } else {
11            None
12        };
13
14        let (_, colon_span) = input.cursor.expect_colon()?;
15        if let Some(prefix_arg) = &prefix_arg {
16            util::assert_no_ws_or_comment(&prefix_arg.span, &colon_span)?;
17        }
18        let name = input.parse::<Ident>()?;
19        util::assert_no_ws_or_comment(&colon_span, &name.span)?;
20
21        let args = if matches!(input.cursor.peek()?.token, Token::LParen(..)) {
22            Some(input.parse::<CustomSelectorArgs>()?)
23        } else {
24            None
25        };
26
27        let span = Span {
28            start: prefix_arg
29                .as_ref()
30                .map(|prefix_arg| prefix_arg.span.start)
31                .unwrap_or(name.span.start),
32            end: args.as_ref().map(|args| args.span.end).unwrap_or(name.span.end),
33        };
34        Ok(CustomSelector { prefix_arg, name, args, span })
35    }
36}
37
38// A custom-selector argument placeholder: '$' <ident>
39impl<'a> Parse<'a> for CustomSelectorArg<'a> {
40    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
41        let (dollar_var, dollar_var_span) = input.cursor.expect_dollar_var()?;
42        Ok(CustomSelectorArg {
43            name: input.ident(
44                dollar_var.ident,
45                Span { start: dollar_var_span.start + 1, end: dollar_var_span.end },
46            ),
47            span: dollar_var_span,
48        })
49    }
50}
51
52// The argument list of a custom selector: '(' <arg> [ , <arg> ]* ')'
53impl<'a> Parse<'a> for CustomSelectorArgs<'a> {
54    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
55        let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
56
57        let mut args = input.vec();
58        let mut comma_spans = input.vec();
59        while !matches!(input.cursor.peek()?.token, Token::RParen(..)) {
60            args.push(input.parse()?);
61            if !matches!(input.cursor.peek()?.token, Token::RParen(..)) {
62                comma_spans.push(input.cursor.expect_comma()?.1);
63            }
64        }
65
66        let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
67        Ok(CustomSelectorArgs { args, comma_spans, span: Span { start, end } })
68    }
69}
70
71// https://drafts.csswg.org/css-extensions/#custom-selectors
72//
73// @custom-selector : <extension-name> <selector-list> ;
74// <extension-name> = <dashed-ident>  (spelled with a leading `:`, e.g. `:--heading`)
75impl<'a> Parse<'a> for CustomSelectorPrelude<'a> {
76    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
77        let custom_selector = input.parse::<CustomSelector>()?;
78        let selector = input.parse::<SelectorList>()?;
79        let span = Span { start: custom_selector.span.start, end: selector.span.end };
80        Ok(CustomSelectorPrelude { custom_selector, selector, span })
81    }
82}