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(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(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 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 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 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(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(true)?
739 };
740 end = item.span().end;
741 elements.push(item);
742 }
743 }
744 }
745
746 if elements.is_empty() && separator != ListSeparatorKind::Comma {
747 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 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 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(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 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(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 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(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(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] = ¶ms[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 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 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}