malva/doc_gen/
less.rs

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}