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