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                        input.parse().map(PseudoClassSelectorArgKind::CompoundSelector)?
1003                    }
1004                    InterpolableIdent::Literal(Ident { name, .. })
1005                        if input.syntax == Syntax::Less && *name == "extend" =>
1006                    {
1007                        input.parse().map(PseudoClassSelectorArgKind::LessExtendList)?
1008                    }
1009                    _ => {
1010                        input.parse_tokens_in_parens().map(PseudoClassSelectorArgKind::TokenSeq)?
1011                    }
1012                };
1013
1014                let r_paren = input.cursor.expect_r_paren()?.1;
1015                end = r_paren.end;
1016                let span = Span { start: l_paren.start, end: r_paren.end };
1017                Some(PseudoClassSelectorArg { kind, l_paren, r_paren, span })
1018            }
1019            _ => None,
1020        };
1021
1022        let span = Span { start: colon_span.start, end };
1023        Ok(PseudoClassSelector { name, arg, span })
1024    }
1025}
1026
1027// https://www.w3.org/TR/selectors-4/#pseudo-elements
1028//
1029// <pseudo-element-selector> = '::' <ident-token>
1030//                           | '::' <function-token> <any-value> ')'
1031impl<'a> Parse<'a> for PseudoElementSelector<'a> {
1032    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1033        let (_, colon_colon_span) = input.cursor.expect_colon_colon()?;
1034        let mut end;
1035        let name = if input.syntax == Syntax::Css {
1036            let (ident, ident_span) = input.cursor.expect_ident()?;
1037            end = ident_span.end;
1038            util::assert_no_ws(input.source, &colon_colon_span, &ident_span)?;
1039            InterpolableIdent::Literal(input.ident(ident, ident_span))
1040        } else {
1041            let name = input.parse::<InterpolableIdent>()?;
1042            let name_span = name.span();
1043            end = name_span.end;
1044            util::assert_no_ws(input.source, &colon_colon_span, name_span)?;
1045            name
1046        };
1047
1048        let arg = match input.cursor.peek()? {
1049            TokenWithSpan { token: Token::LParen(..), span: l_paren } if l_paren.start == end => {
1050                let l_paren = *l_paren;
1051                input.cursor.bump()?;
1052                let kind = match &name {
1053                    InterpolableIdent::Literal(Ident { name, .. })
1054                        if name.eq_ignore_ascii_case("part") =>
1055                    {
1056                        input.parse().map(PseudoElementSelectorArgKind::Ident)?
1057                    }
1058                    InterpolableIdent::Literal(Ident { name, .. })
1059                        if name.eq_ignore_ascii_case("cue")
1060                            || name.eq_ignore_ascii_case("cue-region") =>
1061                    {
1062                        input.parse().map(PseudoElementSelectorArgKind::CompoundSelector)?
1063                    }
1064                    InterpolableIdent::Literal(Ident { name, .. })
1065                        if name.eq_ignore_ascii_case("slotted") =>
1066                    {
1067                        // formally a single compound selector, but sass extend
1068                        // output produces lists (`::slotted(.c.d, .d.e)`)
1069                        input.parse().map(PseudoElementSelectorArgKind::CompoundSelectorList)?
1070                    }
1071                    _ => input
1072                        .parse_tokens_in_parens()
1073                        .map(PseudoElementSelectorArgKind::TokenSeq)?,
1074                };
1075
1076                let r_paren = input.cursor.expect_r_paren()?.1;
1077                end = r_paren.end;
1078                let span = Span { start: l_paren.start, end: r_paren.end };
1079                Some(PseudoElementSelectorArg { kind, l_paren, r_paren, span })
1080            }
1081            _ => None,
1082        };
1083
1084        let span = Span { start: colon_colon_span.start, end };
1085        Ok(PseudoElementSelector { name, arg, span })
1086    }
1087}
1088
1089// <relative-selector> = <combinator>? <complex-selector>
1090impl<'a> Parse<'a> for RelativeSelector<'a> {
1091    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1092        let pos = input.cursor.tokenizer.current_offset();
1093        let combinator = match input.parse_combinator(pos)? {
1094            Some(Combinator { kind: CombinatorKind::Descendant, .. }) => None,
1095            combinator => combinator,
1096        };
1097        let complex_selector = input.parse::<ComplexSelector>()?;
1098        let mut span = complex_selector.span;
1099        if let Some(combinator) = &combinator {
1100            span.start = combinator.span.start;
1101        }
1102        Ok(RelativeSelector { combinator, complex_selector, span })
1103    }
1104}
1105
1106// <relative-selector-list> = <relative-selector>#
1107impl<'a> Parse<'a> for RelativeSelectorList<'a> {
1108    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1109        let first = input.parse::<RelativeSelector>()?;
1110        let mut span = first.span;
1111
1112        let mut selectors = input.vec1(first);
1113        let mut comma_spans = input.vec();
1114        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1115            comma_spans.push(comma_span);
1116            selectors.push(input.parse()?);
1117        }
1118
1119        // SAFETY: it has at least one element.
1120        span.end = unsafe {
1121            let index = selectors.len() - 1;
1122            selectors.get_unchecked(index).span().end
1123        };
1124        Ok(RelativeSelectorList { selectors, comma_spans, span })
1125    }
1126}
1127
1128// https://www.w3.org/TR/selectors-4/#typedef-selector-list
1129//
1130// <selector-list> = <complex-selector-list> = <complex-selector>#
1131impl<'a> Parse<'a> for SelectorList<'a> {
1132    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1133        let first = input.parse::<ComplexSelector>()?;
1134        let mut span = first.span;
1135
1136        let mut selectors = input.vec_with_capacity(2);
1137        selectors.push(first);
1138        let mut comma_spans = input.vec();
1139
1140        let is_css = input.syntax == Syntax::Css;
1141        while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1142            span.end = comma_span.end;
1143            comma_spans.push(comma_span);
1144            // legacy corpora carry doubled/trailing commas (`div,, span,, {`);
1145            // absorb the extras in SCSS like libsass did
1146            if input.syntax == Syntax::Scss {
1147                while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1148                    span.end = comma_span.end;
1149                    comma_spans.push(comma_span);
1150                }
1151            }
1152            // In the indented syntax a deeper line after the comma continues
1153            // the selector list (`a,\n    b\n  c: d`); a same-level line or
1154            // `{` means the comma was trailing.
1155            if input.syntax == Syntax::Sass
1156                && matches!(input.cursor.peek()?.token, Token::Indent(..))
1157            {
1158                input.eat_sass_line_continuation()?;
1159            } else if !is_css
1160                && matches!(
1161                    input.cursor.peek()?.token,
1162                    Token::LBrace(..) | Token::Indent(..) | Token::Linebreak(..)
1163                )
1164            {
1165                break;
1166            }
1167
1168            let selector = input.parse::<ComplexSelector>()?;
1169            span.end = selector.span.end;
1170            selectors.push(selector);
1171        }
1172
1173        // absorbed doubled/trailing commas can outnumber the selectors, so
1174        // phrase the invariants without subtraction (usize underflow)
1175        debug_assert!(if is_css {
1176            selectors.len() == comma_spans.len() + 1
1177        } else {
1178            selectors.len() <= comma_spans.len() + 1
1179        });
1180
1181        Ok(SelectorList { selectors, comma_spans, span })
1182    }
1183}
1184
1185// https://www.w3.org/TR/selectors-4/#ref-for-typedef-simple-selector
1186//
1187// <simple-selector>   = <type-selector> | <subclass-selector>
1188// <subclass-selector> = <id-selector> | <class-selector>
1189//                     | <attribute-selector> | <pseudo-class-selector>
1190impl<'a> Parse<'a> for SimpleSelector<'a> {
1191    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1192        match input.cursor.peek()? {
1193            TokenWithSpan { token: Token::Dot(..), .. } => input.parse().map(SimpleSelector::Class),
1194            TokenWithSpan { token: Token::Hash(..) | Token::NumberSign(..), .. } => {
1195                input.parse().map(SimpleSelector::Id)
1196            }
1197            TokenWithSpan { token: Token::LBracket(..), .. } => {
1198                input.parse().map(SimpleSelector::Attribute)
1199            }
1200            TokenWithSpan { token: Token::Colon(..), .. } => {
1201                input.parse().map(SimpleSelector::PseudoClass)
1202            }
1203            TokenWithSpan { token: Token::ColonColon(..), .. } => {
1204                input.parse().map(SimpleSelector::PseudoElement)
1205            }
1206            TokenWithSpan {
1207                token:
1208                    Token::Ident(..)
1209                    | Token::Asterisk(..)
1210                    | Token::HashLBrace(..)
1211                    | Token::Bar(..)
1212                    | Token::AtLBraceVar(..),
1213                ..
1214            } => input.parse().map(SimpleSelector::Type),
1215            TokenWithSpan { token: Token::Ampersand(..), .. } => {
1216                input.parse().map(SimpleSelector::Nesting)
1217            }
1218            // Css too: postcss-extend-rule uses Sass-style placeholders in
1219            // plain CSS (`%thick-border {}` + `@extend %thick-border;`), and
1220            // postcss parses `%x` as an ordinary selector. A selector-position
1221            // `%` is invalid per spec, so accepting it is purely additive.
1222            TokenWithSpan { token: Token::Percent(..), .. }
1223                if matches!(input.syntax, Syntax::Scss | Syntax::Sass | Syntax::Css) =>
1224            {
1225                input.parse().map(SimpleSelector::SassPlaceholder)
1226            }
1227            TokenWithSpan { token: Token::Placeholder(..), .. } => {
1228                let name = input.parse::<InterpolableIdent>()?;
1229                let span = *name.span();
1230                Ok(SimpleSelector::Type(TypeSelector::TagName(TagNameSelector {
1231                    name: WqName { name, prefix: None, span },
1232                    span,
1233                })))
1234            }
1235            token_with_span => {
1236                Err(Error { kind: ErrorKind::ExpectSimpleSelector, span: token_with_span.span })
1237            }
1238        }
1239    }
1240}
1241
1242// <type-selector> = <wq-name> | <ns-prefix>? '*'
1243// <wq-name>       = <ns-prefix>? <ident-token>
1244// <ns-prefix>     = [ <ident-token> | '*' ]? '|'
1245impl<'a> Parse<'a> for TypeSelector<'a> {
1246    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1247        enum IdentOrAsterisk<'a> {
1248            Ident(InterpolableIdent<'a>),
1249            Asterisk(Span),
1250        }
1251
1252        let ident_or_asterisk = match &input.cursor.peek()?.token {
1253            Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..) => {
1254                input.parse().map(IdentOrAsterisk::Ident).map(Some)?
1255            }
1256            Token::Asterisk(..) => Some(IdentOrAsterisk::Asterisk(input.cursor.bump()?.span)),
1257            Token::Bar(..) => None,
1258            _ => unreachable!(),
1259        };
1260
1261        match input.cursor.peek()? {
1262            TokenWithSpan { token: Token::Bar(..), span }
1263                if ident_or_asterisk
1264                    .as_ref()
1265                    .map(|t| match t {
1266                        IdentOrAsterisk::Ident(ident) => {
1267                            !util::has_ws(input.source, ident.span().end, span.start)
1268                        }
1269                        IdentOrAsterisk::Asterisk(asterisk_span) => {
1270                            !util::has_ws(input.source, asterisk_span.end, span.start)
1271                        }
1272                    })
1273                    .unwrap_or(true) =>
1274            {
1275                let bar_token_span = input.cursor.bump()?.span;
1276
1277                let prefix = match ident_or_asterisk {
1278                    Some(IdentOrAsterisk::Ident(ident)) => {
1279                        let mut span = *ident.span();
1280                        span.end = bar_token_span.end;
1281                        NsPrefix { kind: Some(NsPrefixKind::Ident(ident)), span }
1282                    }
1283                    Some(IdentOrAsterisk::Asterisk(asterisk_span)) => {
1284                        let mut span = asterisk_span;
1285                        span.end = bar_token_span.end;
1286                        NsPrefix {
1287                            kind: Some(NsPrefixKind::Universal(NsPrefixUniversal {
1288                                span: asterisk_span,
1289                            })),
1290                            span,
1291                        }
1292                    }
1293                    None => NsPrefix { kind: None, span: bar_token_span },
1294                };
1295
1296                match input.cursor.peek()? {
1297                    TokenWithSpan { token: Token::Ident(..) | Token::HashLBrace(..), .. } => {
1298                        let name = input.parse::<InterpolableIdent>()?;
1299                        let name_span = name.span();
1300                        util::assert_no_ws(input.source, &prefix.span, name_span)?;
1301                        let span = Span { start: prefix.span.start, end: name_span.end };
1302                        Ok(TypeSelector::TagName(TagNameSelector {
1303                            name: WqName { name, prefix: Some(prefix), span },
1304                            span,
1305                        }))
1306                    }
1307                    TokenWithSpan { token: Token::Asterisk(..), .. } => {
1308                        let asterisk_span = input.cursor.bump()?.span;
1309                        util::assert_no_ws(input.source, &prefix.span, &asterisk_span)?;
1310                        let span = Span { start: prefix.span.start, end: asterisk_span.end };
1311                        Ok(TypeSelector::Universal(UniversalSelector {
1312                            prefix: Some(prefix),
1313                            span,
1314                        }))
1315                    }
1316                    TokenWithSpan { span, .. } => {
1317                        Err(Error { kind: ErrorKind::ExpectTypeSelector, span: *span })
1318                    }
1319                }
1320            }
1321
1322            _ => match ident_or_asterisk {
1323                Some(IdentOrAsterisk::Ident(ident)) => {
1324                    let span = *ident.span();
1325                    Ok(TypeSelector::TagName(TagNameSelector {
1326                        name: WqName { name: ident, prefix: None, span },
1327                        span,
1328                    }))
1329                }
1330                Some(IdentOrAsterisk::Asterisk(span)) => {
1331                    Ok(TypeSelector::Universal(UniversalSelector { prefix: None, span }))
1332                }
1333                None => unreachable!(),
1334            },
1335        }
1336    }
1337}
1338
1339impl<'a> Parser<'a> {
1340    // <combinator> = '>' | '+' | '~' | [ '|' '|' ]
1341    // An absent combinator between two compounds is the descendant combinator
1342    // (whitespace), which this returns as `CombinatorKind::Descendant`.
1343    fn parse_combinator(&mut self, pos: usize) -> PResult<Option<Combinator>> {
1344        match self.cursor.peek()? {
1345            TokenWithSpan {
1346                token:
1347                    Token::Ident(..)
1348                    | Token::Dot(..)
1349                    | Token::Hash(..)
1350                    | Token::Colon(..)
1351                    | Token::ColonColon(..)
1352                    | Token::LBracket(..)
1353                    | Token::Asterisk(..)
1354                    | Token::Ampersand(..)
1355                    | Token::Bar(..) // selector like `|type` (with <ns-prefix>)
1356                    | Token::AtLBraceVar(..)
1357                    | Token::NumberSign(..)
1358                    | Token::HashLBrace(..)
1359                    | Token::Percent(..) // Sass `%placeholder` descendant
1360                    | Token::Placeholder(..), // `${a} ${b}` descendant
1361                span,
1362            } if pos < span.start => Ok(Some(Combinator {
1363                kind: CombinatorKind::Descendant,
1364                span: Span {
1365                    start: pos,
1366                    end: span.start,
1367                },
1368            })),
1369            TokenWithSpan {
1370                token: Token::GreaterThan(..),
1371                ..
1372            } => Ok(Some(Combinator {
1373                kind: CombinatorKind::Child,
1374                span: self.cursor.bump()?.span,
1375            })),
1376            TokenWithSpan {
1377                token: Token::Plus(..),
1378                ..
1379            } => Ok(Some(Combinator {
1380                kind: CombinatorKind::NextSibling,
1381                span: self.cursor.bump()?.span,
1382            })),
1383            TokenWithSpan {
1384                token: Token::Tilde(..),
1385                ..
1386            } => Ok(Some(Combinator {
1387                kind: CombinatorKind::LaterSibling,
1388                span: self.cursor.bump()?.span,
1389            })),
1390            TokenWithSpan {
1391                token: Token::BarBar(..),
1392                ..
1393            } => Ok(Some(Combinator {
1394                kind: CombinatorKind::Column,
1395                span: self.cursor.bump()?.span,
1396            })),
1397            // deprecated shadow-piercing `/deep/` and less.js's arbitrary
1398            // slashed combinators (`.container /shadow/ .content`) — but not
1399            // in Scss/Sass, where dart-sass rejects reference combinators
1400            TokenWithSpan { token: Token::Solidus(..), .. }
1401                if !matches!(self.syntax, Syntax::Scss | Syntax::Sass) =>
1402            {
1403                let deep = self.try_parse(|p| {
1404                    let start = p.cursor.bump()?.span; // `/`
1405                    let ident_end = match p.cursor.peek()? {
1406                        TokenWithSpan { token: Token::Ident(..), span }
1407                            if span.start == start.end =>
1408                        {
1409                            p.cursor.bump()?.span.end
1410                        }
1411                        TokenWithSpan { span, .. } => {
1412                            return Err(Error {
1413                                kind: ErrorKind::TryParseError,
1414                                span: *span,
1415                            });
1416                        }
1417                    };
1418                    match p.cursor.peek()? {
1419                        TokenWithSpan { token: Token::Solidus(..), span }
1420                            if span.start == ident_end =>
1421                        {
1422                            let end = p.cursor.bump()?.span.end;
1423                            Ok(Span { start: start.start, end })
1424                        }
1425                        TokenWithSpan { span, .. } => Err(Error {
1426                            kind: ErrorKind::TryParseError,
1427                            span: *span,
1428                        }),
1429                    }
1430                });
1431                match deep {
1432                    Ok(span) => Ok(Some(Combinator { kind: CombinatorKind::Deep, span })),
1433                    Err(_) => Ok(None),
1434                }
1435            }
1436            // deprecated shadow combinators `^` and `^^` (Less corpora and
1437            // the CSS files Less emits)
1438            TokenWithSpan {
1439                token: Token::Unknown(..),
1440                span,
1441            } if !matches!(self.syntax, Syntax::Scss | Syntax::Sass)
1442                && self.source.as_bytes().get(span.start) == Some(&b'^') =>
1443            {
1444                let start = self.cursor.bump()?.span.start;
1445                if matches!(&self.cursor.peek()?.token, Token::Unknown(..))
1446                    && self.cursor.peek()?.span.start == start + 1
1447                    && self.source.as_bytes().get(start + 1) == Some(&b'^')
1448                {
1449                    let end = self.cursor.bump()?.span.end;
1450                    Ok(Some(Combinator {
1451                        kind: CombinatorKind::ShadowDescendant,
1452                        span: Span { start, end },
1453                    }))
1454                } else {
1455                    Ok(Some(Combinator {
1456                        kind: CombinatorKind::ShadowChild,
1457                        span: Span { start, end: start + 1 },
1458                    }))
1459                }
1460            }
1461            _ => Ok(None),
1462        }
1463    }
1464}
1465
1466fn expect_unsigned_int<'a>(input: &mut Parser<'a>) -> PResult<(token::Number<'a>, Span)> {
1467    let (number, span) = input.cursor.expect_number()?;
1468    if number.raw.chars().any(|c| !c.is_ascii_digit()) {
1469        Err(Error { kind: ErrorKind::ExpectUnsignedInteger, span })
1470    } else {
1471        Ok((number, span))
1472    }
1473}