1use crate::{buffer::Buffer, IndentOptions};
2use dioxus_rsx::*;
3use proc_macro2::{LineColumn, Span};
4use quote::ToTokens;
5use regex::Regex;
6use std::{
7 borrow::Cow,
8 collections::{HashMap, HashSet, VecDeque},
9 fmt::{Result, Write},
10};
11use syn::{spanned::Spanned, token::Brace, Expr};
12
13#[derive(Debug)]
14pub struct Writer<'a> {
15 pub raw_src: &'a str,
16 pub src: Vec<&'a str>,
17 pub cached_formats: HashMap<LineColumn, String>,
18 pub out: Buffer,
19 pub invalid_exprs: Vec<Span>,
20}
21
22impl<'a> Writer<'a> {
23 pub fn new(raw_src: &'a str, indent: IndentOptions) -> Self {
24 Self {
25 src: raw_src.lines().collect(),
26 raw_src,
27 out: Buffer {
28 indent,
29 ..Default::default()
30 },
31 cached_formats: HashMap::new(),
32 invalid_exprs: Vec::new(),
33 }
34 }
35
36 pub fn consume(self) -> Option<String> {
37 Some(self.out.buf)
38 }
39
40 pub fn write_rsx_call(&mut self, body: &CallBody) -> Result {
41 if body.body.roots.is_empty() {
42 return Ok(());
43 }
44
45 if Self::is_short_rsx_call(&body.body.roots) {
46 write!(self.out, " ")?;
47 self.write_ident(&body.body.roots[0])?;
48 write!(self.out, " ")?;
49 } else {
50 self.out.new_line()?;
51 self.write_body_indented(&body.body.roots)?;
52 self.write_trailing_body_comments(body)?;
53 }
54
55 Ok(())
56 }
57
58 fn write_trailing_body_comments(&mut self, body: &CallBody) -> Result {
59 if let Some(span) = body.span {
60 self.out.indent_level += 1;
61 let comments = self.accumulate_full_line_comments(span.span().end());
62 if !comments.is_empty() {
63 self.out.new_line()?;
64 self.apply_line_comments(comments)?;
65 self.out.buf.pop(); }
67 self.out.indent_level -= 1;
68 }
69 Ok(())
70 }
71
72 pub fn write_ident(&mut self, node: &BodyNode) -> Result {
74 match node {
75 BodyNode::Element(el) => self.write_element(el),
76 BodyNode::Component(component) => self.write_component(component),
77 BodyNode::Text(text) => self.write_text_node(text),
78 BodyNode::RawExpr(expr) => self.write_expr_node(expr),
79 BodyNode::ForLoop(forloop) => self.write_for_loop(forloop),
80 BodyNode::IfChain(ifchain) => self.write_if_chain(ifchain),
81 }?;
82
83 let span = Self::final_span_of_node(node);
84
85 self.write_inline_comments(span.end(), 0)?;
86
87 Ok(())
88 }
89
90 pub(crate) fn is_short_rsx_call(roots: &[BodyNode]) -> bool {
92 #[allow(clippy::match_like_matches_macro)]
94 match roots {
95 [] => true,
96 [BodyNode::Text(_text)] => true,
97 _ => false,
98 }
99 }
100
101 fn write_element(&mut self, el: &Element) -> Result {
102 let Element {
103 name,
104 raw_attributes: attributes,
105 children,
106 spreads,
107 brace,
108 ..
109 } = el;
110
111 write!(self.out, "{name} ")?;
112 self.write_rsx_block(attributes, spreads, children, &brace.unwrap_or_default())?;
113
114 Ok(())
115 }
116
117 fn write_component(
118 &mut self,
119 Component {
120 name,
121 fields,
122 children,
123 generics,
124 spreads,
125 brace,
126 ..
127 }: &Component,
128 ) -> Result {
129 let mut name = name.to_token_stream().to_string();
131 name.retain(|c| !c.is_whitespace());
132 write!(self.out, "{name}")?;
133
134 if let Some(generics) = generics {
136 let mut written = generics.to_token_stream().to_string();
137 written.retain(|c| !c.is_whitespace());
138 write!(self.out, "{written}")?;
139 }
140
141 write!(self.out, " ")?;
142 self.write_rsx_block(fields, spreads, &children.roots, &brace.unwrap_or_default())?;
143
144 Ok(())
145 }
146
147 fn write_text_node(&mut self, text: &TextNode) -> Result {
148 self.out.write_text(&text.input)
149 }
150
151 fn write_expr_node(&mut self, expr: &ExprNode) -> Result {
152 self.write_partial_expr(expr.expr.as_expr(), expr.span())
153 }
154
155 fn write_for_loop(&mut self, forloop: &ForLoop) -> std::fmt::Result {
156 write!(
157 self.out,
158 "for {} in ",
159 forloop.pat.clone().into_token_stream(),
160 )?;
161
162 self.write_inline_expr(&forloop.expr)?;
163
164 if forloop.body.is_empty() {
165 write!(self.out, "}}")?;
166 return Ok(());
167 }
168
169 self.out.new_line()?;
170 self.write_body_indented(&forloop.body.roots)?;
171
172 self.out.tabbed_line()?;
173 write!(self.out, "}}")?;
174
175 Ok(())
176 }
177
178 fn write_if_chain(&mut self, ifchain: &IfChain) -> std::fmt::Result {
179 let mut branch = Some(ifchain);
181
182 while let Some(chain) = branch {
183 let IfChain {
184 if_token,
185 cond,
186 then_branch,
187 else_if_branch,
188 else_branch,
189 ..
190 } = chain;
191
192 write!(self.out, "{} ", if_token.to_token_stream(),)?;
193
194 self.write_inline_expr(cond)?;
195
196 self.out.new_line()?;
197 self.write_body_indented(&then_branch.roots)?;
198
199 if let Some(else_if_branch) = else_if_branch {
200 self.out.tabbed_line()?;
202 write!(self.out, "}} else ")?;
203
204 branch = Some(else_if_branch);
205 } else if let Some(else_branch) = else_branch {
206 self.out.tabbed_line()?;
207 write!(self.out, "}} else {{")?;
208
209 self.out.new_line()?;
210 self.write_body_indented(&else_branch.roots)?;
211 branch = None;
212 } else {
213 branch = None;
214 }
215 }
216
217 self.out.tabbed_line()?;
218 write!(self.out, "}}")?;
219
220 Ok(())
221 }
222
223 fn write_inline_expr(&mut self, expr: &Expr) -> std::fmt::Result {
225 let unparsed = self.unparse_expr(expr);
226 let mut lines = unparsed.lines();
227 let first_line = lines.next().ok_or(std::fmt::Error)?;
228
229 write!(self.out, "{first_line}")?;
230
231 let mut was_multiline = false;
232
233 for line in lines {
234 was_multiline = true;
235 self.out.tabbed_line()?;
236 write!(self.out, "{line}")?;
237 }
238
239 if was_multiline {
240 self.out.tabbed_line()?;
241 write!(self.out, "{{")?;
242 } else {
243 write!(self.out, " {{")?;
244 }
245
246 Ok(())
247 }
248
249 fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
251 self.out.indent_level += 1;
252 self.write_body_nodes(children)?;
253 self.out.indent_level -= 1;
254 Ok(())
255 }
256
257 pub fn write_body_nodes(&mut self, children: &[BodyNode]) -> Result {
258 let mut iter = children.iter().peekable();
259
260 while let Some(child) = iter.next() {
261 if self.current_span_is_primary(child.span().start()) {
262 self.write_comments(child.span().start())?;
263 };
264 self.out.tab()?;
265 self.write_ident(child)?;
266 if iter.peek().is_some() {
267 self.out.new_line()?;
268 }
269 }
270
271 Ok(())
272 }
273
274 fn write_rsx_block(
279 &mut self,
280 attributes: &[Attribute],
281 spreads: &[Spread],
282 children: &[BodyNode],
283 brace: &Brace,
284 ) -> Result {
285 #[derive(Debug)]
286 enum ShortOptimization {
287 Empty,
292
293 Oneliner,
299
300 PropsOnTop,
302
303 NoOpt,
305 }
306
307 write!(self.out, "{{")?;
309
310 let mut opt_level = ShortOptimization::NoOpt;
313
314 let attr_len = self.is_short_attrs(brace, attributes, spreads);
316 let has_postbrace_comments = self.brace_has_trailing_comments(brace);
317 let is_short_attr_list =
318 ((attr_len + self.out.indent_level * 4) < 80) && !has_postbrace_comments;
319 let children_len = self
320 .is_short_children(children)
321 .map_err(|_| std::fmt::Error)?;
322 let has_trailing_comments = self.has_trailing_comments(children, brace);
323 let is_small_children = children_len.is_some() && !has_trailing_comments;
324
325 if is_short_attr_list && !is_small_children {
327 opt_level = ShortOptimization::PropsOnTop;
328 }
329
330 if !is_short_attr_list
333 && attributes.len() <= 1
334 && spreads.is_empty()
335 && !has_trailing_comments
336 && !has_postbrace_comments
337 {
338 if children.is_empty() {
339 opt_level = ShortOptimization::Oneliner;
340 } else {
341 opt_level = ShortOptimization::PropsOnTop;
342 }
343 }
344
345 if is_short_attr_list && is_small_children {
347 if children_len.unwrap() + attr_len + self.out.indent_level * 4 < 100 {
348 opt_level = ShortOptimization::Oneliner;
349 } else {
350 opt_level = ShortOptimization::PropsOnTop;
351 }
352 }
353
354 if attributes.is_empty()
356 && children.is_empty()
357 && spreads.is_empty()
358 && !has_trailing_comments
359 {
360 opt_level = ShortOptimization::Empty;
361
362 self.write_inline_comments(brace.span.span().start(), 1)?;
364 self.write_todo_body(brace)?;
365 }
366
367 if attr_len > 1000 || self.out.indent.split_line_attributes() {
369 opt_level = ShortOptimization::NoOpt;
370 }
371
372 let has_children = !children.is_empty();
373
374 match opt_level {
375 ShortOptimization::Empty => {}
376 ShortOptimization::Oneliner => {
377 write!(self.out, " ")?;
378
379 self.write_attributes(attributes, spreads, true, brace, has_children)?;
380
381 if !children.is_empty() && !attributes.is_empty() {
382 write!(self.out, " ")?;
383 }
384
385 let mut children_iter = children.iter().peekable();
386 while let Some(child) = children_iter.next() {
387 self.write_ident(child)?;
388 if children_iter.peek().is_some() {
389 write!(self.out, " ")?;
390 }
391 }
392
393 write!(self.out, " ")?;
394 }
395
396 ShortOptimization::PropsOnTop => {
397 if !attributes.is_empty() {
398 write!(self.out, " ")?;
399 }
400
401 self.write_attributes(attributes, spreads, true, brace, has_children)?;
402
403 if !children.is_empty() {
404 self.out.new_line()?;
405 self.write_body_indented(children)?;
406 }
407
408 self.out.tabbed_line()?;
409 }
410
411 ShortOptimization::NoOpt => {
412 self.write_inline_comments(brace.span.span().start(), 1)?;
413 self.out.new_line()?;
414 self.write_attributes(attributes, spreads, false, brace, has_children)?;
415
416 if !children.is_empty() {
417 self.out.new_line()?;
418 self.write_body_indented(children)?;
419 }
420
421 self.out.tabbed_line()?;
422 }
423 }
424
425 if matches!(
427 opt_level,
428 ShortOptimization::NoOpt | ShortOptimization::PropsOnTop
429 ) && self.leading_row_is_empty(brace.span.span().end())
430 {
431 let comments = self.accumulate_full_line_comments(brace.span.span().end());
432 if !comments.is_empty() {
433 self.apply_line_comments(comments)?;
434 self.out.tab()?;
435 }
436 }
437
438 write!(self.out, "}}")?;
439
440 Ok(())
441 }
442
443 fn write_attributes(
444 &mut self,
445 attributes: &[Attribute],
446 spreads: &[Spread],
447 props_same_line: bool,
448 brace: &Brace,
449 has_children: bool,
450 ) -> Result {
451 enum AttrType<'a> {
452 Attr(&'a Attribute),
453 Spread(&'a Spread),
454 }
455
456 let mut attr_iter = attributes
457 .iter()
458 .map(AttrType::Attr)
459 .chain(spreads.iter().map(AttrType::Spread))
460 .peekable();
461
462 let has_attributes = !attributes.is_empty() || !spreads.is_empty();
463
464 while let Some(attr) = attr_iter.next() {
465 self.out.indent_level += 1;
466
467 if !props_same_line {
468 self.write_attr_comments(
469 brace,
470 match attr {
471 AttrType::Attr(attr) => attr.span(),
472 AttrType::Spread(attr) => attr.expr.span(),
473 },
474 )?;
475 }
476
477 self.out.indent_level -= 1;
478
479 if !props_same_line {
480 self.out.indented_tab()?;
481 }
482
483 match attr {
484 AttrType::Attr(attr) => self.write_attribute(attr)?,
485 AttrType::Spread(attr) => self.write_spread_attribute(&attr.expr)?,
486 }
487
488 let span = match attr {
489 AttrType::Attr(attr) => attr
490 .comma
491 .as_ref()
492 .map(|c| c.span())
493 .unwrap_or_else(|| self.total_span_of_attr(attr)),
494 AttrType::Spread(attr) => attr.span(),
495 };
496
497 let has_more = attr_iter.peek().is_some();
498 let should_finish_comma = has_attributes && has_children || !props_same_line;
499
500 if has_more || should_finish_comma {
501 write!(self.out, ",")?;
502 }
503
504 if !props_same_line {
505 self.write_inline_comments(span.end(), 0)?;
506 }
507
508 if props_same_line && !has_more {
509 self.write_inline_comments(span.end(), 0)?;
510 }
511
512 if props_same_line && has_more {
513 write!(self.out, " ")?;
514 }
515
516 if !props_same_line && has_more {
517 self.out.new_line()?;
518 }
519 }
520
521 Ok(())
522 }
523
524 fn write_attribute(&mut self, attr: &Attribute) -> Result {
525 self.write_attribute_name(&attr.name)?;
526
527 if !attr.can_be_shorthand() {
529 write!(self.out, ": ")?;
530 self.write_attribute_value(&attr.value)?;
531 }
532
533 Ok(())
534 }
535
536 fn write_attribute_name(&mut self, attr: &AttributeName) -> Result {
537 match attr {
538 AttributeName::BuiltIn(name) => write!(self.out, "{}", name),
539 AttributeName::Custom(name) => write!(self.out, "{}", name.to_token_stream()),
540 AttributeName::Spread(_) => unreachable!(),
541 }
542 }
543
544 fn write_attribute_value(&mut self, value: &AttributeValue) -> Result {
545 match value {
546 AttributeValue::IfExpr(if_chain) => {
547 self.write_attribute_if_chain(if_chain)?;
548 }
549 AttributeValue::AttrLiteral(value) => {
550 write!(self.out, "{value}")?;
551 }
552 AttributeValue::Shorthand(value) => {
553 write!(self.out, "{value}")?;
554 }
555 AttributeValue::EventTokens(closure) => {
556 self.out.indent_level += 1;
557 self.write_partial_expr(closure.as_expr(), closure.span())?;
558 self.out.indent_level -= 1;
559 }
560 AttributeValue::AttrExpr(value) => {
561 self.out.indent_level += 1;
562 self.write_partial_expr(value.as_expr(), value.span())?;
563 self.out.indent_level -= 1;
564 }
565 }
566
567 Ok(())
568 }
569
570 fn write_attribute_if_chain(&mut self, if_chain: &IfAttributeValue) -> Result {
571 let cond = self.unparse_expr(&if_chain.if_expr.cond);
572 write!(self.out, "if {cond} {{ ")?;
573 self.write_attribute_value(&if_chain.then_value)?;
574 write!(self.out, " }}")?;
575 match if_chain.else_value.as_deref() {
576 Some(AttributeValue::IfExpr(else_if_chain)) => {
577 write!(self.out, " else ")?;
578 self.write_attribute_if_chain(else_if_chain)?;
579 }
580 Some(other) => {
581 write!(self.out, " else {{ ")?;
582 self.write_attribute_value(other)?;
583 write!(self.out, " }}")?;
584 }
585 None => {}
586 }
587
588 Ok(())
589 }
590
591 fn write_attr_comments(&mut self, brace: &Brace, attr_span: Span) -> Result {
592 let brace_line = brace.span.span().start().line;
598 let attr_line = attr_span.start().line;
599
600 if brace_line != attr_line {
601 let line = self.src.get(attr_line - 1).unwrap_or(&"");
603
604 let row_start = line.get(..attr_span.start().column - 1).unwrap_or("");
606 if !row_start.trim().is_empty() {
607 return Ok(());
608 }
609
610 self.write_comments(attr_span.start())?;
611 }
612
613 Ok(())
614 }
615
616 fn write_inline_comments(&mut self, final_span: LineColumn, offset: usize) -> Result {
617 let line = final_span.line;
618 let column = final_span.column;
619 let Some(src_line) = self.src.get(line - 1) else {
620 return Ok(());
621 };
622
623 let Some(mut whitespace) = src_line.get(column..).map(|s| s.trim()) else {
625 return Ok(());
626 };
627
628 if whitespace.is_empty() {
629 return Ok(());
630 }
631
632 whitespace = whitespace[offset..].trim();
633
634 if final_span.line == 1 && final_span.column == 0 {
636 return Ok(());
637 };
638
639 if whitespace.starts_with("//") {
640 write!(self.out, " {whitespace}")?;
641 }
642
643 Ok(())
644 }
645
646 fn accumulate_full_line_comments(&mut self, loc: LineColumn) -> VecDeque<usize> {
647 let start = loc;
650 let line_start = start.line - 1;
651
652 let mut comments = VecDeque::new();
653
654 if loc.line == 1 && loc.column == 0 {
656 return comments;
657 };
658
659 let Some(lines) = self.src.get(..line_start) else {
660 return comments;
661 };
662
663 let mut last_line_was_empty = false;
666 for (id, line) in lines.iter().enumerate().rev() {
667 let trimmed = line.trim();
668 if trimmed.starts_with("//") {
669 comments.push_front(id);
670 last_line_was_empty = false;
671 } else if trimmed.is_empty() {
672 if !last_line_was_empty {
673 comments.push_front(id);
674 last_line_was_empty = true;
675 }
676
677 continue;
678 } else {
679 break;
680 }
681 }
682
683 if comments.len() > 1 {
685 if let Some(&first) = comments.back() {
686 if self.src[first].trim().is_empty() {
687 comments.pop_back();
688 }
689 }
690 }
691
692 comments
693 }
694
695 fn apply_line_comments(&mut self, mut comments: VecDeque<usize>) -> Result {
696 while let Some(comment_line) = comments.pop_front() {
697 let Some(line) = self.src.get(comment_line) else {
698 continue;
699 };
700
701 let line = &line.trim();
702
703 if line.is_empty() {
704 self.out.new_line()?;
705 } else {
706 self.out.tab()?;
707 writeln!(self.out, "{}", line.trim())?;
708 }
709 }
710 Ok(())
711 }
712
713 fn write_comments(&mut self, loc: LineColumn) -> Result {
714 let comments = self.accumulate_full_line_comments(loc);
715 self.apply_line_comments(comments)?;
716 Ok(())
717 }
718
719 fn attr_value_len(&mut self, value: &AttributeValue) -> usize {
720 match value {
721 AttributeValue::IfExpr(if_chain) => {
722 let condition_len = self.retrieve_formatted_expr(&if_chain.if_expr.cond).len();
723 let value_len = self.attr_value_len(&if_chain.then_value);
724 let if_len = 2;
725 let brace_len = 2;
726 let space_len = 2;
727 let else_len = if_chain
728 .else_value
729 .as_ref()
730 .map(|else_value| self.attr_value_len(else_value) + 1)
731 .unwrap_or_default();
732 condition_len + value_len + if_len + brace_len + space_len + else_len
733 }
734 AttributeValue::AttrLiteral(lit) => lit.to_string().len(),
735 AttributeValue::Shorthand(expr) => {
736 let span = &expr.span();
737 span.end().line - span.start().line
738 }
739 AttributeValue::AttrExpr(expr) => expr
740 .as_expr()
741 .map(|expr| self.attr_expr_len(&expr))
742 .unwrap_or(100000),
743 AttributeValue::EventTokens(closure) => closure
744 .as_expr()
745 .map(|expr| self.attr_expr_len(&expr))
746 .unwrap_or(100000),
747 }
748 }
749
750 fn attr_expr_len(&mut self, expr: &Expr) -> usize {
751 let out = self.retrieve_formatted_expr(expr);
752 if out.contains('\n') {
753 100000
754 } else {
755 out.len()
756 }
757 }
758
759 fn is_short_attrs(
760 &mut self,
761 _brace: &Brace,
762 attributes: &[Attribute],
763 spreads: &[Spread],
764 ) -> usize {
765 let mut total = 0;
766
767 if attributes.len() > 3 {
769 return 100000;
770 }
771
772 for attr in attributes {
773 if self.current_span_is_primary(attr.span().start()) {
774 if let Some(lines) = self.src.get(..attr.span().start().line - 1) {
775 'line: for line in lines.iter().rev() {
776 match (line.trim().starts_with("//"), line.is_empty()) {
777 (true, _) => return 100000,
778 (_, true) => continue 'line,
779 _ => break 'line,
780 }
781 }
782 };
783 }
784
785 total += match &attr.name {
786 AttributeName::BuiltIn(name) => {
787 let name = name.to_string();
788 name.len()
789 }
790 AttributeName::Custom(name) => name.value().len() + 2,
791 AttributeName::Spread(_) => unreachable!(),
792 };
793
794 if attr.can_be_shorthand() {
795 total += 2;
796 } else {
797 total += self.attr_value_len(&attr.value);
798 }
799
800 total += 6;
801 }
802
803 for spread in spreads {
804 let expr_len = self.retrieve_formatted_expr(&spread.expr).len();
805 total += expr_len + 3;
806 }
807
808 total
809 }
810
811 fn write_todo_body(&mut self, brace: &Brace) -> std::fmt::Result {
812 let span = brace.span.span();
813 let start = span.start();
814 let end = span.end();
815
816 if start.line == end.line {
817 return Ok(());
818 }
819
820 writeln!(self.out)?;
821
822 for idx in start.line..end.line {
823 let Some(line) = self.src.get(idx) else {
824 continue;
825 };
826 if line.trim().starts_with("//") {
827 for _ in 0..self.out.indent_level + 1 {
828 write!(self.out, " ")?
829 }
830 writeln!(self.out, "{}", line.trim())?;
831 }
832 }
833
834 for _ in 0..self.out.indent_level {
835 write!(self.out, " ")?
836 }
837
838 Ok(())
839 }
840
841 fn write_partial_expr(&mut self, expr: syn::Result<Expr>, src_span: Span) -> Result {
842 let Ok(expr) = expr else {
843 self.invalid_exprs.push(src_span);
844 return Err(std::fmt::Error);
845 };
846
847 thread_local! {
848 static COMMENT_REGEX: Regex = Regex::new("\"[^\"]*\"|(//.*)").unwrap();
849 }
850
851 let pretty = self.retrieve_formatted_expr(&expr).to_string();
852 let source = src_span.source_text().unwrap_or_default();
853 let mut src_lines = source.lines().peekable();
854
855 let pretty_comments: HashSet<_> = pretty
857 .lines()
858 .filter(|l| l.trim().starts_with("//"))
859 .map(|l| l.trim())
860 .collect();
861
862 let mut out = String::new();
863
864 if src_lines.peek().is_none() {
865 out = pretty;
866 } else {
867 for line in pretty.lines() {
868 let trimmed = line.trim();
869 let compacted = line.replace(" ", "").replace(",", "");
870
871 if trimmed.starts_with("//") {
873 if !out.is_empty() {
874 out.push('\n');
875 }
876 let mut had_empty = false;
877 while let Some(s) = src_lines.peek() {
878 let t = s.trim();
879 if t.is_empty() {
880 had_empty = true;
881 src_lines.next();
882 } else if t == trimmed {
883 src_lines.next();
884 break;
885 } else {
886 break;
887 }
888 }
889 if had_empty {
890 out.push('\n');
891 }
892 out.push_str(line);
893 continue;
894 }
895
896 if trimmed.is_empty() {
898 if !out.is_empty() {
899 out.push('\n');
900 }
901 while src_lines
902 .peek()
903 .map(|s| s.trim().is_empty())
904 .unwrap_or(false)
905 {
906 src_lines.next();
907 }
908 continue;
909 }
910
911 if !out.is_empty() {
912 out.push('\n');
913 }
914
915 let mut pending_comments = Vec::new();
917 let mut had_empty = false;
918 let mut multiline: Option<Vec<&str>> = None;
919
920 while let Some(src) = src_lines.peek() {
921 let src_trimmed = src.trim();
922
923 if src_trimmed.is_empty() || src_trimmed.starts_with("//") {
924 if src_trimmed.is_empty() {
925 if pending_comments.is_empty() {
926 had_empty = true;
927 }
928 } else if !pretty_comments.contains(src_trimmed) {
929 pending_comments.push(src_trimmed);
930 }
931 src_lines.next();
932 continue;
933 }
934
935 let src_compacted = src.replace(" ", "").replace(",", "");
936
937 if src_compacted.contains(&compacted) {
939 break;
940 }
941
942 if !src_compacted.is_empty() && compacted.starts_with(&src_compacted) {
944 let is_call = src_trimmed.ends_with('(')
945 || src_trimmed.ends_with(',')
946 || src_trimmed.ends_with('{');
947 if !is_call {
948 multiline = Some(vec![*src]);
949 break;
950 }
951 }
952
953 pending_comments.clear();
955 had_empty = false;
956 src_lines.next();
957 break;
958 }
959
960 if had_empty {
962 out.push('\n');
963 }
964
965 for comment in &pending_comments {
967 for c in line.chars().take_while(|c| c.is_whitespace()) {
968 out.push(c);
969 }
970 if matches!(trimmed.chars().next(), Some(')' | '}' | ']')) {
971 out.push_str(self.out.indent.indent_str());
972 }
973 out.push_str(comment);
974 out.push('\n');
975 }
976
977 if let Some(mut ml) = multiline {
979 src_lines.next();
980 let mut acc = ml[0].replace(" ", "").replace(",", "");
981
982 while let Some(src) = src_lines.peek() {
983 let t = src.trim();
984 if t.starts_with("//") {
985 ml.push(src);
986 src_lines.next();
987 continue;
988 }
989 if t.is_empty() {
990 src_lines.next();
991 continue;
992 }
993
994 acc.push_str(&src.replace(" ", "").replace(",", ""));
995 ml.push(src);
996
997 if acc.contains(&compacted) {
998 src_lines.next();
999 break;
1000 }
1001
1002 let cont = t.starts_with('.')
1003 || t.starts_with("&&")
1004 || t.starts_with("||")
1005 || matches!(t.chars().next(), Some('+' | '-' | '*' | '/' | '?'));
1006
1007 if cont || compacted.starts_with(&acc) {
1008 src_lines.next();
1009 continue;
1010 }
1011 break;
1012 }
1013
1014 let base_indent = ml[0].chars().take_while(|c| c.is_whitespace()).count();
1016 let target: String = line.chars().take_while(|c| c.is_whitespace()).collect();
1017
1018 for (i, src_line) in ml.iter().enumerate() {
1019 let indent = src_line.chars().take_while(|c| c.is_whitespace()).count();
1020 out.push_str(&target);
1021 for _ in 0..indent.saturating_sub(base_indent) {
1022 out.push(' ');
1023 }
1024 out.push_str(src_line.trim());
1025 if i < ml.len() - 1 {
1026 out.push('\n');
1027 }
1028 }
1029 } else {
1030 out.push_str(line);
1032 if let Some(src_line) = src_lines.next() {
1033 if let Some(cap) = COMMENT_REGEX.with(|r| r.captures(src_line)) {
1034 if let Some(c) = cap.get(1) {
1035 out.push_str(" // ");
1036 out.push_str(c.as_str().replace("//", "").trim());
1037 }
1038 }
1039 }
1040 }
1041 }
1042 }
1043
1044 self.write_mulitiline_tokens(out)?;
1045 Ok(())
1046 }
1047
1048 fn write_mulitiline_tokens(&mut self, out: String) -> Result {
1049 let mut lines = out.split('\n').peekable();
1050 let first = lines.next().unwrap();
1051
1052 if lines.peek().is_none() {
1055 write!(self.out, "{first}")?;
1056 } else {
1057 writeln!(self.out, "{first}")?;
1058
1059 while let Some(line) = lines.next() {
1060 if !line.trim().is_empty() {
1061 self.out.tab()?;
1062 }
1063
1064 write!(self.out, "{line}")?;
1065 if lines.peek().is_none() {
1066 write!(self.out, "")?;
1067 } else {
1068 writeln!(self.out)?;
1069 }
1070 }
1071 }
1072
1073 Ok(())
1074 }
1075
1076 fn write_spread_attribute(&mut self, attr: &Expr) -> Result {
1077 let formatted = self.unparse_expr(attr);
1078
1079 let mut lines = formatted.lines();
1080
1081 let first_line = lines.next().unwrap();
1082
1083 write!(self.out, "..{first_line}")?;
1084 for line in lines {
1085 self.out.indented_tabbed_line()?;
1086 write!(self.out, "{line}")?;
1087 }
1088
1089 Ok(())
1090 }
1091
1092 fn is_short_children(&mut self, children: &[BodyNode]) -> syn::Result<Option<usize>> {
1098 if children.is_empty() {
1099 return Ok(Some(0));
1100 }
1101
1102 if self.children_have_comments(children) {
1104 return Ok(None);
1105 }
1106
1107 let res = match children {
1108 [BodyNode::Text(ref text)] => Some(text.input.to_string_with_quotes().len()),
1109
1110 [BodyNode::RawExpr(ref expr)] => {
1112 let pretty = self.retrieve_formatted_expr(&expr.expr.as_expr()?);
1113 if pretty.contains('\n') {
1114 None
1115 } else {
1116 Some(pretty.len() + 2)
1117 }
1118 }
1119
1120 [BodyNode::Component(ref comp)]
1122 if comp.fields.is_empty()
1124 && comp.children.is_empty()
1125 && comp.spreads.is_empty() =>
1126 {
1127 Some(
1128 comp.name
1129 .segments
1130 .iter()
1131 .map(|s| s.ident.to_string().len() + 2)
1132 .sum::<usize>(),
1133 )
1134 }
1135
1136 _ => None,
1140 };
1141
1142 Ok(res)
1143 }
1144
1145 fn children_have_comments(&self, children: &[BodyNode]) -> bool {
1146 for child in children {
1147 if self.current_span_is_primary(child.span().start()) {
1148 'line: for line in self.src[..child.span().start().line - 1].iter().rev() {
1149 match (line.trim().starts_with("//"), line.is_empty()) {
1150 (true, _) => return true,
1151 (_, true) => continue 'line,
1152 _ => break 'line,
1153 }
1154 }
1155 }
1156 }
1157
1158 false
1159 }
1160
1161 fn current_span_is_primary(&self, location: LineColumn) -> bool {
1164 self.leading_row_is_empty(LineColumn {
1165 line: location.line,
1166 column: location.column + 1,
1167 })
1168 }
1169
1170 fn leading_row_is_empty(&self, location: LineColumn) -> bool {
1171 let Some(line) = self.src.get(location.line - 1) else {
1172 return false;
1173 };
1174
1175 let Some(sub) = line.get(..location.column - 1) else {
1176 return false;
1177 };
1178
1179 sub.trim().is_empty()
1180 }
1181
1182 #[allow(clippy::map_entry)]
1183 fn retrieve_formatted_expr(&mut self, expr: &Expr) -> Cow<'_, str> {
1184 let loc = expr.span().start();
1185
1186 if loc.line == 1 && loc.column == 0 {
1188 return self.unparse_expr(expr).into();
1189 }
1190
1191 if !self.cached_formats.contains_key(&loc) {
1192 let formatted = self.unparse_expr(expr);
1193 self.cached_formats.insert(loc, formatted);
1194 }
1195
1196 self.cached_formats
1197 .get(&loc)
1198 .expect("Just inserted the parsed expr, so it should be in the cache")
1199 .as_str()
1200 .into()
1201 }
1202
1203 fn final_span_of_node(node: &BodyNode) -> Span {
1204 match node {
1206 BodyNode::Element(el) => el
1207 .brace
1208 .as_ref()
1209 .map(|b| b.span.span())
1210 .unwrap_or_else(|| el.name.span()),
1211 BodyNode::Component(el) => el
1212 .brace
1213 .as_ref()
1214 .map(|b| b.span.span())
1215 .unwrap_or_else(|| el.name.span()),
1216 BodyNode::Text(txt) => txt.input.span(),
1217 BodyNode::RawExpr(exp) => exp.span(),
1218 BodyNode::ForLoop(f) => f.brace.span.span(),
1219 BodyNode::IfChain(i) => match i.else_brace {
1220 Some(b) => b.span.span(),
1221 None => i.then_brace.span.span(),
1222 },
1223 }
1224 }
1225
1226 fn total_span_of_attr(&self, attr: &Attribute) -> Span {
1227 match &attr.value {
1228 AttributeValue::Shorthand(s) => s.span(),
1229 AttributeValue::AttrLiteral(l) => l.span(),
1230 AttributeValue::EventTokens(closure) => closure.span(),
1231 AttributeValue::AttrExpr(exp) => exp.span(),
1232 AttributeValue::IfExpr(ex) => ex.span(),
1233 }
1234 }
1235
1236 fn brace_has_trailing_comments(&self, brace: &Brace) -> bool {
1237 let span = brace.span.span();
1238 let line = self.src.get(span.start().line - 1).unwrap_or(&"");
1239 let after_brace = line.get(span.start().column + 1..).unwrap_or("").trim();
1240 after_brace.starts_with("//")
1241 }
1242
1243 fn has_trailing_comments(&self, children: &[BodyNode], brace: &Brace) -> bool {
1244 let brace_span = brace.span.span();
1245
1246 let Some(last_node) = children.last() else {
1247 return false;
1248 };
1249
1250 let final_span = Self::final_span_of_node(last_node);
1252 let final_span = final_span.end();
1253 let mut line = final_span.line;
1254 let mut column = final_span.column;
1255 loop {
1256 let Some(src_line) = self.src.get(line - 1) else {
1257 return false;
1258 };
1259
1260 let Some(mut whitespace) = src_line.get(column..).map(|s| s.trim()) else {
1262 return false;
1263 };
1264
1265 let offset = 0;
1266 whitespace = whitespace[offset..].trim();
1267
1268 if whitespace.starts_with("//") {
1269 return true;
1270 }
1271
1272 if line == brace_span.end().line {
1273 break;
1275 }
1276
1277 line += 1;
1278 column = 0; }
1280
1281 false
1282 }
1283}