raffia/parser/
less.rs

1use super::{
2    state::{ParserState, QualifiedRuleContext, LESS_CTX_ALLOW_DIV, LESS_CTX_ALLOW_KEYFRAME_BLOCK},
3    Parser,
4};
5use crate::{
6    ast::*,
7    bump,
8    config::Syntax,
9    eat,
10    error::{Error, ErrorKind, PResult},
11    expect, expect_without_ws_or_comments, peek,
12    pos::{Span, Spanned},
13    tokenizer::{Token, TokenWithSpan},
14    util, Parse,
15};
16use std::{borrow::Cow, mem};
17
18const PRECEDENCE_AND: u8 = 2;
19const PRECEDENCE_OR: u8 = 1;
20
21const PRECEDENCE_MULTIPLY: u8 = 2;
22const PRECEDENCE_PLUS: u8 = 1;
23
24impl<'cmt, 's: 'cmt> Parser<'cmt, 's> {
25    pub(super) fn parse_less_condition(
26        &mut self,
27        needs_parens: bool,
28    ) -> PResult<LessCondition<'s>> {
29        self.parse_less_condition_recursively(needs_parens, 0)
30    }
31
32    fn parse_less_condition_atom(&mut self) -> PResult<LessCondition<'s>> {
33        let left = self
34            .parse_less_operation(/* allow_mixin_call */ false)
35            .map(LessCondition::Value)?;
36
37        let op = match &peek!(self).token {
38            Token::GreaterThan(..) => LessBinaryConditionOperator {
39                kind: LessBinaryConditionOperatorKind::GreaterThan,
40                span: bump!(self).span,
41            },
42            Token::GreaterThanEqual(..) => LessBinaryConditionOperator {
43                kind: LessBinaryConditionOperatorKind::GreaterThanOrEqual,
44                span: bump!(self).span,
45            },
46            Token::LessThan(..) => LessBinaryConditionOperator {
47                kind: LessBinaryConditionOperatorKind::LessThan,
48                span: bump!(self).span,
49            },
50            Token::LessThanEqual(..) => LessBinaryConditionOperator {
51                kind: LessBinaryConditionOperatorKind::LessThanOrEqual,
52                span: bump!(self).span,
53            },
54            Token::Equal(..) => {
55                let eq_span = bump!(self).span;
56                match peek!(self) {
57                    TokenWithSpan {
58                        token: Token::GreaterThan(..),
59                        span: gt_span,
60                    } if eq_span.end == gt_span.start => LessBinaryConditionOperator {
61                        kind: LessBinaryConditionOperatorKind::EqualOrGreaterThan,
62                        span: Span {
63                            start: eq_span.start,
64                            end: bump!(self).span.end,
65                        },
66                    },
67                    TokenWithSpan {
68                        token: Token::LessThan(..),
69                        span: lt_span,
70                    } if eq_span.end == lt_span.start => LessBinaryConditionOperator {
71                        kind: LessBinaryConditionOperatorKind::EqualOrLessThan,
72                        span: Span {
73                            start: eq_span.start,
74                            end: bump!(self).span.end,
75                        },
76                    },
77                    _ => LessBinaryConditionOperator {
78                        kind: LessBinaryConditionOperatorKind::Equal,
79                        span: eq_span,
80                    },
81                }
82            }
83            _ => return Ok(left),
84        };
85
86        let right = self
87            .parse_less_operation(/* allow_mixin_call */ false)
88            .map(LessCondition::Value)?;
89
90        let span = Span {
91            start: left.span().start,
92            end: right.span().end,
93        };
94        Ok(LessCondition::Binary(LessBinaryCondition {
95            left: Box::new(left),
96            op,
97            right: Box::new(right),
98            span,
99        }))
100    }
101
102    fn parse_less_condition_inside_parens(
103        &mut self,
104        needs_parens: bool,
105    ) -> PResult<LessCondition<'s>> {
106        self.try_parse(|parser| {
107            let condition = parser.parse_less_condition(needs_parens);
108            match &condition {
109                Ok(LessCondition::Parenthesized(LessParenthesizedCondition {
110                    condition: inner_condition,
111                    span,
112                })) => match &**inner_condition {
113                    LessCondition::Value(ComponentValue::LessBinaryOperation(..))
114                        if matches!(
115                            peek!(parser).token,
116                            Token::GreaterThan(..)
117                                | Token::GreaterThanEqual(..)
118                                | Token::LessThan(..)
119                                | Token::LessThanEqual(..)
120                                | Token::Equal(..)
121                                | Token::Plus(..)
122                                | Token::Minus(..)
123                                | Token::Asterisk(..)
124                                | Token::Solidus(..)
125                        ) =>
126                    {
127                        // special case:
128                        // `when ((8 + 6) > 13)`
129                        // the `(8 + 6)` above is operation, not condition
130                        Err(Error {
131                            kind: ErrorKind::TryParseError,
132                            span: span.clone(),
133                        })
134                    }
135                    _ => condition,
136                },
137                _ => condition,
138            }
139        })
140        .or_else(|_| self.parse_less_condition_atom())
141    }
142
143    fn parse_less_condition_recursively(
144        &mut self,
145        needs_parens: bool,
146        precedence: u8,
147    ) -> PResult<LessCondition<'s>> {
148        let mut left = if precedence >= PRECEDENCE_AND {
149            match &peek!(self).token {
150                Token::LParen(..) => {
151                    let Span { start, .. } = bump!(self).span;
152                    let condition = self.parse_less_condition_inside_parens(needs_parens)?;
153                    let (_, Span { end, .. }) = expect!(self, RParen);
154                    LessCondition::Parenthesized(LessParenthesizedCondition {
155                        condition: Box::new(condition),
156                        span: Span { start, end },
157                    })
158                }
159                Token::Ident(ident) if ident.raw == "not" => {
160                    let Span { start, .. } = bump!(self).span;
161                    expect!(self, LParen);
162                    let condition = self.parse_less_condition_inside_parens(needs_parens)?;
163                    let (_, Span { end, .. }) = expect!(self, RParen);
164                    LessCondition::Negated(LessNegatedCondition {
165                        condition: Box::new(condition),
166                        span: Span { start, end },
167                    })
168                }
169                _ => {
170                    if needs_parens {
171                        use crate::{token::LParen, tokenizer::TokenSymbol};
172                        let TokenWithSpan { token, span } = bump!(self);
173                        return Err(Error {
174                            kind: ErrorKind::Unexpected(LParen::symbol(), token.symbol()),
175                            span,
176                        });
177                    } else {
178                        self.parse_less_condition_atom()?
179                    }
180                }
181            }
182        } else {
183            self.parse_less_condition_recursively(needs_parens, precedence + 1)?
184        };
185
186        loop {
187            let op = match &peek!(self).token {
188                Token::Ident(token) if token.raw == "and" && precedence == PRECEDENCE_AND => {
189                    LessBinaryConditionOperator {
190                        kind: LessBinaryConditionOperatorKind::And,
191                        span: bump!(self).span,
192                    }
193                }
194                Token::Ident(token) if token.raw == "or" && precedence == PRECEDENCE_OR => {
195                    LessBinaryConditionOperator {
196                        kind: LessBinaryConditionOperatorKind::Or,
197                        span: bump!(self).span,
198                    }
199                }
200                _ => break,
201            };
202
203            // multiple conditions in Less are right-associated
204            let right = self.parse_less_condition_recursively(needs_parens, precedence)?;
205
206            let span = Span {
207                start: left.span().start,
208                end: right.span().end,
209            };
210            left = LessCondition::Binary(LessBinaryCondition {
211                left: Box::new(left),
212                op,
213                right: Box::new(right),
214                span,
215            });
216        }
217
218        Ok(left)
219    }
220
221    pub(super) fn parse_less_interpolated_ident(&mut self) -> PResult<InterpolableIdent<'s>> {
222        debug_assert_eq!(self.syntax, Syntax::Less);
223
224        let (first, Span { start, mut end }) = match peek!(self) {
225            TokenWithSpan {
226                token: Token::Ident(..),
227                ..
228            } => {
229                let (ident, ident_span) = expect!(self, Ident);
230                (
231                    LessInterpolatedIdentElement::Static((ident, ident_span.clone()).into()),
232                    ident_span,
233                )
234            }
235            TokenWithSpan {
236                token: Token::AtLBraceVar(..),
237                ..
238            } => {
239                let interpolation = self.parse::<LessVariableInterpolation>()?;
240                let span = interpolation.span.clone();
241                (LessInterpolatedIdentElement::Variable(interpolation), span)
242            }
243            TokenWithSpan {
244                token: Token::DollarLBraceVar(..),
245                ..
246            } if matches!(
247                self.state.qualified_rule_ctx,
248                Some(QualifiedRuleContext::DeclarationName)
249            ) =>
250            {
251                let interpolation = self.parse::<LessPropertyInterpolation>()?;
252                let span = interpolation.span.clone();
253                (LessInterpolatedIdentElement::Property(interpolation), span)
254            }
255            TokenWithSpan { token, span } => {
256                use crate::{
257                    token::{AtLBraceVar, Ident},
258                    tokenizer::TokenSymbol,
259                };
260                return Err(Error {
261                    kind: ErrorKind::ExpectOneOf(
262                        vec![Ident::symbol(), AtLBraceVar::symbol()],
263                        token.symbol(),
264                    ),
265                    span: span.clone(),
266                });
267            }
268        };
269
270        let mut elements = self.parse_less_interpolated_ident_rest(&mut end)?;
271        if elements.is_empty() {
272            if let LessInterpolatedIdentElement::Static(ident) = first {
273                return Ok(InterpolableIdent::Literal(Ident {
274                    name: ident.value,
275                    raw: ident.raw,
276                    span: ident.span,
277                }));
278            }
279        }
280
281        elements.insert(0, first);
282        Ok(InterpolableIdent::LessInterpolated(LessInterpolatedIdent {
283            elements,
284            span: Span { start, end },
285        }))
286    }
287
288    pub(super) fn parse_less_interpolated_ident_rest(
289        &mut self,
290        end: &mut usize,
291    ) -> PResult<Vec<LessInterpolatedIdentElement<'s>>> {
292        let mut elements = vec![];
293        loop {
294            if let Some((token, span)) = self.tokenizer.scan_ident_template()? {
295                *end = span.end;
296                elements.push(LessInterpolatedIdentElement::Static((token, span).into()));
297            } else {
298                match peek!(self) {
299                    TokenWithSpan {
300                        token: Token::AtLBraceVar(..),
301                        span: at_lbrace_var_span,
302                    } if *end == at_lbrace_var_span.start => {
303                        let variable = self.parse::<LessVariableInterpolation>()?;
304                        *end = variable.span.end;
305                        elements.push(LessInterpolatedIdentElement::Variable(variable));
306                    }
307                    TokenWithSpan {
308                        token: Token::DollarLBraceVar(..),
309                        span: dollar_lbrace_var_span,
310                    } if matches!(
311                        self.state.qualified_rule_ctx,
312                        Some(QualifiedRuleContext::DeclarationName)
313                    ) && *end == dollar_lbrace_var_span.start =>
314                    {
315                        let property = self.parse::<LessPropertyInterpolation>()?;
316                        *end = property.span.end;
317                        elements.push(LessInterpolatedIdentElement::Property(property));
318                    }
319                    _ => return Ok(elements),
320                }
321            }
322        }
323    }
324
325    pub(super) fn parse_less_maybe_mixin_call_or_with_lookups(
326        &mut self,
327    ) -> PResult<ComponentValue<'s>> {
328        let mixin_call = self.parse::<LessMixinCall>()?;
329        if matches!(peek!(self).token, Token::LBracket(..)) {
330            let lookups = self.parse::<LessLookups>()?;
331            let span = Span {
332                start: mixin_call.span.start,
333                end: lookups.span.end,
334            };
335            Ok(ComponentValue::LessNamespaceValue(Box::new(
336                LessNamespaceValue {
337                    callee: LessNamespaceValueCallee::LessMixinCall(mixin_call),
338                    lookups,
339                    span,
340                },
341            )))
342        } else {
343            Ok(ComponentValue::LessMixinCall(mixin_call))
344        }
345    }
346
347    pub(super) fn parse_less_maybe_variable_or_with_lookups(
348        &mut self,
349    ) -> PResult<ComponentValue<'s>> {
350        let variable = self.parse::<LessVariable>()?;
351        match peek!(self) {
352            TokenWithSpan {
353                token: Token::LBracket(..),
354                span,
355            } if variable.span.end == span.start => {
356                let lookups = self.parse::<LessLookups>()?;
357                let span = Span {
358                    start: variable.span.start,
359                    end: lookups.span.end,
360                };
361                Ok(ComponentValue::LessNamespaceValue(Box::new(
362                    LessNamespaceValue {
363                        callee: LessNamespaceValueCallee::LessVariable(variable),
364                        lookups,
365                        span,
366                    },
367                )))
368            }
369            _ => Ok(ComponentValue::LessVariable(variable)),
370        }
371    }
372
373    pub(super) fn parse_less_operation(
374        &mut self,
375        allow_mixin_call: bool,
376    ) -> PResult<ComponentValue<'s>> {
377        self.parse_less_operation_recursively(allow_mixin_call, 0)
378    }
379
380    fn parse_less_operation_recursively(
381        &mut self,
382        allow_mixin_call: bool,
383        precedence: u8,
384    ) -> PResult<ComponentValue<'s>> {
385        let mut left = if precedence >= PRECEDENCE_MULTIPLY {
386            match peek!(self).token {
387                Token::LParen(..) => self
388                    .parse_less_parenthesized_operation(allow_mixin_call)
389                    .map(ComponentValue::LessParenthesizedOperation)?,
390                Token::Minus(..) => self
391                    .parse::<LessNegativeValue>()
392                    .map(ComponentValue::LessNegativeValue)?,
393                _ => {
394                    let value = self.parse_component_value_atom()?;
395                    if let ComponentValue::LessMixinCall(mixin_call) = &value {
396                        if !allow_mixin_call {
397                            self.recoverable_errors.push(Error {
398                                kind: ErrorKind::UnexpectedLessMixinCall,
399                                span: mixin_call.span.clone(),
400                            });
401                        }
402                    }
403                    value
404                }
405            }
406        } else {
407            self.parse_less_operation_recursively(allow_mixin_call, precedence + 1)?
408        };
409
410        loop {
411            let op = match peek!(self) {
412                TokenWithSpan {
413                    token: Token::Asterisk(..),
414                    ..
415                } if precedence == PRECEDENCE_MULTIPLY => LessOperationOperator {
416                    kind: LessOperationOperatorKind::Multiply,
417                    span: bump!(self).span,
418                },
419                TokenWithSpan {
420                    token: Token::Solidus(..),
421                    ..
422                } if precedence == PRECEDENCE_MULTIPLY
423                    && (self.state.less_ctx & LESS_CTX_ALLOW_DIV != 0
424                        || can_be_division_operand(&left)) =>
425                {
426                    LessOperationOperator {
427                        kind: LessOperationOperatorKind::Division,
428                        span: bump!(self).span,
429                    }
430                }
431                TokenWithSpan {
432                    token: Token::Dot(..),
433                    ..
434                } if precedence == PRECEDENCE_MULTIPLY => {
435                    // `./` is also division
436                    let Span { start, .. } = bump!(self).span;
437                    let (_, Span { end, .. }) = expect_without_ws_or_comments!(self, Solidus);
438                    LessOperationOperator {
439                        kind: LessOperationOperatorKind::Division,
440                        span: Span { start, end },
441                    }
442                }
443                TokenWithSpan {
444                    token: Token::Plus(..),
445                    ..
446                } if precedence == PRECEDENCE_PLUS => LessOperationOperator {
447                    kind: LessOperationOperatorKind::Plus,
448                    span: bump!(self).span,
449                },
450                TokenWithSpan {
451                    token: Token::Minus(..),
452                    ..
453                } if precedence == PRECEDENCE_PLUS => LessOperationOperator {
454                    kind: LessOperationOperatorKind::Minus,
455                    span: bump!(self).span,
456                },
457                TokenWithSpan {
458                    token: Token::Number(token),
459                    span,
460                } if precedence == PRECEDENCE_PLUS
461                    && (token.raw.starts_with('+')
462                        || token.raw.starts_with('-') && span.start == left.span().end) =>
463                {
464                    let (number, number_span) = expect!(self, Number);
465                    let op = LessOperationOperator {
466                        kind: if number.raw.starts_with('+') {
467                            LessOperationOperatorKind::Plus
468                        } else {
469                            LessOperationOperatorKind::Minus
470                        },
471                        span: Span {
472                            start: number_span.start,
473                            end: number_span.start + 1,
474                        },
475                    };
476                    let span = Span {
477                        start: left.span().start,
478                        end: number_span.end,
479                    };
480                    let right = {
481                        let span = Span {
482                            start: number_span.start + 1,
483                            end: number_span.end,
484                        };
485                        let raw = unsafe { number.raw.get_unchecked(1..number.raw.len()) };
486                        raw.parse()
487                            .map_err(|_| Error {
488                                kind: ErrorKind::InvalidNumber,
489                                span: span.clone(),
490                            })
491                            .map(|value| ComponentValue::Number(Number { value, raw, span }))?
492                    };
493                    left = ComponentValue::LessBinaryOperation(LessBinaryOperation {
494                        left: Box::new(left),
495                        op,
496                        right: Box::new(right),
497                        span,
498                    });
499                    continue;
500                }
501                TokenWithSpan {
502                    token: Token::Dimension(token),
503                    span,
504                } if precedence == PRECEDENCE_PLUS
505                    && (token.value.raw.starts_with('+')
506                        || token.value.raw.starts_with('-') && span.start == left.span().end) =>
507                {
508                    let (dimension, dimension_span) = expect!(self, Dimension);
509                    let op = LessOperationOperator {
510                        kind: if dimension.value.raw.starts_with('+') {
511                            LessOperationOperatorKind::Plus
512                        } else {
513                            LessOperationOperatorKind::Minus
514                        },
515                        span: Span {
516                            start: dimension_span.start,
517                            end: dimension_span.start + 1,
518                        },
519                    };
520                    let span = Span {
521                        start: left.span().start,
522                        end: dimension_span.end,
523                    };
524                    let right = {
525                        (
526                            crate::token::Dimension {
527                                value: crate::token::Number {
528                                    raw: unsafe {
529                                        dimension
530                                            .value
531                                            .raw
532                                            .get_unchecked(1..dimension.value.raw.len())
533                                    },
534                                },
535                                unit: dimension.unit,
536                            },
537                            Span {
538                                start: dimension_span.start + 1,
539                                end: dimension_span.end,
540                            },
541                        )
542                            .try_into()
543                            .map(ComponentValue::Dimension)?
544                    };
545                    left = ComponentValue::LessBinaryOperation(LessBinaryOperation {
546                        left: Box::new(left),
547                        op,
548                        right: Box::new(right),
549                        span,
550                    });
551                    continue;
552                }
553                _ => break,
554            };
555
556            let right = self.parse_less_operation_recursively(allow_mixin_call, precedence + 1)?;
557            let span = Span {
558                start: left.span().start,
559                end: right.span().end,
560            };
561            left = ComponentValue::LessBinaryOperation(LessBinaryOperation {
562                left: Box::new(left),
563                op,
564                right: Box::new(right),
565                span,
566            });
567        }
568
569        Ok(left)
570    }
571
572    fn parse_less_parenthesized_operation(
573        &mut self,
574        allow_mixin_call: bool,
575    ) -> PResult<LessParenthesizedOperation<'s>> {
576        let (_, Span { start, .. }) = expect!(self, LParen);
577        let operation = self
578            .with_state(ParserState {
579                less_ctx: self.state.less_ctx | LESS_CTX_ALLOW_DIV,
580                ..self.state.clone()
581            })
582            .parse_less_operation(allow_mixin_call)?;
583        let (_, Span { end, .. }) = expect!(self, RParen);
584        Ok(LessParenthesizedOperation {
585            operation: Box::new(operation),
586            span: Span { start, end },
587        })
588    }
589
590    pub(super) fn parse_less_qualified_rule(&mut self) -> PResult<Statement<'s>> {
591        debug_assert_eq!(self.syntax, Syntax::Less);
592
593        let selector_list = self
594            .with_state(ParserState {
595                qualified_rule_ctx: Some(QualifiedRuleContext::Selector),
596                ..self.state
597            })
598            .parse::<SelectorList>()?;
599
600        match &peek!(self).token {
601            Token::Ident(ident) if ident.raw == "when" => {
602                let guard = self.parse::<LessConditions>()?;
603                let block = self.parse::<SimpleBlock>()?;
604                let span = Span {
605                    start: selector_list.span.start,
606                    end: block.span.end,
607                };
608                if selector_list.selectors.len() > 1 {
609                    self.recoverable_errors.push(Error {
610                        kind: ErrorKind::LessGuardOnMultipleComplexSelectors,
611                        span: guard.span.clone(),
612                    });
613                }
614                return Ok(Statement::LessConditionalQualifiedRule(
615                    LessConditionalQualifiedRule {
616                        selector: selector_list,
617                        guard,
618                        block,
619                        span,
620                    },
621                ));
622            }
623            _ => {}
624        }
625
626        let block = self.parse::<SimpleBlock>()?;
627        let span = Span {
628            start: selector_list.span.start,
629            end: block.span.end,
630        };
631        Ok(Statement::QualifiedRule(QualifiedRule {
632            selector: selector_list,
633            block,
634            span,
635        }))
636    }
637
638    pub(super) fn parse_maybe_hex_color_or_less_mixin_call(
639        &mut self,
640    ) -> PResult<ComponentValue<'s>> {
641        debug_assert_eq!(self.syntax, Syntax::Less);
642
643        let attempt = self.try_parse(|parser| {
644            let hex_color = parser.parse::<HexColor>()?;
645            match peek!(parser) {
646                TokenWithSpan {
647                    token: Token::LParen(..),
648                    span,
649                } => Err(Error {
650                    kind: ErrorKind::TryParseError,
651                    span: span.clone(),
652                }),
653                TokenWithSpan {
654                    token: Token::LBracket(..) | Token::Dot(..) | Token::Hash(..),
655                    span,
656                } if hex_color.span.end == span.start => Err(Error {
657                    kind: ErrorKind::TryParseError,
658                    span: span.clone(),
659                }),
660                _ => Ok(hex_color),
661            }
662        });
663        match attempt {
664            Err(Error {
665                kind: ErrorKind::TryParseError,
666                ..
667            }) => self.parse_less_maybe_mixin_call_or_with_lookups(),
668            hex_color => hex_color.map(ComponentValue::HexColor),
669        }
670    }
671
672    pub(super) fn parse_maybe_less_list(
673        &mut self,
674        allow_comma: bool,
675    ) -> PResult<ComponentValue<'s>> {
676        use util::ListSeparatorKind;
677
678        let single_value = if allow_comma {
679            self.parse_maybe_less_list(false)?
680        } else if let Token::Exclamation(..) = peek!(self).token {
681            self.parse().map(ComponentValue::ImportantAnnotation)?
682        } else {
683            self.parse_less_operation(/* allow_mixin_call */ true)?
684        };
685
686        let mut elements = vec![];
687        let mut comma_spans: Option<Vec<_>> = None;
688        let mut separator = ListSeparatorKind::Unknown;
689        let mut end = single_value.span().end;
690        loop {
691            match peek!(self).token {
692                Token::LBrace(..)
693                | Token::RBrace(..)
694                | Token::RParen(..)
695                | Token::Semicolon(..)
696                | Token::Colon(..)
697                | Token::DotDotDot(..)
698                | Token::Eof(..) => break,
699                Token::Comma(..) => {
700                    if !allow_comma {
701                        break;
702                    }
703                    if separator == ListSeparatorKind::Space {
704                        break;
705                    } else {
706                        if separator == ListSeparatorKind::Unknown {
707                            separator = ListSeparatorKind::Comma;
708                        }
709                        let TokenWithSpan { span, .. } = bump!(self);
710                        end = span.end;
711                        if let Some(spans) = &mut comma_spans {
712                            spans.push(span);
713                        } else {
714                            comma_spans = Some(vec![span]);
715                        }
716                    }
717                }
718                Token::Exclamation(..) => {
719                    if let Ok(important_annotation) = self.try_parse(ImportantAnnotation::parse) {
720                        if end < important_annotation.span.start
721                            && separator == ListSeparatorKind::Unknown
722                        {
723                            separator = ListSeparatorKind::Space;
724                        }
725                        end = important_annotation.span.end;
726                        elements.push(ComponentValue::ImportantAnnotation(important_annotation));
727                    } else {
728                        break;
729                    }
730                }
731                _ => {
732                    if separator == ListSeparatorKind::Unknown {
733                        separator = ListSeparatorKind::Space;
734                    }
735                    let item = if separator == ListSeparatorKind::Comma {
736                        self.parse_maybe_less_list(false)?
737                    } else {
738                        self.parse_less_operation(/* allow_mixin_call */ true)?
739                    };
740                    end = item.span().end;
741                    elements.push(item);
742                }
743            }
744        }
745
746        if elements.is_empty() && separator != ListSeparatorKind::Comma {
747            // If there is a trailing comma it can be a list,
748            // though there is only one element.
749            Ok(single_value)
750        } else {
751            debug_assert_ne!(separator, ListSeparatorKind::Unknown);
752
753            let span = Span {
754                start: single_value.span().start,
755                end,
756            };
757            elements.insert(0, single_value);
758            Ok(ComponentValue::LessList(LessList {
759                elements,
760                comma_spans,
761                span,
762            }))
763        }
764    }
765}
766
767impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessConditions<'s> {
768    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
769        let when_span = match bump!(input) {
770            TokenWithSpan {
771                token: Token::Ident(ident),
772                span,
773            } if ident.raw == "when" => span,
774            TokenWithSpan { span, .. } => {
775                return Err(Error {
776                    kind: ErrorKind::ExpectLessKeyword("when"),
777                    span,
778                });
779            }
780        };
781
782        let first = input.parse_less_condition(true)?;
783        let mut span = first.span().clone();
784
785        let mut conditions = vec![first];
786        let mut comma_spans = vec![];
787        while let Some((_, comma_span)) = eat!(input, Comma) {
788            comma_spans.push(comma_span);
789            conditions.push(input.parse_less_condition(true)?);
790        }
791        debug_assert_eq!(comma_spans.len() + 1, conditions.len());
792
793        if let Some(last) = conditions.last() {
794            span.end = last.span().end;
795        }
796        Ok(LessConditions {
797            conditions,
798            when_span,
799            comma_spans,
800            span,
801        })
802    }
803}
804
805impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessDetachedRuleset<'s> {
806    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
807        let block = input.parse::<SimpleBlock>()?;
808        let span = block.span.clone();
809        Ok(LessDetachedRuleset { block, span })
810    }
811}
812
813impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessEscapedStr<'s> {
814    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
815        let (_, Span { start, .. }) = expect!(input, Tilde);
816        let str: Str = input.tokenizer.scan_string_only()?.into();
817        let span = Span {
818            start,
819            end: str.span().end,
820        };
821        Ok(LessEscapedStr { str, span })
822    }
823}
824
825impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessExtend<'s> {
826    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
827        let mut selector = input.parse::<ComplexSelector>()?;
828
829        let span = selector.span.clone();
830        let mut all = None;
831
832        if let [.., complex_child, ComplexSelectorChild::Combinator(Combinator {
833            kind: CombinatorKind::Descendant,
834            ..
835        }), ComplexSelectorChild::CompoundSelector(CompoundSelector { children, .. })] =
836            &selector.children[..]
837        {
838            if let [SimpleSelector::Type(TypeSelector::TagName(TagNameSelector {
839                name:
840                    WqName {
841                        name: InterpolableIdent::Literal(token_all @ Ident { raw: "all", .. }),
842                        prefix: None,
843                        ..
844                    },
845                ..
846            }))] = &children[..]
847            {
848                all = Some(token_all.clone());
849                selector.span.end = complex_child.span().end;
850                selector.children.truncate(selector.children.len() - 2);
851            }
852        }
853
854        Ok(LessExtend {
855            selector,
856            all,
857            span,
858        })
859    }
860}
861
862impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessExtendList<'s> {
863    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
864        debug_assert_eq!(input.syntax, Syntax::Less);
865
866        let first = input.parse::<LessExtend>()?;
867        let mut span = first.span.clone();
868
869        let mut elements = vec![first];
870        let mut comma_spans = vec![];
871        while let Some((_, comma_span)) = eat!(input, Comma) {
872            comma_spans.push(comma_span);
873            elements.push(input.parse()?);
874        }
875        debug_assert_eq!(comma_spans.len() + 1, elements.len());
876
877        if let Some(last) = elements.last() {
878            span.end = last.span.end;
879        }
880        Ok(LessExtendList {
881            elements,
882            comma_spans,
883            span,
884        })
885    }
886}
887
888impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessExtendRule<'s> {
889    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
890        let nesting_selector = input.parse::<NestingSelector>()?;
891        if nesting_selector.suffix.is_some() {
892            return Err(Error {
893                kind: ErrorKind::ExpectLessExtendRule,
894                span: nesting_selector.span,
895            });
896        }
897
898        let pseudo_class_selector = input.parse::<PseudoClassSelector>()?;
899        util::assert_no_ws_or_comment(&nesting_selector.span, &pseudo_class_selector.span)?;
900        let span = Span {
901            start: nesting_selector.span.start,
902            end: pseudo_class_selector.span.end,
903        };
904
905        let InterpolableIdent::Literal(name_of_extend @ Ident { raw: "extend", .. }) =
906            pseudo_class_selector.name
907        else {
908            return Err(Error {
909                kind: ErrorKind::ExpectLessExtendRule,
910                span,
911            });
912        };
913        let Some(PseudoClassSelectorArg {
914            kind: PseudoClassSelectorArgKind::LessExtendList(extend),
915            ..
916        }) = pseudo_class_selector.arg
917        else {
918            return Err(Error {
919                kind: ErrorKind::ExpectLessExtendRule,
920                span,
921            });
922        };
923
924        Ok(LessExtendRule {
925            nesting_selector,
926            name_of_extend,
927            extend,
928            span,
929        })
930    }
931}
932
933impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessFormatFunction {
934    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
935        let (_, span) = expect!(input, Percent);
936        Ok(LessFormatFunction { span })
937    }
938}
939
940impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessImportOptions<'s> {
941    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
942        let (_, Span { start, .. }) = expect!(input, LParen);
943
944        let mut names = Vec::with_capacity(1);
945        let mut comma_spans = vec![];
946        while let Token::Ident(crate::token::Ident {
947            raw: "less" | "css" | "multiple" | "once" | "inline" | "reference" | "optional",
948            ..
949        }) = peek!(input).token
950        {
951            names.push(input.parse()?);
952            if !matches!(peek!(input).token, Token::RParen(..)) {
953                comma_spans.push(expect!(input, Comma).1);
954            }
955        }
956        debug_assert!(names.len() - comma_spans.len() <= 1);
957
958        let (_, Span { end, .. }) = expect!(input, RParen);
959
960        Ok(LessImportOptions {
961            names,
962            comma_spans,
963            span: Span { start, end },
964        })
965    }
966}
967
968impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessImportPrelude<'s> {
969    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
970        let options = input.parse::<LessImportOptions>()?;
971        let start = options.span.start;
972
973        let href = match &peek!(input).token {
974            Token::Str(..) | Token::StrTemplate(..) => input.parse().map(ImportPreludeHref::Str)?,
975            _ => input.parse().map(ImportPreludeHref::Url)?,
976        };
977        let mut end = href.span().end;
978
979        let media = if matches!(peek!(input).token, Token::Semicolon(..)) {
980            None
981        } else {
982            let media = input.parse::<MediaQueryList>()?;
983            end = media.span.end;
984            Some(media)
985        };
986
987        Ok(LessImportPrelude {
988            href,
989            options,
990            media,
991            span: Span { start, end },
992        })
993    }
994}
995
996impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessInterpolatedStr<'s> {
997    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
998        let (first, first_span) = expect!(input, StrTemplate);
999        let quote = first.raw.chars().next().unwrap();
1000        debug_assert!(quote == '\'' || quote == '"');
1001        let mut span = first_span.clone();
1002        let mut elements = vec![LessInterpolatedStrElement::Static(
1003            (first, first_span).into(),
1004        )];
1005
1006        let mut is_parsing_static_part = false;
1007        loop {
1008            if is_parsing_static_part {
1009                let (token, str_tpl_span) = input.tokenizer.scan_string_template(quote)?;
1010                let tail = token.tail;
1011                let end = str_tpl_span.end;
1012                elements.push(LessInterpolatedStrElement::Static(
1013                    (token, str_tpl_span).into(),
1014                ));
1015                if tail {
1016                    span.end = end;
1017                    break;
1018                }
1019            } else {
1020                // '@' or '$' is consumed, so '{' left only
1021                let start = expect!(input, LBrace).1.start - 1;
1022                let (name, name_span) = expect_without_ws_or_comments!(input, Ident);
1023
1024                let end = expect!(input, RBrace).1.end;
1025                elements.push(match input.source.as_bytes().get(start) {
1026                    Some(b'@') => LessInterpolatedStrElement::Variable(LessVariableInterpolation {
1027                        name: (name, name_span).into(),
1028                        span: Span { start, end },
1029                    }),
1030                    Some(b'$') => LessInterpolatedStrElement::Property(LessPropertyInterpolation {
1031                        name: (name, name_span).into(),
1032                        span: Span { start, end },
1033                    }),
1034                    _ => unreachable!(),
1035                });
1036            }
1037            is_parsing_static_part = !is_parsing_static_part;
1038        }
1039
1040        Ok(LessInterpolatedStr { elements, span })
1041    }
1042}
1043
1044impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessJavaScriptSnippet<'s> {
1045    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1046        let tilde = eat!(input, Tilde);
1047        let (token, span) = expect!(input, BacktickCode);
1048
1049        Ok(LessJavaScriptSnippet {
1050            code: &token.raw[1..token.raw.len() - 1],
1051            raw: token.raw,
1052            escaped: tilde.is_some(),
1053            span: Span {
1054                start: tilde.map(|(_, span)| span.start).unwrap_or(span.start),
1055                end: span.end,
1056            },
1057        })
1058    }
1059}
1060
1061impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessListFunction {
1062    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1063        let (_, span) = expect!(input, Tilde);
1064        Ok(LessListFunction { span })
1065    }
1066}
1067
1068impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessLookup<'s> {
1069    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1070        debug_assert_eq!(input.syntax, Syntax::Less);
1071
1072        let (_, Span { start, .. }) = expect!(input, LBracket);
1073        let name = if let Token::RBracket(..) = peek!(input).token {
1074            None
1075        } else {
1076            Some(input.parse()?)
1077        };
1078        let (_, Span { end, .. }) = expect!(input, RBracket);
1079        Ok(LessLookup {
1080            name,
1081            span: Span { start, end },
1082        })
1083    }
1084}
1085
1086impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessLookupName<'s> {
1087    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1088        debug_assert_eq!(input.syntax, Syntax::Less);
1089
1090        match peek!(input).token {
1091            Token::AtKeyword(..) => input.parse().map(LessLookupName::LessVariable),
1092            Token::At(..) => input.parse().map(LessLookupName::LessVariableVariable),
1093            Token::DollarVar(..) => input.parse().map(LessLookupName::LessPropertyVariable),
1094            _ => input.parse().map(LessLookupName::Ident),
1095        }
1096    }
1097}
1098
1099impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessLookups<'s> {
1100    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1101        debug_assert_eq!(input.syntax, Syntax::Less);
1102
1103        let first = input.parse::<LessLookup>()?;
1104        let mut span = first.span.clone();
1105
1106        let mut lookups = vec![first];
1107        while let Token::LBracket(..) = peek!(input).token {
1108            lookups.push(input.parse()?);
1109        }
1110
1111        if let Some(last) = lookups.last() {
1112            span.end = last.span.end;
1113        }
1114        Ok(LessLookups { lookups, span })
1115    }
1116}
1117
1118impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessMixinCall<'s> {
1119    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1120        debug_assert_eq!(input.syntax, Syntax::Less);
1121
1122        let callee = input.parse::<LessMixinCallee>()?;
1123
1124        let mut end = callee.span.end;
1125        let args = if let Some((_, lparen_span)) = eat!(input, LParen) {
1126            let mut semicolon_comes_at = 0;
1127            let mut args = vec![];
1128            let mut comma_spans = vec![];
1129            let mut semicolon_spans = vec![];
1130            'args: loop {
1131                match peek!(input).token {
1132                    Token::RParen(..) => {
1133                        let TokenWithSpan { span, .. } = bump!(input);
1134                        if semicolon_comes_at > 0 {
1135                            wrap_less_mixin_args_into_less_list(
1136                                &mut args,
1137                                mem::take(&mut comma_spans),
1138                                semicolon_comes_at,
1139                            )
1140                            .map_err(|kind| Error {
1141                                kind,
1142                                span: Span {
1143                                    // We've checked `semicolon_comes_at` must be greater than 0,
1144                                    // so `args` won't be empty.
1145                                    start: args.first().unwrap().span().start,
1146                                    end: args.last().unwrap().span().end,
1147                                },
1148                            })?;
1149                        }
1150                        end = span.end;
1151                        break;
1152                    }
1153                    Token::LBrace(..) => args.push(LessMixinArgument::Value(
1154                        ComponentValue::LessDetachedRuleset(input.parse()?),
1155                    )),
1156                    Token::Comma(..) => {
1157                        return Err(Error {
1158                            kind: ErrorKind::ExpectComponentValue,
1159                            span: bump!(input).span,
1160                        });
1161                    }
1162                    _ => 'maybe: {
1163                        let value = input.parse_maybe_less_list(/* allow_comma */ false)?;
1164                        let name = {
1165                            match value {
1166                                ComponentValue::LessVariable(variable) => {
1167                                    LessMixinParameterName::Variable(variable)
1168                                }
1169                                ComponentValue::LessPropertyVariable(property) => {
1170                                    LessMixinParameterName::PropertyVariable(property)
1171                                }
1172                                value => {
1173                                    args.push(LessMixinArgument::Value(value));
1174                                    break 'maybe;
1175                                }
1176                            }
1177                        };
1178                        if let Some((_, colon_span)) = eat!(input, Colon) {
1179                            let value = if matches!(peek!(input).token, Token::LBrace(..)) {
1180                                input.parse().map(ComponentValue::LessDetachedRuleset)?
1181                            } else {
1182                                input.parse_maybe_less_list(
1183                                    /* allow_comma */ semicolon_comes_at > 0,
1184                                )?
1185                            };
1186                            let span = Span {
1187                                start: name.span().start,
1188                                end: value.span().end,
1189                            };
1190                            args.push(LessMixinArgument::Named(LessMixinNamedArgument {
1191                                name,
1192                                colon_span,
1193                                value,
1194                                span,
1195                            }));
1196                        } else if let Some((_, dotdotdot_span)) = eat!(input, DotDotDot) {
1197                            let span = Span {
1198                                start: name.span().start,
1199                                end: dotdotdot_span.end,
1200                            };
1201                            args.push(LessMixinArgument::Variadic(LessMixinVariadicArgument {
1202                                name,
1203                                span,
1204                            }));
1205                            if let Some((_, semicolon_span)) = eat!(input, Semicolon) {
1206                                semicolon_spans.push(semicolon_span);
1207                            };
1208                            end = expect!(input, RParen).1.end;
1209                            break 'args;
1210                        } else {
1211                            args.push(LessMixinArgument::Value(match name {
1212                                LessMixinParameterName::Variable(variable) => {
1213                                    ComponentValue::LessVariable(variable)
1214                                }
1215                                LessMixinParameterName::PropertyVariable(property_variable) => {
1216                                    ComponentValue::LessPropertyVariable(property_variable)
1217                                }
1218                            }));
1219                        }
1220                    }
1221                };
1222
1223                match peek!(input).token {
1224                    Token::RParen(..) => {}
1225                    Token::Comma(..) => {
1226                        comma_spans.push(bump!(input).span);
1227                    }
1228                    Token::Semicolon(..) => {
1229                        let TokenWithSpan { span, .. } = bump!(input);
1230                        wrap_less_mixin_args_into_less_list(
1231                            &mut args,
1232                            mem::take(&mut comma_spans),
1233                            semicolon_comes_at,
1234                        )
1235                        .map_err(|kind| Error {
1236                            kind,
1237                            span: span.clone(),
1238                        })?;
1239                        semicolon_comes_at = args.len();
1240                        semicolon_spans.push(span);
1241                    }
1242                    _ => {
1243                        let TokenWithSpan { token, span } = bump!(input);
1244                        use crate::{token::RParen, tokenizer::TokenSymbol};
1245                        return Err(Error {
1246                            kind: ErrorKind::Unexpected(RParen::symbol(), token.symbol()),
1247                            span,
1248                        });
1249                    }
1250                }
1251            }
1252            let is_comma_separated = semicolon_spans.is_empty();
1253            let separator_spans = if semicolon_spans.is_empty() {
1254                comma_spans
1255            } else {
1256                semicolon_spans
1257            };
1258            debug_assert!(args.len() - separator_spans.len() <= 1);
1259            Some(LessMixinArguments {
1260                args,
1261                is_comma_separated,
1262                separator_spans,
1263                span: Span {
1264                    start: lparen_span.start,
1265                    end,
1266                },
1267            })
1268        } else {
1269            None
1270        };
1271
1272        let important = if !matches!(
1273            input.state.qualified_rule_ctx,
1274            Some(QualifiedRuleContext::DeclarationValue)
1275        ) && matches!(peek!(input).token, Token::Exclamation(..))
1276        {
1277            input.parse::<ImportantAnnotation>().map(Some)?
1278        } else {
1279            None
1280        };
1281
1282        let span = Span {
1283            start: callee.span.start,
1284            end: important
1285                .as_ref()
1286                .map(|important| important.span.end)
1287                .unwrap_or(end),
1288        };
1289        Ok(LessMixinCall {
1290            callee,
1291            args,
1292            important,
1293            span,
1294        })
1295    }
1296}
1297
1298impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessMixinCallee<'s> {
1299    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1300        let first_name = input.parse::<LessMixinName>()?;
1301        let mut span = first_name.span().clone();
1302
1303        let mut children = vec![LessMixinCalleeChild {
1304            name: first_name,
1305            combinator: None,
1306            span: span.clone(),
1307        }];
1308        loop {
1309            let combinator = eat!(input, GreaterThan).map(|(_, span)| Combinator {
1310                kind: CombinatorKind::Child,
1311                span,
1312            });
1313            if let Token::Dot(..) | Token::Hash(..) = peek!(input).token {
1314                let name = input.parse::<LessMixinName>()?;
1315                let name_span = name.span();
1316                let span = Span {
1317                    start: combinator
1318                        .as_ref()
1319                        .map(|combinator| combinator.span.start)
1320                        .unwrap_or(name_span.start),
1321                    end: name_span.end,
1322                };
1323                children.push(LessMixinCalleeChild {
1324                    name,
1325                    combinator,
1326                    span,
1327                });
1328            } else {
1329                break;
1330            }
1331        }
1332
1333        if let Some(last) = children.last() {
1334            span.end = last.span.end;
1335        }
1336        Ok(LessMixinCallee { children, span })
1337    }
1338}
1339
1340impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessMixinDefinition<'s> {
1341    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1342        debug_assert_eq!(input.syntax, Syntax::Less);
1343
1344        let name = input.parse::<LessMixinName>()?;
1345
1346        let (_, lparen_span) = expect!(input, LParen);
1347        let rparen_span;
1348        let mut semicolon_comes_at = 0;
1349        let mut params = vec![];
1350        let mut comma_spans = vec![];
1351        let mut semicolon_spans = vec![];
1352        'params: loop {
1353            match peek!(input).token {
1354                Token::RParen(..) => {
1355                    rparen_span = bump!(input).span;
1356                    break;
1357                }
1358                Token::DotDotDot(..) => {
1359                    let TokenWithSpan { span, .. } = bump!(input);
1360                    params.push(LessMixinParameter::Variadic(LessMixinVariadicParameter {
1361                        name: None,
1362                        span,
1363                    }));
1364                    eat!(input, Semicolon);
1365                    (_, rparen_span) = expect!(input, RParen);
1366                    break;
1367                }
1368                Token::Comma(..) => {
1369                    return Err(Error {
1370                        kind: ErrorKind::ExpectComponentValue,
1371                        span: bump!(input).span,
1372                    });
1373                }
1374                _ => 'maybe: {
1375                    let value = input
1376                        .with_state(ParserState {
1377                            less_ctx: input.state.less_ctx | LESS_CTX_ALLOW_DIV,
1378                            ..input.state.clone()
1379                        })
1380                        .parse::<ComponentValue>()?;
1381                    let name = {
1382                        match value {
1383                            ComponentValue::LessVariable(variable) => {
1384                                LessMixinParameterName::Variable(variable)
1385                            }
1386                            ComponentValue::LessPropertyVariable(property) => {
1387                                LessMixinParameterName::PropertyVariable(property)
1388                            }
1389                            value => {
1390                                let span = value.span().clone();
1391                                params.push(LessMixinParameter::Unnamed(
1392                                    LessMixinUnnamedParameter { value, span },
1393                                ));
1394                                break 'maybe;
1395                            }
1396                        }
1397                    };
1398                    let name_span = name.span();
1399                    if let Some((_, colon_span)) = eat!(input, Colon) {
1400                        let value = if matches!(peek!(input).token, Token::LBrace(..)) {
1401                            input.parse().map(ComponentValue::LessDetachedRuleset)?
1402                        } else {
1403                            input
1404                                .with_state(ParserState {
1405                                    less_ctx: input.state.less_ctx | LESS_CTX_ALLOW_DIV,
1406                                    ..input.state.clone()
1407                                })
1408                                .parse_maybe_less_list(/* allow_comma */ false)?
1409                        };
1410                        let end = value.span().end;
1411                        let default_value = {
1412                            let span = Span {
1413                                start: colon_span.start,
1414                                end,
1415                            };
1416                            LessMixinNamedParameterDefaultValue {
1417                                colon_span,
1418                                value,
1419                                span,
1420                            }
1421                        };
1422                        let span = Span {
1423                            start: name_span.start,
1424                            end,
1425                        };
1426                        params.push(LessMixinParameter::Named(LessMixinNamedParameter {
1427                            name,
1428                            value: Some(default_value),
1429                            span,
1430                        }));
1431                    } else if let Some((_, Span { end, .. })) = eat!(input, DotDotDot) {
1432                        let span = Span {
1433                            start: name_span.start,
1434                            end,
1435                        };
1436                        params.push(LessMixinParameter::Variadic(LessMixinVariadicParameter {
1437                            name: Some(name),
1438                            span,
1439                        }));
1440                        if let Some((_, semicolon_span)) = eat!(input, Semicolon) {
1441                            semicolon_spans.push(semicolon_span);
1442                        };
1443                        (_, rparen_span) = expect!(input, RParen);
1444                        break 'params;
1445                    } else {
1446                        let span = name_span.clone();
1447                        params.push(LessMixinParameter::Named(LessMixinNamedParameter {
1448                            name,
1449                            value: None,
1450                            span,
1451                        }));
1452                    }
1453                }
1454            }
1455
1456            match bump!(input) {
1457                TokenWithSpan {
1458                    token: Token::RParen(..),
1459                    span,
1460                } => {
1461                    if semicolon_comes_at > 0 {
1462                        wrap_less_mixin_params_into_less_list(
1463                            &mut params,
1464                            mem::take(&mut comma_spans),
1465                            semicolon_comes_at,
1466                        )
1467                        .map_err(|kind| Error {
1468                            kind,
1469                            span: Span {
1470                                // We've checked `semicolon_comes_at` must be greater than 0,
1471                                // so `params` won't be empty.
1472                                start: params.first().unwrap().span().start,
1473                                end: params.last().unwrap().span().end,
1474                            },
1475                        })?;
1476                    }
1477                    rparen_span = span;
1478                    break;
1479                }
1480                TokenWithSpan {
1481                    token: Token::Comma(..),
1482                    span,
1483                } => {
1484                    comma_spans.push(span);
1485                }
1486                TokenWithSpan {
1487                    token: Token::Semicolon(..),
1488                    span,
1489                } => {
1490                    wrap_less_mixin_params_into_less_list(
1491                        &mut params,
1492                        mem::take(&mut comma_spans),
1493                        semicolon_comes_at,
1494                    )
1495                    .map_err(|kind| Error {
1496                        kind,
1497                        span: span.clone(),
1498                    })?;
1499                    semicolon_comes_at = params.len();
1500                    semicolon_spans.push(span);
1501                }
1502                TokenWithSpan { token, span } => {
1503                    use crate::{token::RParen, tokenizer::TokenSymbol};
1504                    return Err(Error {
1505                        kind: ErrorKind::Unexpected(RParen::symbol(), token.symbol()),
1506                        span,
1507                    });
1508                }
1509            }
1510        }
1511        let is_comma_separated = semicolon_spans.is_empty();
1512        let separator_spans = if semicolon_spans.is_empty() {
1513            comma_spans
1514        } else {
1515            semicolon_spans
1516        };
1517        debug_assert!(params.len() - separator_spans.len() <= 1);
1518        let params = LessMixinParameters {
1519            params,
1520            is_comma_separated,
1521            separator_spans,
1522            span: Span {
1523                start: lparen_span.start,
1524                end: rparen_span.end,
1525            },
1526        };
1527
1528        let guard = match &peek!(input).token {
1529            Token::Ident(ident) if ident.raw == "when" => Some(input.parse()?),
1530            _ => None,
1531        };
1532
1533        let block = input
1534            .with_state(ParserState {
1535                less_ctx: input.state.less_ctx | LESS_CTX_ALLOW_KEYFRAME_BLOCK,
1536                ..input.state.clone()
1537            })
1538            .parse::<SimpleBlock>()?;
1539
1540        let span = Span {
1541            start: name.span().start,
1542            end: block.span.end,
1543        };
1544
1545        Ok(LessMixinDefinition {
1546            name,
1547            params,
1548            guard,
1549            block,
1550            span,
1551        })
1552    }
1553}
1554
1555impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessMixinName<'s> {
1556    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1557        match bump!(input) {
1558            TokenWithSpan {
1559                token: Token::Dot(..),
1560                span: dot_span,
1561            } => {
1562                let ident: Ident = expect_without_ws_or_comments!(input, Ident).into();
1563                let span = Span {
1564                    start: dot_span.start,
1565                    end: ident.span.end,
1566                };
1567                Ok(LessMixinName::ClassSelector(ClassSelector {
1568                    name: InterpolableIdent::Literal(ident),
1569                    span,
1570                }))
1571            }
1572            TokenWithSpan {
1573                token: Token::Hash(hash),
1574                span,
1575            } => {
1576                let raw = hash.raw;
1577                if raw.starts_with(|c: char| c.is_ascii_digit())
1578                    || matches!(raw.as_bytes(), [b'-', c, ..] if *c != b'-')
1579                {
1580                    input.recoverable_errors.push(Error {
1581                        kind: ErrorKind::InvalidIdSelectorName,
1582                        span: span.clone(),
1583                    });
1584                }
1585                let name = if hash.escaped {
1586                    util::handle_escape(raw)
1587                } else {
1588                    Cow::from(raw)
1589                };
1590                let name_span = Span {
1591                    start: span.start + 1,
1592                    end: span.end,
1593                };
1594                Ok(LessMixinName::IdSelector(IdSelector {
1595                    name: InterpolableIdent::Literal(Ident {
1596                        name,
1597                        raw,
1598                        span: name_span,
1599                    }),
1600                    span,
1601                }))
1602            }
1603            TokenWithSpan { token, span } => {
1604                use crate::{
1605                    token::{Dot, Hash},
1606                    tokenizer::TokenSymbol,
1607                };
1608                Err(Error {
1609                    kind: ErrorKind::ExpectOneOf(
1610                        vec![Dot::symbol(), Hash::symbol()],
1611                        token.symbol(),
1612                    ),
1613                    span,
1614                })
1615            }
1616        }
1617    }
1618}
1619
1620impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessMixinParameterName<'s> {
1621    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1622        if matches!(peek!(input).token, Token::AtKeyword(..)) {
1623            input.parse().map(LessMixinParameterName::Variable)
1624        } else {
1625            input.parse().map(LessMixinParameterName::PropertyVariable)
1626        }
1627    }
1628}
1629
1630impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessNamespaceValue<'s> {
1631    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1632        let callee = input.parse::<LessNamespaceValueCallee>()?;
1633        let callee_span = callee.span();
1634
1635        let lookups = input.parse::<LessLookups>()?;
1636        util::assert_no_ws_or_comment(callee_span, &lookups.span)?;
1637
1638        let span = Span {
1639            start: callee_span.start,
1640            end: lookups.span.end,
1641        };
1642        Ok(LessNamespaceValue {
1643            callee,
1644            lookups,
1645            span,
1646        })
1647    }
1648}
1649
1650impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessNamespaceValueCallee<'s> {
1651    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1652        if matches!(peek!(input).token, Token::AtKeyword(..)) {
1653            input.parse().map(LessNamespaceValueCallee::LessVariable)
1654        } else {
1655            input.parse().map(LessNamespaceValueCallee::LessMixinCall)
1656        }
1657    }
1658}
1659
1660impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessNegativeValue<'s> {
1661    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1662        let (_, minus_span) = expect!(input, Minus);
1663        let value = match peek!(input) {
1664            TokenWithSpan {
1665                token: Token::AtKeyword(..) | Token::At(..) | Token::DollarVar(..),
1666                span,
1667            } if minus_span.end == span.start => Box::new(input.parse_component_value_atom()?),
1668            TokenWithSpan {
1669                token: Token::LParen(..),
1670                span,
1671            } if minus_span.end == span.start => {
1672                Box::new(ComponentValue::LessParenthesizedOperation(
1673                    input.parse_less_parenthesized_operation(/* allow_mixin_call */ true)?,
1674                ))
1675            }
1676            TokenWithSpan { token, span } => {
1677                use crate::{
1678                    token::{AtKeyword, DollarVar, LParen},
1679                    tokenizer::TokenSymbol,
1680                };
1681                return Err(Error {
1682                    kind: ErrorKind::ExpectOneOf(
1683                        vec![AtKeyword::symbol(), DollarVar::symbol(), LParen::symbol()],
1684                        token.symbol(),
1685                    ),
1686                    span: span.clone(),
1687                });
1688            }
1689        };
1690
1691        let span = Span {
1692            start: minus_span.start,
1693            end: value.span().end,
1694        };
1695        Ok(LessNegativeValue { value, span })
1696    }
1697}
1698
1699impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessPercentKeyword {
1700    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1701        let (_, span) = expect!(input, Percent);
1702        Ok(LessPercentKeyword { span })
1703    }
1704}
1705
1706impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessPlugin<'s> {
1707    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1708        debug_assert_eq!(input.syntax, Syntax::Less);
1709
1710        let mut start = None;
1711
1712        let args = if let Some((_, span)) = eat!(input, LParen) {
1713            start = Some(span.start);
1714            let args = input.parse_tokens_in_parens()?;
1715            expect!(input, RParen);
1716            Some(args)
1717        } else {
1718            None
1719        };
1720
1721        let path = input.parse::<LessPluginPath>()?;
1722        let path_span = path.span();
1723
1724        let span = Span {
1725            start: start.unwrap_or(path_span.start),
1726            end: path_span.end,
1727        };
1728        Ok(LessPlugin { path, args, span })
1729    }
1730}
1731
1732impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessPluginPath<'s> {
1733    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1734        if let Token::Str(..) = peek!(input).token {
1735            input.parse().map(LessPluginPath::Str)
1736        } else {
1737            input.parse().map(LessPluginPath::Url)
1738        }
1739    }
1740}
1741
1742impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessPropertyInterpolation<'s> {
1743    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1744        let (dollar_lbrace_var, span) = expect!(input, DollarLBraceVar);
1745        Ok(LessPropertyInterpolation {
1746            name: (
1747                dollar_lbrace_var.ident,
1748                Span {
1749                    start: span.start + 2,
1750                    end: span.end - 1,
1751                },
1752            )
1753                .into(),
1754            span,
1755        })
1756    }
1757}
1758
1759impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for Option<LessPropertyMerge> {
1760    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1761        debug_assert_eq!(input.syntax, Syntax::Less);
1762
1763        match &peek!(input).token {
1764            Token::Plus(..) => Ok(Some(LessPropertyMerge {
1765                kind: LessPropertyMergeKind::Comma,
1766                span: bump!(input).span,
1767            })),
1768            Token::PlusUnderscore(..) => Ok(Some(LessPropertyMerge {
1769                kind: LessPropertyMergeKind::Space,
1770                span: bump!(input).span,
1771            })),
1772            _ => Ok(None),
1773        }
1774    }
1775}
1776
1777impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessPropertyVariable<'s> {
1778    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1779        let (dollar_var, span) = expect!(input, DollarVar);
1780        Ok(LessPropertyVariable {
1781            name: Ident::from((
1782                dollar_var.ident,
1783                Span {
1784                    start: span.start + 1,
1785                    end: span.end,
1786                },
1787            )),
1788            span,
1789        })
1790    }
1791}
1792
1793impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessVariable<'s> {
1794    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1795        let (at_keyword, span) = expect!(input, AtKeyword);
1796        Ok(LessVariable {
1797            name: (
1798                at_keyword.ident,
1799                Span {
1800                    start: span.start + 1,
1801                    end: span.end,
1802                },
1803            )
1804                .into(),
1805            span,
1806        })
1807    }
1808}
1809
1810impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessVariableCall<'s> {
1811    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1812        let variable = input.parse::<LessVariable>()?;
1813        expect_without_ws_or_comments!(input, LParen);
1814        let (_, Span { end, .. }) = expect!(input, RParen);
1815
1816        let span = Span {
1817            start: variable.span.start,
1818            end,
1819        };
1820        Ok(LessVariableCall { variable, span })
1821    }
1822}
1823
1824impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessVariableDeclaration<'s> {
1825    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1826        debug_assert_eq!(input.syntax, Syntax::Less);
1827
1828        let name = input.parse::<LessVariable>()?;
1829        let (_, colon_span) = expect!(input, Colon);
1830        let value = if matches!(peek!(input).token, Token::LBrace(..)) {
1831            ComponentValue::LessDetachedRuleset(input.parse()?)
1832        } else {
1833            input
1834                .with_state(ParserState {
1835                    less_ctx: input.state.less_ctx | LESS_CTX_ALLOW_DIV,
1836                    ..input.state.clone()
1837                })
1838                .parse_maybe_less_list(/* allow_comma */ true)?
1839        };
1840
1841        let span = Span {
1842            start: name.span.start,
1843            end: value.span().end,
1844        };
1845        Ok(LessVariableDeclaration {
1846            name,
1847            colon_span,
1848            value,
1849            span,
1850        })
1851    }
1852}
1853
1854impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessVariableInterpolation<'s> {
1855    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1856        let (at_lbrace_var, span) = expect!(input, AtLBraceVar);
1857        Ok(LessVariableInterpolation {
1858            name: (
1859                at_lbrace_var.ident,
1860                Span {
1861                    start: span.start + 2,
1862                    end: span.end - 1,
1863                },
1864            )
1865                .into(),
1866            span,
1867        })
1868    }
1869}
1870
1871impl<'cmt, 's: 'cmt> Parse<'cmt, 's> for LessVariableVariable<'s> {
1872    fn parse(input: &mut Parser<'cmt, 's>) -> PResult<Self> {
1873        let (_, at_span) = expect!(input, At);
1874        let variable = input.parse::<LessVariable>()?;
1875        util::assert_no_ws_or_comment(&at_span, &variable.span)?;
1876
1877        let span = Span {
1878            start: at_span.start,
1879            end: variable.span.end,
1880        };
1881        Ok(LessVariableVariable { variable, span })
1882    }
1883}
1884
1885fn wrap_less_mixin_params_into_less_list(
1886    params: &mut Vec<LessMixinParameter<'_>>,
1887    comma_spans: Vec<Span>,
1888    index: usize,
1889) -> Result<(), ErrorKind> {
1890    if let [first, .., last] = &params[index..] {
1891        let span = Span {
1892            start: first.span().start,
1893            end: last.span().end,
1894        };
1895        let elements = params
1896            .drain(index..)
1897            .map(|param| {
1898                if let LessMixinParameter::Unnamed(LessMixinUnnamedParameter { value, .. }) = param
1899                {
1900                    Ok(value)
1901                } else {
1902                    // reject code like this:
1903                    // .mixin(@a: 5, @b: 6; @c: 7) {}
1904                    // .mixin(@a: 5; @b: 6, @c: 7) {}
1905                    Err(ErrorKind::MixedDelimiterKindInLessMixin)
1906                }
1907            })
1908            .collect::<Result<Vec<_>, _>>()?;
1909        debug_assert!(elements.len() - comma_spans.len() <= 1);
1910        params.push(LessMixinParameter::Unnamed(LessMixinUnnamedParameter {
1911            value: ComponentValue::LessList(LessList {
1912                elements,
1913                comma_spans: Some(comma_spans),
1914                span: span.clone(),
1915            }),
1916            span,
1917        }));
1918    }
1919    Ok(())
1920}
1921
1922fn wrap_less_mixin_args_into_less_list(
1923    args: &mut Vec<LessMixinArgument<'_>>,
1924    comma_spans: Vec<Span>,
1925    index: usize,
1926) -> Result<(), ErrorKind> {
1927    if let [first, .., last] = &args[index..] {
1928        let span = Span {
1929            start: first.span().start,
1930            end: last.span().end,
1931        };
1932        let elements = args
1933            .drain(index..)
1934            .map(|param| {
1935                if let LessMixinArgument::Value(value) = param {
1936                    Ok(value)
1937                } else {
1938                    // reject code like this:
1939                    // .mixin(@a: 5, @b: 6; @c: 7) {}
1940                    // .mixin(@a: 5; @b: 6, @c: 7) {}
1941                    Err(ErrorKind::MixedDelimiterKindInLessMixin)
1942                }
1943            })
1944            .collect::<Result<Vec<_>, _>>()?;
1945        debug_assert!(elements.len() - comma_spans.len() <= 1);
1946        args.push(LessMixinArgument::Value(ComponentValue::LessList(
1947            LessList {
1948                elements,
1949                comma_spans: Some(comma_spans),
1950                span,
1951            },
1952        )));
1953    }
1954    Ok(())
1955}
1956
1957fn can_be_division_operand(left: &ComponentValue) -> bool {
1958    matches!(
1959        left,
1960        ComponentValue::LessVariable(..)
1961            | ComponentValue::LessPropertyVariable(..)
1962            | ComponentValue::LessBinaryOperation(..)
1963            | ComponentValue::LessParenthesizedOperation(..)
1964    )
1965}