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