1use alloc::string::String;
12
13use crate::ast::{
14 Attribute, AttributeContent, Comment, CommentKind, Document, Expr, StructBody, Trivia,
15};
16
17#[derive(Clone, Debug)]
19pub struct FormatConfig {
20 pub indent: String,
22
23 pub spacing: Spacing,
25
26 pub comments: CommentMode,
28
29 pub compaction: Compaction,
31}
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
35pub enum Spacing {
36 None,
38 #[default]
40 Normal,
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
45pub enum CommentMode {
46 Delete,
48 Preserve,
50 #[default]
54 Auto,
55}
56
57#[derive(Clone, Debug)]
68pub struct Compaction {
69 pub char_limit: usize,
73
74 pub compact_from_depth: Option<usize>,
80
81 pub compact_types: CompactTypes,
84}
85
86#[derive(Clone, Debug, Default)]
88#[allow(clippy::struct_excessive_bools)]
89pub struct CompactTypes {
90 pub tuples: bool,
92 pub arrays: bool,
94 pub maps: bool,
96 pub structs: bool,
98}
99
100impl Default for FormatConfig {
101 fn default() -> Self {
102 Self {
103 indent: String::from(" "),
104 spacing: Spacing::Normal,
105 comments: CommentMode::Auto,
106 compaction: Compaction::default(),
107 }
108 }
109}
110
111impl Default for Compaction {
112 fn default() -> Self {
113 Self {
114 char_limit: 20,
115 compact_from_depth: None,
116 compact_types: CompactTypes::default(),
117 }
118 }
119}
120
121impl CompactTypes {
122 #[must_use]
124 pub fn all() -> Self {
125 Self {
126 tuples: true,
127 arrays: true,
128 maps: true,
129 structs: true,
130 }
131 }
132
133 #[must_use]
135 pub fn none() -> Self {
136 Self::default()
137 }
138}
139
140impl FormatConfig {
141 #[must_use]
145 pub fn new() -> Self {
146 Self::default()
147 }
148
149 #[must_use]
154 pub fn minimal() -> Self {
155 Self {
156 indent: String::new(),
157 spacing: Spacing::None,
158 comments: CommentMode::Delete,
159 compaction: Compaction {
160 char_limit: usize::MAX,
161 compact_from_depth: Some(0),
162 compact_types: CompactTypes::default(),
163 },
164 }
165 }
166
167 #[must_use]
169 pub fn indent(mut self, indent: impl Into<String>) -> Self {
170 self.indent = indent.into();
171 self
172 }
173
174 #[must_use]
176 pub fn spacing(mut self, spacing: Spacing) -> Self {
177 self.spacing = spacing;
178 self
179 }
180
181 #[must_use]
183 pub fn comments(mut self, comments: CommentMode) -> Self {
184 self.comments = comments;
185 self
186 }
187
188 #[must_use]
191 pub fn char_limit(mut self, char_limit: usize) -> Self {
192 self.compaction.char_limit = char_limit;
193 self
194 }
195
196 #[must_use]
198 pub fn compact_from_depth(mut self, depth: usize) -> Self {
199 self.compaction.compact_from_depth = Some(depth);
200 self
201 }
202
203 #[must_use]
205 pub fn compact_types(mut self, types: CompactTypes) -> Self {
206 self.compaction.compact_types = types;
207 self
208 }
209}
210
211#[derive(Default, Clone, Copy)]
224pub struct ItemTrivia<'a> {
225 pub leading: Option<&'a Trivia<'a>>,
227 pub pre_colon: Option<&'a Trivia<'a>>,
229 pub post_colon: Option<&'a Trivia<'a>>,
231 pub trailing: Option<&'a Trivia<'a>>,
233}
234
235impl<'a> ItemTrivia<'a> {
236 #[inline]
238 #[must_use]
239 pub const fn empty() -> Self {
240 Self {
241 leading: None,
242 pre_colon: None,
243 post_colon: None,
244 trailing: None,
245 }
246 }
247
248 #[inline]
250 #[must_use]
251 pub const fn seq(leading: &'a Trivia<'a>, trailing: &'a Trivia<'a>) -> Self {
252 Self {
253 leading: Some(leading),
254 pre_colon: None,
255 post_colon: None,
256 trailing: Some(trailing),
257 }
258 }
259
260 #[inline]
262 #[must_use]
263 pub const fn kv(
264 leading: &'a Trivia<'a>,
265 pre_colon: &'a Trivia<'a>,
266 post_colon: &'a Trivia<'a>,
267 trailing: &'a Trivia<'a>,
268 ) -> Self {
269 Self {
270 leading: Some(leading),
271 pre_colon: Some(pre_colon),
272 post_colon: Some(post_colon),
273 trailing: Some(trailing),
274 }
275 }
276
277 #[inline]
279 #[must_use]
280 pub fn has_line_comment(&self) -> bool {
281 self.leading.is_some_and(has_line_comment)
283 || self.pre_colon.is_some_and(has_line_comment)
284 || self.post_colon.is_some_and(has_line_comment)
285 || self.trailing.is_some_and(has_line_comment)
286 }
287}
288
289pub trait SerializeRon {
299 fn serialize(&self, fmt: &mut RonFormatter<'_>);
301}
302
303impl SerializeRon for Expr<'_> {
304 fn serialize(&self, fmt: &mut RonFormatter<'_>) {
305 match self {
306 Expr::Unit(_) => fmt.write_str("()"),
308 Expr::Bool(b) => fmt.write_str(if b.value { "true" } else { "false" }),
309 Expr::Char(c) => fmt.write_str(&c.raw),
310 Expr::Byte(b) => fmt.write_str(&b.raw),
311 Expr::Number(n) => fmt.write_str(&n.raw),
312 Expr::String(s) => fmt.write_str(&s.raw),
313 Expr::Bytes(b) => fmt.write_str(&b.raw),
314
315 Expr::Option(opt) => {
317 let value = opt
318 .value
319 .as_ref()
320 .map(|v| (&v.expr, ItemTrivia::seq(&v.leading, &v.trailing)));
321 fmt.format_option_with(value);
322 }
323
324 Expr::Seq(seq) => fmt.format_seq_items(seq),
326
327 Expr::Map(map) => fmt.format_map_items(map),
329
330 Expr::Tuple(tuple) => fmt.format_tuple_items(tuple),
332
333 Expr::AnonStruct(s) => fmt.format_anon_struct_items(s),
335
336 Expr::Struct(s) => {
338 fmt.write_str(&s.name.name);
339 if let Some(ref body) = s.body {
340 match body {
341 StructBody::Tuple(tuple) => fmt.format_tuple_body(tuple),
342 StructBody::Fields(fields) => fmt.format_fields_body(fields),
343 }
344 }
345 }
346
347 Expr::Error(_) => fmt.write_str("/* parse error */"),
349 }
350 }
351}
352
353#[must_use]
377pub fn format_document(doc: &Document<'_>, config: &FormatConfig) -> String {
378 let mut formatter = RonFormatter::new(config);
379 formatter.format_document(doc);
380 formatter.output
381}
382
383#[must_use]
401pub fn format_expr(expr: &Expr<'_>, config: &FormatConfig) -> String {
402 let mut formatter = RonFormatter::new(config);
403 formatter.is_root = !config.indent.is_empty();
406 expr.serialize(&mut formatter);
407 formatter.output
408}
409
410#[must_use]
426pub fn to_ron_string<T: SerializeRon>(value: &T) -> String {
427 to_ron_string_with(value, &FormatConfig::default())
428}
429
430#[must_use]
443pub fn to_ron_string_with<T: SerializeRon>(value: &T, config: &FormatConfig) -> String {
444 let mut formatter = RonFormatter::new(config);
445 formatter.is_root = !config.indent.is_empty();
447 value.serialize(&mut formatter);
448 formatter.output
449}
450
451#[derive(Clone, Copy, Debug, PartialEq, Eq)]
453enum CollectionType {
454 Tuple,
455 Array,
456 Map,
457 Struct,
458}
459
460pub struct RonFormatter<'a> {
462 config: &'a FormatConfig,
463 output: String,
464 indent_level: usize,
465 depth: usize,
467 is_root: bool,
469 is_compact: bool,
471}
472
473impl<'a> RonFormatter<'a> {
474 fn new(config: &'a FormatConfig) -> Self {
475 Self {
476 config,
477 output: String::new(),
478 indent_level: 0,
479 depth: 0,
480 is_root: true,
481 is_compact: false,
482 }
483 }
484
485 fn char_limit(&self) -> usize {
487 self.config.compaction.char_limit
488 }
489
490 fn wants_compact(&self, collection_type: CollectionType, depth: usize) -> bool {
495 let compaction = &self.config.compaction;
496
497 if let Some(threshold) = compaction.compact_from_depth
499 && depth >= threshold
500 {
501 return true;
502 }
503
504 match collection_type {
506 CollectionType::Tuple => compaction.compact_types.tuples,
507 CollectionType::Array => compaction.compact_types.arrays,
508 CollectionType::Map => compaction.compact_types.maps,
509 CollectionType::Struct => compaction.compact_types.structs,
510 }
511 }
512
513 fn is_pretty(&self) -> bool {
515 !self.config.indent.is_empty()
516 }
517
518 fn format_document(&mut self, doc: &Document<'_>) {
519 let is_pretty = self.is_pretty();
520
521 self.format_leading_comments(&doc.leading);
523
524 for attr in &doc.attributes {
526 self.format_attribute(attr);
527 }
528
529 if is_pretty && !doc.attributes.is_empty() && doc.value.is_some() {
531 self.output.push('\n');
532 }
533
534 self.format_leading_comments(&doc.pre_value);
536
537 if let Some(ref value) = doc.value {
539 value.serialize(self);
540 }
541
542 self.format_leading_comments(&doc.trailing);
544
545 if is_pretty && !self.output.is_empty() && !self.output.ends_with('\n') {
547 self.output.push('\n');
548 }
549 }
550
551 fn format_attribute(&mut self, attr: &Attribute<'_>) {
552 self.format_leading_comments(&attr.leading);
554
555 self.output.push_str("#![");
556 self.output.push_str(&attr.name);
557
558 match &attr.content {
559 AttributeContent::None => {}
560 AttributeContent::Value(v) => {
561 self.output.push_str(" = ");
562 self.output.push_str(v);
563 }
564 AttributeContent::Args(args) => {
565 self.output.push('(');
566 for (i, arg) in args.iter().enumerate() {
567 if i > 0 {
568 self.output.push_str(", ");
569 }
570 self.output.push_str(arg);
571 }
572 self.output.push(')');
573 }
574 }
575
576 self.output.push_str("]\n");
577 }
578
579 pub fn format_seq_items(&mut self, seq: &crate::ast::SeqExpr<'_>) {
585 let has_line_comments = seq
586 .items
587 .iter()
588 .any(|item| has_line_comment(&item.leading) || has_line_comment(&item.trailing))
589 || has_line_comment(&seq.leading)
590 || has_line_comment(&seq.trailing);
591
592 self.format_collection_generic(
593 CollectionType::Array,
594 '[',
595 ']',
596 &seq.leading,
597 &seq.trailing,
598 &seq.items,
599 has_line_comments,
600 |fmt, item| {
601 fmt.format_leading_comments(&item.leading);
602 fmt.write_indent();
603 item.expr.serialize(fmt);
604 fmt.format_trailing_inline_comment(&item.trailing);
605 },
606 );
607 }
608
609 pub fn format_tuple_items(&mut self, tuple: &crate::ast::TupleExpr<'_>) {
611 let has_line_comments = tuple
612 .elements
613 .iter()
614 .any(|e| has_line_comment(&e.leading) || has_line_comment(&e.trailing))
615 || has_line_comment(&tuple.leading)
616 || has_line_comment(&tuple.trailing);
617
618 self.format_collection_generic(
619 CollectionType::Tuple,
620 '(',
621 ')',
622 &tuple.leading,
623 &tuple.trailing,
624 &tuple.elements,
625 has_line_comments,
626 |fmt, elem| {
627 fmt.format_leading_comments(&elem.leading);
628 fmt.write_indent();
629 elem.expr.serialize(fmt);
630 fmt.format_trailing_inline_comment(&elem.trailing);
631 },
632 );
633 }
634
635 pub fn format_tuple_body(&mut self, tuple: &crate::ast::TupleBody<'_>) {
637 let has_line_comments = tuple
638 .elements
639 .iter()
640 .any(|e| has_line_comment(&e.leading) || has_line_comment(&e.trailing))
641 || has_line_comment(&tuple.leading)
642 || has_line_comment(&tuple.trailing);
643
644 self.format_collection_generic(
645 CollectionType::Tuple,
646 '(',
647 ')',
648 &tuple.leading,
649 &tuple.trailing,
650 &tuple.elements,
651 has_line_comments,
652 |fmt, elem| {
653 fmt.format_leading_comments(&elem.leading);
654 fmt.write_indent();
655 elem.expr.serialize(fmt);
656 fmt.format_trailing_inline_comment(&elem.trailing);
657 },
658 );
659 }
660
661 pub fn format_map_items(&mut self, map: &crate::ast::MapExpr<'_>) {
663 let has_line_comments = map.entries.iter().any(|e| {
664 has_line_comment(&e.leading)
665 || has_line_comment(&e.pre_colon)
666 || has_line_comment(&e.post_colon)
667 || has_line_comment(&e.trailing)
668 }) || has_line_comment(&map.leading)
669 || has_line_comment(&map.trailing);
670
671 self.format_collection_generic(
672 CollectionType::Map,
673 '{',
674 '}',
675 &map.leading,
676 &map.trailing,
677 &map.entries,
678 has_line_comments,
679 |fmt, entry| {
680 fmt.format_leading_comments(&entry.leading);
681 fmt.write_indent();
682 entry.key.serialize(fmt);
683 fmt.format_trailing_inline_comment(&entry.pre_colon);
684 fmt.write_colon();
685 fmt.format_leading_comments_inline(&entry.post_colon);
686 entry.value.serialize(fmt);
687 fmt.format_trailing_inline_comment(&entry.trailing);
688 },
689 );
690 }
691
692 pub fn format_anon_struct_items(&mut self, s: &crate::ast::AnonStructExpr<'_>) {
694 let has_line_comments = s.fields.iter().any(|f| {
695 has_line_comment(&f.leading)
696 || has_line_comment(&f.pre_colon)
697 || has_line_comment(&f.post_colon)
698 || has_line_comment(&f.trailing)
699 }) || has_line_comment(&s.leading)
700 || has_line_comment(&s.trailing);
701
702 self.format_collection_generic(
703 CollectionType::Struct,
704 '(',
705 ')',
706 &s.leading,
707 &s.trailing,
708 &s.fields,
709 has_line_comments,
710 |fmt, field| {
711 fmt.format_leading_comments(&field.leading);
712 fmt.write_indent();
713 fmt.write_str(&field.name.name);
714 fmt.format_trailing_inline_comment(&field.pre_colon);
715 fmt.write_colon();
716 fmt.format_leading_comments_inline(&field.post_colon);
717 field.value.serialize(fmt);
718 fmt.format_trailing_inline_comment(&field.trailing);
719 },
720 );
721 }
722
723 pub fn format_fields_body(&mut self, fields: &crate::ast::FieldsBody<'_>) {
725 let has_line_comments = fields.fields.iter().any(|f| {
726 has_line_comment(&f.leading)
727 || has_line_comment(&f.pre_colon)
728 || has_line_comment(&f.post_colon)
729 || has_line_comment(&f.trailing)
730 }) || has_line_comment(&fields.leading)
731 || has_line_comment(&fields.trailing);
732
733 self.format_collection_generic(
734 CollectionType::Struct,
735 '(',
736 ')',
737 &fields.leading,
738 &fields.trailing,
739 &fields.fields,
740 has_line_comments,
741 |fmt, field| {
742 fmt.format_leading_comments(&field.leading);
743 fmt.write_indent();
744 fmt.write_str(&field.name.name);
745 fmt.format_trailing_inline_comment(&field.pre_colon);
746 fmt.write_colon();
747 fmt.format_leading_comments_inline(&field.post_colon);
748 field.value.serialize(fmt);
749 fmt.format_trailing_inline_comment(&field.trailing);
750 },
751 );
752 }
753
754 pub fn format_seq_slice<'t, T>(
760 &mut self,
761 leading: &Trivia<'_>,
762 trailing: &Trivia<'_>,
763 items: &[(ItemTrivia<'t>, &'t T)],
764 ) where
765 T: SerializeRon + 't,
766 {
767 let has_line_comments = items.iter().any(|(trivia, _)| trivia.has_line_comment())
768 || has_line_comment(leading)
769 || has_line_comment(trailing);
770
771 self.format_collection_generic(
772 CollectionType::Array,
773 '[',
774 ']',
775 leading,
776 trailing,
777 items,
778 has_line_comments,
779 |fmt, &(trivia, item)| {
780 fmt.format_leading_comments_opt(trivia.leading);
781 fmt.write_indent();
782 item.serialize(fmt);
783 fmt.format_trailing_inline_comment_opt(trivia.trailing);
784 },
785 );
786 }
787
788 pub fn format_seq_with<'t, T, I>(
790 &mut self,
791 leading: Option<&Trivia<'_>>,
792 trailing: Option<&Trivia<'_>>,
793 items: I,
794 ) where
795 T: SerializeRon + 't,
796 I: IntoIterator<Item = (ItemTrivia<'t>, &'t T)>,
797 {
798 let empty_trivia = Trivia::empty();
799 let leading_ref = leading.unwrap_or(&empty_trivia);
800 let trailing_ref = trailing.unwrap_or(&empty_trivia);
801 let items_vec: Vec<_> = items.into_iter().collect();
802 self.format_seq_slice(leading_ref, trailing_ref, &items_vec);
803 }
804
805 pub fn format_tuple_slice<'t, T>(
807 &mut self,
808 leading: &Trivia<'_>,
809 trailing: &Trivia<'_>,
810 items: &[(ItemTrivia<'t>, &'t T)],
811 ) where
812 T: SerializeRon + 't,
813 {
814 let has_line_comments = items.iter().any(|(trivia, _)| trivia.has_line_comment())
815 || has_line_comment(leading)
816 || has_line_comment(trailing);
817
818 self.format_collection_generic(
819 CollectionType::Tuple,
820 '(',
821 ')',
822 leading,
823 trailing,
824 items,
825 has_line_comments,
826 |fmt, &(trivia, item)| {
827 fmt.format_leading_comments_opt(trivia.leading);
828 fmt.write_indent();
829 item.serialize(fmt);
830 fmt.format_trailing_inline_comment_opt(trivia.trailing);
831 },
832 );
833 }
834
835 pub fn format_tuple_with<'t, T, I>(
837 &mut self,
838 leading: Option<&Trivia<'_>>,
839 trailing: Option<&Trivia<'_>>,
840 items: I,
841 ) where
842 T: SerializeRon + 't,
843 I: IntoIterator<Item = (ItemTrivia<'t>, &'t T)>,
844 {
845 let empty_trivia = Trivia::empty();
846 let leading_ref = leading.unwrap_or(&empty_trivia);
847 let trailing_ref = trailing.unwrap_or(&empty_trivia);
848 let items_vec: Vec<_> = items.into_iter().collect();
849 self.format_tuple_slice(leading_ref, trailing_ref, &items_vec);
850 }
851
852 pub fn format_map_slice<'t, K, V>(
854 &mut self,
855 leading: &Trivia<'_>,
856 trailing: &Trivia<'_>,
857 entries: &[(ItemTrivia<'t>, &'t K, &'t V)],
858 ) where
859 K: SerializeRon + 't,
860 V: SerializeRon + 't,
861 {
862 let has_line_comments = entries
863 .iter()
864 .any(|(trivia, _, _)| trivia.has_line_comment())
865 || has_line_comment(leading)
866 || has_line_comment(trailing);
867
868 self.format_collection_generic(
869 CollectionType::Map,
870 '{',
871 '}',
872 leading,
873 trailing,
874 entries,
875 has_line_comments,
876 |fmt, &(trivia, key, value)| {
877 fmt.format_leading_comments_opt(trivia.leading);
878 fmt.write_indent();
879 key.serialize(fmt);
880 fmt.format_trailing_inline_comment_opt(trivia.pre_colon);
881 fmt.write_colon();
882 fmt.format_leading_comments_inline_opt(trivia.post_colon);
883 value.serialize(fmt);
884 fmt.format_trailing_inline_comment_opt(trivia.trailing);
885 },
886 );
887 }
888
889 pub fn format_map_with<'t, K, V, I>(
891 &mut self,
892 leading: Option<&Trivia<'_>>,
893 trailing: Option<&Trivia<'_>>,
894 entries: I,
895 ) where
896 K: SerializeRon + 't,
897 V: SerializeRon + 't,
898 I: IntoIterator<Item = (ItemTrivia<'t>, &'t K, &'t V)>,
899 {
900 let empty_trivia = Trivia::empty();
901 let leading_ref = leading.unwrap_or(&empty_trivia);
902 let trailing_ref = trailing.unwrap_or(&empty_trivia);
903 let entries_vec: Vec<_> = entries.into_iter().collect();
904 self.format_map_slice(leading_ref, trailing_ref, &entries_vec);
905 }
906
907 pub fn format_anon_struct_slice<'t, V>(
909 &mut self,
910 leading: &Trivia<'_>,
911 trailing: &Trivia<'_>,
912 fields: &[(ItemTrivia<'t>, &'t str, &'t V)],
913 ) where
914 V: SerializeRon + 't,
915 {
916 let has_line_comments = fields
917 .iter()
918 .any(|(trivia, _, _)| trivia.has_line_comment())
919 || has_line_comment(leading)
920 || has_line_comment(trailing);
921
922 self.format_collection_generic(
923 CollectionType::Struct,
924 '(',
925 ')',
926 leading,
927 trailing,
928 fields,
929 has_line_comments,
930 |fmt, &(trivia, name, value)| {
931 fmt.format_leading_comments_opt(trivia.leading);
932 fmt.write_indent();
933 fmt.write_str(name);
934 fmt.format_trailing_inline_comment_opt(trivia.pre_colon);
935 fmt.write_colon();
936 fmt.format_leading_comments_inline_opt(trivia.post_colon);
937 value.serialize(fmt);
938 fmt.format_trailing_inline_comment_opt(trivia.trailing);
939 },
940 );
941 }
942
943 pub fn format_anon_struct_with<'t, V, I>(
945 &mut self,
946 leading: Option<&Trivia<'_>>,
947 trailing: Option<&Trivia<'_>>,
948 fields: I,
949 ) where
950 V: SerializeRon + 't,
951 I: IntoIterator<Item = (ItemTrivia<'t>, &'t str, &'t V)>,
952 {
953 let empty_trivia = Trivia::empty();
954 let leading_ref = leading.unwrap_or(&empty_trivia);
955 let trailing_ref = trailing.unwrap_or(&empty_trivia);
956 let fields_vec: Vec<_> = fields.into_iter().collect();
957 self.format_anon_struct_slice(leading_ref, trailing_ref, &fields_vec);
958 }
959
960 pub fn format_struct_fields_with<'t, V, I>(
962 &mut self,
963 leading: Option<&Trivia<'_>>,
964 trailing: Option<&Trivia<'_>>,
965 fields: I,
966 ) where
967 V: SerializeRon + 't,
968 I: IntoIterator<Item = (ItemTrivia<'t>, &'t str, &'t V)>,
969 I::IntoIter: Clone,
970 {
971 self.format_anon_struct_with(leading, trailing, fields);
973 }
974
975 pub fn format_option_with<T: SerializeRon>(&mut self, value: Option<(&T, ItemTrivia<'_>)>) {
977 match value {
978 Some((inner, trivia)) => {
979 self.write_str("Some(");
980 self.format_leading_comments_opt(trivia.leading);
981 inner.serialize(self);
982 self.format_trailing_inline_comment_opt(trivia.trailing);
983 self.write_char(')');
984 }
985 None => self.write_str("None"),
986 }
987 }
988
989 #[allow(clippy::too_many_arguments)]
991 fn format_collection_generic<T, F>(
992 &mut self,
993 collection_type: CollectionType,
994 open: char,
995 close: char,
996 leading: &Trivia<'_>,
997 trailing: &Trivia<'_>,
998 items: &[T],
999 has_line_comments: bool,
1000 format_item: F,
1001 ) where
1002 F: Fn(&mut Self, &T),
1003 {
1004 let current_depth = self.depth;
1006 self.depth += 1;
1007
1008 let is_root = self.is_root;
1009 if is_root {
1010 self.is_root = false;
1011 }
1012
1013 let wants_compact = self.wants_compact(collection_type, current_depth);
1015
1016 let can_compact = match self.config.comments {
1018 CommentMode::Delete => true,
1019 CommentMode::Preserve => !has_line_comments,
1020 CommentMode::Auto => {
1021 if wants_compact {
1022 true
1023 } else {
1024 !has_line_comments
1025 }
1026 }
1027 };
1028
1029 if wants_compact && can_compact {
1031 let compact = self.try_format_compact_generic(
1032 open,
1033 close,
1034 leading,
1035 trailing,
1036 items,
1037 &format_item,
1038 );
1039 self.output.push_str(&compact);
1040 self.depth = current_depth;
1041 return;
1042 }
1043
1044 if is_root && !items.is_empty() {
1046 self.format_multiline_generic(open, close, leading, trailing, items, format_item);
1047 self.depth = current_depth;
1048 return;
1049 }
1050
1051 if !can_compact {
1053 self.format_multiline_generic(open, close, leading, trailing, items, format_item);
1054 self.depth = current_depth;
1055 return;
1056 }
1057
1058 let compact =
1060 self.try_format_compact_generic(open, close, leading, trailing, items, &format_item);
1061 if compact.len() <= self.char_limit() {
1062 self.output.push_str(&compact);
1063 } else {
1064 self.format_multiline_generic(open, close, leading, trailing, items, format_item);
1065 }
1066 self.depth = current_depth;
1067 }
1068
1069 fn try_format_compact_generic<T, F>(
1070 &self,
1071 open: char,
1072 close: char,
1073 leading: &Trivia<'_>,
1074 trailing: &Trivia<'_>,
1075 items: &[T],
1076 format_item: F,
1077 ) -> String
1078 where
1079 F: Fn(&mut Self, &T),
1080 {
1081 let mut compact_formatter = RonFormatter::new(self.config);
1082 compact_formatter.is_root = false;
1083 compact_formatter.is_compact = true;
1084 compact_formatter.depth = self.depth;
1085 compact_formatter.output.push(open);
1086
1087 compact_formatter.format_trivia_compact(leading);
1089
1090 for (i, item) in items.iter().enumerate() {
1091 if i > 0 {
1092 compact_formatter.write_separator();
1093 }
1094 format_item(&mut compact_formatter, item);
1095 }
1096
1097 compact_formatter.format_trivia_compact(trailing);
1099
1100 compact_formatter.output.push(close);
1101 compact_formatter.output
1102 }
1103
1104 fn format_multiline_generic<T, F>(
1105 &mut self,
1106 open: char,
1107 close: char,
1108 leading: &Trivia<'_>,
1109 trailing: &Trivia<'_>,
1110 items: &[T],
1111 format_item: F,
1112 ) where
1113 F: Fn(&mut Self, &T),
1114 {
1115 self.output.push(open);
1116
1117 if items.is_empty() {
1118 self.format_leading_comments(leading);
1120 self.format_trailing_inline_comment(trailing);
1121 self.output.push(close);
1122 return;
1123 }
1124
1125 self.output.push('\n');
1126 self.indent_level += 1;
1127
1128 self.format_leading_comments(leading);
1130
1131 for (i, item) in items.iter().enumerate() {
1132 format_item(self, item);
1133 self.output.push(',');
1134 if i < items.len() - 1 {
1135 self.output.push('\n');
1136 }
1137 }
1138
1139 if has_line_comment(trailing) {
1141 self.output.push('\n');
1142 self.format_leading_comments(trailing);
1143 }
1144
1145 self.output.push('\n');
1146 self.indent_level -= 1;
1147 self.write_indent();
1148 self.output.push(close);
1149 }
1150
1151 fn format_leading_comments_opt(&mut self, trivia: Option<&Trivia<'_>>) {
1156 if let Some(t) = trivia {
1157 self.format_leading_comments(t);
1158 }
1159 }
1160
1161 fn format_trailing_inline_comment_opt(&mut self, trivia: Option<&Trivia<'_>>) {
1162 if let Some(t) = trivia {
1163 self.format_trailing_inline_comment(t);
1164 }
1165 }
1166
1167 fn format_leading_comments_inline_opt(&mut self, trivia: Option<&Trivia<'_>>) {
1168 if let Some(t) = trivia {
1169 self.format_leading_comments_inline(t);
1170 }
1171 }
1172
1173 #[inline]
1179 pub fn write_str(&mut self, s: &str) {
1180 self.output.push_str(s);
1181 }
1182
1183 #[inline]
1185 pub fn write_char(&mut self, c: char) {
1186 self.output.push(c);
1187 }
1188
1189 #[inline]
1191 pub fn write_fmt(&mut self, args: core::fmt::Arguments<'_>) {
1192 use core::fmt::Write;
1193 let _ = self.output.write_fmt(args);
1194 }
1195
1196 pub fn format_char_value(&mut self, c: char) {
1202 match c {
1203 '\'' => self.output.push_str("'\\''"),
1204 '\\' => self.output.push_str("'\\\\'"),
1205 '\n' => self.output.push_str("'\\n'"),
1206 '\r' => self.output.push_str("'\\r'"),
1207 '\t' => self.output.push_str("'\\t'"),
1208 '\0' => self.output.push_str("'\\0'"),
1209 c if c.is_ascii_control() => {
1210 use core::fmt::Write;
1211 let _ = write!(self.output, "'\\x{:02x}'", c as u8);
1212 }
1213 c => {
1214 self.output.push('\'');
1215 self.output.push(c);
1216 self.output.push('\'');
1217 }
1218 }
1219 }
1220
1221 pub fn format_string_value(&mut self, s: &str) {
1223 self.output.push('"');
1224 for c in s.chars() {
1225 match c {
1226 '"' => self.output.push_str("\\\""),
1227 '\\' => self.output.push_str("\\\\"),
1228 '\n' => self.output.push_str("\\n"),
1229 '\r' => self.output.push_str("\\r"),
1230 '\t' => self.output.push_str("\\t"),
1231 '\0' => self.output.push_str("\\0"),
1232 c if c.is_ascii_control() => {
1233 use core::fmt::Write;
1234 let _ = write!(self.output, "\\x{:02x}", c as u8);
1235 }
1236 c => self.output.push(c),
1237 }
1238 }
1239 self.output.push('"');
1240 }
1241
1242 pub fn format_bytes_value(&mut self, bytes: &[u8]) {
1244 self.output.push_str("b\"");
1245 for &b in bytes {
1246 match b {
1247 b'"' => self.output.push_str("\\\""),
1248 b'\\' => self.output.push_str("\\\\"),
1249 b'\n' => self.output.push_str("\\n"),
1250 b'\r' => self.output.push_str("\\r"),
1251 b'\t' => self.output.push_str("\\t"),
1252 0 => self.output.push_str("\\0"),
1253 b if b.is_ascii_graphic() || b == b' ' => self.output.push(b as char),
1254 b => {
1255 use core::fmt::Write;
1256 let _ = write!(self.output, "\\x{b:02x}");
1257 }
1258 }
1259 }
1260 self.output.push('"');
1261 }
1262
1263 fn write_indent(&mut self) {
1264 if self.is_compact || self.config.indent.is_empty() {
1266 return;
1267 }
1268 for _ in 0..self.indent_level {
1269 self.output.push_str(&self.config.indent);
1270 }
1271 }
1272
1273 fn write_colon(&mut self) {
1274 match self.config.spacing {
1275 Spacing::None => self.output.push(':'),
1276 Spacing::Normal => self.output.push_str(": "),
1277 }
1278 }
1279
1280 fn write_separator(&mut self) {
1281 match self.config.spacing {
1282 Spacing::None => self.output.push(','),
1283 Spacing::Normal => self.output.push_str(", "),
1284 }
1285 }
1286
1287 fn format_leading_comments(&mut self, trivia: &Trivia<'_>) {
1289 if self.config.comments == CommentMode::Delete {
1291 return;
1292 }
1293 if trivia.comments.is_empty() {
1294 return;
1295 }
1296 if self.is_compact {
1298 self.format_trivia_compact(trivia);
1299 return;
1300 }
1301 for comment in &trivia.comments {
1302 self.format_comment(comment);
1303 }
1304 }
1305
1306 fn format_leading_comments_inline(&mut self, trivia: &Trivia<'_>) {
1308 if self.config.comments == CommentMode::Delete {
1310 return;
1311 }
1312 if trivia.comments.is_empty() {
1313 return;
1314 }
1315 if self.is_compact {
1317 self.format_trivia_compact(trivia);
1318 return;
1319 }
1320 for comment in &trivia.comments {
1321 match comment.kind {
1322 CommentKind::Block => {
1323 self.output.push(' ');
1324 self.output.push_str(&comment.text);
1325 self.output.push(' ');
1326 }
1327 CommentKind::Line => {
1328 if self.is_pretty() {
1331 self.output.push_str(" ");
1332 self.output.push_str(&comment.text);
1333 }
1334 }
1335 }
1336 }
1337 }
1338
1339 fn format_trailing_inline_comment(&mut self, trivia: &Trivia<'_>) {
1341 if self.config.comments == CommentMode::Delete {
1343 return;
1344 }
1345 if trivia.comments.is_empty() {
1346 return;
1347 }
1348 if self.is_compact {
1350 self.format_trivia_compact(trivia);
1351 return;
1352 }
1353 for comment in &trivia.comments {
1354 match comment.kind {
1355 CommentKind::Line => {
1356 if self.is_pretty() {
1358 self.output.push_str(" ");
1359 self.output.push_str(&comment.text);
1360 }
1361 }
1362 CommentKind::Block => {
1363 self.output.push(' ');
1364 self.output.push_str(&comment.text);
1365 }
1366 }
1367 }
1368 }
1369
1370 fn format_comment(&mut self, comment: &Comment<'_>) {
1371 match comment.kind {
1373 CommentKind::Line => {
1374 self.write_indent();
1375 self.output.push_str(&comment.text);
1376 if !comment.text.ends_with('\n') {
1377 self.output.push('\n');
1378 }
1379 }
1380 CommentKind::Block => {
1381 self.write_indent();
1382 self.output.push_str(&comment.text);
1383 self.output.push('\n');
1384 }
1385 }
1386 }
1387
1388 fn format_comment_compact(&mut self, comment: &Comment<'_>) {
1392 match comment.kind {
1393 CommentKind::Line => {
1394 let text = comment
1396 .text
1397 .trim_start_matches("//")
1398 .trim_end_matches('\n')
1399 .trim();
1400 self.output.push_str("/* ");
1401 self.output.push_str(text);
1402 self.output.push_str(" */");
1403 }
1404 CommentKind::Block => {
1405 self.output.push_str(&comment.text);
1406 }
1407 }
1408 }
1409
1410 fn format_trivia_compact(&mut self, trivia: &Trivia<'_>) {
1415 for comment in &trivia.comments {
1416 self.output.push(' ');
1417 self.format_comment_compact(comment);
1418 }
1419 }
1420}
1421
1422fn has_line_comment(trivia: &Trivia<'_>) -> bool {
1424 trivia.comments.iter().any(|c| c.kind == CommentKind::Line)
1425}
1426
1427#[cfg(test)]
1428mod tests {
1429 use super::*;
1430 use crate::ast::parse_document;
1431
1432 fn format(source: &str) -> String {
1433 let doc = parse_document(source).unwrap();
1434 format_document(&doc, &FormatConfig::default())
1435 }
1436
1437 fn format_with(source: &str, config: &FormatConfig) -> String {
1438 let doc = parse_document(source).unwrap();
1439 format_document(&doc, config)
1440 }
1441
1442 #[test]
1447 fn test_simple_values() {
1448 assert_eq!(format("42"), "42\n");
1449 assert_eq!(format("true"), "true\n");
1450 assert_eq!(format("false"), "false\n");
1451 assert_eq!(format("\"hello\""), "\"hello\"\n");
1452 assert_eq!(format("'c'"), "'c'\n");
1453 assert_eq!(format("()"), "()\n");
1454 assert_eq!(format("None"), "None\n");
1455 assert_eq!(format("Some(42)"), "Some(42)\n");
1456 }
1457
1458 #[test]
1459 fn test_preserves_number_format() {
1460 assert_eq!(format("0xFF"), "0xFF\n");
1462 assert_eq!(format("0b1010"), "0b1010\n");
1463 assert_eq!(format("0o777"), "0o777\n");
1464 assert_eq!(format("1_000_000"), "1_000_000\n");
1465 }
1466
1467 #[test]
1468 fn test_preserves_string_format() {
1469 assert_eq!(format(r#"r"raw string""#), "r\"raw string\"\n");
1471 assert_eq!(format(r##"r#"hash raw"#"##), "r#\"hash raw\"#\n");
1472 }
1473
1474 #[test]
1479 fn test_root_seq_multiline() {
1480 assert_eq!(format("[1, 2, 3]"), "[\n 1,\n 2,\n 3,\n]\n");
1482 assert_eq!(format("[1,2,3]"), "[\n 1,\n 2,\n 3,\n]\n");
1483 assert_eq!(format("[ 1 , 2 , 3 ]"), "[\n 1,\n 2,\n 3,\n]\n");
1484 }
1485
1486 #[test]
1487 fn test_empty_seq() {
1488 assert_eq!(format("[]"), "[]\n");
1489 assert_eq!(format("[ ]"), "[]\n");
1490 }
1491
1492 #[test]
1493 fn test_seq_exceeds_limit() {
1494 let config = FormatConfig::default().char_limit(10);
1495 let formatted = format_with("[1, 2, 3, 4, 5]", &config);
1496 assert_eq!(formatted, "[\n 1,\n 2,\n 3,\n 4,\n 5,\n]\n");
1497 }
1498
1499 #[test]
1500 fn test_seq_with_line_comment() {
1501 let source = "[\n // first item\n 1,\n 2,\n]";
1502 let formatted = format(source);
1503 assert!(formatted.contains("// first item"));
1504 assert!(formatted.contains(" 1,"));
1505 }
1506
1507 #[test]
1512 fn test_root_struct_multiline() {
1513 assert_eq!(
1515 format("Point(x:1,y:2)"),
1516 "Point(\n x: 1,\n y: 2,\n)\n"
1517 );
1518 assert_eq!(
1519 format("Point(x: 1, y: 2)"),
1520 "Point(\n x: 1,\n y: 2,\n)\n"
1521 );
1522 assert_eq!(
1523 format("Point( x : 1 , y : 2 )"),
1524 "Point(\n x: 1,\n y: 2,\n)\n"
1525 );
1526 }
1527
1528 #[test]
1529 fn test_unit_struct() {
1530 assert_eq!(format("Empty"), "Empty\n");
1531 }
1532
1533 #[test]
1534 fn test_tuple_struct() {
1535 assert_eq!(
1537 format("Point(1, 2, 3)"),
1538 "Point(\n 1,\n 2,\n 3,\n)\n"
1539 );
1540 }
1541
1542 #[test]
1543 fn test_struct_exceeds_limit() {
1544 let config = FormatConfig::default().char_limit(20);
1545 let formatted = format_with("Config(name: \"test\", value: 42)", &config);
1546 assert!(
1547 formatted.contains('\n'),
1548 "Expected multiline, got: {formatted:?}"
1549 );
1550 assert!(formatted.contains("name: \"test\","));
1551 assert!(formatted.contains("value: 42,"));
1552 }
1553
1554 #[test]
1555 fn test_struct_with_comments() {
1556 let source = r#"Config(
1557 // Server port
1558 port: 8080,
1559 host: "localhost",
1560)"#;
1561 let formatted = format(source);
1562 assert!(formatted.contains("// Server port"));
1563 assert!(formatted.contains("port: 8080,"));
1564 assert!(formatted.contains("host: \"localhost\","));
1565 }
1566
1567 #[test]
1568 fn test_nested_struct() {
1569 let source = "Config(inner: Point(x: 1, y: 2))";
1571 let formatted = format(source);
1572 assert_eq!(formatted, "Config(\n inner: Point(x: 1, y: 2),\n)\n");
1573 }
1574
1575 #[test]
1576 fn test_deeply_nested() {
1577 let config = FormatConfig::default().char_limit(30);
1578 let source = "A(b: B(c: C(d: D(e: 1))))";
1579 let formatted = format_with(source, &config);
1580 assert!(formatted.contains('\n'));
1582 }
1583
1584 #[test]
1589 fn test_root_map_multiline() {
1590 let source = "{\"a\": 1, \"b\": 2}";
1592 let formatted = format(source);
1593 assert_eq!(formatted, "{\n \"a\": 1,\n \"b\": 2,\n}\n");
1594 }
1595
1596 #[test]
1597 fn test_empty_map() {
1598 assert_eq!(format("{}"), "{}\n");
1599 }
1600
1601 #[test]
1602 fn test_map_exceeds_limit() {
1603 let config = FormatConfig::default().char_limit(15);
1604 let formatted = format_with("{\"key\": \"value\"}", &config);
1605 assert!(formatted.contains('\n'));
1606 }
1607
1608 #[test]
1613 fn test_root_tuple_multiline() {
1614 assert_eq!(format("(1, 2, 3)"), "(\n 1,\n 2,\n 3,\n)\n");
1616 assert_eq!(format("(1,2,3)"), "(\n 1,\n 2,\n 3,\n)\n");
1617 }
1618
1619 #[test]
1620 fn test_single_element_tuple() {
1621 assert_eq!(format("(42,)"), "(\n 42,\n)\n");
1623 }
1624
1625 #[test]
1630 fn test_leading_comment() {
1631 let source = "// header comment\n42";
1632 let formatted = format(source);
1633 assert!(formatted.starts_with("// header comment\n"));
1634 assert!(formatted.contains("42"));
1635 }
1636
1637 #[test]
1638 fn test_block_comment() {
1639 let source = "/* block */ 42";
1640 let formatted = format(source);
1641 assert!(formatted.contains("/* block */"));
1642 }
1643
1644 #[test]
1645 fn test_comment_between_fields() {
1646 let source = "(
1647 x: 1,
1648 // separator
1649 y: 2,
1650)";
1651 let formatted = format(source);
1652 assert!(formatted.contains("// separator"));
1653 assert!(formatted.contains("x: 1,"));
1654 assert!(formatted.contains("y: 2,"));
1655 }
1656
1657 #[test]
1662 fn test_single_attribute() {
1663 let source = "#![type = \"Foo\"]\n42";
1664 let formatted = format(source);
1665 assert!(formatted.starts_with("#![type = \"Foo\"]"));
1666 assert!(formatted.contains("\n\n42"));
1667 }
1668
1669 #[test]
1670 fn test_multiple_attributes() {
1671 let source = "#![type = \"Foo\"]\n#![enable(unwrap_newtypes)]\n42";
1672 let formatted = format(source);
1673 assert!(formatted.contains("#![type = \"Foo\"]"));
1674 assert!(formatted.contains("#![enable(unwrap_newtypes)]"));
1675 }
1676
1677 #[test]
1678 fn test_attribute_with_args() {
1679 let source = "#![enable(implicit_some, unwrap_newtypes)]\n42";
1680 let formatted = format(source);
1681 assert!(formatted.contains("#![enable(implicit_some, unwrap_newtypes)]"));
1682 }
1683
1684 #[test]
1689 fn test_custom_indent() {
1690 let config = FormatConfig::new().indent(" ").char_limit(5);
1691 let formatted = format_with("[1, 2, 3]", &config);
1692 assert!(
1693 formatted.contains(" 1,"),
1694 "Expected 2-space indent in: {formatted:?}"
1695 );
1696 }
1697
1698 #[test]
1699 fn test_tab_indent() {
1700 let config = FormatConfig::new().indent("\t").char_limit(5);
1701 let formatted = format_with("[1, 2, 3]", &config);
1702 assert!(
1703 formatted.contains("\t1,"),
1704 "Expected tab indent in: {formatted:?}"
1705 );
1706 }
1707
1708 #[test]
1709 fn test_large_char_limit_nested() {
1710 let config = FormatConfig::new().char_limit(1000);
1712 let long_array = (1..50)
1714 .map(|n| n.to_string())
1715 .collect::<Vec<_>>()
1716 .join(", ");
1717 let source = format!("Config(items: [{long_array}])");
1718 let formatted = format_with(&source, &config);
1719 assert!(formatted.contains(&format!("[{long_array}]")));
1721 }
1722
1723 #[test]
1728 fn test_empty_document() {
1729 assert_eq!(format(""), "");
1731 }
1732
1733 #[test]
1734 fn test_comment_only_document() {
1735 let source = "// just a comment\n";
1736 let formatted = format(source);
1737 assert!(formatted.contains("// just a comment"));
1738 }
1739
1740 #[test]
1741 fn test_trailing_comma_preserved_multiline() {
1742 let config = FormatConfig::default().char_limit(5);
1743 let formatted = format_with("[1, 2]", &config);
1744 assert!(formatted.contains("1,"), "Expected '1,' in: {formatted:?}");
1746 assert!(formatted.contains("2,"), "Expected '2,' in: {formatted:?}");
1747 }
1748
1749 #[test]
1750 fn test_no_trailing_comma_compact_nested() {
1751 let formatted = format("Config(items: [1, 2, 3])");
1753 assert!(formatted.contains("[1, 2, 3]"));
1755 }
1756
1757 #[test]
1758 fn test_anonymous_struct() {
1759 let source = "(x: 1, y: 2)";
1761 let formatted = format(source);
1762 assert_eq!(formatted, "(\n x: 1,\n y: 2,\n)\n");
1763 }
1764
1765 #[test]
1766 fn test_option_some() {
1767 assert_eq!(format("Some(42)"), "Some(42)\n");
1768 assert_eq!(format("Some( 42 )"), "Some(42)\n");
1769 }
1770
1771 #[test]
1772 fn test_option_none() {
1773 assert_eq!(format("None"), "None\n");
1774 }
1775
1776 #[test]
1777 fn test_bytes() {
1778 assert_eq!(format("b\"hello\""), "b\"hello\"\n");
1779 }
1780
1781 #[test]
1782 fn test_empty_collections() {
1783 assert_eq!(format("[]"), "[]\n");
1784 assert_eq!(format("{}"), "{}\n");
1785 assert_eq!(format("()"), "()\n");
1786 }
1787
1788 #[test]
1793 fn test_compact_from_depth_0() {
1794 let config = FormatConfig::new().compact_from_depth(0);
1796 let source = "Config(items: [1, 2, 3], point: (1, 2))";
1798 let formatted = format_with(source, &config);
1799 assert!(formatted.contains("[1, 2, 3]"));
1801 assert!(formatted.contains("(1, 2)"));
1802 }
1803
1804 #[test]
1805 fn test_compact_from_depth_1() {
1806 let config = FormatConfig::new().compact_from_depth(1);
1808 let source = "Config(items: [1, 2, 3])";
1809 let formatted = format_with(source, &config);
1810 assert!(
1812 formatted.contains("items: [1, 2, 3]"),
1813 "Expected compact array: {formatted:?}"
1814 );
1815 }
1816
1817 #[test]
1822 fn test_compact_types_arrays() {
1823 let config = FormatConfig::new()
1824 .char_limit(5) .compact_types(CompactTypes { arrays: true, ..Default::default() });
1826 let source = "Config(items: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])";
1827 let formatted = format_with(source, &config);
1828 assert!(
1830 formatted.contains("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"),
1831 "Expected compact array: {formatted:?}"
1832 );
1833 }
1834
1835 #[test]
1836 fn test_compact_types_tuples() {
1837 let config = FormatConfig::new()
1838 .char_limit(5)
1839 .compact_types(CompactTypes {
1840 tuples: true,
1841 ..Default::default()
1842 });
1843 let source = "Config(point: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10))";
1844 let formatted = format_with(source, &config);
1845 assert!(
1847 formatted.contains("(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)"),
1848 "Expected compact tuple: {formatted:?}"
1849 );
1850 }
1851
1852 #[test]
1853 fn test_compact_types_structs() {
1854 let config = FormatConfig::new()
1855 .char_limit(5)
1856 .compact_types(CompactTypes {
1857 structs: true,
1858 ..Default::default()
1859 });
1860 let source = "Outer(inner: Inner(x: 1, y: 2, z: 3))";
1861 let formatted = format_with(source, &config);
1862 assert!(
1864 formatted.contains("Inner(x: 1, y: 2, z: 3)"),
1865 "Expected compact struct: {formatted:?}"
1866 );
1867 }
1868
1869 #[test]
1870 fn test_compact_types_all() {
1871 let config = FormatConfig::new()
1872 .char_limit(5)
1873 .compact_types(CompactTypes::all());
1874 let source = "Config(a: [1, 2], b: (3, 4), c: Point(x: 5))";
1875 let formatted = format_with(source, &config);
1876 assert!(formatted.contains("[1, 2]"));
1877 assert!(formatted.contains("(3, 4)"));
1878 assert!(formatted.contains("Point(x: 5)"));
1879 }
1880
1881 #[test]
1886 fn test_compact_converts_line_comments_to_block() {
1887 let config = FormatConfig::new().compact_from_depth(1);
1889 let source = "Config(
1890 items: [
1891 // comment
1892 1,
1893 2,
1894 ],
1895)";
1896 let formatted = format_with(source, &config);
1897 assert!(
1899 formatted.contains("/* comment */"),
1900 "Expected block comment: {formatted:?}"
1901 );
1902 assert!(formatted.contains("items: ["));
1904 }
1905
1906 #[test]
1907 fn test_compact_preserves_block_comments() {
1908 let config = FormatConfig::new().compact_from_depth(1);
1909 let source = "Config(
1910 items: [
1911 /* existing block */
1912 1,
1913 ],
1914)";
1915 let formatted = format_with(source, &config);
1916 assert!(
1917 formatted.contains("/* existing block */"),
1918 "Expected block comment preserved: {formatted:?}"
1919 );
1920 }
1921
1922 #[test]
1927 fn test_minimal_no_whitespace() {
1928 let source = "Point(x: 1, y: 2)";
1930 let formatted = format_with(source, &FormatConfig::minimal());
1931 assert_eq!(formatted, "Point(x:1,y:2)");
1932 }
1933
1934 #[test]
1935 fn test_minimal_strips_comments() {
1936 let source = "// header\nPoint(x: 1, /* inline */ y: 2)";
1938 let formatted = format_with(source, &FormatConfig::minimal());
1939 assert_eq!(formatted, "Point(x:1,y:2)");
1940 }
1941
1942 #[test]
1943 fn test_minimal_nested_collections() {
1944 let source = "Config(items: [1, 2], point: (3, 4), map: {\"a\": 1})";
1946 let formatted = format_with(source, &FormatConfig::minimal());
1947 assert_eq!(formatted, "Config(items:[1,2],point:(3,4),map:{\"a\":1})");
1948 }
1949
1950 #[test]
1955 fn test_root_multiline_by_default() {
1956 let config = FormatConfig::new(); let formatted = format_with("[1, 2, 3]", &config);
1959 assert!(
1960 formatted.contains('\n'),
1961 "Root should be multiline: {formatted:?}"
1962 );
1963 }
1964
1965 #[test]
1966 fn test_root_compacts_with_depth_0() {
1967 let config = FormatConfig::new().compact_from_depth(0);
1969 let formatted = format_with("[1, 2, 3]", &config);
1970 assert_eq!(formatted, "[1, 2, 3]\n");
1971 }
1972
1973 #[test]
1974 fn test_root_compacts_with_matching_type() {
1975 let config = FormatConfig::new().compact_types(CompactTypes {
1977 arrays: true,
1978 ..Default::default()
1979 });
1980 let formatted = format_with("[1, 2, 3]", &config);
1981 assert_eq!(formatted, "[1, 2, 3]\n");
1982 }
1983
1984 #[test]
1989 fn test_depth_counting_nested() {
1990 let config = FormatConfig::new().compact_from_depth(2).char_limit(0);
1993 let source = "Outer(a: Inner(b: [1, 2, 3]))";
1994 let formatted = format_with(source, &config);
1995 assert!(
1999 formatted.contains("[1, 2, 3]"),
2000 "Array should be compact: {formatted:?}"
2001 );
2002 assert!(
2003 formatted.contains("a: Inner("),
2004 "Should have newlines: {formatted:?}"
2005 );
2006 }
2007
2008 #[test]
2009 fn test_compact_from_depth_2() {
2010 let config = FormatConfig::new().compact_from_depth(2).char_limit(0);
2012 let source = "Config(items: [1, 2, 3])";
2013 let formatted = format_with(source, &config);
2014 assert!(
2017 formatted.contains("items: [\n"),
2018 "Depth 1 should be multiline: {formatted:?}"
2019 );
2020 }
2021
2022 #[test]
2023 fn test_compact_from_depth_3_deep_nesting() {
2024 let config = FormatConfig::new().compact_from_depth(3).char_limit(0);
2026 let source = "A(b: B(c: C(d: [1, 2])))";
2027 let formatted = format_with(source, &config);
2028 assert!(
2033 formatted.contains("[1, 2]"),
2034 "Depth 3 array should be compact: {formatted:?}"
2035 );
2036 }
2037
2038 #[test]
2043 fn test_compact_types_maps() {
2044 let config = FormatConfig::new()
2046 .char_limit(0) .compact_types(CompactTypes {
2048 maps: true,
2049 ..Default::default()
2050 });
2051 let source = "Config(data: {\"a\": 1, \"b\": 2})";
2052 let formatted = format_with(source, &config);
2053 assert!(
2054 formatted.contains("{\"a\": 1, \"b\": 2}"),
2055 "Map should be compact: {formatted:?}"
2056 );
2057 }
2058
2059 #[test]
2060 fn test_compact_types_anonymous_struct() {
2061 let config = FormatConfig::new()
2063 .char_limit(0)
2064 .compact_types(CompactTypes {
2065 structs: true,
2066 ..Default::default()
2067 });
2068 let source = "Config(point: (x: 1, y: 2))";
2069 let formatted = format_with(source, &config);
2070 assert!(
2071 formatted.contains("(x: 1, y: 2)"),
2072 "Anonymous struct should be compact: {formatted:?}"
2073 );
2074 }
2075
2076 #[test]
2077 fn test_compact_types_ignores_char_limit() {
2078 let config = FormatConfig::new()
2080 .char_limit(5) .compact_types(CompactTypes {
2082 arrays: true,
2083 ..Default::default()
2084 });
2085 let source = "Config(items: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])";
2086 let formatted = format_with(source, &config);
2087 assert!(
2089 formatted.contains("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"),
2090 "Type rule should override char_limit: {formatted:?}"
2091 );
2092 }
2093
2094 #[test]
2099 fn test_char_limit_zero_disables() {
2100 let config = FormatConfig::new().char_limit(0);
2102 let source = "Config(items: [1])"; let formatted = format_with(source, &config);
2104 assert!(
2106 formatted.contains("items: [\n"),
2107 "char_limit=0 should disable length-based: {formatted:?}"
2108 );
2109 }
2110
2111 #[test]
2112 fn test_default_char_limit_80() {
2113 let config = FormatConfig::default();
2115 let short = "Config(items: [1, 2, 3, 4, 5])";
2117 let formatted_short = format_with(short, &config);
2118 assert!(
2119 formatted_short.contains("[1, 2, 3, 4, 5]"),
2120 "Should fit: {formatted_short:?}"
2121 );
2122 }
2123
2124 #[test]
2129 fn test_or_logic_depth_triggers() {
2130 let config = FormatConfig::new().compact_from_depth(1).char_limit(0); let source = "Config(items: [1, 2, 3])";
2133 let formatted = format_with(source, &config);
2134 assert!(
2135 formatted.contains("[1, 2, 3]"),
2136 "Depth rule should trigger: {formatted:?}"
2137 );
2138 }
2139
2140 #[test]
2141 fn test_or_logic_type_triggers() {
2142 let config = FormatConfig::new()
2144 .compact_types(CompactTypes {
2145 arrays: true,
2146 ..Default::default()
2147 })
2148 .char_limit(0); let source = "Config(items: [1, 2, 3])";
2150 let formatted = format_with(source, &config);
2151 assert!(
2152 formatted.contains("[1, 2, 3]"),
2153 "Type rule should trigger: {formatted:?}"
2154 );
2155 }
2156
2157 #[test]
2158 fn test_or_logic_length_triggers() {
2159 let config = FormatConfig::new().char_limit(100); let source = "Config(items: [1, 2, 3])";
2162 let formatted = format_with(source, &config);
2163 assert!(
2164 formatted.contains("[1, 2, 3]"),
2165 "Length rule should trigger: {formatted:?}"
2166 );
2167 }
2168
2169 #[test]
2170 fn test_no_rules_match_multiline() {
2171 let config = FormatConfig::new().char_limit(0); let source = "Config(items: [1, 2, 3])";
2174 let formatted = format_with(source, &config);
2175 assert!(
2176 formatted.contains("items: [\n"),
2177 "No rules = multiline: {formatted:?}"
2178 );
2179 }
2180
2181 #[test]
2186 fn test_length_based_soft_line_comments_prevent() {
2187 let config = FormatConfig::new().char_limit(1000); let source = "Config(
2190 items: [
2191 // comment
2192 1,
2193 2,
2194 ],
2195)";
2196 let formatted = format_with(source, &config);
2197 assert!(
2199 formatted.contains("// comment"),
2200 "Line comment preserved: {formatted:?}"
2201 );
2202 assert!(
2203 formatted.contains("items: [\n"),
2204 "Should stay multiline: {formatted:?}"
2205 );
2206 }
2207
2208 #[test]
2209 fn test_depth_based_hard_converts_comments() {
2210 let config = FormatConfig::new().compact_from_depth(1);
2212 let source = "Config(
2213 items: [
2214 // comment
2215 1,
2216 ],
2217)";
2218 let formatted = format_with(source, &config);
2219 assert!(
2221 formatted.contains("/* comment */"),
2222 "Line → block: {formatted:?}"
2223 );
2224 assert!(
2225 !formatted.contains("// comment"),
2226 "No line comment: {formatted:?}"
2227 );
2228 }
2229
2230 #[test]
2231 fn test_type_based_hard_converts_comments() {
2232 let config = FormatConfig::new()
2234 .char_limit(0)
2235 .compact_types(CompactTypes {
2236 arrays: true,
2237 ..Default::default()
2238 });
2239 let source = "Config(
2240 items: [
2241 // comment
2242 1,
2243 ],
2244)";
2245 let formatted = format_with(source, &config);
2246 assert!(
2248 formatted.contains("/* comment */"),
2249 "Line → block: {formatted:?}"
2250 );
2251 }
2252
2253 #[test]
2254 fn test_compact_multiple_line_comments() {
2255 let config = FormatConfig::new().compact_from_depth(1);
2257 let source = "Config(
2258 items: [
2259 // first
2260 1,
2261 // second
2262 2,
2263 ],
2264)";
2265 let formatted = format_with(source, &config);
2266 assert!(
2267 formatted.contains("/* first */"),
2268 "First comment: {formatted:?}"
2269 );
2270 assert!(
2271 formatted.contains("/* second */"),
2272 "Second comment: {formatted:?}"
2273 );
2274 }
2275
2276 #[test]
2277 fn test_minimal_strips_all_comments() {
2278 let source = "Config(
2280 // line comment
2281 items: [
2282 /* block comment */
2283 1,
2284 ],
2285)";
2286 let formatted = format_with(source, &FormatConfig::minimal());
2287 assert!(
2288 !formatted.contains("comment"),
2289 "No comments in minimal: {formatted:?}"
2290 );
2291 }
2292
2293 #[test]
2294 fn test_hard_compact_block_comments_preserved() {
2295 let config = FormatConfig::new().compact_from_depth(1);
2297 let source = "Config(
2298 items: [
2299 /* existing block */
2300 1,
2301 ],
2302)";
2303 let formatted = format_with(source, &config);
2304 assert!(
2305 formatted.contains("/* existing block */"),
2306 "Block comment unchanged: {formatted:?}"
2307 );
2308 }
2309}