Skip to main content

oxc_css_parser/parser/
selector.rs

1use super::Parser;
2use crate::{
3    Parse, Syntax,
4    ast::*,
5    error::{Error, ErrorKind, PResult},
6    pos::Span,
7    tokenizer::{Token, TokenWithSpan, token},
8    util,
9};
10
11// https://www.w3.org/TR/css-syntax-3/#the-anb-type
12//
13// <an+b> = odd | even | <integer>
14//        | <n-dimension>        [ <signed-integer> | [ '+' | '-' ] <signless-integer> ]?
15//        | '+'? n               [ <signed-integer> | [ '+' | '-' ] <signless-integer> ]?
16//        | -n                   [ <signed-integer> | [ '+' | '-' ] <signless-integer> ]?
17//        | <ndashdigit-dimension> | '+'? <ndashdigit-ident> | <dashndashdigit-ident>
18impl<'a> Parse<'a> for AnPlusB {
19    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
20        match input.cursor.peek()? {
21            TokenWithSpan { token: Token::Dimension(..), .. } => {
22                let (token::Dimension { value, unit }, span) = input.cursor.expect_dimension()?;
23                let value_span = Span { start: span.start, end: span.start + value.raw.len() };
24                let unit_name = unit.name();
25                if unit_name.eq_ignore_ascii_case("n") {
26                    match &input.cursor.peek()?.token {
27                        // syntax: <n-dimension> ['+' | '-'] <signless-integer>
28                        // examples: '1n + 1', '1n - 1', '1n+ 1'
29                        sign @ Token::Plus(..) | sign @ Token::Minus(..) => {
30                            let sign = if let Token::Plus(..) = sign { 1 } else { -1 };
31                            input.cursor.bump()?;
32                            let (number, number_span) = expect_unsigned_int(input)?;
33                            let span = Span { start: span.start, end: number_span.end };
34                            Ok(AnPlusB {
35                                a: value
36                                    .try_into()
37                                    .map_err(|kind| Error { kind, span: value_span })?,
38                                b: sign
39                                    * i32::try_from(number)
40                                        .map_err(|kind| Error { kind, span: number_span })?,
41                                span,
42                            })
43                        }
44
45                        // syntax: <n-dimension> <signed-integer>
46                        // examples: '1n +1', '1n -1'
47                        Token::Number(..) => {
48                            let (number, number_span) = input.cursor.expect_number()?;
49                            let span = Span { start: span.start, end: number_span.end };
50                            Ok(AnPlusB {
51                                a: value
52                                    .try_into()
53                                    .map_err(|kind| Error { kind, span: value_span })?,
54                                b: number
55                                    .try_into()
56                                    .map_err(|kind| Error { kind, span: number_span })?,
57                                span,
58                            })
59                        }
60
61                        // syntax: <n-dimension>
62                        // examples: '1n'
63                        _ => Ok(AnPlusB {
64                            a: value.try_into().map_err(|kind| Error { kind, span: value_span })?,
65                            b: 0,
66                            span,
67                        }),
68                    }
69                } else if unit_name.eq_ignore_ascii_case("n-") {
70                    // syntax: <ndash-dimension> <signless-integer>
71                    // examples: '1n- 1'
72                    let (number, number_span) = expect_unsigned_int(input)?;
73                    let span = Span { start: span.start, end: number_span.end };
74                    Ok(AnPlusB {
75                        a: value.try_into().map_err(|kind| Error { kind, span: value_span })?,
76                        b: -i32::try_from(number)
77                            .map_err(|kind| Error { kind, span: number_span })?,
78                        span,
79                    })
80                } else if let Some(digits) = unit_name.strip_prefix("n-") {
81                    // syntax: <ndashdigit-dimension>
82                    // examples: '1n-1'
83                    if digits.chars().any(|c| !c.is_ascii_digit()) {
84                        return Err(Error {
85                            kind: ErrorKind::ExpectUnsignedInteger,
86                            span: Span { start: span.start + value.raw.len() + 2, end: span.end },
87                        });
88                    }
89                    let b = digits.parse::<i32>().map_err(|_| Error {
90                        kind: ErrorKind::ExpectInteger,
91                        span: Span { start: span.start + value.raw.len() + 2, end: span.end },
92                    })?;
93                    Ok(AnPlusB {
94                        a: value.try_into().map_err(|kind| Error { kind, span: value_span })?,
95                        b: -b,
96                        span,
97                    })
98                } else {
99                    Err(Error { kind: ErrorKind::InvalidAnPlusB, span })
100                }
101            }
102
103            TokenWithSpan { token: Token::Plus(..), .. } => {
104                let plus_span = input.cursor.bump()?.span;
105                let (ident, ident_span) =
106                    input.cursor.expect_ident_without_ws_or_comments(false)?;
107                let ident_name = ident.name();
108                if ident_name.eq_ignore_ascii_case("n") {
109                    match &input.cursor.peek()?.token {
110                        // syntax: +n ['+' | '-'] <signless-integer>
111                        // examples: '+n + 1', '+n - 1', '+n+ 1'
112                        sign @ Token::Plus(..) | sign @ Token::Minus(..) => {
113                            let sign = if let Token::Plus(..) = sign { 1 } else { -1 };
114                            input.cursor.bump()?;
115                            let (number, number_span) = expect_unsigned_int(input)?;
116                            let span = Span { start: plus_span.start, end: number_span.end };
117                            Ok(AnPlusB {
118                                a: 1,
119                                b: sign
120                                    * i32::try_from(number)
121                                        .map_err(|kind| Error { kind, span: number_span })?,
122                                span,
123                            })
124                        }
125
126                        // syntax: +n <signed-integer>
127                        // examples: '+n +1', '+n -1'
128                        Token::Number(..) => {
129                            let (number, number_span) = input.cursor.expect_number()?;
130                            let span = Span { start: plus_span.start, end: number_span.end };
131                            Ok(AnPlusB {
132                                a: 1,
133                                b: number
134                                    .try_into()
135                                    .map_err(|kind| Error { kind, span: number_span })?,
136                                span,
137                            })
138                        }
139
140                        // syntax: +n
141                        _ => Ok(AnPlusB {
142                            a: 1,
143                            b: 0,
144                            span: Span { start: plus_span.start, end: ident_span.end },
145                        }),
146                    }
147                } else if ident_name.eq_ignore_ascii_case("n-") {
148                    // syntax: +n- <signless-integer>
149                    // examples: '+n- 1'
150                    let (number, number_span) = expect_unsigned_int(input)?;
151                    let span = Span { start: plus_span.start, end: number_span.end };
152                    Ok(AnPlusB {
153                        a: 1,
154                        b: -i32::try_from(number)
155                            .map_err(|kind| Error { kind, span: number_span })?,
156                        span,
157                    })
158                } else if let Some(digits) = ident_name.strip_prefix("n-") {
159                    // syntax: +<ndashdigit-ident>
160                    // examples: '+n-1'
161                    if digits.chars().any(|c| !c.is_ascii_digit()) {
162                        return Err(Error {
163                            kind: ErrorKind::ExpectUnsignedInteger,
164                            span: Span { start: ident_span.start + 2, end: ident_span.end },
165                        });
166                    }
167                    let b = digits.parse::<i32>().map_err(|_| Error {
168                        kind: ErrorKind::ExpectInteger,
169                        span: Span { start: ident_span.start + 2, end: ident_span.end },
170                    })?;
171                    Ok(AnPlusB {
172                        a: 1,
173                        b: -b,
174                        span: Span { start: plus_span.start, end: ident_span.end },
175                    })
176                } else {
177                    Err(Error {
178                        kind: ErrorKind::InvalidAnPlusB,
179                        span: Span { start: plus_span.start, end: ident_span.end },
180                    })
181                }
182            }
183
184            TokenWithSpan { token: Token::Ident(..), .. } => {
185                let (ident, ident_span) = input.cursor.expect_ident()?;
186                let ident_name = ident.name();
187                if ident_name.eq_ignore_ascii_case("n") {
188                    match &input.cursor.peek()?.token {
189                        // syntax: n ['+' | '-'] <signless-integer>
190                        // examples: 'n + 1', 'n - 1', 'n+ 1'
191                        sign @ Token::Plus(..) | sign @ Token::Minus(..) => {
192                            let sign = if let Token::Plus(..) = sign { 1 } else { -1 };
193                            input.cursor.bump()?;
194                            let (number, number_span) = expect_unsigned_int(input)?;
195                            let span = Span { start: ident_span.start, end: number_span.end };
196                            Ok(AnPlusB {
197                                a: 1,
198                                b: sign
199                                    * i32::try_from(number)
200                                        .map_err(|kind| Error { kind, span: number_span })?,
201                                span,
202                            })
203                        }
204
205                        // syntax: n <signed-integer>
206                        // examples: 'n +1', 'n -1'
207                        Token::Number(..) => {
208                            let (number, number_span) = input.cursor.expect_number()?;
209                            let span = Span { start: ident_span.start, end: number_span.end };
210                            Ok(AnPlusB {
211                                a: 1,
212                                b: number
213                                    .try_into()
214                                    .map_err(|kind| Error { kind, span: number_span })?,
215                                span,
216                            })
217                        }
218
219                        // syntax: n
220                        _ => Ok(AnPlusB { a: 1, b: 0, span: ident_span }),
221                    }
222                } else if ident_name.eq_ignore_ascii_case("n-") {
223                    // syntax: n- <signless-integer>
224                    // examples: 'n- 1'
225                    let (number, number_span) = expect_unsigned_int(input)?;
226                    let span = Span { start: ident_span.start, end: number_span.end };
227                    Ok(AnPlusB {
228                        a: 1,
229                        b: -i32::try_from(number)
230                            .map_err(|kind| Error { kind, span: number_span })?,
231                        span,
232                    })
233                } else if let Some(digits) = ident_name.strip_prefix("n-") {
234                    // syntax: <ndashdigit-ident>
235                    // examples: 'n-1'
236                    if digits.chars().any(|c| !c.is_ascii_digit()) {
237                        return Err(Error {
238                            kind: ErrorKind::ExpectUnsignedInteger,
239                            span: Span { start: ident_span.start + 2, end: ident_span.end },
240                        });
241                    }
242                    let b = digits.parse::<i32>().map_err(|_| Error {
243                        kind: ErrorKind::ExpectInteger,
244                        span: Span { start: ident_span.start + 2, end: ident_span.end },
245                    })?;
246                    Ok(AnPlusB { a: 1, b: -b, span: ident_span })
247                } else if ident_name.eq_ignore_ascii_case("-n") {
248                    match &input.cursor.peek()?.token {
249                        // syntax: -n ['+' | '-'] <signless-integer>
250                        // examples: '-n + 1', '-n - 1', '-n+ 1'
251                        sign @ Token::Plus(..) | sign @ Token::Minus(..) => {
252                            let sign = if let Token::Plus(..) = sign { 1 } else { -1 };
253                            input.cursor.bump()?;
254                            let (number, number_span) = expect_unsigned_int(input)?;
255                            let span = Span { start: ident_span.start, end: number_span.end };
256                            Ok(AnPlusB {
257                                a: -1,
258                                b: sign
259                                    * i32::try_from(number)
260                                        .map_err(|kind| Error { kind, span: number_span })?,
261                                span,
262                            })
263                        }
264
265                        // syntax: -n <signed-integer>
266                        // examples: '-n +1', '-n -1'
267                        Token::Number(..) => {
268                            let (number, number_span) = input.cursor.expect_number()?;
269                            let span = Span { start: ident_span.start, end: number_span.end };
270                            Ok(AnPlusB {
271                                a: -1,
272                                b: number
273                                    .try_into()
274                                    .map_err(|kind| Error { kind, span: number_span })?,
275                                span,
276                            })
277                        }
278
279                        // syntax: -n
280                        _ => Ok(AnPlusB { a: -1, b: 0, span: ident_span }),
281                    }
282                } else if ident_name.eq_ignore_ascii_case("-n-") {
283                    // syntax: -n- <signless-integer>
284                    // examples: '-n- 1'
285                    let (number, number_span) = expect_unsigned_int(input)?;
286                    let span = Span { start: ident_span.start, end: number_span.end };
287                    Ok(AnPlusB {
288                        a: -1,
289                        b: -i32::try_from(number)
290                            .map_err(|kind| Error { kind, span: number_span })?,
291                        span,
292                    })
293                } else if let Some(digits) = ident_name.strip_prefix("-n-") {
294                    // syntax: -n-<ndashdigit-ident>
295                    // examples: '-n-1'
296                    if digits.chars().any(|c| !c.is_ascii_digit()) {
297                        return Err(Error {
298                            kind: ErrorKind::ExpectUnsignedInteger,
299                            span: Span { start: ident_span.start + 3, end: ident_span.end },
300                        });
301                    }
302                    let b = digits.parse::<i32>().map_err(|_| Error {
303                        kind: ErrorKind::ExpectInteger,
304                        span: Span { start: ident_span.start + 3, end: ident_span.end },
305                    })?;
306                    Ok(AnPlusB { a: -1, b: -b, span: ident_span })
307                } else {
308                    Err(Error { kind: ErrorKind::InvalidAnPlusB, span: ident_span })
309                }
310            }
311
312            TokenWithSpan { span, .. } => {
313                Err(Error { kind: ErrorKind::InvalidAnPlusB, span: *span })
314            }
315        }
316    }
317}
318
319// https://www.w3.org/TR/selectors-4/#attribute-selectors
320//
321// <attribute-selector> = '[' <wq-name> ']'
322//                      | '[' <wq-name> <attr-matcher> [ <string-token> | <ident-token> ] <attr-modifier>? ']'
323// <attr-matcher>  = [ '~' | '|' | '^' | '$' | '*' ]? '='
324// <attr-modifier> = i | s
325impl<'a> Parse<'a> for AttributeSelector<'a> {
326    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
327        let start = input.cursor.expect_l_bracket()?.1.start;
328
329        let name = match input.cursor.peek()? {
330            TokenWithSpan {
331                token: Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..),
332                ..
333            } => {
334                let ident = input.parse::<InterpolableIdent>()?;
335                let ident_span = ident.span();
336                if let Some((_, bar_token_span)) = input.cursor.eat_bar()? {
337                    let name = input.parse::<InterpolableIdent>()?;
338                    let name_span = name.span();
339
340                    let start = ident_span.start;
341                    let end = name_span.end;
342                    WqName {
343                        name,
344                        prefix: Some(NsPrefix {
345                            kind: Some(NsPrefixKind::Ident(ident)),
346                            span: Span { start, end: bar_token_span.end },
347                        }),
348                        span: Span { start, end },
349                    }
350                } else {
351                    let span = *ident_span;
352                    WqName { name: ident, prefix: None, span }
353                }
354            }
355            TokenWithSpan { token: Token::Asterisk(..), .. } => {
356                let asterisk_span = input.cursor.bump()?.span;
357                let bar_token_span = input.cursor.expect_bar()?.1;
358                let name = input.parse::<InterpolableIdent>()?;
359
360                let start = asterisk_span.start;
361                let end = name.span().end;
362                WqName {
363                    name,
364                    prefix: Some(NsPrefix {
365                        kind: Some(NsPrefixKind::Universal(NsPrefixUniversal {
366                            span: asterisk_span,
367                        })),
368                        span: Span { start, end: bar_token_span.end },
369                    }),
370                    span: Span { start, end },
371                }
372            }
373            TokenWithSpan { token: Token::Bar(..), .. } => {
374                let bar_token_span = input.cursor.bump()?.span;
375                let name = input.parse::<InterpolableIdent>()?;
376
377                let start = bar_token_span.start;
378                let end = name.span().end;
379                WqName {
380                    name,
381                    prefix: Some(NsPrefix {
382                        kind: None,
383                        span: Span { start, end: bar_token_span.end },
384                    }),
385                    span: Span { start, end },
386                }
387            }
388            TokenWithSpan { span, .. } => {
389                return Err(Error { kind: ErrorKind::ExpectWqName, span: *span });
390            }
391        };
392
393        let matcher = match input.cursor.peek()? {
394            TokenWithSpan { token: Token::RBracket(..), .. } => None,
395            TokenWithSpan { token: Token::Equal(..), .. } => Some(AttributeSelectorMatcher {
396                kind: AttributeSelectorMatcherKind::Exact,
397                span: input.cursor.bump()?.span,
398            }),
399            TokenWithSpan { token: Token::TildeEqual(..), .. } => Some(AttributeSelectorMatcher {
400                kind: AttributeSelectorMatcherKind::MatchWord,
401                span: input.cursor.bump()?.span,
402            }),
403            TokenWithSpan { token: Token::BarEqual(..), .. } => Some(AttributeSelectorMatcher {
404                kind: AttributeSelectorMatcherKind::ExactOrPrefixThenHyphen,
405                span: input.cursor.bump()?.span,
406            }),
407            TokenWithSpan { token: Token::CaretEqual(..), .. } => Some(AttributeSelectorMatcher {
408                kind: AttributeSelectorMatcherKind::Prefix,
409                span: input.cursor.bump()?.span,
410            }),
411            TokenWithSpan { token: Token::DollarEqual(..), .. } => Some(AttributeSelectorMatcher {
412                kind: AttributeSelectorMatcherKind::Suffix,
413                span: input.cursor.bump()?.span,
414            }),
415            TokenWithSpan { token: Token::AsteriskEqual(..), .. } => {
416                Some(AttributeSelectorMatcher {
417                    kind: AttributeSelectorMatcherKind::Substring,
418                    span: input.cursor.bump()?.span,
419                })
420            }
421            TokenWithSpan { span, .. } => {
422                return Err(Error { kind: ErrorKind::ExpectAttributeSelectorMatcher, span: *span });
423            }
424        };
425
426        let value = if matcher.is_some() {
427            match input.cursor.peek()? {
428                TokenWithSpan {
429                    token:
430                        Token::Ident(..)
431                        | Token::HashLBrace(..)
432                        | Token::AtLBraceVar(..)
433                        | Token::Placeholder(..),
434                    ..
435                } => Some(AttributeSelectorValue::Ident(input.parse()?)),
436                TokenWithSpan { token: Token::Str(..) | Token::StrTemplate(..), .. } => {
437                    Some(AttributeSelectorValue::Str(input.parse()?))
438                }
439                // Unquoted numeric values such as `[size=1]` or `[size=1px]` are
440                // technically non-conforming (Selectors wants an ident or string), but
441                // browsers accept them and they appear in real CSS (incl. UA stylesheets).
442                TokenWithSpan { token: Token::Number(..), .. } => {
443                    Some(AttributeSelectorValue::Number(input.parse()?))
444                }
445                TokenWithSpan { token: Token::Dimension(..), .. } => {
446                    Some(AttributeSelectorValue::Dimension(input.parse()?))
447                }
448                TokenWithSpan { token: Token::Percentage(..), .. }
449                    if input.syntax == Syntax::Less =>
450                {
451                    Some(AttributeSelectorValue::Percentage(input.parse()?))
452                }
453                TokenWithSpan { token: Token::Tilde(..), .. } if input.syntax == Syntax::Less => {
454                    Some(AttributeSelectorValue::LessEscapedStr(input.parse()?))
455                }
456                TokenWithSpan { token: Token::RBracket(..), span } => {
457                    input
458                        .recoverable_errors
459                        .push(Error { kind: ErrorKind::ExpectAttributeSelectorValue, span: *span });
460                    None
461                }
462                // An unusual value like `[attr=;]` is invalid per the
463                // Selectors grammar, but postcss accepts it; preserve the raw
464                // tokens up to the closing `]`.
465                TokenWithSpan { span, .. } if input.syntax == Syntax::Css => {
466                    let start = span.start;
467                    let mut tokens = input.vec();
468                    while !matches!(
469                        input.cursor.peek()?.token,
470                        Token::RBracket(..) | Token::Eof(..)
471                    ) {
472                        tokens.push(input.cursor.bump()?);
473                    }
474                    let end = tokens.last().map_or(start, |t| t.span.end);
475                    Some(AttributeSelectorValue::TokenSeq(TokenSeq {
476                        tokens,
477                        span: Span { start, end },
478                    }))
479                }
480                token_with_span => {
481                    return Err(Error {
482                        kind: ErrorKind::ExpectAttributeSelectorValue,
483                        span: token_with_span.span,
484                    });
485                }
486            }
487        } else {
488            None
489        };
490
491        let modifier = if value.is_some() {
492            match &input.cursor.peek()?.token {
493                Token::Ident(..) | Token::HashLBrace(..) => {
494                    let ident = input.parse::<InterpolableIdent>()?;
495                    let span = *ident.span();
496                    Some(AttributeSelectorModifier { ident, span })
497                }
498                _ => None,
499            }
500        } else {
501            None
502        };
503
504        let end = input.cursor.expect_r_bracket()?.1.end;
505        Ok(AttributeSelector { name, matcher, value, modifier, span: Span { start, end } })
506    }
507}
508
509// <class-selector> = '.' <ident-token>
510impl<'a> Parse<'a> for ClassSelector<'a> {
511    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
512        let (_, dot_span) = input.cursor.expect_dot()?;
513        let start = dot_span.start;
514        let end;
515        // Detect an adjacent placeholder without `peek()`: `peek()` skips
516        // whitespace and caches a token, which would both break the no-ws rule
517        // (the name must immediately follow the dot) and trip the empty-cache
518        // assertion in the `expect_ident_without_ws_or_comments` fallback. `scan_placeholder`
519        // returns `None` (leaving the tokenizer untouched) unless a placeholder
520        // begins exactly here, so the fallback paths run with an empty cache.
521        let placeholder = if input.options.template_placeholder.is_some() {
522            input.cursor.tokenizer.scan_placeholder()
523        } else {
524            None
525        };
526        let name = if let Some(token) = placeholder {
527            let placeholder = token.placeholder(input.source).unwrap();
528            let span = token.span;
529            end = span.end;
530            InterpolableIdent::Placeholder((placeholder, span).into())
531        } else if input.syntax == Syntax::Css {
532            let (ident, ident_span) = input.cursor.expect_ident_without_ws_or_comments(false)?;
533            end = ident_span.end;
534            InterpolableIdent::Literal(input.ident(ident, ident_span))
535        } else {
536            let ident = input.parse::<InterpolableIdent>()?;
537            let ident_span = ident.span();
538            util::assert_no_ws_or_comment(&dot_span, ident_span)?;
539            end = ident_span.end;
540            ident
541        };
542
543        Ok(ClassSelector { name, span: Span { start, end } })
544    }
545}
546
547// <complex-selector> = <compound-selector> [ <combinator>? <compound-selector> ]*
548impl<'a> Parse<'a> for ComplexSelector<'a> {
549    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
550        let mut children = input.vec_with_capacity(3);
551
552        let (span, first, mut is_previous_combinator) = if let Token::GreaterThan(..)
553        | Token::Plus(..)
554        | Token::Tilde(..)
555        | Token::BarBar(..) =
556            input.cursor.peek()?.token
557        {
558            let end = input.cursor.tokenizer.current_offset();
559            if let Some(combinator) = input.parse_combinator(end)? {
560                (combinator.span, ComplexSelectorChild::Combinator(combinator), true)
561            } else {
562                return Err(Error {
563                    kind: ErrorKind::ExpectSimpleSelector,
564                    span: input.cursor.bump()?.span,
565                });
566            }
567        } else {
568            let compound_selector = input.parse::<CompoundSelector>()?;
569            (
570                compound_selector.span,
571                ComplexSelectorChild::CompoundSelector(compound_selector),
572                false,
573            )
574        };
575        let Span { start, mut end } = span;
576
577        children.push(first);
578        let is_less = input.syntax == Syntax::Less;
579        while !matches!(
580            input.cursor.peek()?.token,
581            Token::LBrace(..) | Token::Indent(..) | Token::Linebreak(..)
582        ) {
583            if is_previous_combinator {
584                // dart-sass allows consecutive combinators (`> >`, `+ ~`) and a
585                // trailing combinator (`:is(a +)`); after a combinator, take another
586                // combinator or stop at a selector boundary rather than requiring a
587                // compound selector. CSS keeps the strict alternation.
588                if matches!(input.syntax, Syntax::Scss | Syntax::Sass) {
589                    if matches!(
590                        input.cursor.peek()?.token,
591                        Token::GreaterThan(..)
592                            | Token::Plus(..)
593                            | Token::Tilde(..)
594                            | Token::BarBar(..)
595                    ) && let Some(combinator) = input.parse_combinator(end)?
596                    {
597                        end = combinator.span.end;
598                        children.push(ComplexSelectorChild::Combinator(combinator));
599                        continue;
600                    } else if matches!(
601                        input.cursor.peek()?.token,
602                        Token::RParen(..) | Token::Comma(..) | Token::RBrace(..) | Token::Eof(..)
603                    ) {
604                        break;
605                    }
606                }
607                let compound_selector = input.parse::<CompoundSelector>()?;
608                end = compound_selector.span.end;
609                children.push(ComplexSelectorChild::CompoundSelector(compound_selector));
610            } else if let Some(combinator) = input.parse_combinator(end)? {
611                if is_less
612                    && combinator.kind == CombinatorKind::Descendant
613                    && input.cursor.peek()?.is_ident_raw(input.source, "when")
614                {
615                    break;
616                }
617                children.push(ComplexSelectorChild::Combinator(combinator));
618            } else {
619                break;
620            }
621            is_previous_combinator = !is_previous_combinator;
622        }
623
624        Ok(ComplexSelector { children, span: Span { start, end } })
625    }
626}
627
628// <compound-selector> = [ <type-selector>? <subclass-selector>*
629//                         [ <pseudo-element-selector> <pseudo-class-selector>* ]* ]!
630impl<'a> Parse<'a> for CompoundSelector<'a> {
631    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
632        let first = input.parse::<SimpleSelector>()?;
633        let first_span = first.span();
634        let start = first_span.start;
635        let mut end = first_span.end;
636
637        let mut children = input.vec_with_capacity(2);
638        children.push(first);
639        loop {
640            use token::*;
641            match input.cursor.peek()? {
642                TokenWithSpan {
643                    token:
644                        Token::Dot(..)
645                        | Token::Hash(..)
646                        | Token::NumberSign(..)
647                        | Token::LBracket(..)
648                        | Token::Colon(..)
649                        | Token::ColonColon(..)
650                        | Token::Ident(..)
651                        | Token::Asterisk(..)
652                        | Token::HashLBrace(..)
653                        | Token::Bar(..)
654                        | Token::Ampersand(..)
655                        | Token::AtLBraceVar(..),
656                    span,
657                } if !util::has_ws(input.source, end, span.start) => {
658                    let child = input.parse::<SimpleSelector>()?;
659                    end = child.span().end;
660                    children.push(child);
661                }
662                TokenWithSpan { token: Token::Percent(..), span }
663                    if matches!(input.syntax, Syntax::Scss | Syntax::Sass)
664                        && !util::has_ws(input.source, end, span.start) =>
665                {
666                    let child = input.parse::<SimpleSelector>()?;
667                    end = child.span().end;
668                    children.push(child);
669                }
670                TokenWithSpan { token: Token::Placeholder(..), span }
671                    if !util::has_ws(input.source, end, span.start) =>
672                {
673                    let child = input.parse::<SimpleSelector>()?;
674                    end = child.span().end;
675                    children.push(child);
676                }
677                _ => break,
678            }
679        }
680
681        Ok(CompoundSelector { children, span: Span { start, end } })
682    }
683}
684
685// <compound-selector-list> = <compound-selector>#
686impl<'a> Parse<'a> for CompoundSelectorList<'a> {
687    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
688        let first = input.parse::<CompoundSelector>()?;
689        let mut span = first.span;
690
691        let mut selectors = input.vec1(first);
692        let mut comma_spans = input.vec();
693        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
694            comma_spans.push(comma_span);
695            input.eat_sass_line_continuation()?;
696            selectors.push(input.parse()?);
697        }
698
699        // SAFETY: it has at least one element.
700        span.end = unsafe {
701            let index = selectors.len() - 1;
702            selectors.get_unchecked(index).span().end
703        };
704        Ok(CompoundSelectorList { selectors, comma_spans, span })
705    }
706}
707
708// <id-selector> = <hash-token>
709impl<'a> Parse<'a> for IdSelector<'a> {
710    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
711        match input.cursor.bump()? {
712            token @ TokenWithSpan { token: Token::Hash(..), span } => {
713                let token = token.hash(input.source).unwrap();
714                let first_span = Span { start: span.start + 1, end: span.end };
715                let raw = token.raw;
716                if raw.starts_with(|c: char| c.is_ascii_digit())
717                    || matches!(raw.as_bytes(), [b'-'] | [b'-', b'0'..=b'9', ..])
718                {
719                    input
720                        .recoverable_errors
721                        .push(Error { kind: ErrorKind::InvalidIdSelectorName, span });
722                }
723                let value =
724                    if token.escaped { util::handle_escape_in(raw, input.allocator) } else { raw };
725                let first = Ident { name: value, raw: token.raw, span: first_span };
726                let name = match input.cursor.peek()? {
727                    TokenWithSpan { token: Token::HashLBrace(..), span }
728                        if matches!(input.syntax, Syntax::Scss | Syntax::Sass)
729                            && first.span.end == span.start =>
730                    {
731                        match input.parse()? {
732                            InterpolableIdent::SassInterpolated(mut interpolation) => {
733                                interpolation.elements.insert(
734                                    0,
735                                    SassInterpolatedIdentElement::Static(
736                                        InterpolableIdentStaticPart {
737                                            value: first.name,
738                                            raw: first.raw,
739                                            span: first.span,
740                                        },
741                                    ),
742                                );
743                                InterpolableIdent::SassInterpolated(interpolation)
744                            }
745                            _ => unreachable!(),
746                        }
747                    }
748                    _ => InterpolableIdent::Literal(first),
749                };
750                let span = Span { start: span.start, end: name.span().end };
751                Ok(IdSelector { name, span })
752            }
753            TokenWithSpan { token: Token::NumberSign(..), span } => {
754                let name = input.parse::<InterpolableIdent>()?;
755                let span = Span { start: span.start, end: name.span().end };
756                Ok(IdSelector { name, span })
757            }
758            TokenWithSpan { span, .. } => Err(Error { kind: ErrorKind::ExpectIdSelector, span }),
759        }
760    }
761}
762
763// A `:lang()` argument: <lang-range> = <ident-token> | <string-token>  (BCP 47 range)
764impl<'a> Parse<'a> for LanguageRange<'a> {
765    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
766        match &input.cursor.peek()?.token {
767            Token::Str(..) | Token::StrTemplate(..) => input.parse().map(LanguageRange::Str),
768            _ => input.parse().map(LanguageRange::Ident),
769        }
770    }
771}
772
773// :lang( <lang-range># )
774impl<'a> Parse<'a> for LanguageRangeList<'a> {
775    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
776        let first = input.parse::<LanguageRange>()?;
777        let mut span = *first.span();
778
779        let mut ranges = input.vec1(first);
780        let mut comma_spans = input.vec();
781        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
782            comma_spans.push(comma_span);
783            ranges.push(input.parse()?);
784        }
785        debug_assert_eq!(comma_spans.len() + 1, ranges.len());
786
787        if let Some(end) = ranges.last() {
788            span.end = end.span().end;
789        }
790        Ok(LanguageRangeList { ranges, comma_spans, span })
791    }
792}
793
794// https://drafts.csswg.org/css-nesting-1/#nest-selector
795//
796// The nesting selector `&`. This parser also accepts a glued ident/interpolation
797// suffix (`&__x`, `&#{$m}`, `&-@{v}`) as used by Sass/Less.
798impl<'a> Parse<'a> for NestingSelector<'a> {
799    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
800        let (_, mut span) = input.cursor.expect_ampersand()?;
801        let suffix = match input.syntax {
802            Syntax::Css => {
803                if let Some((ident, ident_span)) = input.cursor.tokenizer.scan_ident_template()? {
804                    span.end = ident_span.end;
805                    Some(InterpolableIdent::Literal(input.ident(ident, ident_span)))
806                } else {
807                    None
808                }
809            }
810            Syntax::Scss | Syntax::Sass => {
811                let start = span.end;
812                let elements = input.parse_sass_interpolated_ident_rest(&mut span.end)?;
813                if elements.is_empty() {
814                    None
815                } else {
816                    Some(InterpolableIdent::SassInterpolated(SassInterpolatedIdent {
817                        elements,
818                        span: Span { start, end: span.end },
819                    }))
820                }
821            }
822            Syntax::Less => {
823                let start = span.end;
824                let elements = input.parse_less_interpolated_ident_rest(&mut span.end)?;
825                if elements.is_empty() {
826                    None
827                } else {
828                    Some(InterpolableIdent::LessInterpolated(LessInterpolatedIdent {
829                        elements,
830                        span: Span { start, end: span.end },
831                    }))
832                }
833            }
834        };
835        Ok(NestingSelector { suffix, span })
836    }
837}
838
839// https://drafts.csswg.org/selectors-4/#the-nth-child-pseudo
840//
841// The `:nth-child()` argument: <nth> [ of <complex-selector-list> ]?
842impl<'a> Parse<'a> for Nth<'a> {
843    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
844        let index = input.parse::<NthIndex>()?;
845        let mut span = *index.span();
846        let matcher = if input.cursor.peek()?.is_ident_name_eq_ignore_ascii_case(input.source, "of")
847        {
848            let matcher = input.parse::<NthMatcher>()?;
849            span.end = matcher.span.end;
850            Some(matcher)
851        } else {
852            None
853        };
854
855        Ok(Nth { index, matcher, span })
856    }
857}
858
859// <nth> = <an+b> | even | odd   (plus a plain <integer>)
860impl<'a> Parse<'a> for NthIndex<'a> {
861    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
862        let peek = input.cursor.peek()?;
863        if peek.ident(input.source).is_some() {
864            if peek.is_ident_name_eq_ignore_ascii_case(input.source, "odd") {
865                input.parse().map(NthIndex::Odd)
866            } else if peek.is_ident_name_eq_ignore_ascii_case(input.source, "even") {
867                input.parse().map(NthIndex::Even)
868            } else {
869                input.parse().map(NthIndex::AnPlusB)
870            }
871        } else if matches!(peek.token, Token::Number(..)) {
872            let number = input.parse::<Number>()?;
873            if number.value.fract() == 0.0 {
874                Ok(NthIndex::Integer(number))
875            } else {
876                Err(Error { kind: ErrorKind::ExpectInteger, span: number.span })
877            }
878        } else {
879            input.parse().map(NthIndex::AnPlusB)
880        }
881    }
882}
883
884// of <complex-selector-list>
885impl<'a> Parse<'a> for NthMatcher<'a> {
886    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
887        let (ident, mut span) = input.cursor.expect_ident()?;
888        if !ident.name().eq_ignore_ascii_case("of") {
889            return Err(Error { kind: ErrorKind::ExpectNthOf, span });
890        }
891
892        let selector = if matches!(&input.cursor.peek()?.token, Token::RParen(..)) {
893            None
894        } else {
895            let selector = input.parse::<SelectorList>()?;
896            span.end = selector.span.end;
897            Some(selector)
898        };
899
900        Ok(NthMatcher { selector, span })
901    }
902}
903
904// https://www.w3.org/TR/selectors-4/#pseudo-classes
905//
906// <pseudo-class-selector> = ':' <ident-token>
907//                         | ':' <function-token> <any-value> ')'
908impl<'a> Parse<'a> for PseudoClassSelector<'a> {
909    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
910        let (_, colon_span) = input.cursor.expect_colon()?;
911        let name = input.parse::<InterpolableIdent>()?;
912        let name_span = name.span();
913        util::assert_no_ws(input.source, &colon_span, name_span)?;
914
915        let mut end = name_span.end;
916
917        let arg = match input.cursor.peek()? {
918            TokenWithSpan { token: Token::LParen(..), span: l_paren } if l_paren.start == end => {
919                let l_paren = *l_paren;
920                input.cursor.bump()?;
921                let kind = match &name {
922                    InterpolableIdent::Literal(Ident { name, .. })
923                        if name.eq_ignore_ascii_case("nth-child")
924                            || name.eq_ignore_ascii_case("nth-last-child") =>
925                    {
926                        if input.syntax == Syntax::Css {
927                            input.parse().map(PseudoClassSelectorArgKind::Nth)?
928                        } else if let Ok(nth) = input.try_parse(Nth::parse) {
929                            PseudoClassSelectorArgKind::Nth(nth)
930                        } else {
931                            input
932                                .parse_tokens_in_parens()
933                                .map(PseudoClassSelectorArgKind::TokenSeq)?
934                        }
935                    }
936                    InterpolableIdent::Literal(Ident { name, .. })
937                        if name.eq_ignore_ascii_case("nth-of-type")
938                            || name.eq_ignore_ascii_case("nth-last-of-type")
939                            || name.eq_ignore_ascii_case("nth-col")
940                            || name.eq_ignore_ascii_case("nth-last-col") =>
941                    'pseudo_arg: {
942                        let nth = if input.syntax == Syntax::Css {
943                            input.parse()?
944                        } else if let Ok(nth) = input.try_parse(Nth::parse) {
945                            nth
946                        } else {
947                            break 'pseudo_arg input
948                                .parse_tokens_in_parens()
949                                .map(PseudoClassSelectorArgKind::TokenSeq)?;
950                        };
951                        if let Some(NthMatcher { span, .. }) = &nth.matcher {
952                            input
953                                .recoverable_errors
954                                .push(Error { kind: ErrorKind::UnexpectedNthMatcher, span: *span });
955                        }
956                        PseudoClassSelectorArgKind::Nth(nth)
957                    }
958                    InterpolableIdent::Literal(Ident { name, .. })
959                        if name.eq_ignore_ascii_case("not")
960                            || name.eq_ignore_ascii_case("is")
961                            || name.eq_ignore_ascii_case("where")
962                            || name.eq_ignore_ascii_case("matches")
963                            || name.eq_ignore_ascii_case("global") =>
964                    {
965                        input.parse().map(PseudoClassSelectorArgKind::SelectorList)?
966                    }
967                    InterpolableIdent::Literal(Ident { name, .. })
968                        if name.eq_ignore_ascii_case("has") =>
969                    {
970                        input.parse().map(PseudoClassSelectorArgKind::RelativeSelectorList)?
971                    }
972                    InterpolableIdent::Literal(Ident { name, .. })
973                        if name.eq_ignore_ascii_case("dir") =>
974                    {
975                        input.parse().map(PseudoClassSelectorArgKind::Ident)?
976                    }
977                    InterpolableIdent::Literal(Ident { name, .. })
978                        if name.eq_ignore_ascii_case("lang") =>
979                    {
980                        input.parse().map(PseudoClassSelectorArgKind::LanguageRangeList)?
981                    }
982                    InterpolableIdent::Literal(Ident { name, .. })
983                        if name.eq_ignore_ascii_case("-moz-any")
984                            || name.eq_ignore_ascii_case("-webkit-any")
985                            || name.eq_ignore_ascii_case("any") =>
986                    {
987                        // formally compound selectors, but real-world usage
988                        // includes complex ones (`:-moz-any(ol p.blah, ul)`)
989                        input.parse().map(PseudoClassSelectorArgKind::SelectorList)?
990                    }
991                    InterpolableIdent::Literal(Ident { name, .. })
992                        if name.eq_ignore_ascii_case("current")
993                            || name.eq_ignore_ascii_case("past")
994                            || name.eq_ignore_ascii_case("future") =>
995                    {
996                        input.parse().map(PseudoClassSelectorArgKind::CompoundSelectorList)?
997                    }
998                    InterpolableIdent::Literal(Ident { name, .. })
999                        if name.eq_ignore_ascii_case("host")
1000                            || name.eq_ignore_ascii_case("host-context") =>
1001                    {
1002                        // formally a single compound selector, but Angular's ShadowCss
1003                        // supports combinators and lists (`:host-context(.parent .child)`)
1004                        input.parse().map(PseudoClassSelectorArgKind::SelectorList)?
1005                    }
1006                    InterpolableIdent::Literal(Ident { name, .. })
1007                        if input.syntax == Syntax::Less && *name == "extend" =>
1008                    {
1009                        input.parse().map(PseudoClassSelectorArgKind::LessExtendList)?
1010                    }
1011                    _ => {
1012                        input.parse_tokens_in_parens().map(PseudoClassSelectorArgKind::TokenSeq)?
1013                    }
1014                };
1015
1016                let r_paren = input.cursor.expect_r_paren()?.1;
1017                end = r_paren.end;
1018                let span = Span { start: l_paren.start, end: r_paren.end };
1019                Some(PseudoClassSelectorArg { kind, l_paren, r_paren, span })
1020            }
1021            _ => None,
1022        };
1023
1024        let span = Span { start: colon_span.start, end };
1025        Ok(PseudoClassSelector { name, arg, span })
1026    }
1027}
1028
1029// https://www.w3.org/TR/selectors-4/#pseudo-elements
1030//
1031// <pseudo-element-selector> = '::' <ident-token>
1032//                           | '::' <function-token> <any-value> ')'
1033impl<'a> Parse<'a> for PseudoElementSelector<'a> {
1034    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1035        let (_, colon_colon_span) = input.cursor.expect_colon_colon()?;
1036        let mut end;
1037        let name = if input.syntax == Syntax::Css {
1038            let (ident, ident_span) = input.cursor.expect_ident()?;
1039            end = ident_span.end;
1040            util::assert_no_ws(input.source, &colon_colon_span, &ident_span)?;
1041            InterpolableIdent::Literal(input.ident(ident, ident_span))
1042        } else {
1043            let name = input.parse::<InterpolableIdent>()?;
1044            let name_span = name.span();
1045            end = name_span.end;
1046            util::assert_no_ws(input.source, &colon_colon_span, name_span)?;
1047            name
1048        };
1049
1050        let arg = match input.cursor.peek()? {
1051            TokenWithSpan { token: Token::LParen(..), span: l_paren } if l_paren.start == end => {
1052                let l_paren = *l_paren;
1053                input.cursor.bump()?;
1054                let kind = match &name {
1055                    InterpolableIdent::Literal(Ident { name, .. })
1056                        if name.eq_ignore_ascii_case("part") =>
1057                    {
1058                        // ::part( <ident>+ ) — CSS Shadow Parts allows
1059                        // selecting multiple part names at once.
1060                        let first = input.parse::<InterpolableIdent>()?;
1061                        if matches!(
1062                            input.cursor.peek()?.token,
1063                            Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..)
1064                        ) {
1065                            let mut span = *first.span();
1066                            let mut idents = input.vec_with_capacity(2);
1067                            idents.push(first);
1068                            while matches!(
1069                                input.cursor.peek()?.token,
1070                                Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..)
1071                            ) {
1072                                let ident = input.parse::<InterpolableIdent>()?;
1073                                span.end = ident.span().end;
1074                                idents.push(ident);
1075                            }
1076                            PseudoElementSelectorArgKind::IdentList(IdentList { idents, span })
1077                        } else {
1078                            PseudoElementSelectorArgKind::Ident(first)
1079                        }
1080                    }
1081                    InterpolableIdent::Literal(Ident { name, .. })
1082                        if name.eq_ignore_ascii_case("cue")
1083                            || name.eq_ignore_ascii_case("cue-region") =>
1084                    {
1085                        input.parse().map(PseudoElementSelectorArgKind::CompoundSelector)?
1086                    }
1087                    InterpolableIdent::Literal(Ident { name, .. })
1088                        if name.eq_ignore_ascii_case("slotted") =>
1089                    {
1090                        // formally a single compound selector, but sass extend
1091                        // output produces lists (`::slotted(.c.d, .d.e)`)
1092                        input.parse().map(PseudoElementSelectorArgKind::CompoundSelectorList)?
1093                    }
1094                    _ => input
1095                        .parse_tokens_in_parens()
1096                        .map(PseudoElementSelectorArgKind::TokenSeq)?,
1097                };
1098
1099                let r_paren = input.cursor.expect_r_paren()?.1;
1100                end = r_paren.end;
1101                let span = Span { start: l_paren.start, end: r_paren.end };
1102                Some(PseudoElementSelectorArg { kind, l_paren, r_paren, span })
1103            }
1104            _ => None,
1105        };
1106
1107        let span = Span { start: colon_colon_span.start, end };
1108        Ok(PseudoElementSelector { name, arg, span })
1109    }
1110}
1111
1112// <relative-selector> = <combinator>? <complex-selector>
1113impl<'a> Parse<'a> for RelativeSelector<'a> {
1114    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1115        let pos = input.cursor.tokenizer.current_offset();
1116        let combinator = match input.parse_combinator(pos)? {
1117            Some(Combinator { kind: CombinatorKind::Descendant, .. }) => None,
1118            combinator => combinator,
1119        };
1120        let complex_selector = input.parse::<ComplexSelector>()?;
1121        let mut span = complex_selector.span;
1122        if let Some(combinator) = &combinator {
1123            span.start = combinator.span.start;
1124        }
1125        Ok(RelativeSelector { combinator, complex_selector, span })
1126    }
1127}
1128
1129// <relative-selector-list> = <relative-selector>#
1130impl<'a> Parse<'a> for RelativeSelectorList<'a> {
1131    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1132        let first = input.parse::<RelativeSelector>()?;
1133        let mut span = first.span;
1134
1135        let mut selectors = input.vec1(first);
1136        let mut comma_spans = input.vec();
1137        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1138            comma_spans.push(comma_span);
1139            selectors.push(input.parse()?);
1140        }
1141
1142        // SAFETY: it has at least one element.
1143        span.end = unsafe {
1144            let index = selectors.len() - 1;
1145            selectors.get_unchecked(index).span().end
1146        };
1147        Ok(RelativeSelectorList { selectors, comma_spans, span })
1148    }
1149}
1150
1151// https://www.w3.org/TR/selectors-4/#typedef-selector-list
1152//
1153// <selector-list> = <complex-selector-list> = <complex-selector>#
1154impl<'a> Parse<'a> for SelectorList<'a> {
1155    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1156        let first = input.parse::<ComplexSelector>()?;
1157        let mut span = first.span;
1158
1159        let mut selectors = input.vec_with_capacity(2);
1160        selectors.push(first);
1161        let mut comma_spans = input.vec();
1162
1163        let is_css = input.syntax == Syntax::Css;
1164        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1165            span.end = comma_span.end;
1166            comma_spans.push(comma_span);
1167            // legacy corpora carry doubled/trailing commas (`div,, span,, {`);
1168            // absorb the extras in SCSS like libsass did
1169            if input.syntax == Syntax::Scss {
1170                while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1171                    span.end = comma_span.end;
1172                    comma_spans.push(comma_span);
1173                }
1174            }
1175            // In the indented syntax a deeper line after the comma continues
1176            // the selector list (`a,\n    b\n  c: d`); a same-level line or
1177            // `{` means the comma was trailing.
1178            if input.syntax == Syntax::Sass
1179                && matches!(input.cursor.peek()?.token, Token::Indent(..))
1180            {
1181                input.eat_sass_line_continuation()?;
1182            } else if !is_css
1183                && matches!(
1184                    input.cursor.peek()?.token,
1185                    Token::LBrace(..) | Token::Indent(..) | Token::Linebreak(..)
1186                )
1187            {
1188                break;
1189            }
1190
1191            let selector = input.parse::<ComplexSelector>()?;
1192            span.end = selector.span.end;
1193            selectors.push(selector);
1194        }
1195
1196        // absorbed doubled/trailing commas can outnumber the selectors, so
1197        // phrase the invariants without subtraction (usize underflow)
1198        debug_assert!(if is_css {
1199            selectors.len() == comma_spans.len() + 1
1200        } else {
1201            selectors.len() <= comma_spans.len() + 1
1202        });
1203
1204        Ok(SelectorList { selectors, comma_spans, span })
1205    }
1206}
1207
1208// https://www.w3.org/TR/selectors-4/#ref-for-typedef-simple-selector
1209//
1210// <simple-selector>   = <type-selector> | <subclass-selector>
1211// <subclass-selector> = <id-selector> | <class-selector>
1212//                     | <attribute-selector> | <pseudo-class-selector>
1213impl<'a> Parse<'a> for SimpleSelector<'a> {
1214    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1215        match input.cursor.peek()? {
1216            TokenWithSpan { token: Token::Dot(..), .. } => input.parse().map(SimpleSelector::Class),
1217            TokenWithSpan { token: Token::Hash(..) | Token::NumberSign(..), .. } => {
1218                input.parse().map(SimpleSelector::Id)
1219            }
1220            TokenWithSpan { token: Token::LBracket(..), .. } => {
1221                let selector = input.parse()?;
1222                Ok(SimpleSelector::Attribute(input.alloc(selector)))
1223            }
1224            TokenWithSpan { token: Token::Colon(..), .. } => {
1225                let selector = input.parse()?;
1226                Ok(SimpleSelector::PseudoClass(input.alloc(selector)))
1227            }
1228            TokenWithSpan { token: Token::ColonColon(..), .. } => {
1229                let selector = input.parse()?;
1230                Ok(SimpleSelector::PseudoElement(input.alloc(selector)))
1231            }
1232            TokenWithSpan {
1233                token:
1234                    Token::Ident(..)
1235                    | Token::Asterisk(..)
1236                    | Token::HashLBrace(..)
1237                    | Token::Bar(..)
1238                    | Token::AtLBraceVar(..),
1239                ..
1240            } => input.parse().map(SimpleSelector::Type),
1241            TokenWithSpan { token: Token::Ampersand(..), .. } => {
1242                input.parse().map(SimpleSelector::Nesting)
1243            }
1244            // Css too: postcss-extend-rule uses Sass-style placeholders in
1245            // plain CSS (`%thick-border {}` + `@extend %thick-border;`), and
1246            // postcss parses `%x` as an ordinary selector. A selector-position
1247            // `%` is invalid per spec, so accepting it is purely additive.
1248            TokenWithSpan { token: Token::Percent(..), .. }
1249                if matches!(input.syntax, Syntax::Scss | Syntax::Sass | Syntax::Css) =>
1250            {
1251                input.parse().map(SimpleSelector::SassPlaceholder)
1252            }
1253            TokenWithSpan { token: Token::Placeholder(..), .. } => {
1254                let name = input.parse::<InterpolableIdent>()?;
1255                let span = *name.span();
1256                Ok(SimpleSelector::Type(TypeSelector::TagName(TagNameSelector {
1257                    name: WqName { name, prefix: None, span },
1258                    span,
1259                })))
1260            }
1261            token_with_span => {
1262                Err(Error { kind: ErrorKind::ExpectSimpleSelector, span: token_with_span.span })
1263            }
1264        }
1265    }
1266}
1267
1268// <type-selector> = <wq-name> | <ns-prefix>? '*'
1269// <wq-name>       = <ns-prefix>? <ident-token>
1270// <ns-prefix>     = [ <ident-token> | '*' ]? '|'
1271impl<'a> Parse<'a> for TypeSelector<'a> {
1272    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1273        enum IdentOrAsterisk<'a> {
1274            Ident(InterpolableIdent<'a>),
1275            Asterisk(Span),
1276        }
1277
1278        let ident_or_asterisk = match &input.cursor.peek()?.token {
1279            Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..) => {
1280                input.parse().map(IdentOrAsterisk::Ident).map(Some)?
1281            }
1282            Token::Asterisk(..) => Some(IdentOrAsterisk::Asterisk(input.cursor.bump()?.span)),
1283            Token::Bar(..) => None,
1284            _ => unreachable!(),
1285        };
1286
1287        match input.cursor.peek()? {
1288            TokenWithSpan { token: Token::Bar(..), span }
1289                if ident_or_asterisk
1290                    .as_ref()
1291                    .map(|t| match t {
1292                        IdentOrAsterisk::Ident(ident) => {
1293                            !util::has_ws(input.source, ident.span().end, span.start)
1294                        }
1295                        IdentOrAsterisk::Asterisk(asterisk_span) => {
1296                            !util::has_ws(input.source, asterisk_span.end, span.start)
1297                        }
1298                    })
1299                    .unwrap_or(true) =>
1300            {
1301                let bar_token_span = input.cursor.bump()?.span;
1302
1303                let prefix = match ident_or_asterisk {
1304                    Some(IdentOrAsterisk::Ident(ident)) => {
1305                        let mut span = *ident.span();
1306                        span.end = bar_token_span.end;
1307                        NsPrefix { kind: Some(NsPrefixKind::Ident(ident)), span }
1308                    }
1309                    Some(IdentOrAsterisk::Asterisk(asterisk_span)) => {
1310                        let mut span = asterisk_span;
1311                        span.end = bar_token_span.end;
1312                        NsPrefix {
1313                            kind: Some(NsPrefixKind::Universal(NsPrefixUniversal {
1314                                span: asterisk_span,
1315                            })),
1316                            span,
1317                        }
1318                    }
1319                    None => NsPrefix { kind: None, span: bar_token_span },
1320                };
1321
1322                match input.cursor.peek()? {
1323                    TokenWithSpan { token: Token::Ident(..) | Token::HashLBrace(..), .. } => {
1324                        let name = input.parse::<InterpolableIdent>()?;
1325                        let name_span = name.span();
1326                        util::assert_no_ws(input.source, &prefix.span, name_span)?;
1327                        let span = Span { start: prefix.span.start, end: name_span.end };
1328                        Ok(TypeSelector::TagName(TagNameSelector {
1329                            name: WqName { name, prefix: Some(prefix), span },
1330                            span,
1331                        }))
1332                    }
1333                    TokenWithSpan { token: Token::Asterisk(..), .. } => {
1334                        let asterisk_span = input.cursor.bump()?.span;
1335                        util::assert_no_ws(input.source, &prefix.span, &asterisk_span)?;
1336                        let span = Span { start: prefix.span.start, end: asterisk_span.end };
1337                        Ok(TypeSelector::Universal(UniversalSelector {
1338                            prefix: Some(prefix),
1339                            span,
1340                        }))
1341                    }
1342                    TokenWithSpan { span, .. } => {
1343                        Err(Error { kind: ErrorKind::ExpectTypeSelector, span: *span })
1344                    }
1345                }
1346            }
1347
1348            _ => match ident_or_asterisk {
1349                Some(IdentOrAsterisk::Ident(ident)) => {
1350                    let span = *ident.span();
1351                    Ok(TypeSelector::TagName(TagNameSelector {
1352                        name: WqName { name: ident, prefix: None, span },
1353                        span,
1354                    }))
1355                }
1356                Some(IdentOrAsterisk::Asterisk(span)) => {
1357                    Ok(TypeSelector::Universal(UniversalSelector { prefix: None, span }))
1358                }
1359                None => unreachable!(),
1360            },
1361        }
1362    }
1363}
1364
1365impl<'a> Parser<'a> {
1366    // <combinator> = '>' | '+' | '~' | [ '|' '|' ]
1367    // An absent combinator between two compounds is the descendant combinator
1368    // (whitespace), which this returns as `CombinatorKind::Descendant`.
1369    fn parse_combinator(&mut self, pos: usize) -> PResult<Option<Combinator>> {
1370        match self.cursor.peek()? {
1371            TokenWithSpan {
1372                token:
1373                    Token::Ident(..)
1374                    | Token::Dot(..)
1375                    | Token::Hash(..)
1376                    | Token::Colon(..)
1377                    | Token::ColonColon(..)
1378                    | Token::LBracket(..)
1379                    | Token::Asterisk(..)
1380                    | Token::Ampersand(..)
1381                    | Token::Bar(..) // selector like `|type` (with <ns-prefix>)
1382                    | Token::AtLBraceVar(..)
1383                    | Token::NumberSign(..)
1384                    | Token::HashLBrace(..)
1385                    | Token::Percent(..) // Sass `%placeholder` descendant
1386                    | Token::Placeholder(..), // `${a} ${b}` descendant
1387                span,
1388            } if pos < span.start => Ok(Some(Combinator {
1389                kind: CombinatorKind::Descendant,
1390                span: Span {
1391                    start: pos,
1392                    end: span.start,
1393                },
1394            })),
1395            TokenWithSpan {
1396                token: Token::GreaterThan(..),
1397                ..
1398            } => Ok(Some(Combinator {
1399                kind: CombinatorKind::Child,
1400                span: self.cursor.bump()?.span,
1401            })),
1402            TokenWithSpan {
1403                token: Token::Plus(..),
1404                ..
1405            } => Ok(Some(Combinator {
1406                kind: CombinatorKind::NextSibling,
1407                span: self.cursor.bump()?.span,
1408            })),
1409            TokenWithSpan {
1410                token: Token::Tilde(..),
1411                ..
1412            } => Ok(Some(Combinator {
1413                kind: CombinatorKind::LaterSibling,
1414                span: self.cursor.bump()?.span,
1415            })),
1416            TokenWithSpan {
1417                token: Token::BarBar(..),
1418                ..
1419            } => Ok(Some(Combinator {
1420                kind: CombinatorKind::Column,
1421                span: self.cursor.bump()?.span,
1422            })),
1423            // deprecated shadow-piercing `/deep/` and less.js's arbitrary
1424            // slashed combinators (`.container /shadow/ .content`) — but not
1425            // in Scss/Sass, where dart-sass rejects reference combinators
1426            TokenWithSpan { token: Token::Solidus(..), .. }
1427                if !matches!(self.syntax, Syntax::Scss | Syntax::Sass) =>
1428            {
1429                let deep = self.try_parse(|p| {
1430                    let start = p.cursor.bump()?.span; // `/`
1431                    let ident_end = match p.cursor.peek()? {
1432                        TokenWithSpan { token: Token::Ident(..), span }
1433                            if span.start == start.end =>
1434                        {
1435                            p.cursor.bump()?.span.end
1436                        }
1437                        TokenWithSpan { span, .. } => {
1438                            return Err(Error {
1439                                kind: ErrorKind::TryParseError,
1440                                span: *span,
1441                            });
1442                        }
1443                    };
1444                    match p.cursor.peek()? {
1445                        TokenWithSpan { token: Token::Solidus(..), span }
1446                            if span.start == ident_end =>
1447                        {
1448                            let end = p.cursor.bump()?.span.end;
1449                            Ok(Span { start: start.start, end })
1450                        }
1451                        TokenWithSpan { span, .. } => Err(Error {
1452                            kind: ErrorKind::TryParseError,
1453                            span: *span,
1454                        }),
1455                    }
1456                });
1457                match deep {
1458                    Ok(span) => Ok(Some(Combinator { kind: CombinatorKind::Deep, span })),
1459                    Err(_) => Ok(None),
1460                }
1461            }
1462            // deprecated shadow combinators `^` and `^^` (Less corpora and
1463            // the CSS files Less emits)
1464            TokenWithSpan {
1465                token: Token::Unknown(..),
1466                span,
1467            } if !matches!(self.syntax, Syntax::Scss | Syntax::Sass)
1468                && self.source.as_bytes().get(span.start) == Some(&b'^') =>
1469            {
1470                let start = self.cursor.bump()?.span.start;
1471                if matches!(&self.cursor.peek()?.token, Token::Unknown(..))
1472                    && self.cursor.peek()?.span.start == start + 1
1473                    && self.source.as_bytes().get(start + 1) == Some(&b'^')
1474                {
1475                    let end = self.cursor.bump()?.span.end;
1476                    Ok(Some(Combinator {
1477                        kind: CombinatorKind::ShadowDescendant,
1478                        span: Span { start, end },
1479                    }))
1480                } else {
1481                    Ok(Some(Combinator {
1482                        kind: CombinatorKind::ShadowChild,
1483                        span: Span { start, end: start + 1 },
1484                    }))
1485                }
1486            }
1487            _ => Ok(None),
1488        }
1489    }
1490}
1491
1492fn expect_unsigned_int<'a>(input: &mut Parser<'a>) -> PResult<(token::Number<'a>, Span)> {
1493    let (number, span) = input.cursor.expect_number()?;
1494    if number.raw.chars().any(|c| !c.is_ascii_digit()) {
1495        Err(Error { kind: ErrorKind::ExpectUnsignedInteger, span })
1496    } else {
1497        Ok((number, span))
1498    }
1499}