Skip to main content

oxc_css_parser/parser/
sass.rs

1use super::{
2    Parser,
3    state::{ParserState, SASS_CTX_ALLOW_DIV, SASS_CTX_IN_PARENS},
4};
5use crate::{
6    Parse, arena_box, arena_vec,
7    ast::*,
8    bump,
9    config::Syntax,
10    eat,
11    error::{Error, ErrorKind, PResult},
12    expect, expect_without_ws_or_comments, peek,
13    pos::{Span, Spanned},
14    tokenizer::{Token, TokenWithSpan},
15    util,
16};
17
18const PRECEDENCE_MULTIPLY: u8 = 6;
19const PRECEDENCE_PLUS: u8 = 5;
20const PRECEDENCE_RELATIONAL: u8 = 4;
21const PRECEDENCE_EQUALITY: u8 = 3;
22const PRECEDENCE_AND: u8 = 2;
23const PRECEDENCE_OR: u8 = 1;
24
25type SassParams<'a> = (
26    oxc_allocator::Vec<'a, SassParameter<'a>>,
27    Option<SassArbitraryParameter<'a>>,
28    oxc_allocator::Vec<'a, Span>, // comma spans
29    usize,                        // end pos
30);
31
32impl<'a> Parser<'a> {
33    pub(super) fn parse_maybe_sass_list(
34        &mut self,
35        allow_comma: bool,
36    ) -> PResult<ComponentValue<'a>> {
37        use util::ListSeparatorKind;
38
39        let single_value = if allow_comma {
40            self.parse_maybe_sass_list(false)?
41        } else if let Token::Exclamation(..) = peek!(self).token {
42            self.parse().map(ComponentValue::ImportantAnnotation)?
43        } else {
44            self.parse_sass_bin_expr(/* allow_comparison */ true)?
45        };
46
47        let mut elements = arena_vec!(self);
48        let mut comma_spans: Option<oxc_allocator::Vec<'a, Span>> = None;
49        let mut separator = ListSeparatorKind::Unknown;
50        let mut end = single_value.span().end;
51        loop {
52            match peek!(self).token {
53                Token::LBrace(..)
54                | Token::RBrace(..)
55                | Token::RParen(..)
56                | Token::Semicolon(..)
57                | Token::Colon(..)
58                | Token::DotDotDot(..)
59                | Token::Eof(..) => break,
60                Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..) => {
61                    if comma_spans.as_ref().is_none_or(|spans| spans.is_empty())
62                        || self.state.sass_ctx & SASS_CTX_IN_PARENS == 0
63                    {
64                        break;
65                    } else {
66                        bump!(self);
67                    }
68                }
69                Token::Comma(..) => {
70                    if !allow_comma {
71                        break;
72                    }
73                    if separator == ListSeparatorKind::Space {
74                        break;
75                    } else {
76                        if separator == ListSeparatorKind::Unknown {
77                            separator = ListSeparatorKind::Comma;
78                        }
79                        let TokenWithSpan { span, .. } = bump!(self);
80                        end = span.end;
81                        if let Some(spans) = &mut comma_spans {
82                            spans.push(span);
83                        } else {
84                            comma_spans = Some(arena_vec!(self; span));
85                        }
86                    }
87                }
88                Token::Exclamation(..) => {
89                    if let Ok(important_annotation) = self.try_parse(ImportantAnnotation::parse) {
90                        if end < important_annotation.span.start
91                            && matches!(separator, ListSeparatorKind::Unknown)
92                        {
93                            separator = ListSeparatorKind::Space;
94                        }
95                        end = important_annotation.span.end;
96                        elements.push(ComponentValue::ImportantAnnotation(important_annotation));
97                    } else {
98                        break;
99                    }
100                }
101                _ => {
102                    if separator == ListSeparatorKind::Unknown {
103                        separator = ListSeparatorKind::Space;
104                    }
105                    let item = if separator == ListSeparatorKind::Comma {
106                        self.parse_maybe_sass_list(false)?
107                    } else {
108                        self.parse_sass_bin_expr(/* allow_comparison */ true)?
109                    };
110                    end = item.span().end;
111                    elements.push(item);
112                }
113            }
114        }
115
116        if elements.is_empty() && separator != ListSeparatorKind::Comma {
117            // If there is a trailing comma it can be a Sass list,
118            // though there is only one element.
119            Ok(single_value)
120        } else {
121            debug_assert_ne!(separator, ListSeparatorKind::Unknown);
122
123            let span = Span { start: single_value.span().start, end };
124            elements.insert(0, single_value);
125            Ok(ComponentValue::SassList(SassList { elements, comma_spans, span }))
126        }
127    }
128
129    pub(super) fn parse_sass_bin_expr(
130        &mut self,
131        allow_comparison: bool,
132    ) -> PResult<ComponentValue<'a>> {
133        debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
134        self.parse_sass_bin_expr_with_min_precedence(PRECEDENCE_OR, allow_comparison)
135    }
136
137    fn parse_sass_bin_expr_with_min_precedence(
138        &mut self,
139        min_precedence: u8,
140        allow_comparison: bool,
141    ) -> PResult<ComponentValue<'a>> {
142        let mut left = self.parse_sass_unary_expression()?;
143
144        // delimiter can't be calculated
145        if matches!(left, ComponentValue::Delimiter(..)) {
146            return Ok(left);
147        }
148
149        loop {
150            if PRECEDENCE_PLUS >= min_precedence {
151                match peek!(self) {
152                    TokenWithSpan { token: Token::Number(token), span }
153                        if token.raw.starts_with('+')
154                            || token.raw.starts_with('-') && span.start == left.span().end =>
155                    {
156                        let (number, number_span) = expect!(self, Number);
157                        let op = SassBinaryOperator {
158                            kind: if number.raw.starts_with('+') {
159                                SassBinaryOperatorKind::Plus
160                            } else {
161                                SassBinaryOperatorKind::Minus
162                            },
163                            span: Span { start: number_span.start, end: number_span.start + 1 },
164                        };
165                        let span = Span { start: left.span().start, end: number_span.end };
166                        let right = {
167                            let span = Span { start: number_span.start + 1, end: number_span.end };
168                            let raw = unsafe { number.raw.get_unchecked(1..number.raw.len()) };
169                            raw.parse()
170                                .map_err(|_| Error {
171                                    kind: ErrorKind::InvalidNumber,
172                                    span: span.clone(),
173                                })
174                                .map(|value| ComponentValue::Number(Number { value, raw, span }))?
175                        };
176                        left = ComponentValue::SassBinaryExpression(SassBinaryExpression {
177                            left: arena_box!(self, left),
178                            op,
179                            right: arena_box!(self, right),
180                            span,
181                        });
182                        continue;
183                    }
184                    TokenWithSpan { token: Token::Dimension(token), span }
185                        if token.value.raw.starts_with('+')
186                            || token.value.raw.starts_with('-')
187                                && span.start == left.span().end =>
188                    {
189                        let (dimension, dimension_span) = expect!(self, Dimension);
190                        let op = SassBinaryOperator {
191                            kind: if dimension.value.raw.starts_with('+') {
192                                SassBinaryOperatorKind::Plus
193                            } else {
194                                SassBinaryOperatorKind::Minus
195                            },
196                            span: Span {
197                                start: dimension_span.start,
198                                end: dimension_span.start + 1,
199                            },
200                        };
201                        let span = Span { start: left.span().start, end: dimension_span.end };
202                        let right = {
203                            self.dimension(
204                                crate::token::Dimension {
205                                    value: crate::token::Number {
206                                        raw: unsafe {
207                                            dimension
208                                                .value
209                                                .raw
210                                                .get_unchecked(1..dimension.value.raw.len())
211                                        },
212                                    },
213                                    unit: dimension.unit,
214                                },
215                                Span { start: dimension_span.start + 1, end: dimension_span.end },
216                            )
217                            .map(ComponentValue::Dimension)?
218                        };
219                        left = ComponentValue::SassBinaryExpression(SassBinaryExpression {
220                            left: arena_box!(self, left),
221                            op,
222                            right: arena_box!(self, right),
223                            span,
224                        });
225                        continue;
226                    }
227                    _ => {}
228                }
229            }
230
231            let (operator, precedence) = match peek!(self) {
232                TokenWithSpan { token: Token::Asterisk(..), .. }
233                    if PRECEDENCE_MULTIPLY >= min_precedence =>
234                {
235                    (
236                        SassBinaryOperator {
237                            kind: SassBinaryOperatorKind::Multiply,
238                            span: bump!(self).span,
239                        },
240                        PRECEDENCE_MULTIPLY,
241                    )
242                }
243                TokenWithSpan { token: Token::Solidus(..), .. }
244                    if PRECEDENCE_MULTIPLY >= min_precedence
245                        && (self.state.sass_ctx & SASS_CTX_ALLOW_DIV != 0
246                            || matches!(
247                                left,
248                                ComponentValue::SassParenthesizedExpression(..)
249                                    | ComponentValue::SassBinaryExpression(..)
250                                    | ComponentValue::SassUnaryExpression(..)
251                                    | ComponentValue::SassVariable(..)
252                                    | ComponentValue::Function(..)
253                                    | ComponentValue::SassQualifiedName(..)
254                            )) =>
255                {
256                    (
257                        SassBinaryOperator {
258                            kind: SassBinaryOperatorKind::Division,
259                            span: bump!(self).span,
260                        },
261                        PRECEDENCE_MULTIPLY,
262                    )
263                }
264                TokenWithSpan { token: Token::Percent(..), .. }
265                    if PRECEDENCE_MULTIPLY >= min_precedence =>
266                {
267                    (
268                        SassBinaryOperator {
269                            kind: SassBinaryOperatorKind::Modulo,
270                            span: bump!(self).span,
271                        },
272                        PRECEDENCE_MULTIPLY,
273                    )
274                }
275                TokenWithSpan { token: Token::Plus(..), .. }
276                    if PRECEDENCE_PLUS >= min_precedence =>
277                {
278                    (
279                        SassBinaryOperator {
280                            kind: SassBinaryOperatorKind::Plus,
281                            span: bump!(self).span,
282                        },
283                        PRECEDENCE_PLUS,
284                    )
285                }
286                TokenWithSpan { token: Token::Minus(..), .. }
287                    if PRECEDENCE_PLUS >= min_precedence =>
288                {
289                    (
290                        SassBinaryOperator {
291                            kind: SassBinaryOperatorKind::Minus,
292                            span: bump!(self).span,
293                        },
294                        PRECEDENCE_PLUS,
295                    )
296                }
297                TokenWithSpan { token: Token::GreaterThan(..), .. }
298                    if allow_comparison && PRECEDENCE_RELATIONAL >= min_precedence =>
299                {
300                    (
301                        SassBinaryOperator {
302                            kind: SassBinaryOperatorKind::GreaterThan,
303                            span: bump!(self).span,
304                        },
305                        PRECEDENCE_RELATIONAL,
306                    )
307                }
308                TokenWithSpan { token: Token::GreaterThanEqual(..), .. }
309                    if allow_comparison && PRECEDENCE_RELATIONAL >= min_precedence =>
310                {
311                    (
312                        SassBinaryOperator {
313                            kind: SassBinaryOperatorKind::GreaterThanOrEqual,
314                            span: bump!(self).span,
315                        },
316                        PRECEDENCE_RELATIONAL,
317                    )
318                }
319                TokenWithSpan { token: Token::LessThan(..), .. }
320                    if allow_comparison && PRECEDENCE_RELATIONAL >= min_precedence =>
321                {
322                    (
323                        SassBinaryOperator {
324                            kind: SassBinaryOperatorKind::LessThan,
325                            span: bump!(self).span,
326                        },
327                        PRECEDENCE_RELATIONAL,
328                    )
329                }
330                TokenWithSpan { token: Token::LessThanEqual(..), .. }
331                    if allow_comparison && PRECEDENCE_RELATIONAL >= min_precedence =>
332                {
333                    (
334                        SassBinaryOperator {
335                            kind: SassBinaryOperatorKind::LessThanOrEqual,
336                            span: bump!(self).span,
337                        },
338                        PRECEDENCE_RELATIONAL,
339                    )
340                }
341                TokenWithSpan { token: Token::EqualEqual(..), .. }
342                    if PRECEDENCE_EQUALITY >= min_precedence =>
343                {
344                    (
345                        SassBinaryOperator {
346                            kind: SassBinaryOperatorKind::EqualsEquals,
347                            span: bump!(self).span,
348                        },
349                        PRECEDENCE_EQUALITY,
350                    )
351                }
352                TokenWithSpan { token: Token::ExclamationEqual(..), .. }
353                    if PRECEDENCE_EQUALITY >= min_precedence =>
354                {
355                    (
356                        SassBinaryOperator {
357                            kind: SassBinaryOperatorKind::ExclamationEquals,
358                            span: bump!(self).span,
359                        },
360                        PRECEDENCE_EQUALITY,
361                    )
362                }
363                TokenWithSpan { token: Token::Ident(token), .. }
364                    if token.raw == "and" && PRECEDENCE_AND >= min_precedence =>
365                {
366                    (
367                        SassBinaryOperator {
368                            kind: SassBinaryOperatorKind::And,
369                            span: bump!(self).span,
370                        },
371                        PRECEDENCE_AND,
372                    )
373                }
374                TokenWithSpan { token: Token::Ident(token), .. }
375                    if token.raw == "or" && PRECEDENCE_OR >= min_precedence =>
376                {
377                    (
378                        SassBinaryOperator {
379                            kind: SassBinaryOperatorKind::Or,
380                            span: bump!(self).span,
381                        },
382                        PRECEDENCE_OR,
383                    )
384                }
385                _ => break,
386            };
387
388            let right =
389                self.parse_sass_bin_expr_with_min_precedence(precedence + 1, allow_comparison)?;
390            // delimiter can't be calculated
391            if let ComponentValue::Delimiter(Delimiter { span, .. }) = right {
392                return Err(Error { kind: ErrorKind::ExpectSassExpression, span });
393            }
394
395            let span = Span { start: left.span().start, end: right.span().end };
396            left = ComponentValue::SassBinaryExpression(SassBinaryExpression {
397                left: arena_box!(self, left),
398                op: operator,
399                right: arena_box!(self, right),
400                span,
401            });
402        }
403
404        Ok(left)
405    }
406
407    fn parse_sass_flags(
408        &mut self,
409    ) -> PResult<(oxc_allocator::Vec<'a, SassFlag<'a>>, Option<usize>)> {
410        let mut flags: oxc_allocator::Vec<'a, SassFlag<'a>> = self.vec_with_capacity(1);
411        let mut end = None;
412        while let Some((_, exclamation_span)) = eat!(self, Exclamation) {
413            let keyword = self.parse::<Ident>()?;
414            let keyword_span = keyword.span.clone();
415            util::assert_no_ws_or_comment(&exclamation_span, &keyword_span)?;
416            end = Some(keyword_span.end);
417
418            match keyword.name {
419                "default" => {
420                    if flags.iter().any(|flag| flag.keyword.name == "default") {
421                        self.recoverable_errors.push(Error {
422                            kind: ErrorKind::DuplicatedSassFlag("default"),
423                            span: Span { start: exclamation_span.start, end: keyword.span.end },
424                        });
425                    }
426                }
427                "global" => {
428                    if flags.iter().any(|flag| flag.keyword.name == "global") {
429                        self.recoverable_errors.push(Error {
430                            kind: ErrorKind::DuplicatedSassFlag("global"),
431                            span: Span { start: exclamation_span.start, end: keyword.span.end },
432                        });
433                    }
434                }
435                _ => self.recoverable_errors.push(Error {
436                    kind: ErrorKind::InvalidSassFlagName(keyword.name.to_string()),
437                    span: keyword.span.clone(),
438                }),
439            }
440
441            flags.push(SassFlag {
442                keyword,
443                span: Span { start: exclamation_span.start, end: keyword_span.end },
444            });
445        }
446
447        Ok((flags, end))
448    }
449
450    pub(super) fn parse_sass_interpolated_ident(&mut self) -> PResult<InterpolableIdent<'a>> {
451        debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
452
453        let (first, Span { start, mut end }) = match peek!(self) {
454            TokenWithSpan { token: Token::Ident(..), .. } => {
455                let (ident, ident_span) = expect!(self, Ident);
456                (
457                    SassInterpolatedIdentElement::Static(
458                        self.interpolable_ident_static_part(ident, ident_span.clone()),
459                    ),
460                    ident_span,
461                )
462            }
463            TokenWithSpan { token: Token::HashLBrace(..), .. } => {
464                self.parse_sass_interpolated_ident_expr()?
465            }
466            TokenWithSpan { token, span } => {
467                use crate::{
468                    token::{HashLBrace, Ident},
469                    tokenizer::TokenSymbol,
470                };
471                return Err(Error {
472                    kind: ErrorKind::ExpectOneOf(
473                        vec![Ident::symbol(), HashLBrace::symbol()],
474                        token.symbol(),
475                    ),
476                    span: span.clone(),
477                });
478            }
479        };
480
481        let mut elements = self.parse_sass_interpolated_ident_rest(&mut end)?;
482        if elements.is_empty()
483            && let SassInterpolatedIdentElement::Static(ident) = first
484        {
485            return Ok(InterpolableIdent::Literal(Ident {
486                name: ident.value,
487                raw: ident.raw,
488                span: ident.span,
489            }));
490        }
491
492        elements.insert(0, first);
493        Ok(InterpolableIdent::SassInterpolated(SassInterpolatedIdent {
494            elements,
495            span: Span { start, end },
496        }))
497    }
498
499    pub(super) fn parse_sass_interpolated_ident_rest(
500        &mut self,
501        end: &mut usize,
502    ) -> PResult<oxc_allocator::Vec<'a, SassInterpolatedIdentElement<'a>>> {
503        let mut elements = arena_vec!(self);
504        loop {
505            if let Some((token, span)) = self.tokenizer.scan_ident_template()? {
506                *end = span.end;
507                elements.push(SassInterpolatedIdentElement::Static(
508                    self.interpolable_ident_static_part(token, span),
509                ));
510            } else if matches!(
511                peek!(self),
512                TokenWithSpan { token: Token::HashLBrace(..), span } if *end == span.start
513            ) {
514                let (element, span) = self.parse_sass_interpolated_ident_expr()?;
515                *end = span.end;
516                elements.push(element);
517            } else {
518                return Ok(elements);
519            }
520        }
521    }
522
523    fn parse_sass_interpolated_ident_expr(
524        &mut self,
525    ) -> PResult<(SassInterpolatedIdentElement<'a>, Span)> {
526        debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
527
528        let start = expect!(self, HashLBrace).1.start;
529        let expr = self.parse_maybe_sass_list(/* allow_comma */ true)?;
530        let end = expect!(self, RBrace).1.end;
531        Ok((SassInterpolatedIdentElement::Expression(expr), Span { start, end }))
532    }
533
534    fn parse_sass_invocation_args(
535        &mut self,
536    ) -> PResult<(oxc_allocator::Vec<'a, ComponentValue<'a>>, oxc_allocator::Vec<'a, Span>)> {
537        debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
538
539        let mut values = self.vec_with_capacity(4);
540        let mut comma_spans = arena_vec!(self);
541        while !matches!(peek!(self).token, Token::RParen(..) | Token::Eof(..)) {
542            match peek!(self).token {
543                Token::Comma(..) => {
544                    let TokenWithSpan { span, .. } = bump!(self);
545                    self.recoverable_errors
546                        .push(Error { kind: ErrorKind::ExpectComponentValue, span });
547                    continue;
548                }
549                _ => {
550                    let value = self.parse_maybe_sass_list(/* allow_comma */ false)?;
551                    if let Some((_, span)) = eat!(self, DotDotDot) {
552                        let span = Span { start: value.span().start, end: span.end };
553                        values.push(ComponentValue::SassArbitraryArgument(SassArbitraryArgument {
554                            value: arena_box!(self, value),
555                            span,
556                        }));
557                    } else if let ComponentValue::SassVariable(sass_var) = value {
558                        if let Some((_, colon_span)) = eat!(self, Colon) {
559                            let value = self.parse_maybe_sass_list(/* allow_comma */ false)?;
560                            let span = Span { start: sass_var.span.start, end: value.span().end };
561                            values.push(ComponentValue::SassKeywordArgument(SassKeywordArgument {
562                                name: sass_var,
563                                colon_span,
564                                value: arena_box!(self, value),
565                                span,
566                            }));
567                        } else {
568                            values.push(ComponentValue::SassVariable(sass_var));
569                        }
570                    } else {
571                        values.push(value);
572                    }
573                }
574            }
575            if !matches!(peek!(self).token, Token::RParen(..) | Token::Eof(..)) {
576                comma_spans.push(expect!(self, Comma).1);
577            }
578        }
579        debug_assert!(values.len() - comma_spans.len() <= 1);
580        Ok((values, comma_spans))
581    }
582
583    fn parse_sass_module_config(
584        &mut self,
585        allow_overridable: bool,
586    ) -> PResult<Option<SassModuleConfig<'a>>> {
587        match &peek!(self).token {
588            Token::Ident(ident) if ident.name().eq_ignore_ascii_case("with") => {
589                let TokenWithSpan { span: with_span, .. } = bump!(self);
590                let start = with_span.start;
591                let end;
592                let (_, lparen_span) = expect!(self, LParen);
593
594                let mut items =
595                    arena_vec!(self; self.parse_sass_module_config_item(allow_overridable)?);
596                let mut comma_spans = arena_vec!(self);
597                if let Some((_, span)) = eat!(self, RParen) {
598                    end = span.end;
599                } else {
600                    comma_spans.push(expect!(self, Comma).1);
601                    loop {
602                        if let Some((_, span)) = eat!(self, RParen) {
603                            end = span.end;
604                            break;
605                        }
606
607                        items.push(self.parse_sass_module_config_item(allow_overridable)?);
608                        if let Some((_, span)) = eat!(self, RParen) {
609                            end = span.end;
610                            break;
611                        } else {
612                            comma_spans.push(expect!(self, Comma).1);
613                        }
614                    }
615                }
616                debug_assert!(items.len() - comma_spans.len() <= 1);
617
618                Ok(Some(SassModuleConfig {
619                    with_span,
620                    lparen_span,
621                    items,
622                    comma_spans,
623                    span: Span { start, end },
624                }))
625            }
626            _ => Ok(None),
627        }
628    }
629
630    fn parse_sass_module_config_item(
631        &mut self,
632        allow_overridable: bool,
633    ) -> PResult<SassModuleConfigItem<'a>> {
634        let variable = self.parse::<SassVariable>()?;
635        let (_, colon_span) = expect!(self, Colon);
636        let value = self.parse_maybe_sass_list(/* allow_comma */ false)?;
637
638        let (flags, end) = if allow_overridable {
639            self.parse_sass_flags()
640                .map(|(flags, end)| (flags, end.unwrap_or_else(|| value.span().end)))?
641        } else {
642            (arena_vec!(self), value.span().end)
643        };
644
645        let span = Span { start: variable.span.start, end };
646        Ok(SassModuleConfigItem { variable, colon_span, value, flags, span })
647    }
648
649    /// This method will consume `)` token.
650    fn parse_sass_params(&mut self) -> PResult<SassParams<'a>> {
651        let mut parameters = arena_vec!(self);
652        let mut arbitrary_parameter = None;
653        let mut comma_spans = arena_vec!(self);
654        let end;
655        loop {
656            if let Some((_, span)) = eat!(self, RParen) {
657                end = span.end;
658                break;
659            }
660
661            let name = self.parse::<SassVariable>()?;
662            let token_with_span = bump!(self);
663            match token_with_span.token {
664                Token::Comma(..) => {
665                    let span = name.span.clone();
666                    parameters.push(SassParameter { name, default_value: None, span });
667                    comma_spans.push(token_with_span.span);
668                    continue;
669                }
670                Token::Colon(..) => {
671                    let value = self.parse_maybe_sass_list(/* allow_comma */ false)?;
672                    let end = value.span().end;
673                    let default_value_span = Span { start: token_with_span.span.start, end };
674                    let span = Span { start: name.span.start, end };
675                    parameters.push(SassParameter {
676                        name,
677                        default_value: Some(SassParameterDefaultValue {
678                            colon_span: token_with_span.span,
679                            value,
680                            span: default_value_span,
681                        }),
682                        span,
683                    });
684                }
685                Token::DotDotDot(..) => {
686                    let span = Span { start: name.span().start, end: token_with_span.span.end };
687                    arbitrary_parameter = Some(SassArbitraryParameter { name, span });
688                    if let Some((_, comma_span)) = eat!(self, Comma) {
689                        comma_spans.push(comma_span);
690                    }
691                    end = expect!(self, RParen).1.end;
692                    break;
693                }
694                Token::RParen(..) => {
695                    let span = name.span.clone();
696                    parameters.push(SassParameter { name, default_value: None, span });
697                    end = token_with_span.span.end;
698                    break;
699                }
700                token => {
701                    return Err(Error {
702                        kind: ErrorKind::Unexpected(")", token.symbol()),
703                        span: token_with_span.span,
704                    });
705                }
706            }
707            if let Some((_, span)) = eat!(self, RParen) {
708                end = span.end;
709                break;
710            } else {
711                comma_spans.push(expect!(self, Comma).1);
712            }
713        }
714
715        debug_assert!(
716            parameters.len() + arbitrary_parameter.iter().count() - comma_spans.len() <= 1
717        );
718        Ok((parameters, arbitrary_parameter, comma_spans, end))
719    }
720
721    pub(super) fn parse_sass_qualified_name(
722        &mut self,
723        module: Ident<'a>,
724    ) -> PResult<SassQualifiedName<'a>> {
725        debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
726
727        let (_, dot_span) = expect!(self, Dot);
728        let member = if let Token::DollarVar(..) = peek!(self).token {
729            self.parse().map(SassModuleMemberName::Variable)?
730        } else {
731            self.parse().map(SassModuleMemberName::Ident)?
732        };
733
734        let expr_span = member.span();
735        util::assert_no_ws_or_comment(&dot_span, expr_span)?;
736
737        let span = Span { start: module.span.start, end: expr_span.end };
738        Ok(SassQualifiedName { module, member, span })
739    }
740
741    fn parse_sass_unary_expression(&mut self) -> PResult<ComponentValue<'a>> {
742        let op = match &peek!(self).token {
743            Token::Plus(..) => {
744                SassUnaryOperator { kind: SassUnaryOperatorKind::Plus, span: bump!(self).span }
745            }
746            Token::Minus(..) => {
747                SassUnaryOperator { kind: SassUnaryOperatorKind::Minus, span: bump!(self).span }
748            }
749            Token::Ident(token) if token.raw == "not" => {
750                SassUnaryOperator { kind: SassUnaryOperatorKind::Not, span: bump!(self).span }
751            }
752            _ => return self.parse_component_value_atom(),
753        };
754
755        let expr = self.parse_sass_unary_expression()?;
756        let span = Span { start: op.span.start, end: expr.span().end };
757        Ok(ComponentValue::SassUnaryExpression(SassUnaryExpression {
758            expr: arena_box!(self, expr),
759            op,
760            span,
761        }))
762    }
763}
764
765impl<'a> Parse<'a> for SassAtRoot<'a> {
766    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
767        let kind = if matches!(peek!(input).token, Token::LParen(..)) {
768            SassAtRootKind::Query(input.parse()?)
769        } else {
770            SassAtRootKind::Selector(input.parse()?)
771        };
772
773        let span = kind.span().clone();
774        Ok(SassAtRoot { kind, span })
775    }
776}
777
778impl<'a> Parse<'a> for SassAtRootQuery<'a> {
779    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
780        let start = expect!(input, LParen).1.start;
781
782        let modifier = {
783            let (token, span) = expect!(input, Ident);
784            let ident_name = token.name();
785            if ident_name.eq_ignore_ascii_case("with") {
786                SassAtRootQueryModifier { kind: SassAtRootQueryModifierKind::With, span }
787            } else if ident_name.eq_ignore_ascii_case("without") {
788                SassAtRootQueryModifier { kind: SassAtRootQueryModifierKind::Without, span }
789            } else {
790                return Err(Error { kind: ErrorKind::ExpectSassAtRootWithOrWithout, span });
791            }
792        };
793        let colon_span = expect!(input, Colon).1;
794
795        let mut rules = input.vec_with_capacity(1);
796        loop {
797            match &peek!(input).token {
798                Token::Ident(..) | Token::HashLBrace(..) => {
799                    rules.push(SassAtRootQueryRule::Ident(input.parse()?));
800                }
801                Token::Str(..) | Token::StrTemplate(..) => {
802                    rules.push(SassAtRootQueryRule::Str(input.parse()?));
803                }
804                _ => break,
805            }
806        }
807        let end = expect!(input, RParen).1.end;
808
809        Ok(SassAtRootQuery { modifier, colon_span, rules, span: Span { start, end } })
810    }
811}
812
813impl<'a> Parse<'a> for SassConditionalClause<'a> {
814    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
815        let condition = input.parse::<ComponentValue>()?;
816        let block = input.parse::<SimpleBlock>()?;
817        let span = Span { start: condition.span().start, end: block.span.end };
818        Ok(SassConditionalClause { condition, block, span })
819    }
820}
821
822impl<'a> Parse<'a> for SassContent<'a> {
823    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
824        let (_, Span { start, .. }) = expect!(input, LParen);
825        let (args, comma_spans) = input.parse_sass_invocation_args()?;
826        let (_, Span { end, .. }) = expect!(input, RParen);
827        Ok(SassContent { args, comma_spans, span: Span { start, end } })
828    }
829}
830
831impl<'a> Parse<'a> for SassEach<'a> {
832    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
833        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
834
835        let first_binding = input.parse::<SassVariable>()?;
836        let start = first_binding.span().start;
837
838        let mut bindings = arena_vec!(input; first_binding);
839        let mut comma_spans = arena_vec!(input);
840        while let Some((_, comma_span)) = eat!(input, Comma) {
841            comma_spans.push(comma_span);
842            bindings.push(input.parse()?);
843        }
844        debug_assert_eq!(comma_spans.len() + 1, bindings.len());
845
846        let (keyword_in, keyword_in_span) = expect!(input, Ident);
847        if keyword_in.name() != "in" {
848            return Err(Error { kind: ErrorKind::ExpectSassKeyword("in"), span: keyword_in_span });
849        }
850
851        let expr = input.parse_maybe_sass_list(/* allow_comma */ true)?;
852        let span = Span { start, end: expr.span().end };
853        Ok(SassEach { bindings, comma_spans, in_span: keyword_in_span, expr, span })
854    }
855}
856
857impl<'a> Parse<'a> for SassExtend<'a> {
858    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
859        let selectors = input.parse::<CompoundSelectorList>()?;
860        let start = selectors.span.start;
861        let mut end = selectors.span.end;
862
863        let optional = if let Some((_, exclamation_span)) = eat!(input, Exclamation) {
864            let (keyword, keyword_span) = expect_without_ws_or_comments!(input, Ident);
865            if keyword.name().eq_ignore_ascii_case("optional") {
866                end = keyword_span.end;
867                let span = Span { start: exclamation_span.start, end: keyword_span.end };
868                Some(SassFlag { keyword: input.ident(keyword, keyword_span), span })
869            } else {
870                input.recoverable_errors.push(Error {
871                    kind: ErrorKind::ExpectSassKeyword("optional"),
872                    span: keyword_span,
873                });
874                None
875            }
876        } else {
877            None
878        };
879
880        Ok(SassExtend { selectors, optional, span: Span { start, end } })
881    }
882}
883
884impl<'a> Parse<'a> for SassFor<'a> {
885    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
886        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
887
888        let binding = input.parse::<SassVariable>()?;
889
890        let (keyword_from, keyword_from_span) = expect!(input, Ident);
891        if keyword_from.name() != "from" {
892            return Err(Error {
893                kind: ErrorKind::ExpectSassKeyword("from"),
894                span: keyword_from_span,
895            });
896        }
897
898        let start = input.parse()?;
899        let boundary = input.parse()?;
900        let end = input.parse::<ComponentValue>()?;
901
902        let span = Span { start: binding.span.start, end: end.span().end };
903        Ok(SassFor { binding, from_span: keyword_from_span, start, end, boundary, span })
904    }
905}
906
907impl<'a> Parse<'a> for SassForBoundary {
908    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
909        let (keyword, span) = expect!(input, Ident);
910        match &*keyword.name() {
911            "to" => Ok(SassForBoundary { kind: SassForBoundaryKind::Exclusive, span }),
912            "through" => Ok(SassForBoundary { kind: SassForBoundaryKind::Inclusive, span }),
913            _ => Err(Error { kind: ErrorKind::ExpectSassKeyword("to"), span }),
914        }
915    }
916}
917
918impl<'a> Parse<'a> for SassForward<'a> {
919    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
920        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
921
922        let path = input.parse::<InterpolableStr>()?;
923        let mut span = path.span().clone();
924
925        let prefix = match &peek!(input).token {
926            Token::Ident(ident) if ident.name().eq_ignore_ascii_case("as") => {
927                let TokenWithSpan { span: as_span, .. } = bump!(input);
928                let name = input.parse()?;
929                let (_, Span { end, .. }) = expect_without_ws_or_comments!(input, Asterisk);
930                let span = Span { start: as_span.start, end };
931                Some(SassForwardPrefix { as_span, name, span })
932            }
933            _ => None,
934        };
935
936        let visibility = if let TokenWithSpan { token: Token::Ident(keyword), span: keyword_span } =
937            peek!(input)
938        {
939            let start = keyword_span.start;
940            let name = keyword.name();
941            if name.eq_ignore_ascii_case("hide") {
942                let keyword_span = bump!(input).span;
943                let mut members = arena_vec!(input);
944                let mut comma_spans = arena_vec!(input);
945                loop {
946                    match &peek!(input).token {
947                        Token::Ident(..) => {
948                            members.push(input.parse().map(SassForwardMember::Ident)?)
949                        }
950                        _ => members.push(input.parse().map(SassForwardMember::Variable)?),
951                    }
952                    if let Some((_, span)) = eat!(input, Comma) {
953                        comma_spans.push(span);
954                    } else {
955                        break;
956                    }
957                }
958                Some(SassForwardVisibility {
959                    modifier: SassForwardVisibilityModifier {
960                        kind: SassForwardVisibilityModifierKind::Hide,
961                        span: keyword_span,
962                    },
963                    members,
964                    comma_spans,
965                    span: Span { start, end: input.tokenizer.current_offset() },
966                })
967            } else if name.eq_ignore_ascii_case("show") {
968                let keyword_span = bump!(input).span;
969                let mut members = arena_vec!(input);
970                let mut comma_spans = arena_vec!(input);
971                loop {
972                    match &peek!(input).token {
973                        Token::Ident(..) => {
974                            members.push(input.parse().map(SassForwardMember::Ident)?)
975                        }
976                        _ => members.push(input.parse().map(SassForwardMember::Variable)?),
977                    }
978                    if let Some((_, span)) = eat!(input, Comma) {
979                        comma_spans.push(span);
980                    } else {
981                        break;
982                    }
983                }
984                Some(SassForwardVisibility {
985                    modifier: SassForwardVisibilityModifier {
986                        kind: SassForwardVisibilityModifierKind::Show,
987                        span: keyword_span,
988                    },
989                    members,
990                    comma_spans,
991                    span: Span { start, end: input.tokenizer.current_offset() },
992                })
993            } else {
994                None
995            }
996        } else {
997            None
998        };
999
1000        let config = input.parse_sass_module_config(/* allow_overridable */ true)?;
1001        if let Some(config) = &config {
1002            span.end = config.span.end;
1003        }
1004
1005        Ok(SassForward { path, prefix, visibility, config, span })
1006    }
1007}
1008
1009impl<'a> Parse<'a> for SassFunction<'a> {
1010    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1011        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1012
1013        let name = input.parse::<Ident>()?;
1014        let start = name.span.start;
1015
1016        let parameters = input.parse::<SassParameters>()?;
1017
1018        let span = Span { start, end: parameters.span.end };
1019        Ok(SassFunction { name, parameters, span })
1020    }
1021}
1022
1023impl<'a> Parse<'a> for SassIfAtRule<'a> {
1024    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1025        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1026
1027        let start = expect!(input, AtKeyword).1.start;
1028
1029        let if_clause = input.parse::<SassConditionalClause>()?;
1030        let mut else_if_clauses: oxc_allocator::Vec<'a, SassConditionalClause<'a>> = input.vec();
1031        let mut else_clause: Option<SimpleBlock> = None;
1032        let mut else_spans = arena_vec!(input);
1033
1034        while let Token::AtKeyword(at_keyword) = &peek!(input).token {
1035            match &*at_keyword.ident.name() {
1036                "else" => {
1037                    else_spans.push(bump!(input).span);
1038                    match &peek!(input).token {
1039                        Token::Ident(ident) if ident.name() == "if" => {
1040                            bump!(input);
1041                            else_if_clauses.push(input.parse()?);
1042                        }
1043                        _ => {
1044                            else_clause = Some(input.parse()?);
1045                            break;
1046                        }
1047                    }
1048                }
1049                // `elseif` is deprecated by Sass
1050                "elseif" => {
1051                    else_spans.push(bump!(input).span);
1052                    else_if_clauses.push(input.parse()?);
1053                }
1054                _ => break,
1055            }
1056        }
1057
1058        debug_assert_eq!(else_spans.len(), else_if_clauses.len() + else_clause.iter().count());
1059        let span = Span {
1060            start,
1061            end: else_clause
1062                .as_ref()
1063                .map(|else_clause| else_clause.span.end)
1064                .or_else(|| else_if_clauses.last().map(|else_if_clause| else_if_clause.span.end))
1065                .unwrap_or(if_clause.span.end),
1066        };
1067        Ok(SassIfAtRule { if_clause, else_if_clauses, else_clause, else_spans, span })
1068    }
1069}
1070
1071impl<'a> Parse<'a> for SassImportPrelude<'a> {
1072    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1073        let first = input.parse::<Str>()?;
1074        let mut span = first.span.clone();
1075
1076        let mut paths = arena_vec!(input; first);
1077        let mut comma_spans = arena_vec!(input);
1078        while let Some((_, comma_span)) = eat!(input, Comma) {
1079            comma_spans.push(comma_span);
1080            paths.push(input.parse()?);
1081        }
1082        debug_assert_eq!(comma_spans.len() + 1, paths.len());
1083
1084        if let Some(Str { span: Span { end, .. }, .. }) = paths.last() {
1085            span.end = *end;
1086        }
1087        Ok(SassImportPrelude { paths, comma_spans, span })
1088    }
1089}
1090
1091impl<'a> Parse<'a> for SassInclude<'a> {
1092    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1093        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1094
1095        let name = input.parse::<FunctionName>()?;
1096        let mut span = name.span().clone();
1097
1098        let arguments = if matches!(peek!(input).token, Token::LParen(..)) {
1099            let arguments = input.parse::<SassIncludeArgs>()?;
1100            span.end = arguments.span.end;
1101            Some(arguments)
1102        } else {
1103            None
1104        };
1105
1106        let content_block_params = match &peek!(input).token {
1107            Token::Ident(ident) if ident.name().eq_ignore_ascii_case("using") => {
1108                let content_block_params = input.parse::<SassIncludeContentBlockParams>()?;
1109                span.end = content_block_params.span.end;
1110                Some(content_block_params)
1111            }
1112            _ => None,
1113        };
1114
1115        Ok(SassInclude { name, arguments, content_block_params, span })
1116    }
1117}
1118
1119impl<'a> Parse<'a> for SassIncludeArgs<'a> {
1120    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1121        let (_, Span { start, .. }) = expect!(input, LParen);
1122        let (args, comma_spans) = input.parse_sass_invocation_args()?;
1123        let (_, Span { end, .. }) = expect!(input, RParen);
1124        Ok(SassIncludeArgs { args, comma_spans, span: Span { start, end } })
1125    }
1126}
1127
1128impl<'a> Parse<'a> for SassIncludeContentBlockParams<'a> {
1129    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1130        match bump!(input) {
1131            TokenWithSpan { token: Token::Ident(ident), span: using_span }
1132                if ident.name().eq_ignore_ascii_case("using") =>
1133            {
1134                let params = input.parse::<SassParameters>()?;
1135                let span = Span { start: using_span.start, end: params.span.end };
1136                Ok(SassIncludeContentBlockParams { using_span, params, span })
1137            }
1138            TokenWithSpan { span, .. } => {
1139                Err(Error { kind: ErrorKind::ExpectSassKeyword("using"), span })
1140            }
1141        }
1142    }
1143}
1144
1145impl<'a> Parse<'a> for SassInterpolatedStr<'a> {
1146    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1147        let (first, first_span) = expect!(input, StrTemplate);
1148        let quote = first.raw.chars().next().unwrap();
1149        debug_assert!(quote == '\'' || quote == '"');
1150        let mut span = first_span.clone();
1151        let first = input.interpolable_str_static_part(first, first_span);
1152        let mut elements = arena_vec!(input; SassInterpolatedStrElement::Static(first));
1153
1154        let mut is_parsing_static_part = false;
1155        loop {
1156            if is_parsing_static_part {
1157                let (token, str_tpl_span) = input.tokenizer.scan_string_template(quote)?;
1158                let tail = token.tail;
1159                let end = str_tpl_span.end;
1160                elements.push(SassInterpolatedStrElement::Static(
1161                    input.interpolable_str_static_part(token, str_tpl_span),
1162                ));
1163                if tail {
1164                    span.end = end;
1165                    break;
1166                }
1167            } else {
1168                // '#' is consumed, so '{' left only
1169                expect!(input, LBrace);
1170                elements.push(SassInterpolatedStrElement::Expression(
1171                    input.parse_maybe_sass_list(/* allow_comma */ true)?,
1172                ));
1173                expect!(input, RBrace);
1174            }
1175            is_parsing_static_part = !is_parsing_static_part;
1176        }
1177
1178        Ok(SassInterpolatedStr { elements, span })
1179    }
1180}
1181
1182impl<'a> Parse<'a> for SassInterpolatedUrl<'a> {
1183    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1184        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1185
1186        let (first, first_span) = match input.tokenizer.scan_url_raw_or_template()? {
1187            TokenWithSpan { token: Token::UrlTemplate(template), span } => (template, span),
1188            TokenWithSpan { token, span } => {
1189                return Err(Error {
1190                    kind: ErrorKind::Unexpected("<url template>", token.symbol()),
1191                    span,
1192                });
1193            }
1194        };
1195        let mut span = first_span.clone();
1196        let first = input.interpolable_url_static_part(first, first_span);
1197        let mut elements = arena_vec!(input; SassInterpolatedUrlElement::Static(first));
1198
1199        let mut is_parsing_static_part = false;
1200        loop {
1201            if is_parsing_static_part {
1202                let (token, url_tpl_span @ Span { end, .. }) =
1203                    input.tokenizer.scan_url_template()?;
1204                let tail = token.tail;
1205                elements.push(SassInterpolatedUrlElement::Static(
1206                    input.interpolable_url_static_part(token, url_tpl_span),
1207                ));
1208                if tail {
1209                    span.end = end;
1210                    break;
1211                }
1212            } else {
1213                // '#' is consumed, so '{' left only
1214                expect!(input, LBrace);
1215                elements.push(SassInterpolatedUrlElement::Expression(
1216                    input.parse_maybe_sass_list(/* allow_comma */ true)?,
1217                ));
1218                expect!(input, RBrace);
1219            }
1220            is_parsing_static_part = !is_parsing_static_part;
1221        }
1222
1223        Ok(SassInterpolatedUrl { elements, span })
1224    }
1225}
1226
1227impl<'a> Parse<'a> for SassList<'a> {
1228    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1229        if let ComponentValue::SassList(list) =
1230            input.parse_maybe_sass_list(/* allow_comma */ true)?
1231        {
1232            Ok(list)
1233        } else {
1234            use crate::{token::Comma, tokenizer::TokenSymbol};
1235            let TokenWithSpan { token, span } = bump!(input);
1236            Err(Error { kind: ErrorKind::Unexpected(Comma::symbol(), token.symbol()), span })
1237        }
1238    }
1239}
1240
1241impl<'a> Parse<'a> for SassMap<'a> {
1242    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1243        let start = expect!(input, LParen).1.start;
1244
1245        let mut items = arena_vec!(input);
1246        let mut comma_spans = arena_vec!(input);
1247        loop {
1248            match peek!(input).token {
1249                Token::RParen(..) => break,
1250                Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..) => {
1251                    bump!(input);
1252                }
1253                _ => {
1254                    items.push(input.parse()?);
1255                    if matches!(
1256                        peek!(input).token,
1257                        Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..)
1258                    ) {
1259                        bump!(input);
1260                    }
1261                    if !matches!(&peek!(input).token, Token::RParen(..)) {
1262                        comma_spans.push(expect!(input, Comma).1);
1263                    }
1264                }
1265            }
1266        }
1267        debug_assert!(items.len() - comma_spans.len() <= 1);
1268
1269        let end = expect!(input, RParen).1.end;
1270        Ok(SassMap { items, comma_spans, span: Span { start, end } })
1271    }
1272}
1273
1274impl<'a> Parse<'a> for SassMapItem<'a> {
1275    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1276        let key = input.parse_maybe_sass_list(/* allow_comma */ false)?;
1277        let (_, colon_span) = expect!(input, Colon);
1278        let value = input.parse_maybe_sass_list(/* allow_comma */ false)?;
1279        let span = Span { start: key.span().start, end: value.span().end };
1280        Ok(SassMapItem { key, colon_span, value, span })
1281    }
1282}
1283
1284impl<'a> Parse<'a> for SassMixin<'a> {
1285    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1286        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1287
1288        let name = input.parse::<Ident>()?;
1289        let start = name.span.start;
1290        let mut end = name.span.end;
1291
1292        let parameters = if matches!(peek!(input).token, Token::LParen(..)) {
1293            let parameters = input.parse::<SassParameters>()?;
1294            end = parameters.span.end;
1295            Some(parameters)
1296        } else {
1297            None
1298        };
1299
1300        Ok(SassMixin { name, parameters, span: Span { start, end } })
1301    }
1302}
1303
1304impl<'a> Parse<'a> for SassParameters<'a> {
1305    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1306        let (_, Span { start, .. }) = expect!(input, LParen);
1307        let (params, arbitrary_param, comma_spans, end) = input.parse_sass_params()?;
1308        Ok(SassParameters { params, arbitrary_param, comma_spans, span: Span { start, end } })
1309    }
1310}
1311
1312impl<'a> Parse<'a> for SassNestingDeclaration<'a> {
1313    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1314        let block = input.parse::<SimpleBlock>()?;
1315        let span = block.span.clone();
1316
1317        Ok(SassNestingDeclaration { block, span })
1318    }
1319}
1320
1321impl<'a> Parse<'a> for SassParenthesizedExpression<'a> {
1322    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1323        let start = expect!(input, LParen).1.start;
1324        eat!(input, Indent);
1325        let expr = input
1326            .with_state(ParserState {
1327                sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_DIV | SASS_CTX_IN_PARENS,
1328                ..input.state.clone()
1329            })
1330            .parse_maybe_sass_list(/* allow_comma */ true)?;
1331        let expr = arena_box!(input, expr);
1332        let end = expect!(input, RParen).1.end;
1333        Ok(SassParenthesizedExpression { expr, span: Span { start, end } })
1334    }
1335}
1336
1337impl<'a> Parse<'a> for SassPlaceholderSelector<'a> {
1338    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1339        let (_, percent_span) = expect!(input, Percent);
1340        let name = input.parse::<InterpolableIdent>()?;
1341        let name_span = name.span();
1342        util::assert_no_ws_or_comment(&percent_span, name_span)?;
1343        let span = Span { start: percent_span.start, end: name_span.end };
1344        Ok(SassPlaceholderSelector { name, span })
1345    }
1346}
1347
1348impl<'a> Parse<'a> for SassUse<'a> {
1349    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1350        let path = input.parse::<InterpolableStr>()?;
1351        let mut span = path.span().clone();
1352
1353        let namespace = match &peek!(input).token {
1354            Token::Ident(ident) if ident.name().eq_ignore_ascii_case("as") => {
1355                let namespace = input.parse::<SassUseNamespace>()?;
1356                span.end = namespace.span.end;
1357                Some(namespace)
1358            }
1359            _ => None,
1360        };
1361
1362        let config = input.parse_sass_module_config(/* allow_overridable */ false)?;
1363        if let Some(config) = &config {
1364            span.end = config.span.end;
1365        }
1366
1367        Ok(SassUse { path, namespace, config, span })
1368    }
1369}
1370
1371impl<'a> Parse<'a> for SassUseNamespace<'a> {
1372    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1373        let as_span = match peek!(input) {
1374            TokenWithSpan { token: Token::Ident(ident), .. }
1375                if ident.name().eq_ignore_ascii_case("as") =>
1376            {
1377                bump!(input).span
1378            }
1379            TokenWithSpan { span, .. } => {
1380                return Err(Error { kind: ErrorKind::ExpectSassKeyword("as"), span: span.clone() });
1381            }
1382        };
1383        match bump!(input) {
1384            TokenWithSpan { token: Token::Asterisk(..), span: asterisk_span } => {
1385                let span = Span { start: as_span.start, end: asterisk_span.end };
1386                Ok(SassUseNamespace {
1387                    as_span,
1388                    kind: SassUseNamespaceKind::Unnamed(SassUnnamedNamespace {
1389                        span: asterisk_span,
1390                    }),
1391                    span,
1392                })
1393            }
1394            TokenWithSpan { token: Token::Ident(ident), span: ident_span } => {
1395                let span = Span { start: as_span.start, end: ident_span.end };
1396                Ok(SassUseNamespace {
1397                    as_span,
1398                    kind: SassUseNamespaceKind::Named(input.ident(ident, ident_span)),
1399                    span,
1400                })
1401            }
1402            TokenWithSpan { span, .. } => {
1403                Err(Error { kind: ErrorKind::ExpectSassUseNamespace, span })
1404            }
1405        }
1406    }
1407}
1408
1409impl<'a> Parse<'a> for SassVariable<'a> {
1410    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1411        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1412
1413        let (name, span) = input.parse_dollar_var_ident()?;
1414        Ok(SassVariable { name, span })
1415    }
1416}
1417
1418impl<'a> Parse<'a> for SassVariableDeclaration<'a> {
1419    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1420        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1421
1422        let namespace = if let Some((ident_token, span)) = eat!(input, Ident) {
1423            let (_, dot_span) = expect!(input, Dot);
1424            util::assert_no_ws_or_comment(&span, &dot_span)?;
1425            let TokenWithSpan { span: next_span, .. } = peek!(input);
1426            util::assert_no_ws_or_comment(&dot_span, next_span)?;
1427            Some(input.ident(ident_token, span))
1428        } else {
1429            None
1430        };
1431
1432        let name = input.parse::<SassVariable>()?;
1433        let (_, colon_span) = expect!(input, Colon);
1434        let value = input
1435            .with_state(ParserState {
1436                sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_DIV,
1437                ..input.state.clone()
1438            })
1439            .parse_maybe_sass_list(/* allow_comma */ true)?;
1440
1441        let (flags, end) = input.parse_sass_flags()?;
1442
1443        let span = Span {
1444            start: namespace
1445                .as_ref()
1446                .map(|namespace| namespace.span.start)
1447                .unwrap_or(name.span.start),
1448            end: end.unwrap_or_else(|| value.span().end),
1449        };
1450
1451        if namespace.is_some() && flags.iter().any(|flag| flag.keyword.name == "global") {
1452            input
1453                .recoverable_errors
1454                .push(Error { kind: ErrorKind::UnexpectedSassFlag("global"), span: span.clone() });
1455        }
1456
1457        Ok(SassVariableDeclaration { namespace, name, colon_span, value, flags, span })
1458    }
1459}
1460
1461impl<'a> Parse<'a> for UnknownSassAtRule<'a> {
1462    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1463        debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1464
1465        let (_, at_span) = expect!(input, At);
1466        let name = input.parse_sass_interpolated_ident()?;
1467        let name_span = name.span();
1468        util::assert_no_ws_or_comment(&at_span, name_span)?;
1469
1470        let (prelude, block, end) = input.parse_unknown_at_rule()?;
1471        let span = Span { start: at_span.start, end: end.unwrap_or(name_span.end) };
1472        Ok(UnknownSassAtRule { name, prelude, block, span })
1473    }
1474}