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 SassArbitraryArgument<'s> {
15 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
16 self.value.doc(ctx, state).append(Doc::text("..."))
17 }
18}
19
20impl<'s> DocGen<'s> for SassArbitraryParameter<'s> {
21 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
22 self.name.doc(ctx, state).append(Doc::text("..."))
23 }
24}
25
26impl<'s> DocGen<'s> for SassAtRoot<'s> {
27 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
28 match &self.kind {
29 SassAtRootKind::Selector(selector) => selector.doc(ctx, state),
30 SassAtRootKind::Query(query) => query.doc(ctx, state),
31 }
32 }
33}
34
35impl<'s> DocGen<'s> for SassAtRootQuery<'s> {
36 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
37 let mut docs = Vec::with_capacity(7);
38 docs.push(Doc::text("("));
39 docs.extend(ctx.end_spaced_comments(
40 ctx.get_comments_between(self.span.start, self.modifier.span.start),
41 ));
42
43 docs.push(self.modifier.doc(ctx, state));
44 docs.extend(ctx.start_spaced_comments(
45 ctx.get_comments_between(self.modifier.span.end, self.colon_span.start),
46 ));
47 docs.push(Doc::text(": "));
48
49 docs.extend(
50 itertools::intersperse(
51 self.rules.iter().scan(self.colon_span.start, |pos, rule| {
52 let rule_span = rule.span();
53 Some(
54 ctx.end_spaced_comments(ctx.get_comments_between(
55 mem::replace(pos, rule_span.end),
56 rule_span.start,
57 ))
58 .chain(iter::once(rule.doc(ctx, state)))
59 .collect::<Vec<_>>()
60 .into_iter(),
61 )
62 }),
63 vec![Doc::soft_line()].into_iter(),
64 )
65 .flatten(),
66 );
67
68 if let Some(last) = self.rules.last() {
69 docs.extend(
70 ctx.start_spaced_comments(ctx.get_comments_between(last.span().end, self.span.end)),
71 );
72 }
73
74 docs.push(Doc::text(")"));
75 Doc::list(docs)
76 }
77}
78
79impl<'s> DocGen<'s> for SassAtRootQueryModifier {
80 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
81 match self.kind {
82 SassAtRootQueryModifierKind::With => Doc::text("with"),
83 SassAtRootQueryModifierKind::Without => Doc::text("without"),
84 }
85 }
86}
87
88impl<'s> DocGen<'s> for SassAtRootQueryRule<'s> {
89 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
90 match self {
91 SassAtRootQueryRule::Ident(ident) => ident.doc(ctx, state),
92 SassAtRootQueryRule::Str(str) => str.doc(ctx, state),
93 }
94 }
95}
96
97impl<'s> DocGen<'s> for SassBinaryExpression<'s> {
98 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
99 self.left
100 .doc(ctx, state)
101 .append(helpers::format_operator_prefix_space(ctx))
102 .concat(ctx.end_spaced_comments(
103 ctx.get_comments_between(self.left.span().end, self.op.span.start),
104 ))
105 .append(self.op.doc(ctx, state))
106 .append(helpers::format_operator_suffix_space(ctx))
107 .concat(ctx.end_spaced_comments(
108 ctx.get_comments_between(self.op.span.end, self.right.span().start),
109 ))
110 .append(self.right.doc(ctx, state))
111 .group()
112 }
113}
114
115impl<'s> DocGen<'s> for SassBinaryOperator {
116 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
117 Doc::text(match self.kind {
118 SassBinaryOperatorKind::Multiply => "*",
119 SassBinaryOperatorKind::Division => "/",
120 SassBinaryOperatorKind::Modulo => "%",
121 SassBinaryOperatorKind::Plus => "+",
122 SassBinaryOperatorKind::Minus => "-",
123 SassBinaryOperatorKind::GreaterThan => ">",
124 SassBinaryOperatorKind::GreaterThanOrEqual => ">=",
125 SassBinaryOperatorKind::LessThan => "<",
126 SassBinaryOperatorKind::LessThanOrEqual => "<=",
127 SassBinaryOperatorKind::EqualsEquals => "==",
128 SassBinaryOperatorKind::ExclamationEquals => "!=",
129 SassBinaryOperatorKind::And => "and",
130 SassBinaryOperatorKind::Or => "or",
131 })
132 }
133}
134
135impl<'s> DocGen<'s> for SassConditionalClause<'s> {
136 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
137 self.condition
138 .doc(ctx, state)
139 .append(helpers::format_space_before_block(
140 self.condition.span().end,
141 self.block.span.start,
142 ctx,
143 ))
144 .append(self.block.doc(ctx, state))
145 }
146}
147
148impl<'s> DocGen<'s> for SassContent<'s> {
149 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
150 helpers::format_parenthesized(
151 helpers::SeparatedListFormatter::new(
152 ",",
153 helpers::get_smart_linebreak(
154 self.span.start,
155 &self.args,
156 ctx.options.sass_content_at_rule_prefer_single_line,
157 ctx,
158 ),
159 )
160 .with_trailing()
161 .format(&self.args, &self.comma_spans, self.span.start, ctx, state),
162 self.args
163 .len()
164 .checked_sub(1)
165 .and_then(|i| self.comma_spans.get(i))
166 .or_else(|| self.args.last().map(|param| param.span()))
167 .map(|span| span.end)
168 .unwrap_or(self.span.start),
169 self.span.end,
170 ctx,
171 )
172 }
173}
174
175impl<'s> DocGen<'s> for SassEach<'s> {
176 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
177 helpers::SeparatedListFormatter::new(",", Doc::line_or_space())
178 .format(
179 &self.bindings,
180 &self.comma_spans,
181 self.span.start,
182 ctx,
183 state,
184 )
185 .group()
186 .nest(ctx.indent_width)
187 .append(helpers::format_operator_prefix_space(ctx))
188 .concat(ctx.end_spaced_comments(
189 ctx.get_comments_between(
190 self.bindings.last().unwrap().span.end,
191 self.in_span.start,
192 ),
193 ))
194 .append(Doc::text("in"))
195 .append(helpers::format_operator_suffix_space(ctx))
196 .concat(ctx.end_spaced_comments(
197 ctx.get_comments_between(self.in_span.end, self.expr.span().start),
198 ))
199 .append(self.expr.doc(ctx, state).nest(ctx.indent_width))
200 .group()
201 }
202}
203
204impl<'s> DocGen<'s> for SassExtend<'s> {
205 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
206 let selectors =
207 helpers::SeparatedListFormatter::new(",", Doc::line_or_space().nest(ctx.indent_width))
208 .format(
209 &self.selectors.selectors,
210 &self.selectors.comma_spans,
211 self.selectors.span.start,
212 ctx,
213 state,
214 )
215 .group();
216 if let Some(optional) = &self.optional {
217 selectors
218 .append(Doc::space())
219 .concat(ctx.end_spaced_comments(
220 ctx.get_comments_between(self.selectors.span().end, optional.span.start),
221 ))
222 .append(optional.doc(ctx, state))
223 } else {
224 selectors
225 }
226 }
227}
228
229impl<'s> DocGen<'s> for SassFlag<'s> {
230 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
231 Doc::text(format!("!{}", self.keyword.raw))
232 }
233}
234
235impl<'s> DocGen<'s> for SassFor<'s> {
236 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
237 use crate::config::OperatorLineBreak;
238
239 let start_value_span = self.start.span();
240 self.binding
241 .doc(ctx, state)
242 .append(match ctx.options.operator_linebreak {
243 OperatorLineBreak::Before => Doc::soft_line().nest(ctx.indent_width),
244 OperatorLineBreak::After => Doc::space(),
245 })
246 .concat(ctx.end_spaced_comments(
247 ctx.get_comments_between(self.binding.span.end, self.from_span.start),
248 ))
249 .append(Doc::text("from"))
250 .append(match ctx.options.operator_linebreak {
251 OperatorLineBreak::Before => Doc::space(),
252 OperatorLineBreak::After => Doc::soft_line().nest(ctx.indent_width),
253 })
254 .concat(ctx.end_spaced_comments(
255 ctx.get_comments_between(self.from_span.end, start_value_span.start),
256 ))
257 .append(self.start.doc(ctx, state))
258 .append(match ctx.options.operator_linebreak {
259 OperatorLineBreak::Before => Doc::soft_line().nest(ctx.indent_width),
260 OperatorLineBreak::After => Doc::space(),
261 })
262 .concat(ctx.end_spaced_comments(
263 ctx.get_comments_between(start_value_span.end, self.boundary.span.start),
264 ))
265 .append(self.boundary.doc(ctx, state))
266 .append(match ctx.options.operator_linebreak {
267 OperatorLineBreak::Before => Doc::space(),
268 OperatorLineBreak::After => Doc::soft_line().nest(ctx.indent_width),
269 })
270 .concat(ctx.end_spaced_comments(
271 ctx.get_comments_between(self.boundary.span.end, self.end.span().start),
272 ))
273 .append(self.end.doc(ctx, state))
274 }
275}
276
277impl<'s> DocGen<'s> for SassForBoundary {
278 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
279 match self.kind {
280 SassForBoundaryKind::Exclusive => Doc::text("to"),
281 SassForBoundaryKind::Inclusive => Doc::text("through"),
282 }
283 }
284}
285
286impl<'s> DocGen<'s> for SassForward<'s> {
287 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
288 let mut docs = vec![self.path.doc(ctx, state)];
289 let mut pos = self.path.span().end;
290
291 if let Some(prefix) = &self.prefix {
292 docs.reserve(2);
293 docs.push(Doc::space());
294 docs.extend(ctx.end_spaced_comments(
295 ctx.get_comments_between(
296 mem::replace(&mut pos, prefix.span.end),
297 prefix.span.start,
298 ),
299 ));
300 docs.push(prefix.doc(ctx, state));
301 }
302
303 if let Some(visibility) = &self.visibility {
304 docs.reserve(2);
305 docs.push(Doc::space());
306 docs.extend(ctx.end_spaced_comments(ctx.get_comments_between(
307 mem::replace(&mut pos, visibility.span.end),
308 visibility.span.start,
309 )));
310 docs.push(visibility.doc(ctx, state));
311 }
312
313 if let Some(config) = &self.config {
314 docs.reserve(2);
315 docs.push(Doc::space());
316 docs.extend(ctx.end_spaced_comments(
317 ctx.get_comments_between(
318 mem::replace(&mut pos, config.span.end),
319 config.span.start,
320 ),
321 ));
322 docs.push(config.doc(ctx, state));
323 }
324
325 Doc::list(docs)
326 }
327}
328
329impl<'s> DocGen<'s> for SassForwardMember<'s> {
330 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
331 match self {
332 SassForwardMember::Ident(ident) => ident.doc(ctx, state),
333 SassForwardMember::Variable(variable) => variable.doc(ctx, state),
334 }
335 }
336}
337
338impl<'s> DocGen<'s> for SassForwardPrefix<'s> {
339 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
340 Doc::text("as ")
341 .concat(ctx.end_spaced_comments(
342 ctx.get_comments_between(self.as_span.end, self.name.span.start),
343 ))
344 .append(self.name.doc(ctx, state))
345 .append(Doc::text("*"))
346 }
347}
348
349impl<'s> DocGen<'s> for SassForwardVisibility<'s> {
350 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
351 self.modifier.doc(ctx, state).append(Doc::space()).append(
352 helpers::SeparatedListFormatter::new(",", Doc::soft_line()).format(
353 &self.members,
354 &self.comma_spans,
355 self.modifier.span.end,
356 ctx,
357 state,
358 ),
359 )
360 }
361}
362
363impl<'s> DocGen<'s> for SassForwardVisibilityModifier {
364 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
365 match self.kind {
366 SassForwardVisibilityModifierKind::Hide => Doc::text("hide"),
367 SassForwardVisibilityModifierKind::Show => Doc::text("show"),
368 }
369 }
370}
371
372impl<'s> DocGen<'s> for SassFunction<'s> {
373 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
374 self.name
375 .doc(ctx, state)
376 .append(self.parameters.doc(ctx, state))
377 }
378}
379
380impl<'s> DocGen<'s> for SassIfAtRule<'s> {
381 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
382 let mut docs = vec![Doc::text("@if ")];
383 docs.extend(ctx.end_spaced_comments(
384 ctx.get_comments_between(self.span.start, self.if_clause.span.start),
385 ));
386 docs.push(self.if_clause.doc(ctx, state));
387 let mut pos = self.if_clause.span.end;
388
389 docs.extend(
390 self.else_if_clauses
391 .iter()
392 .zip(self.else_spans.iter())
393 .scan(&mut pos, |pos, (clause, elseif_span)| {
394 Some(
395 iter::once(Doc::space())
396 .chain(ctx.end_spaced_comments(ctx.get_comments_between(
397 mem::replace(*pos, elseif_span.end),
398 elseif_span.start,
399 )))
400 .chain(iter::once(Doc::text("@else if ")))
401 .chain(ctx.end_spaced_comments(ctx.get_comments_between(
402 mem::replace(*pos, clause.span.end),
403 clause.span.start,
404 )))
405 .chain(iter::once(clause.doc(ctx, state))),
406 )
407 })
408 .flatten(),
409 );
410
411 if let Some((else_clause, else_span)) =
412 self.else_clause.as_ref().zip(self.else_spans.last())
413 {
414 docs.reserve(3);
415 docs.push(Doc::space());
416 docs.extend(ctx.end_spaced_comments(ctx.get_comments_between(pos, else_span.start)));
417 docs.push(Doc::text("@else"));
418 docs.push(helpers::format_space_before_block(
419 else_span.end,
420 else_clause.span.start,
421 ctx,
422 ));
423 docs.push(else_clause.doc(ctx, state));
424 }
425
426 Doc::list(docs)
427 }
428}
429
430impl<'s> DocGen<'s> for SassImportPrelude<'s> {
431 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
432 helpers::SeparatedListFormatter::new(",", Doc::line_or_space().nest(ctx.indent_width))
433 .format(&self.paths, &self.comma_spans, self.span.start, ctx, state)
434 .group()
435 }
436}
437
438impl<'s> DocGen<'s> for SassInclude<'s> {
439 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
440 let mut docs = vec![self.name.doc(ctx, state)];
441 let mut pos = self.name.span().end;
442
443 if let Some(arguments) = &self.arguments {
444 docs.extend(ctx.end_spaced_comments(ctx.get_comments_between(
445 mem::replace(&mut pos, arguments.span.end),
446 arguments.span.start,
447 )));
448 docs.push(arguments.doc(ctx, state));
449 }
450
451 if let Some(content_block_params) = &self.content_block_params {
452 docs.reserve(2);
453 docs.push(Doc::space());
454 docs.extend(ctx.end_spaced_comments(ctx.get_comments_between(
455 mem::replace(&mut pos, content_block_params.span.end),
456 content_block_params.span.start,
457 )));
458 docs.push(content_block_params.doc(ctx, state));
459 }
460
461 Doc::list(docs)
462 }
463}
464
465impl<'s> DocGen<'s> for SassIncludeArgs<'s> {
466 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
467 helpers::format_parenthesized(
468 helpers::SeparatedListFormatter::new(
469 ",",
470 helpers::get_smart_linebreak(
471 self.span.start,
472 &self.args,
473 ctx.options.sass_include_at_rule_prefer_single_line,
474 ctx,
475 ),
476 )
477 .with_trailing()
478 .format(&self.args, &self.comma_spans, self.span.start, ctx, state),
479 self.args
480 .len()
481 .checked_sub(1)
482 .and_then(|i| self.comma_spans.get(i))
483 .or_else(|| self.args.last().map(|param| param.span()))
484 .map(|span| span.end)
485 .unwrap_or(self.span.start),
486 self.span.end,
487 ctx,
488 )
489 }
490}
491
492impl<'s> DocGen<'s> for SassIncludeContentBlockParams<'s> {
493 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
494 Doc::text("using ")
495 .concat(ctx.end_spaced_comments(
496 ctx.get_comments_between(self.using_span.end, self.params.span.start),
497 ))
498 .append(self.params.doc(ctx, state))
499 }
500}
501
502impl<'s> DocGen<'s> for SassInterpolatedIdent<'s> {
503 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
504 let mut docs = Vec::with_capacity(self.elements.len());
505 let mut iter = self.elements.iter().peekable();
506 let mut pos = self.span.start;
507 while let Some(element) = iter.next() {
508 match element {
509 SassInterpolatedIdentElement::Static(s) => {
510 pos = s.span.end;
511 docs.push(s.doc(ctx, state));
512 }
513 SassInterpolatedIdentElement::Expression(expr) => {
514 let expr_span = expr.span();
515 docs.push(Doc::text("#{"));
516 docs.extend(
517 ctx.end_spaced_comments(ctx.get_comments_between(pos, expr_span.start)),
518 );
519 docs.push(expr.doc(ctx, state));
520 docs.extend(
521 ctx.start_spaced_comments(
522 ctx.get_comments_between(
523 expr_span.end,
524 iter.peek()
525 .map(|element| element.span().start)
526 .unwrap_or(self.span.end),
527 ),
528 ),
529 );
530 docs.push(Doc::text("}"));
531 }
532 }
533 }
534
535 Doc::list(docs)
536 }
537}
538
539impl<'s> DocGen<'s> for SassInterpolatedStr<'s> {
540 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
541 if let [SassInterpolatedStrElement::Static(first), mid @ .., SassInterpolatedStrElement::Static(last)] =
542 &self.elements[..]
543 {
544 let allow_prefer = is_preferred_quote_allowed(self, ctx);
545
546 let mut docs = Vec::with_capacity(self.elements.len());
547 docs.push(Doc::text(format_str(
548 first.raw,
549 InterpolatedFirstStrRawFormatter::new(first.raw),
550 allow_prefer,
551 ctx,
552 )));
553 let mut iter = mid.iter().peekable();
554 let mut pos = first.span.end;
555 while let Some(element) = iter.next() {
556 match element {
557 SassInterpolatedStrElement::Static(s) => {
558 pos = s.span.end;
559 docs.push(Doc::text(format_str(
560 s.raw,
561 InterpolatedMidStrRawFormatter::new(s.raw),
562 allow_prefer,
563 ctx,
564 )));
565 }
566 SassInterpolatedStrElement::Expression(expr) => {
567 let expr_span = expr.span();
568 docs.push(Doc::text("#{"));
569 docs.extend(
570 ctx.end_spaced_comments(ctx.get_comments_between(pos, expr_span.start)),
571 );
572 docs.push(expr.doc(ctx, state));
573 docs.extend(
574 ctx.start_spaced_comments(
575 ctx.get_comments_between(
576 expr_span.end,
577 iter.peek()
578 .map(|element| element.span().start)
579 .unwrap_or(self.span.end),
580 ),
581 ),
582 );
583 docs.push(Doc::text("}"));
584 }
585 }
586 }
587 docs.push(Doc::text(format_str(
588 last.raw,
589 InterpolatedLastStrRawFormatter::new(last.raw),
590 allow_prefer,
591 ctx,
592 )));
593 Doc::list(docs)
594 } else {
595 unreachable!()
596 }
597 }
598}
599
600impl<'s> DocGen<'s> for SassInterpolatedUrl<'s> {
601 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
602 let mut docs = Vec::with_capacity(self.elements.len());
603 let mut iter = self.elements.iter().peekable();
604 let mut pos = self.span.start;
605 while let Some(element) = iter.next() {
606 match element {
607 SassInterpolatedUrlElement::Static(s) => {
608 pos = s.span.end;
609 docs.push(s.doc(ctx, state));
610 }
611 SassInterpolatedUrlElement::Expression(expr) => {
612 let expr_span = expr.span();
613 docs.push(Doc::text("#{"));
614 docs.extend(
615 ctx.end_spaced_comments(ctx.get_comments_between(pos, expr_span.start)),
616 );
617 docs.push(expr.doc(ctx, state));
618 docs.extend(
619 ctx.start_spaced_comments(
620 ctx.get_comments_between(
621 expr_span.end,
622 iter.peek()
623 .map(|element| element.span().start)
624 .unwrap_or(self.span.end),
625 ),
626 ),
627 );
628 docs.push(Doc::text("}"));
629 }
630 }
631 }
632
633 Doc::list(docs)
634 }
635}
636
637impl<'s> DocGen<'s> for SassKeywordArgument<'s> {
638 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
639 self.name
640 .doc(ctx, state)
641 .concat(ctx.start_spaced_comments(
642 ctx.get_comments_between(self.name.span.start, self.colon_span.start),
643 ))
644 .append(Doc::text(": "))
645 .concat(ctx.end_spaced_comments(
646 ctx.get_comments_between(self.colon_span.end, self.value.span().start),
647 ))
648 .append(self.value.doc(ctx, state))
649 }
650}
651
652impl<'s> DocGen<'s> for SassList<'s> {
653 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
654 helpers::format_values_list(
655 &self.elements,
656 self.comma_spans.as_deref(),
657 &self.span,
658 ctx,
659 state,
660 )
661 }
662}
663
664impl<'s> DocGen<'s> for SassMap<'s> {
665 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
666 helpers::format_parenthesized(
667 helpers::SeparatedListFormatter::new(
668 ",",
669 helpers::get_smart_linebreak(
670 self.span.start,
671 &self.items,
672 ctx.options.sass_map_prefer_single_line,
673 ctx,
674 ),
675 )
676 .with_trailing()
677 .format(&self.items, &self.comma_spans, self.span.start, ctx, state),
678 self.items
679 .last()
680 .map(|item| item.span.end)
681 .unwrap_or(self.span.start),
682 self.span.end,
683 ctx,
684 )
685 }
686}
687
688impl<'s> DocGen<'s> for SassMapItem<'s> {
689 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
690 self.key
691 .doc(ctx, state)
692 .concat(ctx.start_spaced_comments(
693 ctx.get_comments_between(self.key.span().end, self.colon_span.start),
694 ))
695 .append(Doc::text(": "))
696 .concat(ctx.end_spaced_comments(
697 ctx.get_comments_between(self.colon_span.end, self.value.span().start),
698 ))
699 .append(self.value.doc(ctx, state))
700 }
701}
702
703impl<'s> DocGen<'s> for SassMixin<'s> {
704 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
705 let name = self.name.doc(ctx, state);
706 if let Some(parameters) = &self.parameters {
707 name.append(parameters.doc(ctx, state))
708 } else {
709 name
710 }
711 }
712}
713
714impl<'s> DocGen<'s> for SassModuleConfig<'s> {
715 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
716 Doc::text("with ")
717 .concat(ctx.end_spaced_comments(
718 ctx.get_comments_between(self.with_span.end, self.lparen_span.start),
719 ))
720 .append(helpers::format_parenthesized(
721 helpers::SeparatedListFormatter::new(
722 ",",
723 helpers::get_smart_linebreak(
724 self.span.start,
725 &self.items,
726 ctx.options.sass_module_config_prefer_single_line,
727 ctx,
728 ),
729 )
730 .with_trailing()
731 .format(
732 &self.items,
733 &self.comma_spans,
734 self.lparen_span.end,
735 ctx,
736 state,
737 ),
738 self.items
739 .last()
740 .map(|item| item.span.end)
741 .unwrap_or(self.lparen_span.end),
742 self.span.end,
743 ctx,
744 ))
745 }
746}
747
748impl<'s> DocGen<'s> for SassModuleConfigItem<'s> {
749 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
750 let value_span = self.value.span();
751 self.variable
752 .doc(ctx, state)
753 .concat(ctx.start_spaced_comments(
754 ctx.get_comments_between(self.variable.span.end, self.colon_span.start),
755 ))
756 .append(Doc::text(": "))
757 .concat(ctx.end_spaced_comments(
758 ctx.get_comments_between(self.colon_span.end, value_span.start),
759 ))
760 .append(self.value.doc(ctx, state))
761 .concat(
762 self.flags
763 .iter()
764 .scan(value_span.end, |pos, flag| {
765 Some(
766 iter::once(Doc::soft_line())
767 .chain(ctx.end_spaced_comments(ctx.get_comments_between(
768 mem::replace(pos, flag.span.end),
769 flag.span.start,
770 )))
771 .chain(iter::once(flag.doc(ctx, state)))
772 .collect::<Vec<_>>()
773 .into_iter(),
774 )
775 })
776 .flatten(),
777 )
778 }
779}
780
781impl<'s> DocGen<'s> for SassModuleMemberName<'s> {
782 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
783 match self {
784 SassModuleMemberName::Ident(ident) => ident.doc(ctx, state),
785 SassModuleMemberName::Variable(variable) => variable.doc(ctx, state),
786 }
787 }
788}
789
790impl<'s> DocGen<'s> for SassNestingDeclaration<'s> {
791 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
792 self.block.doc(ctx, state)
793 }
794}
795
796impl<'s> DocGen<'s> for SassParameter<'s> {
797 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
798 let name = self.name.doc(ctx, state);
799 if let Some(default_value) = &self.default_value {
800 name.concat(ctx.start_spaced_comments(
801 ctx.get_comments_between(self.name.span.end, default_value.span.start),
802 ))
803 .append(default_value.doc(ctx, state))
804 } else {
805 name
806 }
807 }
808}
809
810impl<'s> DocGen<'s> for SassParameterDefaultValue<'s> {
811 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
812 Doc::text(": ")
813 .concat(ctx.end_spaced_comments(
814 ctx.get_comments_between(self.colon_span.end, self.value.span().start),
815 ))
816 .append(self.value.doc(ctx, state))
817 }
818}
819
820impl<'s> DocGen<'s> for SassParameters<'s> {
821 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
822 enum ParameterOrArbitrary<'a, 's> {
823 Parameter(&'a SassParameter<'s>),
824 Arbitrary(&'a SassArbitraryParameter<'s>),
825 }
826 impl Spanned for ParameterOrArbitrary<'_, '_> {
827 fn span(&self) -> &raffia::Span {
828 match self {
829 ParameterOrArbitrary::Parameter(p) => p.span(),
830 ParameterOrArbitrary::Arbitrary(a) => a.span(),
831 }
832 }
833 }
834 impl<'s> DocGen<'s> for ParameterOrArbitrary<'_, 's> {
835 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
836 match self {
837 ParameterOrArbitrary::Parameter(p) => p.doc(ctx, state),
838 ParameterOrArbitrary::Arbitrary(a) => a.doc(ctx, state),
839 }
840 }
841 }
842
843 let params = self
844 .params
845 .iter()
846 .map(ParameterOrArbitrary::Parameter)
847 .chain(
848 self.arbitrary_param
849 .iter()
850 .map(ParameterOrArbitrary::Arbitrary),
851 )
852 .collect::<Vec<_>>();
853 helpers::format_parenthesized(
854 helpers::SeparatedListFormatter::new(
855 ",",
856 helpers::get_smart_linebreak(
857 self.span.start,
858 ¶ms,
859 ctx.options.sass_params_prefer_single_line,
860 ctx,
861 ),
862 )
863 .with_trailing()
864 .format(¶ms, &self.comma_spans, self.span.start, ctx, state),
865 self.params
866 .len()
867 .checked_sub(1)
868 .and_then(|i| self.comma_spans.get(i))
869 .or_else(|| self.params.last().map(|param| param.span()))
870 .map(|span| span.end)
871 .unwrap_or(self.span.start),
872 self.span.end,
873 ctx,
874 )
875 }
876}
877
878impl<'s> DocGen<'s> for SassParenthesizedExpression<'s> {
879 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
880 let expr_span = self.expr.span();
881 helpers::format_parenthesized(
882 Doc::list(
883 ctx.end_spaced_comments(ctx.get_comments_between(self.span.start, expr_span.start))
884 .collect(),
885 )
886 .append(self.expr.doc(ctx, state)),
887 expr_span.end,
888 self.span.end,
889 ctx,
890 )
891 }
892}
893
894impl<'s> DocGen<'s> for SassPlaceholderSelector<'s> {
895 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
896 Doc::text("%").append(self.name.doc(ctx, state))
897 }
898}
899
900impl<'s> DocGen<'s> for SassQualifiedName<'s> {
901 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
902 Doc::list(vec![
903 self.module.doc(ctx, state),
904 Doc::text("."),
905 self.member.doc(ctx, state),
906 ])
907 }
908}
909
910impl<'s> DocGen<'s> for SassUnaryExpression<'s> {
911 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
912 self.op.doc(ctx, state).append(self.expr.doc(ctx, state))
913 }
914}
915
916impl<'s> DocGen<'s> for SassUnaryOperator {
917 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
918 match self.kind {
919 SassUnaryOperatorKind::Plus => Doc::text("+"),
920 SassUnaryOperatorKind::Minus => Doc::text("-"),
921 SassUnaryOperatorKind::Not => Doc::text("not "),
922 }
923 }
924}
925
926impl<'s> DocGen<'s> for SassUnnamedNamespace {
927 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
928 Doc::text("*")
929 }
930}
931
932impl<'s> DocGen<'s> for SassUse<'s> {
933 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
934 let mut docs = vec![self.path.doc(ctx, state)];
935 let mut pos = self.path.span().end;
936
937 if let Some(namespace) = &self.namespace {
938 docs.push(Doc::space());
939 docs.extend(ctx.end_spaced_comments(ctx.get_comments_between(
940 mem::replace(&mut pos, namespace.span.end),
941 namespace.span.start,
942 )));
943 docs.push(namespace.doc(ctx, state));
944 }
945
946 if let Some(config) = &self.config {
947 docs.push(Doc::space());
948 docs.extend(ctx.end_spaced_comments(ctx.get_comments_between(pos, config.span.start)));
949 docs.push(config.doc(ctx, state));
950 }
951
952 Doc::list(docs)
953 }
954}
955
956impl<'s> DocGen<'s> for SassUseNamespace<'s> {
957 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
958 Doc::text("as ")
959 .concat(ctx.end_spaced_comments(
960 ctx.get_comments_between(self.as_span.end, self.kind.span().start),
961 ))
962 .append(match &self.kind {
963 SassUseNamespaceKind::Named(named) => named.doc(ctx, state),
964 SassUseNamespaceKind::Unnamed(unnamed) => unnamed.doc(ctx, state),
965 })
966 }
967}
968
969impl<'s> DocGen<'s> for SassVariable<'s> {
970 fn doc(&self, _: &Ctx<'_, 's>, _: &State) -> Doc<'s> {
971 Doc::text(format!("${}", self.name.raw))
972 }
973}
974
975impl<'s> DocGen<'s> for SassVariableDeclaration<'s> {
976 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
977 let mut docs = Vec::with_capacity(3);
978 let value_span = self.value.span();
979
980 if let Some(namespace) = &self.namespace {
981 docs.push(namespace.doc(ctx, state));
982 docs.push(Doc::text("."));
983 }
984 docs.push(self.name.doc(ctx, state));
985
986 docs.extend(ctx.start_spaced_comments(
987 ctx.get_comments_between(self.name.span.end, self.colon_span.start),
988 ));
989 docs.push(Doc::text(":"));
990
991 let should_group = match &self.value {
992 ComponentValue::SassList(SassList {
993 elements,
994 comma_spans: Some(comma_spans),
995 span,
996 ..
997 }) => {
998 docs.push(Doc::line_or_space());
999 docs.extend(ctx.end_spaced_comments(
1000 ctx.get_comments_between(self.colon_span.end, value_span.start),
1001 ));
1002 docs.push(
1003 helpers::SeparatedListFormatter::new(",", Doc::line_or_space())
1004 .with_trailing()
1005 .format(elements, comma_spans, span.start, ctx, state),
1006 );
1007 if elements.len() == 1 {
1008 docs.push(Doc::text(","));
1009 }
1010 true
1011 }
1012 ComponentValue::SassList(sass_list) => {
1013 docs.push(Doc::space());
1014 docs.extend(ctx.end_spaced_comments(
1015 ctx.get_comments_between(self.colon_span.end, value_span.start),
1016 ));
1017 docs.push(sass_list.doc(ctx, state).nest(ctx.indent_width));
1018 false
1019 }
1020 _ => {
1021 docs.push(Doc::space());
1022 docs.extend(ctx.end_spaced_comments(
1023 ctx.get_comments_between(self.colon_span.end, value_span.start),
1024 ));
1025 docs.push(self.value.doc(ctx, state));
1026 false
1027 }
1028 };
1029
1030 docs.extend(
1031 self.flags
1032 .iter()
1033 .scan(value_span.end, |pos, flag| {
1034 Some(
1035 iter::once(Doc::soft_line().nest(ctx.indent_width))
1036 .chain(ctx.end_spaced_comments(ctx.get_comments_between(
1037 mem::replace(pos, flag.span.end),
1038 flag.span.start,
1039 )))
1040 .chain(iter::once(flag.doc(ctx, state)))
1041 .collect::<Vec<_>>()
1042 .into_iter(),
1043 )
1044 })
1045 .flatten(),
1046 );
1047
1048 let doc = Doc::list(docs);
1049 if should_group {
1050 doc.group().nest(ctx.indent_width)
1051 } else {
1052 doc
1053 }
1054 }
1055}
1056
1057impl<'s> DocGen<'s> for UnknownSassAtRule<'s> {
1058 fn doc(&self, ctx: &Ctx<'_, 's>, state: &State) -> Doc<'s> {
1059 let mut docs = Vec::with_capacity(6);
1060 let mut pos = self.name.span().end;
1061
1062 docs.push(Doc::text("@"));
1063 docs.push(self.name.doc(ctx, state));
1064
1065 if let Some(prelude) = &self.prelude {
1066 docs.push(Doc::space());
1067 let span = prelude.span();
1068 docs.extend(ctx.end_spaced_comments(ctx.get_comments_between(pos, span.start)));
1069 docs.push(prelude.doc(ctx, state));
1070 pos = span.end;
1071 }
1072
1073 if let Some(block) = &self.block {
1074 docs.push(helpers::format_space_before_block(
1075 pos,
1076 block.span.start,
1077 ctx,
1078 ));
1079 docs.push(block.doc(ctx, state));
1080 }
1081
1082 Doc::list(docs)
1083 }
1084}
1085
1086fn is_preferred_quote_allowed(interpolated_str: &SassInterpolatedStr, ctx: &Ctx) -> bool {
1087 use crate::config::Quotes;
1088
1089 match ctx.options.quotes {
1090 Quotes::AlwaysDouble | Quotes::AlwaysSingle => false,
1091 Quotes::PreferDouble => interpolated_str
1092 .elements
1093 .iter()
1094 .any(|element| match element {
1095 SassInterpolatedStrElement::Static(InterpolableStrStaticPart { value, .. }) => {
1096 value.contains('"')
1097 }
1098 SassInterpolatedStrElement::Expression(_) => false,
1099 }),
1100 Quotes::PreferSingle => interpolated_str
1101 .elements
1102 .iter()
1103 .any(|element| match element {
1104 SassInterpolatedStrElement::Static(InterpolableStrStaticPart { value, .. }) => {
1105 value.contains('\'')
1106 }
1107 SassInterpolatedStrElement::Expression(_) => false,
1108 }),
1109 }
1110}