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