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 loop {
420 let op = match self.cursor.peek()? {
421 TokenWithSpan { token: Token::Asterisk(..), .. }
422 if precedence == PRECEDENCE_MULTIPLY =>
423 {
424 LessOperationOperator {
425 kind: LessOperationOperatorKind::Multiply,
426 span: self.cursor.bump()?.span,
427 }
428 }
429 TokenWithSpan { token: Token::Solidus(..), .. }
430 if precedence == PRECEDENCE_MULTIPLY
431 && (self.state.less_ctx & LESS_CTX_ALLOW_DIV != 0
432 || can_be_division_operand(&left)) =>
433 {
434 LessOperationOperator {
435 kind: LessOperationOperatorKind::Division,
436 span: self.cursor.bump()?.span,
437 }
438 }
439 TokenWithSpan { token: Token::Dot(..), .. }
440 if precedence == PRECEDENCE_MULTIPLY =>
441 {
442 let Span { start, .. } = self.cursor.bump()?.span;
444 let (_, Span { end, .. }) =
445 self.cursor.expect_solidus_without_ws_or_comments()?;
446 LessOperationOperator {
447 kind: LessOperationOperatorKind::Division,
448 span: Span { start, end },
449 }
450 }
451 TokenWithSpan { token: Token::Plus(..), span }
456 if precedence == PRECEDENCE_PLUS
457 && (is_followed_by_whitespace(self.source, span.end)
458 || self.state.less_ctx & LESS_CTX_ALLOW_DIV != 0) =>
459 {
460 LessOperationOperator {
461 kind: LessOperationOperatorKind::Plus,
462 span: self.cursor.bump()?.span,
463 }
464 }
465 TokenWithSpan { token: Token::Minus(..), span }
466 if precedence == PRECEDENCE_PLUS
467 && (is_followed_by_whitespace(self.source, span.end)
468 || self.state.less_ctx & LESS_CTX_ALLOW_DIV != 0) =>
469 {
470 LessOperationOperator {
471 kind: LessOperationOperatorKind::Minus,
472 span: self.cursor.bump()?.span,
473 }
474 }
475 token @ TokenWithSpan { token: Token::Number(..), span }
476 if precedence == PRECEDENCE_PLUS
477 && token.number_raw(self.source).is_some_and(|raw| {
478 raw.starts_with('+')
479 || raw.starts_with('-') && span.start == left.span().end
480 }) =>
481 {
482 let (number, number_span) = self.cursor.expect_number()?;
483 let op = LessOperationOperator {
484 kind: if number.raw.starts_with('+') {
485 LessOperationOperatorKind::Plus
486 } else {
487 LessOperationOperatorKind::Minus
488 },
489 span: Span { start: number_span.start, end: number_span.start + 1 },
490 };
491 let span = Span { start: left.span().start, end: number_span.end };
492 let right = {
493 let span = Span { start: number_span.start + 1, end: number_span.end };
494 let raw = unsafe { number.raw.get_unchecked(1..number.raw.len()) };
495 raw.parse()
496 .map_err(|_| Error { kind: ErrorKind::InvalidNumber, span })
497 .map(|value| ComponentValue::Number(Number { value, raw, span }))?
498 };
499 left = ComponentValue::LessBinaryOperation(LessBinaryOperation {
500 left: self.alloc(left),
501 op,
502 right: self.alloc(right),
503 span,
504 });
505 continue;
506 }
507 token @ TokenWithSpan { token: Token::Dimension(..), span }
508 if precedence == PRECEDENCE_PLUS
509 && token.dimension_value_raw(self.source).is_some_and(|raw| {
510 raw.starts_with('+')
511 || raw.starts_with('-') && span.start == left.span().end
512 }) =>
513 {
514 let (dimension, dimension_span) = self.cursor.expect_dimension()?;
515 let op = LessOperationOperator {
516 kind: if dimension.value.raw.starts_with('+') {
517 LessOperationOperatorKind::Plus
518 } else {
519 LessOperationOperatorKind::Minus
520 },
521 span: Span { start: dimension_span.start, end: dimension_span.start + 1 },
522 };
523 let mut right = {
524 self.dimension(
525 crate::token::Dimension {
526 value: crate::token::Number {
527 raw: unsafe {
528 dimension
529 .value
530 .raw
531 .get_unchecked(1..dimension.value.raw.len())
532 },
533 },
534 unit: dimension.unit,
535 },
536 Span { start: dimension_span.start + 1, end: dimension_span.end },
537 )
538 .map(ComponentValue::Dimension)?
539 };
540 while matches!(
543 &self.cursor.peek()?.token,
544 Token::Asterisk(..) | Token::Solidus(..)
545 ) {
546 let mul_op = LessOperationOperator {
547 kind: if matches!(&self.cursor.peek()?.token, Token::Asterisk(..)) {
548 LessOperationOperatorKind::Multiply
549 } else {
550 LessOperationOperatorKind::Division
551 },
552 span: self.cursor.bump()?.span,
553 };
554 let mul_rhs = self.parse_less_operation_recursively(
555 allow_mixin_call,
556 PRECEDENCE_MULTIPLY + 1,
557 )?;
558 let span = Span { start: right.span().start, end: mul_rhs.span().end };
559 right = ComponentValue::LessBinaryOperation(LessBinaryOperation {
560 left: self.alloc(right),
561 op: mul_op,
562 right: self.alloc(mul_rhs),
563 span,
564 });
565 }
566 let span = Span { start: left.span().start, end: right.span().end };
567 left = ComponentValue::LessBinaryOperation(LessBinaryOperation {
568 left: self.alloc(left),
569 op,
570 right: self.alloc(right),
571 span,
572 });
573 continue;
574 }
575 _ => break,
576 };
577
578 let right = self.parse_less_operation_recursively(allow_mixin_call, precedence + 1)?;
579 let span = Span { start: left.span().start, end: right.span().end };
580 left = ComponentValue::LessBinaryOperation(LessBinaryOperation {
581 left: self.alloc(left),
582 op,
583 right: self.alloc(right),
584 span,
585 });
586 }
587
588 Ok(left)
589 }
590
591 fn parse_less_parenthesized_operation(
593 &mut self,
594 allow_mixin_call: bool,
595 ) -> PResult<LessParenthesizedOperation<'a>> {
596 let (_, Span { start, .. }) = self.cursor.expect_l_paren()?;
597 let operation = self
598 .with_state(ParserState {
599 less_ctx: self.state.less_ctx | LESS_CTX_ALLOW_DIV,
600 ..self.state.clone()
601 })
602 .parse_less_operation(allow_mixin_call)?;
603 let (_, Span { end, .. }) = self.cursor.expect_r_paren()?;
604 Ok(LessParenthesizedOperation {
605 operation: self.alloc(operation),
606 span: Span { start, end },
607 })
608 }
609
610 pub(super) fn parse_less_qualified_rule(&mut self) -> PResult<Statement<'a>> {
613 debug_assert_eq!(self.syntax, Syntax::Less);
614
615 let selector_list = self
616 .with_state(ParserState {
617 qualified_rule_ctx: Some(QualifiedRuleContext::Selector),
618 ..self.state
619 })
620 .parse::<SelectorList>()?;
621
622 if self.cursor.peek()?.is_ident_raw(self.source, "when") {
623 if selector_list.selectors.len() > 1 {
627 let span = self.cursor.peek()?.span;
628 return Err(Error { kind: ErrorKind::LessGuardOnMultipleComplexSelectors, span });
629 }
630 let guard = self.parse::<LessConditions>()?;
631 let block = self.parse::<SimpleBlock>()?;
632 let span = Span { start: selector_list.span.start, end: block.span.end };
633 return Ok(Statement::LessConditionalQualifiedRule(LessConditionalQualifiedRule {
634 selector: selector_list,
635 guard,
636 block,
637 span,
638 }));
639 }
640
641 let block = self.parse::<SimpleBlock>()?;
642 let span = Span { start: selector_list.span.start, end: block.span.end };
643 Ok(Statement::QualifiedRule(QualifiedRule { selector: selector_list, block, span }))
644 }
645
646 pub(super) fn parse_maybe_hex_color_or_less_mixin_call(
648 &mut self,
649 ) -> PResult<ComponentValue<'a>> {
650 debug_assert_eq!(self.syntax, Syntax::Less);
651
652 let attempt = self.try_parse(|parser| {
653 let hex_color = parser.parse::<HexColor>()?;
654 match parser.cursor.peek()? {
655 TokenWithSpan { token: Token::LParen(..), span } => {
656 Err(Error { kind: ErrorKind::TryParseError, span: *span })
657 }
658 TokenWithSpan {
659 token: Token::LBracket(..) | Token::Dot(..) | Token::Hash(..),
660 span,
661 } if hex_color.span.end == span.start => {
662 Err(Error { kind: ErrorKind::TryParseError, span: *span })
663 }
664 _ => Ok(hex_color),
665 }
666 });
667 match attempt {
668 Err(Error { kind: ErrorKind::TryParseError, .. }) => {
669 self.parse_less_maybe_mixin_call_or_with_lookups()
670 }
671 hex_color => hex_color.map(ComponentValue::HexColor),
672 }
673 }
674
675 pub(super) fn parse_maybe_less_list(
677 &mut self,
678 allow_comma: bool,
679 ) -> PResult<ComponentValue<'a>> {
680 use util::ListSeparatorKind;
681
682 let single_value = if allow_comma {
683 self.parse_maybe_less_list(false)?
684 } else if let Token::Exclamation(..) = self.cursor.peek()?.token {
685 self.parse().map(ComponentValue::ImportantAnnotation)?
686 } else {
687 self.parse_less_operation(true)?
688 };
689
690 let mut elements = self.vec();
691 let mut comma_spans: Option<oxc_allocator::Vec<'a, Span>> = None;
692 let mut separator = ListSeparatorKind::Unknown;
693 let mut end = single_value.span().end;
694 loop {
695 match self.cursor.peek()?.token {
696 Token::LBrace(..)
697 | Token::RBrace(..)
698 | Token::RParen(..)
699 | Token::Semicolon(..)
700 | Token::Colon(..)
701 | Token::DotDotDot(..)
702 | Token::Eof(..) => break,
703 Token::Comma(..) => {
704 if !allow_comma {
705 break;
706 }
707 if separator == ListSeparatorKind::Space {
708 break;
709 } else {
710 if separator == ListSeparatorKind::Unknown {
711 separator = ListSeparatorKind::Comma;
712 }
713 let TokenWithSpan { span, .. } = self.cursor.bump()?;
714 end = span.end;
715 if let Some(spans) = &mut comma_spans {
716 spans.push(span);
717 } else {
718 comma_spans = Some(self.vec1(span));
719 }
720 }
721 }
722 Token::Exclamation(..) => {
723 if let Ok(important_annotation) = self.try_parse(ImportantAnnotation::parse) {
724 if end < important_annotation.span.start
725 && separator == ListSeparatorKind::Unknown
726 {
727 separator = ListSeparatorKind::Space;
728 }
729 end = important_annotation.span.end;
730 elements.push(ComponentValue::ImportantAnnotation(important_annotation));
731 } else {
732 break;
733 }
734 }
735 _ => {
736 if separator == ListSeparatorKind::Unknown {
737 separator = ListSeparatorKind::Space;
738 }
739 let item = if separator == ListSeparatorKind::Comma {
740 self.parse_maybe_less_list(false)?
741 } else {
742 self.parse_less_operation(true)?
743 };
744 end = item.span().end;
745 elements.push(item);
746 }
747 }
748 }
749
750 if elements.is_empty() && separator != ListSeparatorKind::Comma {
751 Ok(single_value)
754 } else {
755 debug_assert_ne!(separator, ListSeparatorKind::Unknown);
756
757 let span = Span { start: single_value.span().start, end };
758 elements.insert(0, single_value);
759 Ok(ComponentValue::LessList(LessList { elements, comma_spans, span }))
760 }
761 }
762}
763
764impl<'a> Parse<'a> for LessConditions<'a> {
768 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
769 let token = input.cursor.bump()?;
770 let when_span = if token.is_ident_raw(input.source, "when") {
771 token.span
772 } else {
773 return Err(Error { kind: ErrorKind::ExpectLessKeyword("when"), span: token.span });
774 };
775
776 let first = input.parse_less_condition(true)?;
777 let mut span = *first.span();
778
779 let mut conditions = input.vec1(first);
780 let mut comma_spans = input.vec();
781 while matches!(input.cursor.peek()?.token, Token::Comma(..)) {
785 let Ok((comma_span, condition)) = input.try_parse(|p| {
786 let (_, comma_span) = p.cursor.expect_comma()?;
787 let condition = p.parse_less_condition(true)?;
788 Ok((comma_span, condition))
789 }) else {
790 break;
791 };
792 comma_spans.push(comma_span);
793 conditions.push(condition);
794 }
795 debug_assert_eq!(comma_spans.len() + 1, conditions.len());
796
797 if let Some(last) = conditions.last() {
798 span.end = last.span().end;
799 }
800 Ok(LessConditions { conditions, when_span, comma_spans, span })
801 }
802}
803
804impl<'a> Parse<'a> for LessDetachedRuleset<'a> {
808 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
809 let block = input.parse::<SimpleBlock>()?;
810 let span = block.span;
811 Ok(LessDetachedRuleset { block, span })
812 }
813}
814
815impl<'a> Parse<'a> for LessEscapedStr<'a> {
819 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
820 let (_, Span { start, .. }) = input.cursor.expect_tilde()?;
821 let str = {
822 let (str, span) = input.cursor.tokenizer.scan_string_only()?;
823 input.str(str, span)
824 };
825 let span = Span { start, end: str.span().end };
826 Ok(LessEscapedStr { str, span })
827 }
828}
829
830impl<'a> Parse<'a> for LessExtend<'a> {
834 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
835 let mut selector = input.parse::<ComplexSelector>()?;
836
837 let span = selector.span;
838 let mut all = None;
839
840 if let [
841 ..,
842 complex_child,
843 ComplexSelectorChild::Combinator(Combinator {
844 kind: CombinatorKind::Descendant, ..
845 }),
846 ComplexSelectorChild::CompoundSelector(CompoundSelector { children, .. }),
847 ] = &selector.children[..]
848 && let [
849 SimpleSelector::Type(TypeSelector::TagName(TagNameSelector {
850 name:
851 WqName {
852 name: InterpolableIdent::Literal(token_all @ Ident { raw: "all", .. }),
853 prefix: None,
854 ..
855 },
856 ..
857 })),
858 ] = &children[..]
859 {
860 all = Some(Ident { name: token_all.name, raw: token_all.raw, span: token_all.span });
861 selector.span.end = complex_child.span().end;
862 let len = selector.children.len();
863 selector.children.truncate(len - 2);
864 }
865
866 Ok(LessExtend { selector, all, span })
867 }
868}
869
870impl<'a> Parse<'a> for LessExtendList<'a> {
872 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
873 debug_assert_eq!(input.syntax, Syntax::Less);
874
875 let first = input.parse::<LessExtend>()?;
876 let mut span = first.span;
877
878 let mut elements = input.vec1(first);
879 let mut comma_spans = input.vec();
880 while let Some((_, comma_span)) = input.cursor.eat_comma()? {
881 comma_spans.push(comma_span);
882 elements.push(input.parse()?);
883 }
884 debug_assert_eq!(comma_spans.len() + 1, elements.len());
885
886 if let Some(last) = elements.last() {
887 span.end = last.span.end;
888 }
889 Ok(LessExtendList { elements, comma_spans, span })
890 }
891}
892
893impl<'a> Parse<'a> for LessExtendRule<'a> {
895 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
896 let nesting_selector = input.parse::<NestingSelector>()?;
897 if nesting_selector.suffix.is_some() {
898 return Err(Error {
899 kind: ErrorKind::ExpectLessExtendRule,
900 span: nesting_selector.span,
901 });
902 }
903
904 let pseudo_class_selector = input.parse::<PseudoClassSelector>()?;
905 util::assert_no_ws_or_comment(&nesting_selector.span, &pseudo_class_selector.span)?;
906 let span = Span { start: nesting_selector.span.start, end: pseudo_class_selector.span.end };
907
908 let InterpolableIdent::Literal(name_of_extend @ Ident { raw: "extend", .. }) =
909 pseudo_class_selector.name
910 else {
911 return Err(Error { kind: ErrorKind::ExpectLessExtendRule, span });
912 };
913 let Some(PseudoClassSelectorArg {
914 kind: PseudoClassSelectorArgKind::LessExtendList(extend),
915 ..
916 }) = pseudo_class_selector.arg
917 else {
918 return Err(Error { kind: ErrorKind::ExpectLessExtendRule, span });
919 };
920
921 Ok(LessExtendRule { nesting_selector, name_of_extend, extend, span })
922 }
923}
924
925impl<'a> Parse<'a> for LessFormatFunction {
928 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
929 let (_, span) = input.cursor.expect_percent()?;
930 Ok(LessFormatFunction { span })
931 }
932}
933
934impl<'a> Parse<'a> for LessImportOptions<'a> {
938 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
939 let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
940
941 let mut names = input.vec_with_capacity(1);
942 let mut comma_spans = input.vec();
943 while input.cursor.peek()?.ident_raw(input.source).is_some_and(|raw| {
944 matches!(
945 raw,
946 "less" | "css" | "multiple" | "once" | "inline" | "reference" | "optional"
947 )
948 }) {
949 names.push(input.parse()?);
950 if !matches!(input.cursor.peek()?.token, Token::RParen(..)) {
951 comma_spans.push(input.cursor.expect_comma()?.1);
952 }
953 }
954 debug_assert!(names.len() - comma_spans.len() <= 1);
955
956 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
957
958 Ok(LessImportOptions { names, comma_spans, span: Span { start, end } })
959 }
960}
961
962impl<'a> Parse<'a> for LessImportPrelude<'a> {
964 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
965 let options = input.parse::<LessImportOptions>()?;
966 let start = options.span.start;
967
968 let href = match &input.cursor.peek()?.token {
969 Token::Str(..) | Token::StrTemplate(..) => input.parse().map(ImportPreludeHref::Str)?,
970 _ => input.parse().map(ImportPreludeHref::Url)?,
971 };
972 let mut end = href.span().end;
973
974 let media = if matches!(input.cursor.peek()?.token, Token::Semicolon(..)) {
975 None
976 } else {
977 let media = input.parse::<MediaQueryList>()?;
978 end = media.span.end;
979 Some(media)
980 };
981
982 Ok(LessImportPrelude { href, options, media, span: Span { start, end } })
983 }
984}
985
986impl<'a> Parse<'a> for LessInterpolatedStr<'a> {
989 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
990 let (first, first_span) = input.cursor.expect_str_template()?;
991 let quote = first.raw.bytes().next().unwrap();
992 debug_assert!(quote == b'\'' || quote == b'"');
993 let mut span = first_span;
994 let mut elements = input.vec1(LessInterpolatedStrElement::Static(
995 input.interpolable_str_static_part(first, first_span),
996 ));
997
998 let mut is_parsing_static_part = false;
999 loop {
1000 if is_parsing_static_part {
1001 let (token, str_tpl_span) = input.cursor.tokenizer.scan_string_template(quote)?;
1002 let tail = token.tail;
1003 let end = str_tpl_span.end;
1004 elements.push(LessInterpolatedStrElement::Static(
1005 input.interpolable_str_static_part(token, str_tpl_span),
1006 ));
1007 if tail {
1008 span.end = end;
1009 break;
1010 }
1011 } else {
1012 let start = input.cursor.expect_l_brace()?.1.start - 1;
1014 let (name, name_span) = input.cursor.expect_ident_without_ws_or_comments(true)?;
1016
1017 let end = input.cursor.expect_r_brace()?.1.end;
1018 elements.push(match input.source.as_bytes().get(start) {
1019 Some(b'@') => LessInterpolatedStrElement::Variable(LessVariableInterpolation {
1020 name: input.ident(name, name_span),
1021 span: Span { start, end },
1022 }),
1023 Some(b'$') => LessInterpolatedStrElement::Property(LessPropertyInterpolation {
1024 name: input.ident(name, name_span),
1025 span: Span { start, end },
1026 }),
1027 _ => unreachable!(),
1028 });
1029 }
1030 is_parsing_static_part = !is_parsing_static_part;
1031 }
1032
1033 Ok(LessInterpolatedStr { elements, span })
1034 }
1035}
1036
1037impl<'a> Parse<'a> for LessJavaScriptSnippet<'a> {
1039 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1040 let tilde = input.cursor.eat_tilde()?;
1041 let (token, span) = input.cursor.expect_backtick_code()?;
1042
1043 Ok(LessJavaScriptSnippet {
1044 code: &token.raw[1..token.raw.len() - 1],
1045 raw: token.raw,
1046 escaped: tilde.is_some(),
1047 span: Span {
1048 start: tilde.map(|(_, span)| span.start).unwrap_or(span.start),
1049 end: span.end,
1050 },
1051 })
1052 }
1053}
1054
1055impl<'a> Parse<'a> for LessListFunction {
1057 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1058 let (_, span) = input.cursor.expect_tilde()?;
1059 Ok(LessListFunction { span })
1060 }
1061}
1062
1063impl<'a> Parse<'a> for LessLookup<'a> {
1067 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1068 debug_assert_eq!(input.syntax, Syntax::Less);
1069
1070 let (_, Span { start, .. }) = input.cursor.expect_l_bracket()?;
1071 let name = if let Token::RBracket(..) = input.cursor.peek()?.token {
1072 None
1073 } else {
1074 Some(input.parse()?)
1075 };
1076 let (_, Span { end, .. }) = input.cursor.expect_r_bracket()?;
1077 Ok(LessLookup { name, span: Span { start, end } })
1078 }
1079}
1080
1081impl<'a> Parse<'a> for LessLookupName<'a> {
1083 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1084 debug_assert_eq!(input.syntax, Syntax::Less);
1085
1086 let is_dollar = {
1087 let TokenWithSpan { token, span } = input.cursor.peek()?;
1088 matches!(token, Token::Unknown(..))
1089 && input.source.as_bytes().get(span.start) == Some(&b'$')
1090 };
1091 match input.cursor.peek()?.token {
1092 Token::AtKeyword(..) => input.parse().map(LessLookupName::LessVariable),
1093 Token::At(..) => input.parse().map(LessLookupName::LessVariableVariable),
1094 Token::DollarVar(..) => input.parse().map(LessLookupName::LessPropertyVariable),
1095 Token::Unknown(..) if is_dollar => {
1098 let dollar_span = input.cursor.bump()?.span;
1099 let variable = input.parse::<LessVariable>()?;
1100 util::assert_no_ws_or_comment(&dollar_span, &variable.span)?;
1101 let span = Span { start: dollar_span.start, end: variable.span.end };
1102 Ok(LessLookupName::LessPropertyInterpolation(LessPropertyInterpolation {
1103 name: variable.name,
1104 span,
1105 }))
1106 }
1107 _ => input.parse().map(LessLookupName::Ident),
1108 }
1109 }
1110}
1111
1112impl<'a> Parse<'a> for LessLookups<'a> {
1114 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1115 debug_assert_eq!(input.syntax, Syntax::Less);
1116
1117 let first = input.parse::<LessLookup>()?;
1118 let mut span = first.span;
1119
1120 let mut lookups = input.vec1(first);
1121 while let Token::LBracket(..) = input.cursor.peek()?.token {
1122 lookups.push(input.parse()?);
1123 }
1124
1125 if let Some(last) = lookups.last() {
1126 span.end = last.span.end;
1127 }
1128 Ok(LessLookups { lookups, span })
1129 }
1130}
1131
1132impl<'a> Parse<'a> for LessMixinCall<'a> {
1136 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1137 debug_assert_eq!(input.syntax, Syntax::Less);
1138
1139 let callee = input.parse::<LessMixinCallee>()?;
1140
1141 let mut end = callee.span.end;
1142 let args = if let Some((_, lparen_span)) = input.cursor.eat_l_paren()? {
1143 let mut semicolon_comes_at = 0;
1144 let mut args = input.vec();
1145 let mut comma_spans = input.vec();
1146 let mut semicolon_spans = input.vec();
1147 loop {
1148 match input.cursor.peek()?.token {
1149 Token::RParen(..) => {
1150 let TokenWithSpan { span, .. } = input.cursor.bump()?;
1151 if semicolon_comes_at > 0 {
1152 let comma_spans = mem::replace(&mut comma_spans, input.vec());
1153 wrap_less_mixin_args_into_less_list(
1154 input.allocator,
1155 &mut args,
1156 comma_spans,
1157 semicolon_comes_at,
1158 )
1159 .map_err(|kind| Error {
1160 kind,
1161 span: Span {
1162 start: args.first().unwrap().span().start,
1165 end: args.last().unwrap().span().end,
1166 },
1167 })?;
1168 }
1169 end = span.end;
1170 break;
1171 }
1172 Token::LBrace(..) => args.push(LessMixinArgument::Value(
1173 ComponentValue::LessDetachedRuleset(input.parse()?),
1174 )),
1175 Token::Comma(..) => {
1176 return Err(Error {
1177 kind: ErrorKind::ExpectComponentValue,
1178 span: input.cursor.bump()?.span,
1179 });
1180 }
1181 _ => 'maybe: {
1182 let value = input.parse_maybe_less_list(false)?;
1183 let name = {
1184 match value {
1185 ComponentValue::LessVariable(variable) => {
1186 LessMixinParameterName::Variable(variable)
1187 }
1188 ComponentValue::LessPropertyVariable(property) => {
1189 LessMixinParameterName::PropertyVariable(property)
1190 }
1191 value => {
1192 args.push(LessMixinArgument::Value(value));
1193 break 'maybe;
1194 }
1195 }
1196 };
1197 if let Some((_, colon_span)) = input.cursor.eat_colon()? {
1198 let value = if matches!(input.cursor.peek()?.token, Token::LBrace(..)) {
1199 input.parse().map(ComponentValue::LessDetachedRuleset)?
1200 } else {
1201 input.parse_maybe_less_list(
1202 semicolon_comes_at > 0,
1203 )?
1204 };
1205 let span = Span { start: name.span().start, end: value.span().end };
1206 args.push(LessMixinArgument::Named(LessMixinNamedArgument {
1207 name,
1208 colon_span,
1209 value,
1210 span,
1211 }));
1212 } else if let Some((_, dotdotdot_span)) = input.cursor.eat_dot_dot_dot()? {
1213 let span = Span { start: name.span().start, end: dotdotdot_span.end };
1217 args.push(LessMixinArgument::Variadic(LessMixinVariadicArgument {
1218 name,
1219 span,
1220 }));
1221 } else {
1222 args.push(LessMixinArgument::Value(match name {
1223 LessMixinParameterName::Variable(variable) => {
1224 ComponentValue::LessVariable(variable)
1225 }
1226 LessMixinParameterName::PropertyVariable(property_variable) => {
1227 ComponentValue::LessPropertyVariable(property_variable)
1228 }
1229 }));
1230 }
1231 }
1232 };
1233
1234 match input.cursor.peek()?.token {
1235 Token::RParen(..) => {}
1236 Token::Comma(..) => {
1237 comma_spans.push(input.cursor.bump()?.span);
1238 }
1239 Token::Semicolon(..) => {
1240 let TokenWithSpan { span, .. } = input.cursor.bump()?;
1241 let comma_spans = mem::replace(&mut comma_spans, input.vec());
1242 wrap_less_mixin_args_into_less_list(
1243 input.allocator,
1244 &mut args,
1245 comma_spans,
1246 semicolon_comes_at,
1247 )
1248 .map_err(|kind| Error { kind, span })?;
1249 semicolon_comes_at = args.len();
1250 semicolon_spans.push(span);
1251 }
1252 _ => {
1253 let TokenWithSpan { token, span } = input.cursor.bump()?;
1254
1255 return Err(Error {
1256 kind: ErrorKind::Unexpected(")", token.symbol()),
1257 span,
1258 });
1259 }
1260 }
1261 }
1262 let is_comma_separated = semicolon_spans.is_empty();
1263 let separator_spans =
1264 if semicolon_spans.is_empty() { comma_spans } else { semicolon_spans };
1265 debug_assert!(args.len() - separator_spans.len() <= 1);
1266 Some(LessMixinArguments {
1267 args,
1268 is_comma_separated,
1269 separator_spans,
1270 span: Span { start: lparen_span.start, end },
1271 })
1272 } else {
1273 None
1274 };
1275
1276 let important = if !matches!(
1277 input.state.qualified_rule_ctx,
1278 Some(QualifiedRuleContext::DeclarationValue)
1279 ) && matches!(input.cursor.peek()?.token, Token::Exclamation(..))
1280 {
1281 input.parse::<ImportantAnnotation>().map(Some)?
1282 } else {
1283 None
1284 };
1285
1286 let span = Span {
1287 start: callee.span.start,
1288 end: important.as_ref().map(|important| important.span.end).unwrap_or(end),
1289 };
1290 Ok(LessMixinCall { callee, args, important, span })
1291 }
1292}
1293
1294impl<'a> Parser<'a> {
1295 fn parse_less_mixin_parameters(&mut self) -> PResult<LessMixinParameters<'a>> {
1298 let (_, lparen_span) = self.cursor.expect_l_paren()?;
1299 let rparen_span;
1300 let mut semicolon_comes_at = 0;
1301 let mut params = self.vec();
1302 let mut comma_spans = self.vec();
1303 let mut semicolon_spans = self.vec();
1304 'params: loop {
1305 match self.cursor.peek()?.token {
1306 Token::RParen(..) => {
1307 rparen_span = self.cursor.bump()?.span;
1308 break;
1309 }
1310 Token::DotDotDot(..) => {
1311 let TokenWithSpan { span, .. } = self.cursor.bump()?;
1312 params.push(LessMixinParameter::Variadic(LessMixinVariadicParameter {
1313 name: None,
1314 span,
1315 }));
1316 self.cursor.eat_semicolon()?;
1317 (_, rparen_span) = self.cursor.expect_r_paren()?;
1318 break;
1319 }
1320 Token::Comma(..) => {
1321 return Err(Error {
1322 kind: ErrorKind::ExpectComponentValue,
1323 span: self.cursor.bump()?.span,
1324 });
1325 }
1326 _ => 'maybe: {
1327 let value = self
1328 .with_state(ParserState {
1329 less_ctx: self.state.less_ctx | LESS_CTX_ALLOW_DIV,
1330 ..self.state.clone()
1331 })
1332 .parse::<ComponentValue>()?;
1333 let name = {
1334 match value {
1335 ComponentValue::LessVariable(variable) => {
1336 LessMixinParameterName::Variable(variable)
1337 }
1338 ComponentValue::LessPropertyVariable(property) => {
1339 LessMixinParameterName::PropertyVariable(property)
1340 }
1341 value => {
1342 let span = *value.span();
1343 params.push(LessMixinParameter::Unnamed(
1344 LessMixinUnnamedParameter { value, span },
1345 ));
1346 break 'maybe;
1347 }
1348 }
1349 };
1350 let name_span = name.span();
1351 if let Some((_, colon_span)) = self.cursor.eat_colon()? {
1352 let value = if matches!(self.cursor.peek()?.token, Token::LBrace(..)) {
1353 self.parse().map(ComponentValue::LessDetachedRuleset)?
1354 } else {
1355 self.with_state(ParserState {
1356 less_ctx: self.state.less_ctx | LESS_CTX_ALLOW_DIV,
1357 ..self.state.clone()
1358 })
1359 .parse_maybe_less_list(false)?
1360 };
1361 let end = value.span().end;
1362 let default_value = {
1363 let span = Span { start: colon_span.start, end };
1364 LessMixinNamedParameterDefaultValue { colon_span, value, span }
1365 };
1366 let span = Span { start: name_span.start, end };
1367 params.push(LessMixinParameter::Named(LessMixinNamedParameter {
1368 name,
1369 value: Some(default_value),
1370 span,
1371 }));
1372 } else if let Some((_, Span { end, .. })) = self.cursor.eat_dot_dot_dot()? {
1373 let span = Span { start: name_span.start, end };
1374 params.push(LessMixinParameter::Variadic(LessMixinVariadicParameter {
1375 name: Some(name),
1376 span,
1377 }));
1378 if let Some((_, semicolon_span)) = self.cursor.eat_semicolon()? {
1379 semicolon_spans.push(semicolon_span);
1380 };
1381 (_, rparen_span) = self.cursor.expect_r_paren()?;
1382 break 'params;
1383 } else {
1384 let span = *name_span;
1385 params.push(LessMixinParameter::Named(LessMixinNamedParameter {
1386 name,
1387 value: None,
1388 span,
1389 }));
1390 }
1391 }
1392 }
1393
1394 match &self.cursor.peek()?.token {
1395 Token::RParen(..) => {
1396 let span = self.cursor.bump()?.span;
1397 if semicolon_comes_at > 0 {
1398 let comma_spans = mem::replace(&mut comma_spans, self.vec());
1399 wrap_less_mixin_params_into_less_list(
1400 self.allocator,
1401 &mut params,
1402 comma_spans,
1403 semicolon_comes_at,
1404 )
1405 .map_err(|kind| Error {
1406 kind,
1407 span: Span {
1408 start: params.first().unwrap().span().start,
1411 end: params.last().unwrap().span().end,
1412 },
1413 })?;
1414 }
1415 rparen_span = span;
1416 break;
1417 }
1418 Token::Comma(..) => {
1419 comma_spans.push(self.cursor.bump()?.span);
1420 }
1421 Token::Semicolon(..) => {
1422 let span = self.cursor.bump()?.span;
1423 let comma_spans = mem::replace(&mut comma_spans, self.vec());
1424 wrap_less_mixin_params_into_less_list(
1425 self.allocator,
1426 &mut params,
1427 comma_spans,
1428 semicolon_comes_at,
1429 )
1430 .map_err(|kind| Error { kind, span })?;
1431 semicolon_comes_at = params.len();
1432 semicolon_spans.push(span);
1433 }
1434 _ => {}
1437 }
1438 }
1439 let is_comma_separated = semicolon_spans.is_empty();
1440 let separator_spans =
1441 if semicolon_spans.is_empty() { comma_spans } else { semicolon_spans };
1442 debug_assert!(separator_spans.len() <= params.len());
1443 Ok(LessMixinParameters {
1444 params,
1445 is_comma_separated,
1446 separator_spans,
1447 span: Span { start: lparen_span.start, end: rparen_span.end },
1448 })
1449 }
1450
1451 pub(super) fn parse_less_anonymous_mixin(&mut self) -> PResult<LessAnonymousMixin<'a>> {
1454 debug_assert_eq!(self.syntax, Syntax::Less);
1455
1456 let TokenWithSpan { token, span: head_span } = self.cursor.bump()?;
1457 if !matches!(token, Token::Dot(..) | Token::NumberSign(..))
1458 || self.cursor.peek()?.span.start != head_span.end
1459 {
1460 return Err(Error { kind: ErrorKind::TryParseError, span: head_span });
1461 }
1462 let params = self.parse_less_mixin_parameters()?;
1463 let block = self.parse::<SimpleBlock>()?;
1464 let span = Span { start: head_span.start, end: block.span.end };
1465 Ok(LessAnonymousMixin { params, block, span })
1466 }
1467}
1468
1469impl<'a> Parse<'a> for LessMixinCallee<'a> {
1471 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1472 let first_name = input.parse::<LessMixinName>()?;
1473 let mut span = *first_name.span();
1474
1475 let mut children =
1476 input.vec1(LessMixinCalleeChild { name: first_name, combinator: None, span });
1477 loop {
1478 let combinator = input
1479 .cursor
1480 .eat_greater_than()?
1481 .map(|(_, span)| Combinator { kind: CombinatorKind::Child, span });
1482 let at_name = {
1483 let TokenWithSpan { token, span } = input.cursor.peek()?;
1484 matches!(token, Token::Dot(..) | Token::Hash(..))
1485 || (matches!(token, Token::Dimension(..))
1486 && input.source.as_bytes().get(span.start) == Some(&b'.'))
1487 };
1488 if at_name {
1489 let name = input.parse::<LessMixinName>()?;
1490 let name_span = name.span();
1491 let span = Span {
1492 start: combinator
1493 .as_ref()
1494 .map(|combinator| combinator.span.start)
1495 .unwrap_or(name_span.start),
1496 end: name_span.end,
1497 };
1498 children.push(LessMixinCalleeChild { name, combinator, span });
1499 } else {
1500 break;
1501 }
1502 }
1503
1504 if let Some(last) = children.last() {
1505 span.end = last.span.end;
1506 }
1507 Ok(LessMixinCallee { children, span })
1508 }
1509}
1510
1511impl<'a> Parse<'a> for LessMixinDefinition<'a> {
1513 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1514 debug_assert_eq!(input.syntax, Syntax::Less);
1515
1516 let name = input.parse::<LessMixinName>()?;
1517
1518 let params = input.parse_less_mixin_parameters()?;
1519
1520 let guard = if input.cursor.peek()?.is_ident_raw(input.source, "when") {
1521 Some(input.parse()?)
1522 } else {
1523 None
1524 };
1525
1526 let block = input
1527 .with_state(ParserState {
1528 less_ctx: input.state.less_ctx | LESS_CTX_ALLOW_KEYFRAME_BLOCK,
1529 ..input.state.clone()
1530 })
1531 .parse::<SimpleBlock>()?;
1532
1533 let span = Span { start: name.span().start, end: block.span.end };
1534
1535 Ok(LessMixinDefinition { name, params, guard, block, span })
1536 }
1537}
1538
1539impl<'a> Parse<'a> for LessMixinName<'a> {
1541 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1542 match input.cursor.bump()? {
1543 TokenWithSpan { token: Token::Dimension(..), span }
1545 if input.source.as_bytes().get(span.start) == Some(&b'.') =>
1546 {
1547 let name_span = Span { start: span.start + 1, end: span.end };
1548 let raw = &input.source[name_span.start..name_span.end];
1549 Ok(LessMixinName::ClassSelector(ClassSelector {
1550 name: InterpolableIdent::Literal(Ident { name: raw, raw, span: name_span }),
1551 span,
1552 }))
1553 }
1554 TokenWithSpan { token: Token::Dot(..), span: dot_span } => {
1555 let (ident, ident_span) =
1556 input.cursor.expect_ident_without_ws_or_comments(false)?;
1557 let ident = input.ident(ident, ident_span);
1558 let span = Span { start: dot_span.start, end: ident.span.end };
1559 Ok(LessMixinName::ClassSelector(ClassSelector {
1560 name: InterpolableIdent::Literal(ident),
1561 span,
1562 }))
1563 }
1564 token @ TokenWithSpan { token: Token::Hash(..), span } => {
1565 let hash = token.hash(input.source).unwrap();
1566 let raw = hash.raw;
1567 if raw.starts_with(|c: char| c.is_ascii_digit())
1568 || matches!(raw.as_bytes(), [b'-'] | [b'-', b'0'..=b'9', ..])
1569 {
1570 input
1571 .recoverable_errors
1572 .push(Error { kind: ErrorKind::InvalidIdSelectorName, span });
1573 }
1574 let name =
1575 if hash.escaped { util::handle_escape_in(raw, input.allocator) } else { raw };
1576 let name_span = Span { start: span.start + 1, end: span.end };
1577 Ok(LessMixinName::IdSelector(IdSelector {
1578 name: InterpolableIdent::Literal(Ident { name, raw, span: name_span }),
1579 span,
1580 }))
1581 }
1582 TokenWithSpan { token, span } => Err(Error {
1583 kind: ErrorKind::ExpectOneOf(vec![".", "<hash>"], token.symbol()),
1584 span,
1585 }),
1586 }
1587 }
1588}
1589
1590impl<'a> Parse<'a> for LessMixinParameterName<'a> {
1592 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1593 if matches!(input.cursor.peek()?.token, Token::AtKeyword(..)) {
1594 input.parse().map(LessMixinParameterName::Variable)
1595 } else {
1596 input.parse().map(LessMixinParameterName::PropertyVariable)
1597 }
1598 }
1599}
1600
1601impl<'a> Parse<'a> for LessNamespaceValue<'a> {
1603 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1604 let callee = input.parse::<LessNamespaceValueCallee>()?;
1605 let callee_span = callee.span();
1606
1607 let lookups = input.parse::<LessLookups>()?;
1608 util::assert_no_ws_or_comment(callee_span, &lookups.span)?;
1609
1610 let span = Span { start: callee_span.start, end: lookups.span.end };
1611 Ok(LessNamespaceValue { callee, lookups, span })
1612 }
1613}
1614
1615impl<'a> Parse<'a> for LessNamespaceValueCallee<'a> {
1617 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1618 if matches!(input.cursor.peek()?.token, Token::AtKeyword(..)) {
1619 input.parse().map(LessNamespaceValueCallee::LessVariable)
1620 } else {
1621 input.parse().map(LessNamespaceValueCallee::LessMixinCall)
1622 }
1623 }
1624}
1625
1626impl<'a> Parse<'a> for LessNegativeValue<'a> {
1628 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1629 let (_, minus_span) = input.cursor.expect_minus()?;
1630 let value = match input.cursor.peek()? {
1631 TokenWithSpan {
1632 token: Token::AtKeyword(..) | Token::At(..) | Token::DollarVar(..),
1633 span,
1634 } if minus_span.end == span.start => {
1635 let value = input.parse_component_value_atom()?;
1636 input.alloc(value)
1637 }
1638 TokenWithSpan { token: Token::LParen(..), span } if minus_span.end == span.start => {
1639 let value = ComponentValue::LessParenthesizedOperation(
1640 input.parse_less_parenthesized_operation(true)?,
1641 );
1642 input.alloc(value)
1643 }
1644 TokenWithSpan { token, span } => {
1645 return Err(Error {
1646 kind: ErrorKind::ExpectOneOf(vec!["<at-keyword>", "$var", "("], token.symbol()),
1647 span: *span,
1648 });
1649 }
1650 };
1651
1652 let span = Span { start: minus_span.start, end: value.span().end };
1653 Ok(LessNegativeValue { value, span })
1654 }
1655}
1656
1657impl<'a> Parse<'a> for LessPercentKeyword {
1659 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1660 let (_, span) = input.cursor.expect_percent()?;
1661 Ok(LessPercentKeyword { span })
1662 }
1663}
1664
1665impl<'a> Parse<'a> for LessPlugin<'a> {
1669 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1670 debug_assert_eq!(input.syntax, Syntax::Less);
1671
1672 let mut start = None;
1673
1674 let args = if let Some((_, span)) = input.cursor.eat_l_paren()? {
1675 start = Some(span.start);
1676 let args = input.parse_tokens_in_parens()?;
1677 input.cursor.expect_r_paren()?;
1678 Some(args)
1679 } else {
1680 None
1681 };
1682
1683 let path = input.parse::<LessPluginPath>()?;
1684 let path_span = path.span();
1685
1686 let span = Span { start: start.unwrap_or(path_span.start), end: path_span.end };
1687 Ok(LessPlugin { path, args, span })
1688 }
1689}
1690
1691impl<'a> Parse<'a> for LessPluginPath<'a> {
1693 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1694 if let Token::Str(..) = input.cursor.peek()?.token {
1695 input.parse().map(LessPluginPath::Str)
1696 } else {
1697 input.parse().map(LessPluginPath::Url)
1698 }
1699 }
1700}
1701
1702impl<'a> Parse<'a> for LessPropertyInterpolation<'a> {
1704 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1705 let (dollar_lbrace_var, span) = input.cursor.expect_dollar_l_brace_var()?;
1706 Ok(LessPropertyInterpolation {
1707 name: input
1708 .ident(dollar_lbrace_var.ident, Span { start: span.start + 2, end: span.end - 1 }),
1709 span,
1710 })
1711 }
1712}
1713
1714impl<'a> Parse<'a> for Option<LessPropertyMerge> {
1718 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1719 debug_assert!(matches!(input.syntax, Syntax::Less | Syntax::Css));
1720
1721 match &input.cursor.peek()?.token {
1722 Token::Plus(..) => Ok(Some(LessPropertyMerge {
1723 kind: LessPropertyMergeKind::Comma,
1724 span: input.cursor.bump()?.span,
1725 })),
1726 Token::PlusUnderscore(..) => Ok(Some(LessPropertyMerge {
1727 kind: LessPropertyMergeKind::Space,
1728 span: input.cursor.bump()?.span,
1729 })),
1730 _ => Ok(None),
1731 }
1732 }
1733}
1734
1735impl<'a> Parse<'a> for LessPropertyVariable<'a> {
1739 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1740 let (dollar_var, span) = input.cursor.expect_dollar_var()?;
1741 Ok(LessPropertyVariable {
1742 name: input.ident(dollar_var.ident, Span { start: span.start + 1, end: span.end }),
1743 span,
1744 })
1745 }
1746}
1747
1748impl<'a> Parse<'a> for LessVariable<'a> {
1752 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1753 let (at_keyword, span) = input.cursor.expect_at_keyword()?;
1754 Ok(LessVariable {
1755 name: input.ident(at_keyword.ident, Span { start: span.start + 1, end: span.end }),
1756 span,
1757 })
1758 }
1759}
1760
1761impl<'a> Parse<'a> for LessVariableCall<'a> {
1763 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1764 let variable = input.parse::<LessVariable>()?;
1765 input.cursor.expect_l_paren_without_ws_or_comments()?;
1766 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
1767
1768 let span = Span { start: variable.span.start, end };
1769 Ok(LessVariableCall { variable, span })
1770 }
1771}
1772
1773impl<'a> Parse<'a> for LessVariableDeclaration<'a> {
1775 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1776 debug_assert_eq!(input.syntax, Syntax::Less);
1777
1778 let name = input.parse::<LessVariable>()?;
1779 let (_, colon_span) = input.cursor.expect_colon()?;
1780 let value = if matches!(input.cursor.peek()?.token, Token::LBrace(..)) {
1781 ComponentValue::LessDetachedRuleset(input.parse()?)
1782 } else {
1783 let typed = input.try_parse(|p| {
1784 let value = p
1785 .with_state(ParserState {
1786 less_ctx: p.state.less_ctx | LESS_CTX_ALLOW_DIV,
1787 ..p.state.clone()
1788 })
1789 .parse_maybe_less_list(true)?;
1790 if !matches!(
1794 &p.cursor.peek()?.token,
1795 Token::Semicolon(..) | Token::RBrace(..) | Token::Eof(..)
1796 ) {
1797 let span = p.cursor.peek()?.span;
1798 return Err(Error { kind: ErrorKind::TryParseError, span });
1799 }
1800 Ok(value)
1801 });
1802 match typed {
1803 Ok(value) => value,
1804 Err(error) => {
1805 let start = input.cursor.peek()?.span.start;
1810 let values = input
1811 .parse_declaration_value_tokens(false)?;
1812 let end = values.last().map(|v| v.span().end);
1813 if !matches!(input.cursor.peek()?.token, Token::Semicolon(..)) {
1814 return Err(error);
1815 }
1816 let Some(end) = end else {
1817 return Err(error);
1818 };
1819 ComponentValue::LessList(LessList {
1820 elements: values,
1821 comma_spans: None,
1822 span: Span { start, end },
1823 })
1824 }
1825 }
1826 };
1827 let span = Span { start: name.span.start, end: value.span().end };
1828 Ok(LessVariableDeclaration { name, colon_span, value, span })
1829 }
1830}
1831
1832impl<'a> Parse<'a> for LessVariableInterpolation<'a> {
1834 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1835 let (at_lbrace_var, span) = input.cursor.expect_at_l_brace_var()?;
1836 Ok(LessVariableInterpolation {
1837 name: input
1838 .ident(at_lbrace_var.ident, Span { start: span.start + 2, end: span.end - 1 }),
1839 span,
1840 })
1841 }
1842}
1843
1844impl<'a> Parse<'a> for LessVariableVariable<'a> {
1846 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1847 let (_, at_span) = input.cursor.expect_at()?;
1848 let variable = input.parse::<LessVariable>()?;
1849 util::assert_no_ws_or_comment(&at_span, &variable.span)?;
1850
1851 let span = Span { start: at_span.start, end: variable.span.end };
1852 Ok(LessVariableVariable { variable, span })
1853 }
1854}
1855
1856fn wrap_less_mixin_params_into_less_list<'a>(
1857 allocator: &'a oxc_allocator::Allocator,
1858 params: &mut oxc_allocator::Vec<'a, LessMixinParameter<'a>>,
1859 comma_spans: oxc_allocator::Vec<'a, Span>,
1860 index: usize,
1861) -> Result<(), ErrorKind> {
1862 if let [first, .., last] = ¶ms[index..] {
1863 let span = Span { start: first.span().start, end: last.span().end };
1864 let named_head = matches!(
1869 ¶ms[index],
1870 LessMixinParameter::Named(LessMixinNamedParameter { value: Some(..), .. })
1871 ) && params.len() - index > 1;
1872 let mut drained = params.drain(index..);
1873 let head = if named_head { drained.next() } else { None };
1874 let mut elements = oxc_allocator::Vec::with_capacity_in(drained.len() + 1, &allocator);
1875 let head = match head {
1876 Some(LessMixinParameter::Named(LessMixinNamedParameter {
1877 name,
1878 value: Some(default),
1879 ..
1880 })) => {
1881 elements.push(default.value);
1882 Some((name, default.colon_span))
1883 }
1884 _ => None,
1885 };
1886 for param in drained {
1887 if let LessMixinParameter::Unnamed(LessMixinUnnamedParameter { value, .. }) = param {
1888 elements.push(value);
1889 } else {
1890 return Err(ErrorKind::MixedDelimiterKindInLessMixin);
1894 }
1895 }
1896 debug_assert!(comma_spans.len() < elements.len());
1897 let list_span =
1898 Span { start: elements.first().map_or(span.start, |v| v.span().start), end: span.end };
1899 let list = ComponentValue::LessList(LessList {
1900 elements,
1901 comma_spans: Some(comma_spans),
1902 span: list_span,
1903 });
1904 params.push(match head {
1905 Some((name, colon_span)) => LessMixinParameter::Named(LessMixinNamedParameter {
1906 span: Span { start: name.span().start, end: span.end },
1907 name,
1908 value: Some(LessMixinNamedParameterDefaultValue {
1909 colon_span,
1910 value: list,
1911 span: list_span,
1912 }),
1913 }),
1914 None => LessMixinParameter::Unnamed(LessMixinUnnamedParameter { value: list, span }),
1915 });
1916 }
1917 Ok(())
1918}
1919
1920fn wrap_less_mixin_args_into_less_list<'a>(
1921 allocator: &'a oxc_allocator::Allocator,
1922 args: &mut oxc_allocator::Vec<'a, LessMixinArgument<'a>>,
1923 comma_spans: oxc_allocator::Vec<'a, Span>,
1924 index: usize,
1925) -> Result<(), ErrorKind> {
1926 if let [first, .., last] = &args[index..] {
1927 let span = Span { start: first.span().start, end: last.span().end };
1928 let named_head =
1933 matches!(&args[index], LessMixinArgument::Named(..)) && args.len() - index > 1;
1934 let mut drained = args.drain(index..);
1935 let head = if named_head { drained.next() } else { None };
1936 let mut elements = oxc_allocator::Vec::with_capacity_in(drained.len() + 1, &allocator);
1937 let mut head = match head {
1938 Some(LessMixinArgument::Named(named)) => {
1939 elements.push(named.value);
1940 Some((named.name, named.colon_span))
1941 }
1942 _ => None,
1943 };
1944 for arg in drained {
1945 if let LessMixinArgument::Value(value) = arg {
1946 elements.push(value);
1947 } else {
1948 return Err(ErrorKind::MixedDelimiterKindInLessMixin);
1952 }
1953 }
1954 debug_assert!(comma_spans.len() < elements.len());
1955 let list_span =
1956 Span { start: elements.first().map_or(span.start, |v| v.span().start), end: span.end };
1957 let list = ComponentValue::LessList(LessList {
1958 elements,
1959 comma_spans: Some(comma_spans),
1960 span: list_span,
1961 });
1962 args.push(match head.take() {
1963 Some((name, colon_span)) => LessMixinArgument::Named(LessMixinNamedArgument {
1964 span: Span { start: name.span().start, end: span.end },
1965 name,
1966 colon_span,
1967 value: list,
1968 }),
1969 None => LessMixinArgument::Value(list),
1970 });
1971 }
1972 Ok(())
1973}
1974
1975fn can_be_division_operand(left: &ComponentValue) -> bool {
1976 matches!(
1977 left,
1978 ComponentValue::LessVariable(..)
1979 | ComponentValue::LessPropertyVariable(..)
1980 | ComponentValue::LessBinaryOperation(..)
1981 | ComponentValue::LessParenthesizedOperation(..)
1982 )
1983}
1984
1985fn is_followed_by_whitespace(source: &str, pos: usize) -> bool {
1988 source.as_bytes().get(pos).is_some_and(u8::is_ascii_whitespace)
1989}