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