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