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