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, 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_comments(span.span().end());
62 if !comments.is_empty() {
63 self.out.new_line()?;
64 self.apply_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(attributes, spreads);
316 let is_short_attr_list = (attr_len + self.out.indent_level * 4) < 80;
317 let children_len = self
318 .is_short_children(children)
319 .map_err(|_| std::fmt::Error)?;
320 let has_trailing_comments = self.has_trailing_comments(children, brace);
321 let is_small_children = children_len.is_some() && !has_trailing_comments;
322
323 if is_short_attr_list && !is_small_children {
325 opt_level = ShortOptimization::PropsOnTop;
326 }
327
328 if !is_short_attr_list
331 && attributes.len() <= 1
332 && spreads.is_empty()
333 && !has_trailing_comments
334 {
335 if children.is_empty() {
336 opt_level = ShortOptimization::Oneliner;
337 } else {
338 opt_level = ShortOptimization::PropsOnTop;
339 }
340 }
341
342 if is_short_attr_list && is_small_children {
344 if children_len.unwrap() + attr_len + self.out.indent_level * 4 < 100 {
345 opt_level = ShortOptimization::Oneliner;
346 } else {
347 opt_level = ShortOptimization::PropsOnTop;
348 }
349 }
350
351 if attributes.is_empty()
353 && children.is_empty()
354 && spreads.is_empty()
355 && !has_trailing_comments
356 {
357 opt_level = ShortOptimization::Empty;
358
359 self.write_inline_comments(brace.span.span().start(), 1)?;
361 self.write_todo_body(brace)?;
362 }
363
364 if attr_len > 1000 || self.out.indent.split_line_attributes() {
366 opt_level = ShortOptimization::NoOpt;
367 }
368
369 let has_children = !children.is_empty();
370
371 match opt_level {
372 ShortOptimization::Empty => {}
373 ShortOptimization::Oneliner => {
374 write!(self.out, " ")?;
375
376 self.write_attributes(attributes, spreads, true, brace, has_children)?;
377
378 if !children.is_empty() && !attributes.is_empty() {
379 write!(self.out, " ")?;
380 }
381
382 let mut children_iter = children.iter().peekable();
383 while let Some(child) = children_iter.next() {
384 self.write_ident(child)?;
385 if children_iter.peek().is_some() {
386 write!(self.out, " ")?;
387 }
388 }
389
390 write!(self.out, " ")?;
391 }
392
393 ShortOptimization::PropsOnTop => {
394 if !attributes.is_empty() {
395 write!(self.out, " ")?;
396 }
397
398 self.write_attributes(attributes, spreads, true, brace, has_children)?;
399
400 if !children.is_empty() {
401 self.out.new_line()?;
402 self.write_body_indented(children)?;
403 }
404
405 self.out.tabbed_line()?;
406 }
407
408 ShortOptimization::NoOpt => {
409 self.write_inline_comments(brace.span.span().start(), 1)?;
410 self.out.new_line()?;
411 self.write_attributes(attributes, spreads, false, brace, has_children)?;
412
413 if !children.is_empty() {
414 self.out.new_line()?;
415 self.write_body_indented(children)?;
416 }
417
418 self.out.tabbed_line()?;
419 }
420 }
421
422 if matches!(
424 opt_level,
425 ShortOptimization::NoOpt | ShortOptimization::PropsOnTop
426 ) && self.leading_row_is_empty(brace.span.span().end())
427 {
428 let comments = self.accumulate_comments(brace.span.span().end());
429 if !comments.is_empty() {
430 self.apply_comments(comments)?;
431 self.out.tab()?;
432 }
433 }
434
435 write!(self.out, "}}")?;
436
437 Ok(())
438 }
439
440 fn write_attributes(
441 &mut self,
442 attributes: &[Attribute],
443 spreads: &[Spread],
444 props_same_line: bool,
445 brace: &Brace,
446 has_children: bool,
447 ) -> Result {
448 enum AttrType<'a> {
449 Attr(&'a Attribute),
450 Spread(&'a Spread),
451 }
452
453 let mut attr_iter = attributes
454 .iter()
455 .map(AttrType::Attr)
456 .chain(spreads.iter().map(AttrType::Spread))
457 .peekable();
458
459 let has_attributes = !attributes.is_empty() || !spreads.is_empty();
460
461 while let Some(attr) = attr_iter.next() {
462 self.out.indent_level += 1;
463
464 if !props_same_line {
465 self.write_attr_comments(
466 brace,
467 match attr {
468 AttrType::Attr(attr) => attr.span(),
469 AttrType::Spread(attr) => attr.expr.span(),
470 },
471 )?;
472 }
473
474 self.out.indent_level -= 1;
475
476 if !props_same_line {
477 self.out.indented_tab()?;
478 }
479
480 match attr {
481 AttrType::Attr(attr) => self.write_attribute(attr)?,
482 AttrType::Spread(attr) => self.write_spread_attribute(&attr.expr)?,
483 }
484
485 let span = match attr {
486 AttrType::Attr(attr) => attr
487 .comma
488 .as_ref()
489 .map(|c| c.span())
490 .unwrap_or_else(|| self.final_span_of_attr(attr)),
491 AttrType::Spread(attr) => attr.span(),
492 };
493
494 let has_more = attr_iter.peek().is_some();
495 let should_finish_comma = has_attributes && has_children || !props_same_line;
496
497 if has_more || should_finish_comma {
498 write!(self.out, ",")?;
499 }
500
501 if !props_same_line {
502 self.write_inline_comments(span.end(), 0)?;
503 }
504
505 if props_same_line && !has_more {
506 self.write_inline_comments(span.end(), 0)?;
507 }
508
509 if props_same_line && has_more {
510 write!(self.out, " ")?;
511 }
512
513 if !props_same_line && has_more {
514 self.out.new_line()?;
515 }
516 }
517
518 Ok(())
519 }
520
521 fn write_attribute(&mut self, attr: &Attribute) -> Result {
522 self.write_attribute_name(&attr.name)?;
523
524 if !attr.can_be_shorthand() {
526 write!(self.out, ": ")?;
527 self.write_attribute_value(&attr.value)?;
528 }
529
530 Ok(())
531 }
532
533 fn write_attribute_name(&mut self, attr: &AttributeName) -> Result {
534 match attr {
535 AttributeName::BuiltIn(name) => write!(self.out, "{}", name),
536 AttributeName::Custom(name) => write!(self.out, "{}", name.to_token_stream()),
537 AttributeName::Spread(_) => unreachable!(),
538 }
539 }
540
541 fn write_attribute_value(&mut self, value: &AttributeValue) -> Result {
542 match value {
543 AttributeValue::IfExpr(if_chain) => {
544 self.write_attribute_if_chain(if_chain)?;
545 }
546 AttributeValue::AttrLiteral(value) => {
547 write!(self.out, "{value}")?;
548 }
549 AttributeValue::Shorthand(value) => {
550 write!(self.out, "{value}")?;
551 }
552 AttributeValue::EventTokens(closure) => {
553 self.out.indent_level += 1;
554 self.write_partial_expr(closure.as_expr(), closure.span())?;
555 self.out.indent_level -= 1;
556 }
557 AttributeValue::AttrExpr(value) => {
558 self.out.indent_level += 1;
559 self.write_partial_expr(value.as_expr(), value.span())?;
560 self.out.indent_level -= 1;
561 }
562 }
563
564 Ok(())
565 }
566
567 fn write_attribute_if_chain(&mut self, if_chain: &IfAttributeValue) -> Result {
568 let cond = self.unparse_expr(&if_chain.condition);
569 write!(self.out, "if {cond} {{ ")?;
570 self.write_attribute_value(&if_chain.then_value)?;
571 write!(self.out, " }}")?;
572 match if_chain.else_value.as_deref() {
573 Some(AttributeValue::IfExpr(else_if_chain)) => {
574 write!(self.out, " else ")?;
575 self.write_attribute_if_chain(else_if_chain)?;
576 }
577 Some(other) => {
578 write!(self.out, " else {{ ")?;
579 self.write_attribute_value(other)?;
580 write!(self.out, " }}")?;
581 }
582 None => {}
583 }
584
585 Ok(())
586 }
587
588 fn write_attr_comments(&mut self, brace: &Brace, attr_span: Span) -> Result {
589 let brace_line = brace.span.span().start().line;
595 let attr_line = attr_span.start().line;
596
597 if brace_line != attr_line {
598 self.write_comments(attr_span.start())?;
599 }
600
601 Ok(())
602 }
603
604 fn write_inline_comments(&mut self, final_span: LineColumn, offset: usize) -> Result {
605 let line = final_span.line;
606 let column = final_span.column;
607 let Some(src_line) = self.src.get(line - 1) else {
608 return Ok(());
609 };
610
611 let Some(mut whitespace) = src_line.get(column..).map(|s| s.trim()) else {
613 return Ok(());
614 };
615
616 if whitespace.is_empty() {
617 return Ok(());
618 }
619
620 whitespace = whitespace[offset..].trim();
621
622 if whitespace.starts_with("//") {
623 write!(self.out, " {whitespace}")?;
624 }
625
626 Ok(())
627 }
628 fn accumulate_comments(&mut self, loc: LineColumn) -> VecDeque<usize> {
629 let start = loc;
632 let line_start = start.line - 1;
633
634 let mut comments = VecDeque::new();
635
636 let Some(lines) = self.src.get(..line_start) else {
637 return comments;
638 };
639
640 for (id, line) in lines.iter().enumerate().rev() {
641 if line.trim().starts_with("//") || line.is_empty() && id != 0 {
642 if id != 0 {
643 comments.push_front(id);
644 }
645 } else {
646 break;
647 }
648 }
649
650 comments
651 }
652 fn apply_comments(&mut self, mut comments: VecDeque<usize>) -> Result {
653 while let Some(comment_line) = comments.pop_front() {
654 let Some(line) = self.src.get(comment_line) else {
655 continue;
656 };
657
658 let line = &line.trim();
659
660 if line.is_empty() {
661 self.out.new_line()?;
662 } else {
663 self.out.tab()?;
664 writeln!(self.out, "{}", line.trim())?;
665 }
666 }
667 Ok(())
668 }
669
670 fn write_comments(&mut self, loc: LineColumn) -> Result {
671 let comments = self.accumulate_comments(loc);
672 self.apply_comments(comments)?;
673
674 Ok(())
675 }
676
677 fn attr_value_len(&mut self, value: &AttributeValue) -> usize {
678 match value {
679 AttributeValue::IfExpr(if_chain) => {
680 let condition_len = self.retrieve_formatted_expr(&if_chain.condition).len();
681 let value_len = self.attr_value_len(&if_chain.then_value);
682 let if_len = 2;
683 let brace_len = 2;
684 let space_len = 2;
685 let else_len = if_chain
686 .else_value
687 .as_ref()
688 .map(|else_value| self.attr_value_len(else_value) + 1)
689 .unwrap_or_default();
690 condition_len + value_len + if_len + brace_len + space_len + else_len
691 }
692 AttributeValue::AttrLiteral(lit) => lit.to_string().len(),
693 AttributeValue::Shorthand(expr) => {
694 let span = &expr.span();
695 span.end().line - span.start().line
696 }
697 AttributeValue::AttrExpr(expr) => expr
698 .as_expr()
699 .map(|expr| self.attr_expr_len(&expr))
700 .unwrap_or(100000),
701 AttributeValue::EventTokens(closure) => closure
702 .as_expr()
703 .map(|expr| self.attr_expr_len(&expr))
704 .unwrap_or(100000),
705 }
706 }
707
708 fn attr_expr_len(&mut self, expr: &Expr) -> usize {
709 let out = self.retrieve_formatted_expr(expr);
710 if out.contains('\n') {
711 100000
712 } else {
713 out.len()
714 }
715 }
716
717 fn is_short_attrs(&mut self, attributes: &[Attribute], spreads: &[Spread]) -> usize {
718 let mut total = 0;
719
720 if attributes.len() > 3 {
722 return 100000;
723 }
724
725 for attr in attributes {
726 if self.current_span_is_primary(attr.span().start()) {
727 if let Some(lines) = self.src.get(..attr.span().start().line - 1) {
728 'line: for line in lines.iter().rev() {
729 match (line.trim().starts_with("//"), line.is_empty()) {
730 (true, _) => return 100000,
731 (_, true) => continue 'line,
732 _ => break 'line,
733 }
734 }
735 };
736 }
737
738 let name_len = match &attr.name {
739 AttributeName::BuiltIn(name) => {
740 let name = name.to_string();
741 name.len()
742 }
743 AttributeName::Custom(name) => name.value().len() + 2,
744 AttributeName::Spread(_) => unreachable!(),
745 };
746 total += name_len;
747
748 if attr.can_be_shorthand() {
749 total += 2;
750 } else {
751 total += self.attr_value_len(&attr.value);
752 }
753
754 total += 6;
755 }
756
757 for spread in spreads {
758 let expr_len = self.retrieve_formatted_expr(&spread.expr).len();
759 total += expr_len + 3;
760 }
761
762 total
763 }
764
765 fn write_todo_body(&mut self, brace: &Brace) -> std::fmt::Result {
766 let span = brace.span.span();
767 let start = span.start();
768 let end = span.end();
769
770 if start.line == end.line {
771 return Ok(());
772 }
773
774 writeln!(self.out)?;
775
776 for idx in start.line..end.line {
777 let Some(line) = self.src.get(idx) else {
778 continue;
779 };
780 if line.trim().starts_with("//") {
781 for _ in 0..self.out.indent_level + 1 {
782 write!(self.out, " ")?
783 }
784 writeln!(self.out, "{}", line.trim())?;
785 }
786 }
787
788 for _ in 0..self.out.indent_level {
789 write!(self.out, " ")?
790 }
791
792 Ok(())
793 }
794
795 fn write_partial_expr(&mut self, expr: syn::Result<Expr>, src_span: Span) -> Result {
796 let Ok(expr) = expr else {
797 self.invalid_exprs.push(src_span);
798 return Err(std::fmt::Error);
799 };
800
801 thread_local! {
802 static COMMENT_REGEX: Regex = Regex::new("\"[^\"]*\"|(//.*)").unwrap();
803 }
804
805 let pretty_expr = self.retrieve_formatted_expr(&expr).to_string();
806
807 let source_text = src_span.source_text().unwrap_or_default();
809 let mut source_lines = source_text.lines().peekable();
810 let mut output = String::from("");
811 let mut printed_empty_line = false;
812
813 if source_lines.peek().is_none() {
814 output = pretty_expr;
815 } else {
816 for line in pretty_expr.lines() {
817 let compacted_pretty_line = line.replace(" ", "").replace(",", "");
818 let trimmed_pretty_line = line.trim();
819
820 if trimmed_pretty_line.starts_with("//") {
823 continue;
824 }
825
826 if !output.is_empty() {
827 output.push('\n');
828 }
829
830 while let Some(src) = source_lines.peek() {
832 let trimmed_src = src.trim();
833
834 if trimmed_src.starts_with("//") || trimmed_src.is_empty() {
836 if !trimmed_src.is_empty() {
837 for s in line.chars().take_while(|c| c.is_whitespace()) {
839 output.push(s);
840 }
841
842 if matches!(trimmed_pretty_line.chars().next(), Some(')' | '}' | ']')) {
844 output.push_str(self.out.indent.indent_str());
845 }
846
847 printed_empty_line = false;
848 output.push_str(trimmed_src);
849 output.push('\n');
850 } else if !printed_empty_line {
851 output.push('\n');
852 printed_empty_line = true;
853 }
854
855 _ = source_lines.next();
856 continue;
857 }
858
859 let compacted_src_line = src.replace(" ", "").replace(",", "");
860
861 if compacted_src_line.contains(&compacted_pretty_line) {
863 break;
864 }
865
866 _ = source_lines.next();
868 }
869
870 output.push_str(line);
872 printed_empty_line = false;
873
874 let source_line = source_lines.next();
876
877 if let Some(source_line) = source_line {
879 if let Some(captures) = COMMENT_REGEX.with(|f| f.captures(source_line)) {
880 if let Some(comment) = captures.get(1) {
881 output.push_str(" // ");
882 output.push_str(comment.as_str().replace("//", "").trim());
883 }
884 }
885 }
886 }
887 }
888
889 self.write_mulitiline_tokens(output)?;
890
891 Ok(())
892 }
893
894 fn write_mulitiline_tokens(&mut self, out: String) -> Result {
895 let mut lines = out.split('\n').peekable();
896 let first = lines.next().unwrap();
897
898 if lines.peek().is_none() {
901 write!(self.out, "{first}")?;
902 } else {
903 writeln!(self.out, "{first}")?;
904
905 while let Some(line) = lines.next() {
906 if !line.trim().is_empty() {
907 self.out.tab()?;
908 }
909
910 write!(self.out, "{line}")?;
911 if lines.peek().is_none() {
912 write!(self.out, "")?;
913 } else {
914 writeln!(self.out)?;
915 }
916 }
917 }
918
919 Ok(())
920 }
921
922 fn write_spread_attribute(&mut self, attr: &Expr) -> Result {
923 let formatted = self.unparse_expr(attr);
924
925 let mut lines = formatted.lines();
926
927 let first_line = lines.next().unwrap();
928
929 write!(self.out, "..{first_line}")?;
930 for line in lines {
931 self.out.indented_tabbed_line()?;
932 write!(self.out, "{line}")?;
933 }
934
935 Ok(())
936 }
937
938 fn is_short_children(&mut self, children: &[BodyNode]) -> syn::Result<Option<usize>> {
944 if children.is_empty() {
945 return Ok(Some(0));
946 }
947
948 if self.children_have_comments(children) {
950 return Ok(None);
951 }
952
953 let res = match children {
954 [BodyNode::Text(ref text)] => Some(text.input.to_string_with_quotes().len()),
955
956 [BodyNode::RawExpr(ref expr)] => {
958 let pretty = self.retrieve_formatted_expr(&expr.expr.as_expr()?);
959 if pretty.contains('\n') {
960 None
961 } else {
962 Some(pretty.len() + 2)
963 }
964 }
965
966 [BodyNode::Component(ref comp)]
968 if comp.fields.is_empty()
970 && comp.children.is_empty()
971 && comp.spreads.is_empty() =>
972 {
973 Some(
974 comp.name
975 .segments
976 .iter()
977 .map(|s| s.ident.to_string().len() + 2)
978 .sum::<usize>(),
979 )
980 }
981
982 _ => None,
986 };
987
988 Ok(res)
989 }
990
991 fn children_have_comments(&self, children: &[BodyNode]) -> bool {
992 for child in children {
993 if self.current_span_is_primary(child.span().start()) {
994 'line: for line in self.src[..child.span().start().line - 1].iter().rev() {
995 match (line.trim().starts_with("//"), line.is_empty()) {
996 (true, _) => return true,
997 (_, true) => continue 'line,
998 _ => break 'line,
999 }
1000 }
1001 }
1002 }
1003
1004 false
1005 }
1006
1007 fn current_span_is_primary(&self, location: LineColumn) -> bool {
1010 self.leading_row_is_empty(LineColumn {
1011 line: location.line,
1012 column: location.column + 1,
1013 })
1014 }
1015
1016 fn leading_row_is_empty(&self, location: LineColumn) -> bool {
1017 let Some(line) = self.src.get(location.line - 1) else {
1018 return false;
1019 };
1020
1021 let Some(sub) = line.get(..location.column - 1) else {
1022 return false;
1023 };
1024
1025 sub.trim().is_empty()
1026 }
1027
1028 #[allow(clippy::map_entry)]
1029 fn retrieve_formatted_expr(&mut self, expr: &Expr) -> Cow<'_, str> {
1030 let loc = expr.span().start();
1031
1032 if loc.line == 1 && loc.column == 0 {
1034 return self.unparse_expr(expr).into();
1035 }
1036
1037 if !self.cached_formats.contains_key(&loc) {
1038 let formatted = self.unparse_expr(expr);
1039 self.cached_formats.insert(loc, formatted);
1040 }
1041
1042 self.cached_formats
1043 .get(&loc)
1044 .expect("Just inserted the parsed expr, so it should be in the cache")
1045 .as_str()
1046 .into()
1047 }
1048
1049 fn final_span_of_node(node: &BodyNode) -> Span {
1050 match node {
1052 BodyNode::Element(el) => el
1053 .brace
1054 .as_ref()
1055 .map(|b| b.span.span())
1056 .unwrap_or_else(|| el.name.span()),
1057 BodyNode::Component(el) => el
1058 .brace
1059 .as_ref()
1060 .map(|b| b.span.span())
1061 .unwrap_or_else(|| el.name.span()),
1062 BodyNode::Text(txt) => txt.input.span(),
1063 BodyNode::RawExpr(exp) => exp.span(),
1064 BodyNode::ForLoop(f) => f.brace.span.span(),
1065 BodyNode::IfChain(i) => match i.else_brace {
1066 Some(b) => b.span.span(),
1067 None => i.then_brace.span.span(),
1068 },
1069 }
1070 }
1071
1072 fn final_span_of_attr(&self, attr: &Attribute) -> Span {
1073 match &attr.value {
1074 AttributeValue::Shorthand(s) => s.span(),
1075 AttributeValue::AttrLiteral(l) => l.span(),
1076 AttributeValue::EventTokens(closure) => closure.body.span(),
1077 AttributeValue::AttrExpr(exp) => exp.span(),
1078 AttributeValue::IfExpr(ex) => ex
1079 .else_value
1080 .as_ref()
1081 .map(|v| v.span())
1082 .unwrap_or_else(|| ex.then_value.span()),
1083 }
1084 }
1085
1086 fn has_trailing_comments(&self, children: &[BodyNode], brace: &Brace) -> bool {
1087 let brace_span = brace.span.span();
1088
1089 let Some(last_node) = children.last() else {
1090 return false;
1091 };
1092
1093 let final_span = Self::final_span_of_node(last_node);
1095 let final_span = final_span.end();
1096 let mut line = final_span.line;
1097 let mut column = final_span.column;
1098 loop {
1099 let Some(src_line) = self.src.get(line - 1) else {
1100 return false;
1101 };
1102
1103 let Some(mut whitespace) = src_line.get(column..).map(|s| s.trim()) else {
1105 return false;
1106 };
1107
1108 let offset = 0;
1109 whitespace = whitespace[offset..].trim();
1110
1111 if whitespace.starts_with("//") {
1112 return true;
1113 }
1114
1115 if line == brace_span.end().line {
1116 break;
1118 }
1119
1120 line += 1;
1121 column = 0; }
1123
1124 false
1125 }
1126}