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