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.clone() })
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.clone();
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.clone() });
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 {
423                    kind: ErrorKind::ExpectAttributeSelectorMatcher,
424                    span: span.clone(),
425                });
426            }
427        };
428
429        let value = if matcher.is_some() {
430            match input.cursor.peek()? {
431                TokenWithSpan {
432                    token:
433                        Token::Ident(..)
434                        | Token::HashLBrace(..)
435                        | Token::AtLBraceVar(..)
436                        | Token::Placeholder(..),
437                    ..
438                } => Some(AttributeSelectorValue::Ident(input.parse()?)),
439                TokenWithSpan { token: Token::Str(..) | Token::StrTemplate(..), .. } => {
440                    Some(AttributeSelectorValue::Str(input.parse()?))
441                }
442                // Unquoted numeric values such as `[size=1]` or `[size=1px]` are
443                // technically non-conforming (Selectors wants an ident or string), but
444                // browsers accept them and they appear in real CSS (incl. UA stylesheets).
445                TokenWithSpan { token: Token::Number(..), .. } => {
446                    Some(AttributeSelectorValue::Number(input.parse()?))
447                }
448                TokenWithSpan { token: Token::Dimension(..), .. } => {
449                    Some(AttributeSelectorValue::Dimension(input.parse()?))
450                }
451                TokenWithSpan { token: Token::Percentage(..), .. }
452                    if input.syntax == Syntax::Less =>
453                {
454                    Some(AttributeSelectorValue::Percentage(input.parse()?))
455                }
456                TokenWithSpan { token: Token::Tilde(..), .. } if input.syntax == Syntax::Less => {
457                    Some(AttributeSelectorValue::LessEscapedStr(input.parse()?))
458                }
459                TokenWithSpan { token: Token::RBracket(..), span } => {
460                    input.recoverable_errors.push(Error {
461                        kind: ErrorKind::ExpectAttributeSelectorValue,
462                        span: span.clone(),
463                    });
464                    None
465                }
466                // An unusual value like `[attr=;]` is invalid per the
467                // Selectors grammar, but postcss accepts it; preserve the raw
468                // tokens up to the closing `]`.
469                TokenWithSpan { span, .. } if input.syntax == Syntax::Css => {
470                    let start = span.start;
471                    let mut tokens = input.vec();
472                    while !matches!(
473                        input.cursor.peek()?.token,
474                        Token::RBracket(..) | Token::Eof(..)
475                    ) {
476                        tokens.push(input.cursor.bump()?);
477                    }
478                    let end = tokens.last().map_or(start, |t| t.span.end);
479                    Some(AttributeSelectorValue::TokenSeq(TokenSeq {
480                        tokens,
481                        span: Span { start, end },
482                    }))
483                }
484                token_with_span => {
485                    return Err(Error {
486                        kind: ErrorKind::ExpectAttributeSelectorValue,
487                        span: token_with_span.span.clone(),
488                    });
489                }
490            }
491        } else {
492            None
493        };
494
495        let modifier = if value.is_some() {
496            match &input.cursor.peek()?.token {
497                Token::Ident(..) | Token::HashLBrace(..) => {
498                    let ident = input.parse::<InterpolableIdent>()?;
499                    let span = ident.span().clone();
500                    Some(AttributeSelectorModifier { ident, span })
501                }
502                _ => None,
503            }
504        } else {
505            None
506        };
507
508        let end = input.cursor.expect_r_bracket()?.1.end;
509        Ok(AttributeSelector { name, matcher, value, modifier, span: Span { start, end } })
510    }
511}
512
513// <class-selector> = '.' <ident-token>
514impl<'a> Parse<'a> for ClassSelector<'a> {
515    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
516        let (_, dot_span) = input.cursor.expect_dot()?;
517        let start = dot_span.start;
518        let end;
519        // Detect an adjacent placeholder without `peek()`: `peek()` skips
520        // whitespace and caches a token, which would both break the no-ws rule
521        // (the name must immediately follow the dot) and trip the empty-cache
522        // assertion in the `expect_ident_without_ws_or_comments` fallback. `scan_placeholder`
523        // returns `None` (leaving the tokenizer untouched) unless a placeholder
524        // begins exactly here, so the fallback paths run with an empty cache.
525        let placeholder = if input.options.template_placeholder.is_some() {
526            input.cursor.tokenizer.scan_placeholder()
527        } else {
528            None
529        };
530        let name = if let Some(TokenWithSpan { token: Token::Placeholder(placeholder), span }) =
531            placeholder
532        {
533            end = span.end;
534            InterpolableIdent::Placeholder((placeholder, span).into())
535        } else if input.syntax == Syntax::Css {
536            let (ident, ident_span) = input.cursor.expect_ident_without_ws_or_comments(false)?;
537            end = ident_span.end;
538            InterpolableIdent::Literal(input.ident(ident, ident_span))
539        } else {
540            let ident = input.parse::<InterpolableIdent>()?;
541            let ident_span = ident.span();
542            util::assert_no_ws_or_comment(&dot_span, ident_span)?;
543            end = ident_span.end;
544            ident
545        };
546
547        Ok(ClassSelector { name, span: Span { start, end } })
548    }
549}
550
551// <complex-selector> = <compound-selector> [ <combinator>? <compound-selector> ]*
552impl<'a> Parse<'a> for ComplexSelector<'a> {
553    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
554        let mut children = input.vec_with_capacity(3);
555
556        let (span, first, mut is_previous_combinator) = if let Token::GreaterThan(..)
557        | Token::Plus(..)
558        | Token::Tilde(..)
559        | Token::BarBar(..) =
560            input.cursor.peek()?.token
561        {
562            let end = input.cursor.tokenizer.current_offset();
563            if let Some(combinator) = input.parse_combinator(end)? {
564                (combinator.span.clone(), ComplexSelectorChild::Combinator(combinator), true)
565            } else {
566                return Err(Error {
567                    kind: ErrorKind::ExpectSimpleSelector,
568                    span: input.cursor.bump()?.span,
569                });
570            }
571        } else {
572            let compound_selector = input.parse::<CompoundSelector>()?;
573            (
574                compound_selector.span.clone(),
575                ComplexSelectorChild::CompoundSelector(compound_selector),
576                false,
577            )
578        };
579        let Span { start, mut end } = span;
580
581        children.push(first);
582        let is_less = input.syntax == Syntax::Less;
583        while !matches!(
584            input.cursor.peek()?.token,
585            Token::LBrace(..) | Token::Indent(..) | Token::Linebreak(..)
586        ) {
587            if is_previous_combinator {
588                // dart-sass allows consecutive combinators (`> >`, `+ ~`) and a
589                // trailing combinator (`:is(a +)`); after a combinator, take another
590                // combinator or stop at a selector boundary rather than requiring a
591                // compound selector. CSS keeps the strict alternation.
592                if matches!(input.syntax, Syntax::Scss | Syntax::Sass) {
593                    if matches!(
594                        input.cursor.peek()?.token,
595                        Token::GreaterThan(..)
596                            | Token::Plus(..)
597                            | Token::Tilde(..)
598                            | Token::BarBar(..)
599                    ) && let Some(combinator) = input.parse_combinator(end)?
600                    {
601                        end = combinator.span.end;
602                        children.push(ComplexSelectorChild::Combinator(combinator));
603                        continue;
604                    } else if matches!(
605                        input.cursor.peek()?.token,
606                        Token::RParen(..) | Token::Comma(..) | Token::RBrace(..) | Token::Eof(..)
607                    ) {
608                        break;
609                    }
610                }
611                let compound_selector = input.parse::<CompoundSelector>()?;
612                end = compound_selector.span.end;
613                children.push(ComplexSelectorChild::CompoundSelector(compound_selector));
614            } else if let Some(combinator) = input.parse_combinator(end)? {
615                if is_less && combinator.kind == CombinatorKind::Descendant {
616                    match &input.cursor.peek()?.token {
617                        Token::Ident(ident) if ident.raw == "when" => break,
618                        _ => {}
619                    }
620                }
621                children.push(ComplexSelectorChild::Combinator(combinator));
622            } else {
623                break;
624            }
625            is_previous_combinator = !is_previous_combinator;
626        }
627
628        Ok(ComplexSelector { children, span: Span { start, end } })
629    }
630}
631
632// <compound-selector> = [ <type-selector>? <subclass-selector>*
633//                         [ <pseudo-element-selector> <pseudo-class-selector>* ]* ]!
634impl<'a> Parse<'a> for CompoundSelector<'a> {
635    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
636        let first = input.parse::<SimpleSelector>()?;
637        let first_span = first.span();
638        let start = first_span.start;
639        let mut end = first_span.end;
640
641        let mut children = input.vec_with_capacity(2);
642        children.push(first);
643        loop {
644            use token::*;
645            match input.cursor.peek()? {
646                TokenWithSpan {
647                    token:
648                        Token::Dot(..)
649                        | Token::Hash(..)
650                        | Token::NumberSign(..)
651                        | Token::LBracket(..)
652                        | Token::Colon(..)
653                        | Token::ColonColon(..)
654                        | Token::Ident(..)
655                        | Token::Asterisk(..)
656                        | Token::HashLBrace(..)
657                        | Token::Bar(..)
658                        | Token::Ampersand(..)
659                        | Token::AtLBraceVar(..),
660                    span,
661                } if !util::has_ws(input.source, end, span.start) => {
662                    let child = input.parse::<SimpleSelector>()?;
663                    end = child.span().end;
664                    children.push(child);
665                }
666                TokenWithSpan { token: Token::Percent(..), span }
667                    if matches!(input.syntax, Syntax::Scss | Syntax::Sass)
668                        && !util::has_ws(input.source, end, span.start) =>
669                {
670                    let child = input.parse::<SimpleSelector>()?;
671                    end = child.span().end;
672                    children.push(child);
673                }
674                TokenWithSpan { token: Token::Placeholder(..), span }
675                    if !util::has_ws(input.source, end, span.start) =>
676                {
677                    let child = input.parse::<SimpleSelector>()?;
678                    end = child.span().end;
679                    children.push(child);
680                }
681                _ => break,
682            }
683        }
684
685        Ok(CompoundSelector { children, span: Span { start, end } })
686    }
687}
688
689// <compound-selector-list> = <compound-selector>#
690impl<'a> Parse<'a> for CompoundSelectorList<'a> {
691    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
692        let first = input.parse::<CompoundSelector>()?;
693        let mut span = first.span.clone();
694
695        let mut selectors = input.vec1(first);
696        let mut comma_spans = input.vec();
697        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
698            comma_spans.push(comma_span);
699            input.eat_sass_line_continuation()?;
700            selectors.push(input.parse()?);
701        }
702
703        // SAFETY: it has at least one element.
704        span.end = unsafe {
705            let index = selectors.len() - 1;
706            selectors.get_unchecked(index).span().end
707        };
708        Ok(CompoundSelectorList { selectors, comma_spans, span })
709    }
710}
711
712// <id-selector> = <hash-token>
713impl<'a> Parse<'a> for IdSelector<'a> {
714    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
715        match input.cursor.bump()? {
716            TokenWithSpan { token: Token::Hash(token), span } => {
717                let first_span = Span { start: span.start + 1, end: span.end };
718                let raw = token.raw;
719                if raw.starts_with(|c: char| c.is_ascii_digit())
720                    || matches!(raw.as_bytes(), [b'-'] | [b'-', b'0'..=b'9', ..])
721                {
722                    input
723                        .recoverable_errors
724                        .push(Error { kind: ErrorKind::InvalidIdSelectorName, span: span.clone() });
725                }
726                let value =
727                    if token.escaped { util::handle_escape_in(raw, input.allocator) } else { raw };
728                let first = Ident { name: value, raw: token.raw, span: first_span };
729                let name = match input.cursor.peek()? {
730                    TokenWithSpan { token: Token::HashLBrace(..), span }
731                        if matches!(input.syntax, Syntax::Scss | Syntax::Sass)
732                            && first.span.end == span.start =>
733                    {
734                        match input.parse()? {
735                            InterpolableIdent::SassInterpolated(mut interpolation) => {
736                                interpolation.elements.insert(
737                                    0,
738                                    SassInterpolatedIdentElement::Static(
739                                        InterpolableIdentStaticPart {
740                                            value: first.name,
741                                            raw: first.raw,
742                                            span: first.span,
743                                        },
744                                    ),
745                                );
746                                InterpolableIdent::SassInterpolated(interpolation)
747                            }
748                            _ => unreachable!(),
749                        }
750                    }
751                    _ => InterpolableIdent::Literal(first),
752                };
753                let span = Span { start: span.start, end: name.span().end };
754                Ok(IdSelector { name, span })
755            }
756            TokenWithSpan { token: Token::NumberSign(..), span } => {
757                let name = input.parse::<InterpolableIdent>()?;
758                let span = Span { start: span.start, end: name.span().end };
759                Ok(IdSelector { name, span })
760            }
761            TokenWithSpan { span, .. } => Err(Error { kind: ErrorKind::ExpectIdSelector, span }),
762        }
763    }
764}
765
766// A `:lang()` argument: <lang-range> = <ident-token> | <string-token>  (BCP 47 range)
767impl<'a> Parse<'a> for LanguageRange<'a> {
768    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
769        match &input.cursor.peek()?.token {
770            Token::Str(..) | Token::StrTemplate(..) => input.parse().map(LanguageRange::Str),
771            _ => input.parse().map(LanguageRange::Ident),
772        }
773    }
774}
775
776// :lang( <lang-range># )
777impl<'a> Parse<'a> for LanguageRangeList<'a> {
778    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
779        let first = input.parse::<LanguageRange>()?;
780        let mut span = first.span().clone();
781
782        let mut ranges = input.vec1(first);
783        let mut comma_spans = input.vec();
784        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
785            comma_spans.push(comma_span);
786            ranges.push(input.parse()?);
787        }
788        debug_assert_eq!(comma_spans.len() + 1, ranges.len());
789
790        if let Some(end) = ranges.last() {
791            span.end = end.span().end;
792        }
793        Ok(LanguageRangeList { ranges, comma_spans, span })
794    }
795}
796
797// https://drafts.csswg.org/css-nesting-1/#nest-selector
798//
799// The nesting selector `&`. This parser also accepts a glued ident/interpolation
800// suffix (`&__x`, `&#{$m}`, `&-@{v}`) as used by Sass/Less.
801impl<'a> Parse<'a> for NestingSelector<'a> {
802    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
803        let (_, mut span) = input.cursor.expect_ampersand()?;
804        let suffix = match input.syntax {
805            Syntax::Css => {
806                if let Some((ident, ident_span)) = input.cursor.tokenizer.scan_ident_template()? {
807                    span.end = ident_span.end;
808                    Some(InterpolableIdent::Literal(input.ident(ident, ident_span)))
809                } else {
810                    None
811                }
812            }
813            Syntax::Scss | Syntax::Sass => {
814                let start = span.end;
815                let elements = input.parse_sass_interpolated_ident_rest(&mut span.end)?;
816                if elements.is_empty() {
817                    None
818                } else {
819                    Some(InterpolableIdent::SassInterpolated(SassInterpolatedIdent {
820                        elements,
821                        span: Span { start, end: span.end },
822                    }))
823                }
824            }
825            Syntax::Less => {
826                let start = span.end;
827                let elements = input.parse_less_interpolated_ident_rest(&mut span.end)?;
828                if elements.is_empty() {
829                    None
830                } else {
831                    Some(InterpolableIdent::LessInterpolated(LessInterpolatedIdent {
832                        elements,
833                        span: Span { start, end: span.end },
834                    }))
835                }
836            }
837        };
838        Ok(NestingSelector { suffix, span })
839    }
840}
841
842// https://drafts.csswg.org/selectors-4/#the-nth-child-pseudo
843//
844// The `:nth-child()` argument: <nth> [ of <complex-selector-list> ]?
845impl<'a> Parse<'a> for Nth<'a> {
846    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
847        let index = input.parse::<NthIndex>()?;
848        let mut span = index.span().clone();
849        let matcher = match &input.cursor.peek()?.token {
850            Token::Ident(ident) if ident.name().eq_ignore_ascii_case("of") => {
851                let matcher = input.parse::<NthMatcher>()?;
852                span.end = matcher.span.end;
853                Some(matcher)
854            }
855            _ => None,
856        };
857
858        Ok(Nth { index, matcher, span })
859    }
860}
861
862// <nth> = <an+b> | even | odd   (plus a plain <integer>)
863impl<'a> Parse<'a> for NthIndex<'a> {
864    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
865        match &input.cursor.peek()?.token {
866            Token::Ident(ident) => {
867                let name = ident.name();
868                if name.eq_ignore_ascii_case("odd") {
869                    input.parse().map(NthIndex::Odd)
870                } else if name.eq_ignore_ascii_case("even") {
871                    input.parse().map(NthIndex::Even)
872                } else {
873                    input.parse().map(NthIndex::AnPlusB)
874                }
875            }
876            Token::Number(..) => {
877                let number = input.parse::<Number>()?;
878                if number.value.fract() == 0.0 {
879                    Ok(NthIndex::Integer(number))
880                } else {
881                    Err(Error { kind: ErrorKind::ExpectInteger, span: number.span })
882                }
883            }
884            _ => input.parse().map(NthIndex::AnPlusB),
885        }
886    }
887}
888
889// of <complex-selector-list>
890impl<'a> Parse<'a> for NthMatcher<'a> {
891    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
892        let (ident, mut span) = input.cursor.expect_ident()?;
893        if !ident.name().eq_ignore_ascii_case("of") {
894            return Err(Error { kind: ErrorKind::ExpectNthOf, span });
895        }
896
897        let selector = if matches!(&input.cursor.peek()?.token, Token::RParen(..)) {
898            None
899        } else {
900            let selector = input.parse::<SelectorList>()?;
901            span.end = selector.span.end;
902            Some(selector)
903        };
904
905        Ok(NthMatcher { selector, span })
906    }
907}
908
909// https://www.w3.org/TR/selectors-4/#pseudo-classes
910//
911// <pseudo-class-selector> = ':' <ident-token>
912//                         | ':' <function-token> <any-value> ')'
913impl<'a> Parse<'a> for PseudoClassSelector<'a> {
914    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
915        let (_, colon_span) = input.cursor.expect_colon()?;
916        let name = input.parse::<InterpolableIdent>()?;
917        let name_span = name.span();
918        util::assert_no_ws(input.source, &colon_span, name_span)?;
919
920        let mut end = name_span.end;
921
922        let arg = match input.cursor.peek()? {
923            TokenWithSpan { token: Token::LParen(..), span: l_paren } if l_paren.start == end => {
924                let l_paren = l_paren.clone();
925                input.cursor.bump()?;
926                let kind = match &name {
927                    InterpolableIdent::Literal(Ident { name, .. })
928                        if name.eq_ignore_ascii_case("nth-child")
929                            || name.eq_ignore_ascii_case("nth-last-child") =>
930                    {
931                        if input.syntax == Syntax::Css {
932                            input.parse().map(PseudoClassSelectorArgKind::Nth)?
933                        } else if let Ok(nth) = input.try_parse(Nth::parse) {
934                            PseudoClassSelectorArgKind::Nth(nth)
935                        } else {
936                            input
937                                .parse_tokens_in_parens()
938                                .map(PseudoClassSelectorArgKind::TokenSeq)?
939                        }
940                    }
941                    InterpolableIdent::Literal(Ident { name, .. })
942                        if name.eq_ignore_ascii_case("nth-of-type")
943                            || name.eq_ignore_ascii_case("nth-last-of-type")
944                            || name.eq_ignore_ascii_case("nth-col")
945                            || name.eq_ignore_ascii_case("nth-last-col") =>
946                    'pseudo_arg: {
947                        let nth = if input.syntax == Syntax::Css {
948                            input.parse()?
949                        } else if let Ok(nth) = input.try_parse(Nth::parse) {
950                            nth
951                        } else {
952                            break 'pseudo_arg input
953                                .parse_tokens_in_parens()
954                                .map(PseudoClassSelectorArgKind::TokenSeq)?;
955                        };
956                        if let Some(NthMatcher { span, .. }) = &nth.matcher {
957                            input.recoverable_errors.push(Error {
958                                kind: ErrorKind::UnexpectedNthMatcher,
959                                span: span.clone(),
960                            });
961                        }
962                        PseudoClassSelectorArgKind::Nth(nth)
963                    }
964                    InterpolableIdent::Literal(Ident { name, .. })
965                        if name.eq_ignore_ascii_case("not")
966                            || name.eq_ignore_ascii_case("is")
967                            || name.eq_ignore_ascii_case("where")
968                            || name.eq_ignore_ascii_case("matches")
969                            || name.eq_ignore_ascii_case("global") =>
970                    {
971                        input.parse().map(PseudoClassSelectorArgKind::SelectorList)?
972                    }
973                    InterpolableIdent::Literal(Ident { name, .. })
974                        if name.eq_ignore_ascii_case("has") =>
975                    {
976                        input.parse().map(PseudoClassSelectorArgKind::RelativeSelectorList)?
977                    }
978                    InterpolableIdent::Literal(Ident { name, .. })
979                        if name.eq_ignore_ascii_case("dir") =>
980                    {
981                        input.parse().map(PseudoClassSelectorArgKind::Ident)?
982                    }
983                    InterpolableIdent::Literal(Ident { name, .. })
984                        if name.eq_ignore_ascii_case("lang") =>
985                    {
986                        input.parse().map(PseudoClassSelectorArgKind::LanguageRangeList)?
987                    }
988                    InterpolableIdent::Literal(Ident { name, .. })
989                        if name.eq_ignore_ascii_case("-moz-any")
990                            || name.eq_ignore_ascii_case("-webkit-any")
991                            || name.eq_ignore_ascii_case("any") =>
992                    {
993                        // formally compound selectors, but real-world usage
994                        // includes complex ones (`:-moz-any(ol p.blah, ul)`)
995                        input.parse().map(PseudoClassSelectorArgKind::SelectorList)?
996                    }
997                    InterpolableIdent::Literal(Ident { name, .. })
998                        if name.eq_ignore_ascii_case("current")
999                            || name.eq_ignore_ascii_case("past")
1000                            || name.eq_ignore_ascii_case("future") =>
1001                    {
1002                        input.parse().map(PseudoClassSelectorArgKind::CompoundSelectorList)?
1003                    }
1004                    InterpolableIdent::Literal(Ident { name, .. })
1005                        if name.eq_ignore_ascii_case("host")
1006                            || name.eq_ignore_ascii_case("host-context") =>
1007                    {
1008                        input.parse().map(PseudoClassSelectorArgKind::CompoundSelector)?
1009                    }
1010                    InterpolableIdent::Literal(Ident { name, .. })
1011                        if input.syntax == Syntax::Less && *name == "extend" =>
1012                    {
1013                        input.parse().map(PseudoClassSelectorArgKind::LessExtendList)?
1014                    }
1015                    _ => {
1016                        input.parse_tokens_in_parens().map(PseudoClassSelectorArgKind::TokenSeq)?
1017                    }
1018                };
1019
1020                let r_paren = input.cursor.expect_r_paren()?.1;
1021                end = r_paren.end;
1022                let span = Span { start: l_paren.start, end: r_paren.end };
1023                Some(PseudoClassSelectorArg { kind, l_paren, r_paren, span })
1024            }
1025            _ => None,
1026        };
1027
1028        let span = Span { start: colon_span.start, end };
1029        Ok(PseudoClassSelector { name, arg, span })
1030    }
1031}
1032
1033// https://www.w3.org/TR/selectors-4/#pseudo-elements
1034//
1035// <pseudo-element-selector> = '::' <ident-token>
1036//                           | '::' <function-token> <any-value> ')'
1037impl<'a> Parse<'a> for PseudoElementSelector<'a> {
1038    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1039        let (_, colon_colon_span) = input.cursor.expect_colon_colon()?;
1040        let mut end;
1041        let name = if input.syntax == Syntax::Css {
1042            let (ident, ident_span) = input.cursor.expect_ident()?;
1043            end = ident_span.end;
1044            util::assert_no_ws(input.source, &colon_colon_span, &ident_span)?;
1045            InterpolableIdent::Literal(input.ident(ident, ident_span))
1046        } else {
1047            let name = input.parse::<InterpolableIdent>()?;
1048            let name_span = name.span();
1049            end = name_span.end;
1050            util::assert_no_ws(input.source, &colon_colon_span, name_span)?;
1051            name
1052        };
1053
1054        let arg = match input.cursor.peek()? {
1055            TokenWithSpan { token: Token::LParen(..), span: l_paren } if l_paren.start == end => {
1056                let l_paren = l_paren.clone();
1057                input.cursor.bump()?;
1058                let kind = match &name {
1059                    InterpolableIdent::Literal(Ident { name, .. })
1060                        if name.eq_ignore_ascii_case("part") =>
1061                    {
1062                        input.parse().map(PseudoElementSelectorArgKind::Ident)?
1063                    }
1064                    InterpolableIdent::Literal(Ident { name, .. })
1065                        if name.eq_ignore_ascii_case("cue")
1066                            || name.eq_ignore_ascii_case("cue-region") =>
1067                    {
1068                        input.parse().map(PseudoElementSelectorArgKind::CompoundSelector)?
1069                    }
1070                    InterpolableIdent::Literal(Ident { name, .. })
1071                        if name.eq_ignore_ascii_case("slotted") =>
1072                    {
1073                        // formally a single compound selector, but sass extend
1074                        // output produces lists (`::slotted(.c.d, .d.e)`)
1075                        input.parse().map(PseudoElementSelectorArgKind::CompoundSelectorList)?
1076                    }
1077                    _ => input
1078                        .parse_tokens_in_parens()
1079                        .map(PseudoElementSelectorArgKind::TokenSeq)?,
1080                };
1081
1082                let r_paren = input.cursor.expect_r_paren()?.1;
1083                end = r_paren.end;
1084                let span = Span { start: l_paren.start, end: r_paren.end };
1085                Some(PseudoElementSelectorArg { kind, l_paren, r_paren, span })
1086            }
1087            _ => None,
1088        };
1089
1090        let span = Span { start: colon_colon_span.start, end };
1091        Ok(PseudoElementSelector { name, arg, span })
1092    }
1093}
1094
1095// <relative-selector> = <combinator>? <complex-selector>
1096impl<'a> Parse<'a> for RelativeSelector<'a> {
1097    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1098        let pos = input.cursor.tokenizer.current_offset();
1099        let combinator = match input.parse_combinator(pos)? {
1100            Some(Combinator { kind: CombinatorKind::Descendant, .. }) => None,
1101            combinator => combinator,
1102        };
1103        let complex_selector = input.parse::<ComplexSelector>()?;
1104        let mut span = complex_selector.span.clone();
1105        if let Some(combinator) = &combinator {
1106            span.start = combinator.span.start;
1107        }
1108        Ok(RelativeSelector { combinator, complex_selector, span })
1109    }
1110}
1111
1112// <relative-selector-list> = <relative-selector>#
1113impl<'a> Parse<'a> for RelativeSelectorList<'a> {
1114    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1115        let first = input.parse::<RelativeSelector>()?;
1116        let mut span = first.span.clone();
1117
1118        let mut selectors = input.vec1(first);
1119        let mut comma_spans = input.vec();
1120        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1121            comma_spans.push(comma_span);
1122            selectors.push(input.parse()?);
1123        }
1124
1125        // SAFETY: it has at least one element.
1126        span.end = unsafe {
1127            let index = selectors.len() - 1;
1128            selectors.get_unchecked(index).span().end
1129        };
1130        Ok(RelativeSelectorList { selectors, comma_spans, span })
1131    }
1132}
1133
1134// https://www.w3.org/TR/selectors-4/#typedef-selector-list
1135//
1136// <selector-list> = <complex-selector-list> = <complex-selector>#
1137impl<'a> Parse<'a> for SelectorList<'a> {
1138    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1139        let first = input.parse::<ComplexSelector>()?;
1140        let mut span = first.span.clone();
1141
1142        let mut selectors = input.vec_with_capacity(2);
1143        selectors.push(first);
1144        let mut comma_spans = input.vec();
1145
1146        let is_css = input.syntax == Syntax::Css;
1147        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1148            span.end = comma_span.end;
1149            comma_spans.push(comma_span);
1150            // legacy corpora carry doubled/trailing commas (`div,, span,, {`);
1151            // absorb the extras in SCSS like libsass did
1152            if input.syntax == Syntax::Scss {
1153                while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1154                    span.end = comma_span.end;
1155                    comma_spans.push(comma_span);
1156                }
1157            }
1158            // In the indented syntax a deeper line after the comma continues
1159            // the selector list (`a,\n    b\n  c: d`); a same-level line or
1160            // `{` means the comma was trailing.
1161            if input.syntax == Syntax::Sass
1162                && matches!(input.cursor.peek()?.token, Token::Indent(..))
1163            {
1164                input.eat_sass_line_continuation()?;
1165            } else if !is_css
1166                && matches!(
1167                    input.cursor.peek()?.token,
1168                    Token::LBrace(..) | Token::Indent(..) | Token::Linebreak(..)
1169                )
1170            {
1171                break;
1172            }
1173
1174            let selector = input.parse::<ComplexSelector>()?;
1175            span.end = selector.span.end;
1176            selectors.push(selector);
1177        }
1178
1179        // absorbed doubled/trailing commas can outnumber the selectors, so
1180        // phrase the invariants without subtraction (usize underflow)
1181        debug_assert!(if is_css {
1182            selectors.len() == comma_spans.len() + 1
1183        } else {
1184            selectors.len() <= comma_spans.len() + 1
1185        });
1186
1187        Ok(SelectorList { selectors, comma_spans, span })
1188    }
1189}
1190
1191// https://www.w3.org/TR/selectors-4/#ref-for-typedef-simple-selector
1192//
1193// <simple-selector>   = <type-selector> | <subclass-selector>
1194// <subclass-selector> = <id-selector> | <class-selector>
1195//                     | <attribute-selector> | <pseudo-class-selector>
1196impl<'a> Parse<'a> for SimpleSelector<'a> {
1197    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1198        match input.cursor.peek()? {
1199            TokenWithSpan { token: Token::Dot(..), .. } => input.parse().map(SimpleSelector::Class),
1200            TokenWithSpan { token: Token::Hash(..) | Token::NumberSign(..), .. } => {
1201                input.parse().map(SimpleSelector::Id)
1202            }
1203            TokenWithSpan { token: Token::LBracket(..), .. } => {
1204                input.parse().map(SimpleSelector::Attribute)
1205            }
1206            TokenWithSpan { token: Token::Colon(..), .. } => {
1207                input.parse().map(SimpleSelector::PseudoClass)
1208            }
1209            TokenWithSpan { token: Token::ColonColon(..), .. } => {
1210                input.parse().map(SimpleSelector::PseudoElement)
1211            }
1212            TokenWithSpan {
1213                token:
1214                    Token::Ident(..)
1215                    | Token::Asterisk(..)
1216                    | Token::HashLBrace(..)
1217                    | Token::Bar(..)
1218                    | Token::AtLBraceVar(..),
1219                ..
1220            } => input.parse().map(SimpleSelector::Type),
1221            TokenWithSpan { token: Token::Ampersand(..), .. } => {
1222                input.parse().map(SimpleSelector::Nesting)
1223            }
1224            // Css too: postcss-extend-rule uses Sass-style placeholders in
1225            // plain CSS (`%thick-border {}` + `@extend %thick-border;`), and
1226            // postcss parses `%x` as an ordinary selector. A selector-position
1227            // `%` is invalid per spec, so accepting it is purely additive.
1228            TokenWithSpan { token: Token::Percent(..), .. }
1229                if matches!(input.syntax, Syntax::Scss | Syntax::Sass | Syntax::Css) =>
1230            {
1231                input.parse().map(SimpleSelector::SassPlaceholder)
1232            }
1233            TokenWithSpan { token: Token::Placeholder(..), .. } => {
1234                let name = input.parse::<InterpolableIdent>()?;
1235                let span = name.span().clone();
1236                Ok(SimpleSelector::Type(TypeSelector::TagName(TagNameSelector {
1237                    name: WqName { name, prefix: None, span: span.clone() },
1238                    span,
1239                })))
1240            }
1241            token_with_span => Err(Error {
1242                kind: ErrorKind::ExpectSimpleSelector,
1243                span: token_with_span.span.clone(),
1244            }),
1245        }
1246    }
1247}
1248
1249// <type-selector> = <wq-name> | <ns-prefix>? '*'
1250// <wq-name>       = <ns-prefix>? <ident-token>
1251// <ns-prefix>     = [ <ident-token> | '*' ]? '|'
1252impl<'a> Parse<'a> for TypeSelector<'a> {
1253    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1254        enum IdentOrAsterisk<'a> {
1255            Ident(InterpolableIdent<'a>),
1256            Asterisk(Span),
1257        }
1258
1259        let ident_or_asterisk = match &input.cursor.peek()?.token {
1260            Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..) => {
1261                input.parse().map(IdentOrAsterisk::Ident).map(Some)?
1262            }
1263            Token::Asterisk(..) => Some(IdentOrAsterisk::Asterisk(input.cursor.bump()?.span)),
1264            Token::Bar(..) => None,
1265            _ => unreachable!(),
1266        };
1267
1268        match input.cursor.peek()? {
1269            TokenWithSpan { token: Token::Bar(..), span }
1270                if ident_or_asterisk
1271                    .as_ref()
1272                    .map(|t| match t {
1273                        IdentOrAsterisk::Ident(ident) => {
1274                            !util::has_ws(input.source, ident.span().end, span.start)
1275                        }
1276                        IdentOrAsterisk::Asterisk(asterisk_span) => {
1277                            !util::has_ws(input.source, asterisk_span.end, span.start)
1278                        }
1279                    })
1280                    .unwrap_or(true) =>
1281            {
1282                let bar_token_span = input.cursor.bump()?.span;
1283
1284                let prefix = match ident_or_asterisk {
1285                    Some(IdentOrAsterisk::Ident(ident)) => {
1286                        let mut span = ident.span().clone();
1287                        span.end = bar_token_span.end;
1288                        NsPrefix { kind: Some(NsPrefixKind::Ident(ident)), span }
1289                    }
1290                    Some(IdentOrAsterisk::Asterisk(asterisk_span)) => {
1291                        let mut span = asterisk_span.clone();
1292                        span.end = bar_token_span.end;
1293                        NsPrefix {
1294                            kind: Some(NsPrefixKind::Universal(NsPrefixUniversal {
1295                                span: asterisk_span,
1296                            })),
1297                            span,
1298                        }
1299                    }
1300                    None => NsPrefix { kind: None, span: bar_token_span },
1301                };
1302
1303                match input.cursor.peek()? {
1304                    TokenWithSpan { token: Token::Ident(..) | Token::HashLBrace(..), .. } => {
1305                        let name = input.parse::<InterpolableIdent>()?;
1306                        let name_span = name.span();
1307                        util::assert_no_ws(input.source, &prefix.span, name_span)?;
1308                        let span = Span { start: prefix.span.start, end: name_span.end };
1309                        Ok(TypeSelector::TagName(TagNameSelector {
1310                            name: WqName { name, prefix: Some(prefix), span: span.clone() },
1311                            span,
1312                        }))
1313                    }
1314                    TokenWithSpan { token: Token::Asterisk(..), .. } => {
1315                        let asterisk_span = input.cursor.bump()?.span;
1316                        util::assert_no_ws(input.source, &prefix.span, &asterisk_span)?;
1317                        let span = Span { start: prefix.span.start, end: asterisk_span.end };
1318                        Ok(TypeSelector::Universal(UniversalSelector {
1319                            prefix: Some(prefix),
1320                            span,
1321                        }))
1322                    }
1323                    TokenWithSpan { span, .. } => {
1324                        Err(Error { kind: ErrorKind::ExpectTypeSelector, span: span.clone() })
1325                    }
1326                }
1327            }
1328
1329            _ => match ident_or_asterisk {
1330                Some(IdentOrAsterisk::Ident(ident)) => {
1331                    let span = ident.span().clone();
1332                    Ok(TypeSelector::TagName(TagNameSelector {
1333                        name: WqName { name: ident, prefix: None, span: span.clone() },
1334                        span,
1335                    }))
1336                }
1337                Some(IdentOrAsterisk::Asterisk(span)) => {
1338                    Ok(TypeSelector::Universal(UniversalSelector { prefix: None, span }))
1339                }
1340                None => unreachable!(),
1341            },
1342        }
1343    }
1344}
1345
1346impl<'a> Parser<'a> {
1347    // <combinator> = '>' | '+' | '~' | [ '|' '|' ]
1348    // An absent combinator between two compounds is the descendant combinator
1349    // (whitespace), which this returns as `CombinatorKind::Descendant`.
1350    fn parse_combinator(&mut self, pos: usize) -> PResult<Option<Combinator>> {
1351        match self.cursor.peek()? {
1352            TokenWithSpan {
1353                token:
1354                    Token::Ident(..)
1355                    | Token::Dot(..)
1356                    | Token::Hash(..)
1357                    | Token::Colon(..)
1358                    | Token::ColonColon(..)
1359                    | Token::LBracket(..)
1360                    | Token::Asterisk(..)
1361                    | Token::Ampersand(..)
1362                    | Token::Bar(..) // selector like `|type` (with <ns-prefix>)
1363                    | Token::AtLBraceVar(..)
1364                    | Token::NumberSign(..)
1365                    | Token::HashLBrace(..)
1366                    | Token::Percent(..) // Sass `%placeholder` descendant
1367                    | Token::Placeholder(..), // `${a} ${b}` descendant
1368                span,
1369            } if pos < span.start => Ok(Some(Combinator {
1370                kind: CombinatorKind::Descendant,
1371                span: Span {
1372                    start: pos,
1373                    end: span.start,
1374                },
1375            })),
1376            TokenWithSpan {
1377                token: Token::GreaterThan(..),
1378                ..
1379            } => Ok(Some(Combinator {
1380                kind: CombinatorKind::Child,
1381                span: self.cursor.bump()?.span,
1382            })),
1383            TokenWithSpan {
1384                token: Token::Plus(..),
1385                ..
1386            } => Ok(Some(Combinator {
1387                kind: CombinatorKind::NextSibling,
1388                span: self.cursor.bump()?.span,
1389            })),
1390            TokenWithSpan {
1391                token: Token::Tilde(..),
1392                ..
1393            } => Ok(Some(Combinator {
1394                kind: CombinatorKind::LaterSibling,
1395                span: self.cursor.bump()?.span,
1396            })),
1397            TokenWithSpan {
1398                token: Token::BarBar(..),
1399                ..
1400            } => Ok(Some(Combinator {
1401                kind: CombinatorKind::Column,
1402                span: self.cursor.bump()?.span,
1403            })),
1404            // deprecated shadow-piercing `/deep/` and less.js's arbitrary
1405            // slashed combinators (`.container /shadow/ .content`) — but not
1406            // in Scss/Sass, where dart-sass rejects reference combinators
1407            TokenWithSpan { token: Token::Solidus(..), .. }
1408                if !matches!(self.syntax, Syntax::Scss | Syntax::Sass) =>
1409            {
1410                let deep = self.try_parse(|p| {
1411                    let start = p.cursor.bump()?.span; // `/`
1412                    let ident_end = match p.cursor.peek()? {
1413                        TokenWithSpan { token: Token::Ident(..), span }
1414                            if span.start == start.end =>
1415                        {
1416                            p.cursor.bump()?.span.end
1417                        }
1418                        TokenWithSpan { span, .. } => {
1419                            return Err(Error {
1420                                kind: ErrorKind::TryParseError,
1421                                span: span.clone(),
1422                            });
1423                        }
1424                    };
1425                    match p.cursor.peek()? {
1426                        TokenWithSpan { token: Token::Solidus(..), span }
1427                            if span.start == ident_end =>
1428                        {
1429                            let end = p.cursor.bump()?.span.end;
1430                            Ok(Span { start: start.start, end })
1431                        }
1432                        TokenWithSpan { span, .. } => Err(Error {
1433                            kind: ErrorKind::TryParseError,
1434                            span: span.clone(),
1435                        }),
1436                    }
1437                });
1438                match deep {
1439                    Ok(span) => Ok(Some(Combinator { kind: CombinatorKind::Deep, span })),
1440                    Err(_) => Ok(None),
1441                }
1442            }
1443            // deprecated shadow combinators `^` and `^^` (Less corpora and
1444            // the CSS files Less emits)
1445            TokenWithSpan {
1446                token: Token::Unknown(..),
1447                span,
1448            } if !matches!(self.syntax, Syntax::Scss | Syntax::Sass)
1449                && self.source.as_bytes().get(span.start) == Some(&b'^') =>
1450            {
1451                let start = self.cursor.bump()?.span.start;
1452                if matches!(&self.cursor.peek()?.token, Token::Unknown(..))
1453                    && self.cursor.peek()?.span.start == start + 1
1454                    && self.source.as_bytes().get(start + 1) == Some(&b'^')
1455                {
1456                    let end = self.cursor.bump()?.span.end;
1457                    Ok(Some(Combinator {
1458                        kind: CombinatorKind::ShadowDescendant,
1459                        span: Span { start, end },
1460                    }))
1461                } else {
1462                    Ok(Some(Combinator {
1463                        kind: CombinatorKind::ShadowChild,
1464                        span: Span { start, end: start + 1 },
1465                    }))
1466                }
1467            }
1468            _ => Ok(None),
1469        }
1470    }
1471}
1472
1473fn expect_unsigned_int<'a>(input: &mut Parser<'a>) -> PResult<(token::Number<'a>, Span)> {
1474    let (number, span) = input.cursor.expect_number()?;
1475    if number.raw.chars().any(|c| !c.is_ascii_digit()) {
1476        Err(Error { kind: ErrorKind::ExpectUnsignedInteger, span })
1477    } else {
1478        Ok((number, span))
1479    }
1480}