1use super::{
2 helpers,
3 str::{
4 format_str, InterpolatedFirstStrRawFormatter, InterpolatedLastStrRawFormatter,
5 InterpolatedMidStrRawFormatter,
6 },
7 DocGen,
8};
9use crate::{ctx::Ctx, state::State};
10use raffia::{ast::*, Spanned};
11use std::{iter, mem};
12use tiny_pretty::Doc;
13
14impl<'s> DocGen<'s> for LessBinaryCondition<'s> {
15 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
16 self.left
17 .doc(ctx, state)
18 .append(helpers::format_operator_prefix_space(ctx))
19 .concat(ctx.end_spaced_comments(
20 ctx.get_comments_between(self.left.span().end, self.op.span.start),
21 ))
22 .append(self.op.doc(ctx, state))
23 .append(helpers::format_operator_suffix_space(ctx))
24 .concat(ctx.end_spaced_comments(
25 ctx.get_comments_between(self.op.span.end, self.right.span().start),
26 ))
27 .append(self.right.doc(ctx, state))
28 .group()
29 }
30}
31
32impl<'s> DocGen<'s> for LessBinaryConditionOperator {
33 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
34 match self.kind {
35 LessBinaryConditionOperatorKind::GreaterThan => Doc::text(">"),
36 LessBinaryConditionOperatorKind::GreaterThanOrEqual => Doc::text(">="),
37 LessBinaryConditionOperatorKind::LessThan => Doc::text("<"),
38 LessBinaryConditionOperatorKind::LessThanOrEqual => Doc::text("<="),
39 LessBinaryConditionOperatorKind::Equal => Doc::text("="),
40 LessBinaryConditionOperatorKind::EqualOrGreaterThan => Doc::text("=>"),
41 LessBinaryConditionOperatorKind::EqualOrLessThan => Doc::text("=<"),
42 LessBinaryConditionOperatorKind::And => Doc::text("and"),
43 LessBinaryConditionOperatorKind::Or => Doc::text("or"),
44 }
45 }
46}
47
48impl<'s> DocGen<'s> for LessBinaryOperation<'s> {
49 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
50 self.left
51 .doc(ctx, state)
52 .append(helpers::format_operator_prefix_space(ctx))
53 .concat(ctx.end_spaced_comments(
54 ctx.get_comments_between(self.left.span().end, self.op.span.start),
55 ))
56 .append(self.op.doc(ctx, state))
57 .append(helpers::format_operator_suffix_space(ctx))
58 .concat(ctx.end_spaced_comments(
59 ctx.get_comments_between(self.op.span.end, self.right.span().start),
60 ))
61 .append(self.right.doc(ctx, state))
62 .group()
63 }
64}
65
66impl<'s> DocGen<'s> for LessCondition<'s> {
67 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
68 match self {
69 LessCondition::Binary(binary) => binary.doc(ctx, state),
70 LessCondition::Negated(negated) => negated.doc(ctx, state),
71 LessCondition::Parenthesized(parenthesized) => parenthesized.doc(ctx, state),
72 LessCondition::Value(value) => value.doc(ctx, state),
73 }
74 }
75}
76
77impl<'s> DocGen<'s> for LessConditionalQualifiedRule<'s> {
78 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
79 helpers::format_selectors_before_block(
80 &self.selector.selectors,
81 &self.selector.comma_spans,
82 self.selector.span.start,
83 ctx,
84 state,
85 )
86 .append(Doc::soft_line())
87 .append(self.guard.doc(ctx, state))
88 .concat(ctx.end_spaced_comments(
89 ctx.get_comments_between(self.selector.span.end, self.guard.span.start),
90 ))
91 .append(helpers::format_space_before_block(
92 self.guard.span.end,
93 self.block.span.start,
94 ctx,
95 ))
96 .append(self.block.doc(ctx, state))
97 }
98}
99
100impl<'s> DocGen<'s> for LessConditions<'s> {
101 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
102 Doc::text("when ")
103 .append(
104 helpers::SeparatedListFormatter::new(
105 ",",
106 Doc::line_or_space().nest(ctx.indent_width),
107 )
108 .format(
109 &self.conditions,
110 &self.comma_spans,
111 self.span.start,
112 ctx,
113 state,
114 ),
115 )
116 .group()
117 }
118}
119
120impl<'s> DocGen<'s> for LessDetachedRuleset<'s> {
121 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
122 self.block.doc(
123 ctx,
124 &State {
125 keep_decl_name_case: true,
126 ..state.clone()
127 },
128 )
129 }
130}
131
132impl<'s> DocGen<'s> for LessEscapedStr<'s> {
133 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
134 Doc::text("~").append(self.str.doc(ctx, state))
135 }
136}
137
138impl<'s> DocGen<'s> for LessExtend<'s> {
139 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
140 let selector = self.selector.doc(ctx, state);
141 if self.all.is_some() {
142 selector.append(Doc::text(" all"))
143 } else {
144 selector
145 }
146 }
147}
148
149impl<'s> DocGen<'s> for LessExtendList<'s> {
150 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
151 helpers::SeparatedListFormatter::new(",", Doc::space()).format(
152 &self.elements,
153 &self.comma_spans,
154 self.span.start,
155 ctx,
156 state,
157 )
158 }
159}
160
161impl<'s> DocGen<'s> for LessExtendRule<'s> {
162 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
163 self.nesting_selector
164 .doc(ctx, state)
165 .concat(ctx.unspaced_comments(ctx.get_comments_between(
166 self.nesting_selector.span.end,
167 self.name_of_extend.span.start,
168 )))
169 .append(Doc::text(":extend("))
170 .append({
171 let mut extend_doc = vec![];
172
173 if ctx.options.linebreak_in_pseudo_parens {
174 extend_doc.push(Doc::line_or_nil());
175 }
176
177 extend_doc.extend(ctx.end_spaced_comments(
178 ctx.get_comments_between(self.span.start, self.extend.span.start),
179 ));
180 extend_doc.push(self.extend.doc(ctx, state));
181
182 extend_doc.extend(ctx.start_spaced_comments(
183 ctx.get_comments_between(self.extend.span.end, self.span.end),
184 ));
185 if ctx.options.linebreak_in_pseudo_parens {
186 Doc::list(extend_doc)
187 .nest(ctx.indent_width)
188 .append(Doc::line_or_nil())
189 .group()
190 } else {
191 Doc::list(extend_doc)
192 }
193 })
194 .append(Doc::text(")"))
195 }
196}
197
198impl<'s> DocGen<'s> for LessFormatFunction {
199 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
200 Doc::text("%")
201 }
202}
203
204impl<'s> DocGen<'s> for LessImportOptions<'s> {
205 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
206 helpers::format_parenthesized(
207 helpers::SeparatedListFormatter::new(
208 ",",
209 helpers::get_smart_linebreak(
210 self.span.start,
211 &self.names,
212 ctx.options.less_import_options_prefer_single_line,
213 ctx,
214 ),
215 )
216 .with_trailing()
217 .format(&self.names, &self.comma_spans, self.span.start, ctx, state),
218 self.names
219 .last()
220 .map(|name| name.span.end)
221 .unwrap_or(self.span.start),
222 self.span.end,
223 ctx,
224 )
225 }
226}
227
228impl<'s> DocGen<'s> for LessImportPrelude<'s> {
229 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
230 let mut docs = Vec::with_capacity(3);
231 docs.push(self.options.doc(ctx, state));
232 let mut pos = self.options.span.end;
233
234 docs.push(Doc::soft_line().nest(ctx.indent_width));
235 let href_span = self.href.span();
236 docs.extend(ctx.end_spaced_comments(
237 ctx.get_comments_between(mem::replace(&mut pos, href_span.end), href_span.start),
238 ));
239 docs.push(self.href.doc(ctx, state));
240
241 if let Some(media) = &self.media {
242 docs.push(Doc::soft_line().nest(ctx.indent_width));
243 docs.extend(ctx.end_spaced_comments(ctx.get_comments_between(pos, media.span.start)));
244 docs.push(media.doc(ctx, state));
245 }
246
247 Doc::list(docs).group()
248 }
249}
250
251impl<'s> DocGen<'s> for LessInterpolatedIdent<'s> {
252 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
253 Doc::list(
254 self.elements
255 .iter()
256 .map(|element| match element {
257 LessInterpolatedIdentElement::Static(s) => s.doc(ctx, state),
258 LessInterpolatedIdentElement::Variable(variable) => variable.doc(ctx, state),
259 LessInterpolatedIdentElement::Property(property) => property.doc(ctx, state),
260 })
261 .collect(),
262 )
263 }
264}
265
266impl<'s> DocGen<'s> for LessInterpolatedStr<'s> {
267 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
268 if let [LessInterpolatedStrElement::Static(first), mid @ .., LessInterpolatedStrElement::Static(last)] =
269 &self.elements[..]
270 {
271 let allow_prefer = is_preferred_quote_allowed(self, ctx);
272
273 let mut docs = Vec::with_capacity(self.elements.len());
274 docs.push(Doc::text(format_str(
275 first.raw,
276 InterpolatedFirstStrRawFormatter::new(first.raw),
277 allow_prefer,
278 ctx.options.quotes,
279 )));
280 docs.extend(mid.iter().map(|element| match element {
281 LessInterpolatedStrElement::Static(s) => Doc::text(format_str(
282 s.raw,
283 InterpolatedMidStrRawFormatter::new(s.raw),
284 allow_prefer,
285 ctx.options.quotes,
286 )),
287 LessInterpolatedStrElement::Variable(variable) => variable.doc(ctx, state),
288 LessInterpolatedStrElement::Property(property) => property.doc(ctx, state),
289 }));
290 docs.push(Doc::text(format_str(
291 last.raw,
292 InterpolatedLastStrRawFormatter::new(last.raw),
293 allow_prefer,
294 ctx.options.quotes,
295 )));
296 Doc::list(docs)
297 } else {
298 unreachable!()
299 }
300 }
301}
302
303impl<'s> DocGen<'s> for LessJavaScriptSnippet<'s> {
304 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
305 let code = Doc::list(
306 itertools::intersperse(
307 self.raw
308 .split('\n')
309 .map(|s| Doc::text(s.strip_suffix('\r').unwrap_or(s))),
310 Doc::empty_line(),
311 )
312 .collect(),
313 );
314 if self.escaped {
315 Doc::text("~").append(code)
316 } else {
317 code
318 }
319 }
320}
321
322impl<'s> DocGen<'s> for LessList<'s> {
323 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
324 helpers::format_values_list(
325 &self.elements,
326 self.comma_spans.as_deref(),
327 &self.span,
328 ctx,
329 state,
330 )
331 }
332}
333
334impl<'s> DocGen<'s> for LessListFunction {
335 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
336 Doc::text("~")
337 }
338}
339
340impl<'s> DocGen<'s> for LessLookup<'s> {
341 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
342 if let Some(name) = &self.name {
343 let name_span = name.span();
344 Doc::text("[")
345 .concat(ctx.end_spaced_comments(
346 ctx.get_comments_between(self.span.start, name_span.start),
347 ))
348 .append(name.doc(ctx, state))
349 .concat(
350 ctx.start_spaced_comments(
351 ctx.get_comments_between(name_span.end, self.span.end),
352 ),
353 )
354 .append(Doc::text("]"))
355 } else {
356 Doc::text("[")
357 .concat(
358 ctx.end_spaced_comments(
359 ctx.get_comments_between(self.span.start, self.span.end),
360 ),
361 )
362 .append(Doc::text("]"))
363 }
364 }
365}
366
367impl<'s> DocGen<'s> for LessLookupName<'s> {
368 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
369 match self {
370 LessLookupName::LessVariable(less_variable) => less_variable.doc(ctx, state),
371 LessLookupName::LessVariableVariable(less_variable_variable) => {
372 less_variable_variable.doc(ctx, state)
373 }
374 LessLookupName::LessPropertyVariable(less_property_variable) => {
375 less_property_variable.doc(ctx, state)
376 }
377 LessLookupName::Ident(ident) => ident.doc(ctx, state),
378 }
379 }
380}
381
382impl<'s> DocGen<'s> for LessLookups<'s> {
383 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
384 Doc::list(
385 self.lookups
386 .iter()
387 .scan(self.span.start, |pos, lookup| {
388 Some(
389 ctx.start_spaced_comments(ctx.get_comments_between(
390 mem::replace(pos, lookup.span.end),
391 lookup.span.start,
392 ))
393 .chain(iter::once(lookup.doc(ctx, state))),
394 )
395 })
396 .flatten()
397 .collect(),
398 )
399 }
400}
401
402impl<'s> DocGen<'s> for LessMixinArgument<'s> {
403 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
404 match self {
405 LessMixinArgument::Named(named) => named.doc(ctx, state),
406 LessMixinArgument::Value(value) => value.doc(ctx, state),
407 LessMixinArgument::Variadic(variadic) => variadic.doc(ctx, state),
408 }
409 }
410}
411
412impl<'s> DocGen<'s> for LessMixinArguments<'s> {
413 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
414 let mut has_last_line_comment = false;
415
416 let is_detached_rulset_only = matches!(
417 &self.args[..],
418 [LessMixinArgument::Value(
419 ComponentValue::LessDetachedRuleset(..)
420 )]
421 );
422 let doc_close_to_paren = if is_detached_rulset_only {
423 Doc::nil()
424 } else {
425 Doc::line_or_nil()
426 };
427
428 Doc::text("(")
429 .append(doc_close_to_paren.clone())
430 .append(
431 helpers::SeparatedListFormatter::new(
432 if self.is_comma_separated { "," } else { ";" },
433 helpers::get_smart_linebreak(
434 self.span.start,
435 &self.args,
436 ctx.options.less_mixin_args_prefer_single_line,
437 ctx,
438 ),
439 )
440 .with_trailing()
441 .format(
442 &self.args,
443 &self.separator_spans,
444 self.span.start,
445 ctx,
446 state,
447 ),
448 )
449 .concat(
450 ctx.start_spaced_comments_without_last_hard_line(
451 ctx.get_comments_between(
452 self.args
453 .last()
454 .map(|arg| arg.span().end)
455 .unwrap_or(self.span.start),
456 self.span.end,
457 ),
458 &mut has_last_line_comment,
459 ),
460 )
461 .nest(if is_detached_rulset_only {
462 0
463 } else {
464 ctx.indent_width
465 })
466 .append(if has_last_line_comment {
467 Doc::hard_line()
468 } else {
469 doc_close_to_paren
470 })
471 .group()
472 .append(Doc::text(")"))
473 }
474}
475
476impl<'s> DocGen<'s> for LessMixinCall<'s> {
477 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
478 let mut docs = vec![self.callee.doc(ctx, state)];
479 let mut pos = self.callee.span.end;
480
481 if let Some(args) = &self.args {
482 docs.push(args.doc(ctx, state));
483 pos = args.span.end;
484 }
485
486 if let Some(important) = &self.important {
487 docs.push(Doc::soft_line().nest(ctx.indent_width));
488 docs.extend(
489 ctx.end_spaced_comments(ctx.get_comments_between(pos, important.span.start)),
490 );
491 docs.push(important.doc(ctx, state));
492 }
493
494 Doc::list(docs)
495 }
496}
497
498impl<'s> DocGen<'s> for LessMixinCallee<'s> {
499 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
500 let mut docs = Vec::with_capacity(1);
501 let mut pos = self.span.start;
502
503 let mut iter = self.children.iter();
504 if let Some(first) = iter.next() {
505 docs.push(first.doc(ctx, state));
506 pos = first.span.end;
507 }
508
509 let (docs, _) = iter.fold((docs, pos), |(mut docs, pos), child| {
510 if pos < child.span.start {
511 docs.push(Doc::line_or_space().nest(ctx.indent_width));
512 docs.extend(
513 ctx.end_spaced_comments(ctx.get_comments_between(pos, child.span.start)),
514 );
515 } else {
516 docs.extend(ctx.unspaced_comments(ctx.get_comments_between(pos, child.span.start)));
517 }
518 docs.push(child.doc(ctx, state));
519 (docs, child.span.end)
520 });
521
522 Doc::list(docs).group()
523 }
524}
525
526impl<'s> DocGen<'s> for LessMixinCalleeChild<'s> {
527 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
528 if let Some(combinator) = &self.combinator {
529 combinator
530 .doc(ctx, state)
531 .append(Doc::space())
532 .concat(ctx.end_spaced_comments(
533 ctx.get_comments_between(combinator.span.end, self.name.span().start),
534 ))
535 .append(self.name.doc(ctx, state))
536 } else {
537 self.name.doc(ctx, state)
538 }
539 }
540}
541
542impl<'s> DocGen<'s> for LessMixinDefinition<'s> {
543 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
544 let mut docs = vec![self.name.doc(ctx, state), self.params.doc(ctx, state)];
545 let mut pos = self.params.span.end;
546
547 if let Some(guard) = &self.guard {
548 docs.push(Doc::soft_line());
549 docs.extend(ctx.end_spaced_comments(
550 ctx.get_comments_between(self.params.span.end, guard.span.start),
551 ));
552 docs.push(guard.doc(ctx, state));
553 pos = guard.span.end;
554 }
555
556 docs.push(helpers::format_space_before_block(
557 pos,
558 self.block.span.start,
559 ctx,
560 ));
561 docs.push(self.block.doc(ctx, state));
562
563 Doc::list(docs)
564 }
565}
566
567impl<'s> DocGen<'s> for LessMixinName<'s> {
568 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
569 match self {
570 LessMixinName::ClassSelector(class_selector) => class_selector.doc(ctx, state),
571 LessMixinName::IdSelector(id_selector) => id_selector.doc(ctx, state),
572 }
573 }
574}
575
576impl<'s> DocGen<'s> for LessMixinNamedArgument<'s> {
577 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
578 self.name
579 .doc(ctx, state)
580 .concat(ctx.start_spaced_comments(
581 ctx.get_comments_between(self.name.span().end, self.colon_span.start),
582 ))
583 .append(Doc::text(": "))
584 .concat(ctx.end_spaced_comments(
585 ctx.get_comments_between(self.colon_span.end, self.value.span().start),
586 ))
587 .append(self.value.doc(ctx, state))
588 }
589}
590
591impl<'s> DocGen<'s> for LessMixinNamedParameter<'s> {
592 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
593 let name = self.name.doc(ctx, state);
594 if let Some(value) = &self.value {
595 name.concat(ctx.start_spaced_comments(
596 ctx.get_comments_between(self.name.span().end, value.span.start),
597 ))
598 .append(value.doc(ctx, state))
599 } else {
600 name
601 }
602 }
603}
604
605impl<'s> DocGen<'s> for LessMixinNamedParameterDefaultValue<'s> {
606 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
607 Doc::text(": ")
608 .concat(ctx.end_spaced_comments(
609 ctx.get_comments_between(self.colon_span.end, self.value.span().start),
610 ))
611 .append(self.value.doc(ctx, state))
612 }
613}
614
615impl<'s> DocGen<'s> for LessMixinParameter<'s> {
616 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
617 match self {
618 LessMixinParameter::Named(named) => named.doc(ctx, state),
619 LessMixinParameter::Unnamed(unnamed) => unnamed.doc(ctx, state),
620 LessMixinParameter::Variadic(variadic) => variadic.doc(ctx, state),
621 }
622 }
623}
624
625impl<'s> DocGen<'s> for LessMixinParameters<'s> {
626 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
627 helpers::format_parenthesized(
628 helpers::SeparatedListFormatter::new(
629 if self.is_comma_separated { "," } else { ";" },
630 helpers::get_smart_linebreak(
631 self.span.start,
632 &self.params,
633 ctx.options.less_mixin_params_prefer_single_line,
634 ctx,
635 ),
636 )
637 .with_trailing()
638 .format(
639 &self.params,
640 &self.separator_spans,
641 self.span.start,
642 ctx,
643 state,
644 ),
645 self.params
646 .last()
647 .map(|param| param.span().end)
648 .unwrap_or(self.span.start),
649 self.span.end,
650 ctx,
651 )
652 }
653}
654
655impl<'s> DocGen<'s> for LessMixinParameterName<'s> {
656 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
657 match self {
658 LessMixinParameterName::Variable(variable) => variable.doc(ctx, state),
659 LessMixinParameterName::PropertyVariable(property_variable) => {
660 property_variable.doc(ctx, state)
661 }
662 }
663 }
664}
665
666impl<'s> DocGen<'s> for LessMixinUnnamedParameter<'s> {
667 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
668 self.value.doc(ctx, state)
669 }
670}
671
672impl<'s> DocGen<'s> for LessMixinVariadicArgument<'s> {
673 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
674 self.name.doc(ctx, state).append(Doc::text("..."))
675 }
676}
677
678impl<'s> DocGen<'s> for LessMixinVariadicParameter<'s> {
679 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
680 if let Some(name) = &self.name {
681 name.doc(ctx, state).append(Doc::text("..."))
682 } else {
683 Doc::text("...")
684 }
685 }
686}
687
688impl<'s> DocGen<'s> for LessNamespaceValue<'s> {
689 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
690 self.callee
691 .doc(ctx, state)
692 .concat(ctx.unspaced_comments(
693 ctx.get_comments_between(self.callee.span().end, self.lookups.span.start),
694 ))
695 .append(self.lookups.doc(ctx, state))
696 }
697}
698
699impl<'s> DocGen<'s> for LessNamespaceValueCallee<'s> {
700 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
701 match self {
702 LessNamespaceValueCallee::LessMixinCall(mixin_call) => mixin_call.doc(ctx, state),
703 LessNamespaceValueCallee::LessVariable(variable) => variable.doc(ctx, state),
704 }
705 }
706}
707
708impl<'s> DocGen<'s> for LessNegatedCondition<'s> {
709 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
710 let condition_span = self.condition.span();
711 Doc::text("not").append(helpers::format_parenthesized(
712 Doc::list(
713 ctx.end_spaced_comments(
714 ctx.get_comments_between(self.span.start, condition_span.start),
715 )
716 .collect(),
717 )
718 .append(self.condition.doc(ctx, state)),
719 condition_span.end,
720 self.span.end,
721 ctx,
722 ))
723 }
724}
725
726impl<'s> DocGen<'s> for LessNegativeValue<'s> {
727 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
728 Doc::text("-").append(self.value.doc(ctx, state))
729 }
730}
731
732impl<'s> DocGen<'s> for LessOperationOperator {
733 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
734 match self.kind {
735 LessOperationOperatorKind::Multiply => Doc::text("*"),
736 LessOperationOperatorKind::Division => Doc::text("/"),
737 LessOperationOperatorKind::Plus => Doc::text("+"),
738 LessOperationOperatorKind::Minus => Doc::text("-"),
739 }
740 }
741}
742
743impl<'s> DocGen<'s> for LessParenthesizedCondition<'s> {
744 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
745 let condition_span = self.condition.span();
746 helpers::format_parenthesized(
747 Doc::list(
748 ctx.end_spaced_comments(
749 ctx.get_comments_between(self.span.start, condition_span.start),
750 )
751 .collect(),
752 )
753 .append(self.condition.doc(ctx, state)),
754 condition_span.end,
755 self.span.end,
756 ctx,
757 )
758 }
759}
760
761impl<'s> DocGen<'s> for LessParenthesizedOperation<'s> {
762 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
763 let operation_span = self.operation.span();
764 helpers::format_parenthesized(
765 Doc::list(
766 ctx.end_spaced_comments(
767 ctx.get_comments_between(self.span.start, operation_span.start),
768 )
769 .collect(),
770 )
771 .append(self.operation.doc(ctx, state)),
772 operation_span.end,
773 self.span.end,
774 ctx,
775 )
776 }
777}
778
779impl<'s> DocGen<'s> for LessPercentKeyword {
780 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
781 Doc::text("%")
782 }
783}
784
785impl<'s> DocGen<'s> for LessPlugin<'s> {
786 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
787 let path = self.path.doc(ctx, state);
788 if let Some(args) = &self.args {
789 Doc::text("(")
790 .append(
791 Doc::line_or_nil()
792 .append(args.doc(ctx, state))
793 .nest(ctx.indent_width),
794 )
795 .append(Doc::line_or_nil())
796 .append(Doc::text(")"))
797 .append(Doc::line_or_space())
798 .append(path)
799 .group()
800 .nest(ctx.indent_width)
801 } else {
802 path
803 }
804 }
805}
806
807impl<'s> DocGen<'s> for LessPluginPath<'s> {
808 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
809 match self {
810 LessPluginPath::Str(str) => str.doc(ctx, state),
811 LessPluginPath::Url(url) => url.doc(ctx, state),
812 }
813 }
814}
815
816impl<'s> DocGen<'s> for LessPropertyInterpolation<'s> {
817 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
818 Doc::text(format!("${}{}{}", '{', self.name.raw, '}'))
819 }
820}
821
822impl<'s> DocGen<'s> for LessPropertyMerge {
823 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
824 match self.kind {
825 LessPropertyMergeKind::Comma => Doc::text("+"),
826 LessPropertyMergeKind::Space => Doc::text("+_"),
827 }
828 }
829}
830
831impl<'s> DocGen<'s> for LessPropertyVariable<'s> {
832 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
833 Doc::text(format!("${}", self.name.raw))
834 }
835}
836
837impl<'s> DocGen<'s> for LessVariable<'s> {
838 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
839 Doc::text("@").append(self.name.doc(ctx, state))
840 }
841}
842
843impl<'s> DocGen<'s> for LessVariableCall<'s> {
844 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
845 self.variable.doc(ctx, state).append(Doc::text("()"))
846 }
847}
848
849impl<'s> DocGen<'s> for LessVariableDeclaration<'s> {
850 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
851 let mut docs = Vec::with_capacity(3);
852 let value_span = self.value.span();
853
854 docs.push(self.name.doc(ctx, state));
855
856 docs.extend(ctx.start_spaced_comments(
857 ctx.get_comments_between(self.name.span.end, self.colon_span.start),
858 ));
859 docs.push(Doc::text(":"));
860
861 let should_group = if let ComponentValue::LessList(LessList {
862 elements,
863 comma_spans: Some(comma_spans),
864 span,
865 ..
866 }) = &self.value
867 {
868 docs.push(Doc::line_or_space());
869 docs.extend(ctx.end_spaced_comments(
870 ctx.get_comments_between(self.colon_span.end, value_span.start),
871 ));
872 docs.push(
873 helpers::SeparatedListFormatter::new(",", Doc::line_or_space()).format(
874 elements,
875 comma_spans,
876 span.start,
877 ctx,
878 state,
879 ),
880 );
881 if elements.len() == 1 {
882 docs.push(Doc::text(","));
883 }
884 true
885 } else {
886 docs.push(Doc::space());
887 docs.extend(ctx.end_spaced_comments(
888 ctx.get_comments_between(self.colon_span.end, value_span.start),
889 ));
890 docs.push(self.value.doc(ctx, state));
891 false
892 };
893
894 let doc = Doc::list(docs);
895 if should_group {
896 doc.group().nest(ctx.indent_width)
897 } else {
898 doc
899 }
900 }
901}
902
903impl<'s> DocGen<'s> for LessVariableInterpolation<'s> {
904 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
905 Doc::text(format!("@{}{}{}", '{', self.name.raw, '}'))
906 }
907}
908
909impl<'s> DocGen<'s> for LessVariableVariable<'s> {
910 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
911 Doc::text("@@").append(self.variable.name.doc(ctx, state))
912 }
913}
914
915fn is_preferred_quote_allowed(interpolated_str: &LessInterpolatedStr, ctx: &Ctx) -> bool {
916 use crate::config::Quotes;
917
918 match ctx.options.quotes {
919 Quotes::AlwaysDouble | Quotes::AlwaysSingle => false,
920 Quotes::PreferDouble => interpolated_str
921 .elements
922 .iter()
923 .any(|element| match element {
924 LessInterpolatedStrElement::Static(InterpolableStrStaticPart { value, .. }) => {
925 value.contains('"')
926 }
927 _ => false,
928 }),
929 Quotes::PreferSingle => interpolated_str
930 .elements
931 .iter()
932 .any(|element| match element {
933 LessInterpolatedStrElement::Static(InterpolableStrStaticPart { value, .. }) => {
934 value.contains('\'')
935 }
936 _ => false,
937 }),
938 }
939}