1use styx_tree::{Entry, Object, Payload, Sequence, Value};
4
5use crate::{FormatOptions, StyxWriter};
6
7pub fn format_value(value: &Value, options: FormatOptions) -> String {
12 let mut formatter = ValueFormatter::new(options);
13 formatter.format_root(value);
14 formatter.finish_document()
15}
16
17pub fn format_value_default(value: &Value) -> String {
19 format_value(value, FormatOptions::default())
20}
21
22pub fn format_object_braced(obj: &Object, options: FormatOptions) -> String {
27 let mut formatter = ValueFormatter::new(options);
28 formatter.format_object(obj);
29 formatter.finish()
30}
31
32struct ValueFormatter {
33 writer: StyxWriter,
34}
35
36impl ValueFormatter {
37 fn new(options: FormatOptions) -> Self {
38 Self {
39 writer: StyxWriter::with_options(options),
40 }
41 }
42
43 fn finish(self) -> String {
45 self.writer.finish_string()
46 }
47
48 fn finish_document(self) -> String {
50 String::from_utf8(self.writer.finish_document())
51 .expect("Styx output should always be valid UTF-8")
52 }
53
54 fn format_root(&mut self, value: &Value) {
55 if value.tag.is_none()
57 && let Some(Payload::Object(obj)) = &value.payload
58 {
59 self.writer.begin_struct(true);
61 self.format_object_entries(obj);
62 self.writer.end_struct().ok();
63 return;
64 }
65 self.format_value(value);
67 }
68
69 fn format_value(&mut self, value: &Value) {
70 let has_tag = value.tag.is_some();
71
72 if let Some(tag) = &value.tag {
74 self.writer.write_tag(&tag.name);
75 }
76
77 match &value.payload {
79 None => {
80 if !has_tag {
82 self.writer.write_str("@");
83 }
84 }
86 Some(Payload::Scalar(s)) => {
87 if has_tag {
89 self.writer.begin_seq_after_tag();
90 self.writer.write_scalar(&s.text);
91 self.writer.end_seq().ok();
92 } else {
93 self.writer.write_scalar(&s.text);
94 }
95 }
96 Some(Payload::Sequence(seq)) => {
97 self.format_sequence_inner(seq, has_tag);
99 }
100 Some(Payload::Object(obj)) => {
101 self.format_object_inner(obj, has_tag);
103 }
104 }
105 }
106
107 fn format_sequence_inner(&mut self, seq: &Sequence, after_tag: bool) {
108 if after_tag {
109 self.writer.begin_seq_after_tag();
110 } else {
111 self.writer.begin_seq();
112 }
113 for item in &seq.items {
114 self.format_value(item);
115 }
116 self.writer.end_seq().ok();
117 }
118
119 fn format_object(&mut self, obj: &Object) {
120 self.format_object_inner(obj, false);
121 }
122
123 fn format_object_inner(&mut self, obj: &Object, after_tag: bool) {
124 let force_multiline = matches!(obj.separator, styx_parse::Separator::Newline);
126 if after_tag {
127 self.writer.begin_struct_after_tag(force_multiline);
128 } else {
129 self.writer
130 .begin_struct_with_options(false, force_multiline);
131 }
132 self.format_object_entries(obj);
133 self.writer.end_struct().ok();
134 }
135
136 fn format_object_entries(&mut self, obj: &Object) {
137 for entry in &obj.entries {
138 self.format_entry(entry);
139 }
140 }
141
142 fn format_entry(&mut self, entry: &Entry) {
143 let key_str = self.format_key(&entry.key);
145
146 if let Some(doc) = &entry.doc_comment {
148 self.writer.write_doc_comment_and_key_raw(doc, &key_str);
149 } else {
150 self.writer.field_key_raw(&key_str).ok();
151 }
152
153 self.format_value(&entry.value);
154 }
155
156 fn format_key(&self, key: &Value) -> String {
159 let mut result = String::new();
160
161 if let Some(tag) = &key.tag {
163 result.push('@');
164 result.push_str(&tag.name);
165 }
166
167 match &key.payload {
169 None => {
170 if key.tag.is_none() {
172 result.push('@');
173 }
174 }
176 Some(Payload::Scalar(s)) => {
177 if crate::scalar::can_be_bare(&s.text) {
179 result.push_str(&s.text);
180 } else {
181 result.push('"');
182 result.push_str(&crate::scalar::escape_quoted(&s.text));
183 result.push('"');
184 }
185 }
186 Some(Payload::Sequence(_) | Payload::Object(_)) => {
187 panic!("object key cannot be a sequence or object: {:?}", key);
188 }
189 }
190
191 result
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use styx_parse::{ScalarKind, Separator};
199 use styx_tree::{Object, Payload, Scalar, Sequence, Tag};
200
201 fn scalar(text: &str) -> Value {
202 Value {
203 tag: None,
204 payload: Some(Payload::Scalar(Scalar {
205 text: text.to_string(),
206 kind: ScalarKind::Bare,
207 span: None,
208 })),
209 span: None,
210 }
211 }
212
213 fn tagged(name: &str) -> Value {
214 Value {
215 tag: Some(Tag {
216 name: name.to_string(),
217 span: None,
218 }),
219 payload: None,
220 span: None,
221 }
222 }
223
224 fn entry(key: &str, value: Value) -> Entry {
225 Entry {
226 key: scalar(key),
227 value,
228 doc_comment: None,
229 }
230 }
231
232 fn entry_with_doc(key: &str, value: Value, doc: &str) -> Entry {
233 Entry {
234 key: scalar(key),
235 value,
236 doc_comment: Some(doc.to_string()),
237 }
238 }
239
240 fn obj_value(entries: Vec<Entry>) -> Value {
241 Value {
242 tag: None,
243 payload: Some(Payload::Object(Object {
244 entries,
245 separator: Separator::Newline,
246 span: None,
247 })),
248 span: None,
249 }
250 }
251
252 fn seq_value(items: Vec<Value>) -> Value {
253 Value {
254 tag: None,
255 payload: Some(Payload::Sequence(Sequence { items, span: None })),
256 span: None,
257 }
258 }
259
260 #[test]
261 fn test_format_simple_object() {
262 let obj = obj_value(vec![
263 entry("name", scalar("Alice")),
264 entry("age", scalar("30")),
265 ]);
266
267 let result = format_value_default(&obj);
268 insta::assert_snapshot!(result);
269 }
270
271 #[test]
272 fn test_format_nested_object() {
273 let inner = Value {
274 tag: None,
275 payload: Some(Payload::Object(Object {
276 entries: vec![entry("name", scalar("Alice")), entry("age", scalar("30"))],
277 separator: Separator::Comma,
278 span: None,
279 })),
280 span: None,
281 };
282
283 let obj = obj_value(vec![entry("user", inner)]);
284
285 let result = format_value_default(&obj);
286 insta::assert_snapshot!(result);
287 }
288
289 #[test]
290 fn test_format_tagged() {
291 let obj = obj_value(vec![entry("type", tagged("string"))]);
292
293 let result = format_value_default(&obj);
294 insta::assert_snapshot!(result);
295 }
296
297 #[test]
298 fn test_format_sequence() {
299 let seq = seq_value(vec![scalar("a"), scalar("b"), scalar("c")]);
300
301 let obj = obj_value(vec![entry("items", seq)]);
302
303 let result = format_value_default(&obj);
304 insta::assert_snapshot!(result);
305 }
306
307 #[test]
308 fn test_format_with_doc_comments() {
309 let obj = obj_value(vec![
310 entry_with_doc("name", scalar("Alice"), "The user's name"),
311 entry_with_doc("age", scalar("30"), "Age in years"),
312 ]);
313
314 let result = format_value_default(&obj);
315 insta::assert_snapshot!(result);
316 }
317
318 #[test]
319 fn test_format_unit() {
320 let obj = obj_value(vec![entry("flag", Value::unit())]);
321
322 let result = format_value_default(&obj);
323 insta::assert_snapshot!(result);
324 }
325
326 fn obj_multiline(entries: Vec<Entry>) -> Value {
332 Value {
333 tag: None,
334 payload: Some(Payload::Object(Object {
335 entries,
336 separator: Separator::Newline,
337 span: None,
338 })),
339 span: None,
340 }
341 }
342
343 fn obj_inline(entries: Vec<Entry>) -> Value {
345 Value {
346 tag: None,
347 payload: Some(Payload::Object(Object {
348 entries,
349 separator: Separator::Comma,
350 span: None,
351 })),
352 span: None,
353 }
354 }
355
356 fn tagged_obj(tag_name: &str, entries: Vec<Entry>, separator: Separator) -> Value {
358 Value {
359 tag: Some(Tag {
360 name: tag_name.to_string(),
361 span: None,
362 }),
363 payload: Some(Payload::Object(Object {
364 entries,
365 separator,
366 span: None,
367 })),
368 span: None,
369 }
370 }
371
372 fn tagged_scalar(tag_name: &str, text: &str) -> Value {
374 Value {
375 tag: Some(Tag {
376 name: tag_name.to_string(),
377 span: None,
378 }),
379 payload: Some(Payload::Scalar(Scalar {
380 text: text.to_string(),
381 kind: ScalarKind::Bare,
382 span: None,
383 })),
384 span: None,
385 }
386 }
387
388 fn unit_entry(value: Value) -> Entry {
390 Entry {
391 key: Value::unit(),
392 value,
393 doc_comment: None,
394 }
395 }
396
397 fn schema_entry(value: Value) -> Entry {
399 Entry {
400 key: Value::tag("schema"),
401 value,
402 doc_comment: None,
403 }
404 }
405
406 #[test]
408 fn test_edge_case_01_schema_declaration_blank_line() {
409 let obj = obj_multiline(vec![
411 schema_entry(scalar("schema.styx")),
412 entry("name", scalar("test")),
413 entry("port", scalar("8080")),
414 ]);
415
416 let result = format_value_default(&obj);
417 insta::assert_snapshot!(result);
418 }
419
420 #[test]
422 fn test_edge_case_02_nested_multiline_objects() {
423 let inner = obj_multiline(vec![
424 entry("host", scalar("localhost")),
425 entry("port", scalar("8080")),
426 ]);
427 let obj = obj_multiline(vec![entry("name", scalar("myapp")), entry("server", inner)]);
428
429 let result = format_value_default(&obj);
430 insta::assert_snapshot!(result);
431 }
432
433 #[test]
435 fn test_edge_case_03_deeply_nested_multiline() {
436 let level3 = obj_multiline(vec![
437 entry("cert", scalar("/path/to/cert")),
438 entry("key", scalar("/path/to/key")),
439 ]);
440 let level2 = obj_multiline(vec![
441 entry("host", scalar("localhost")),
442 entry("tls", level3),
443 ]);
444 let obj = obj_multiline(vec![
445 entry("name", scalar("myapp")),
446 entry("server", level2),
447 ]);
448
449 let result = format_value_default(&obj);
450 insta::assert_snapshot!(result);
451 }
452
453 #[test]
455 fn test_edge_case_04_mixed_inline_multiline() {
456 let inner = obj_inline(vec![entry("x", scalar("1")), entry("y", scalar("2"))]);
458 let obj = obj_multiline(vec![entry("name", scalar("point")), entry("coords", inner)]);
459
460 let result = format_value_default(&obj);
461 insta::assert_snapshot!(result);
462 }
463
464 #[test]
466 fn test_edge_case_05_tagged_multiline_object() {
467 let obj = obj_multiline(vec![entry(
468 "type",
469 tagged_obj(
470 "object",
471 vec![entry("name", tagged("string")), entry("age", tagged("int"))],
472 Separator::Newline,
473 ),
474 )]);
475
476 let result = format_value_default(&obj);
477 insta::assert_snapshot!(result);
478 }
479
480 #[test]
482 fn test_edge_case_06_tagged_inline_object() {
483 let obj = obj_multiline(vec![entry(
484 "point",
485 tagged_obj(
486 "point",
487 vec![entry("x", scalar("1")), entry("y", scalar("2"))],
488 Separator::Comma,
489 ),
490 )]);
491
492 let result = format_value_default(&obj);
493 insta::assert_snapshot!(result);
494 }
495
496 #[test]
498 fn test_edge_case_07_schema_structure() {
499 let meta = obj_multiline(vec![
502 entry("id", scalar("https://example.com/schema")),
503 entry("version", scalar("1.0")),
504 ]);
505 let schema_obj = tagged_obj(
506 "object",
507 vec![
508 entry("name", tagged("string")),
509 entry("port", tagged("int")),
510 ],
511 Separator::Newline,
512 );
513 let schema = obj_multiline(vec![unit_entry(schema_obj)]);
514 let root = obj_multiline(vec![entry("meta", meta), entry("schema", schema)]);
515
516 let result = format_value_default(&root);
517 insta::assert_snapshot!(result);
518 }
519
520 #[test]
522 fn test_edge_case_08_optional_types() {
523 let obj = obj_multiline(vec![
524 entry("required", tagged("string")),
525 entry("optional", tagged_scalar("optional", "@bool")),
526 ]);
527
528 let result = format_value_default(&obj);
529 insta::assert_snapshot!(result);
530 }
531
532 #[test]
534 fn test_edge_case_09_empty_object() {
535 let obj = obj_multiline(vec![entry("empty", obj_multiline(vec![]))]);
536
537 let result = format_value_default(&obj);
538 insta::assert_snapshot!(result);
539 }
540
541 #[test]
543 fn test_edge_case_10_empty_inline_object() {
544 let obj = obj_multiline(vec![entry("empty", obj_inline(vec![]))]);
545
546 let result = format_value_default(&obj);
547 insta::assert_snapshot!(result);
548 }
549
550 #[test]
552 fn test_edge_case_11_sequence_of_objects() {
553 let item1 = obj_inline(vec![entry("name", scalar("Alice"))]);
554 let item2 = obj_inline(vec![entry("name", scalar("Bob"))]);
555 let seq = Value {
556 tag: None,
557 payload: Some(Payload::Sequence(Sequence {
558 items: vec![item1, item2],
559 span: None,
560 })),
561 span: None,
562 };
563 let obj = obj_multiline(vec![entry("users", seq)]);
564
565 let result = format_value_default(&obj);
566 insta::assert_snapshot!(result);
567 }
568
569 #[test]
571 fn test_edge_case_12_quoted_strings() {
572 let obj = obj_multiline(vec![
573 entry("message", scalar(r#""Hello, World!""#)),
574 entry("path", scalar("/path/with spaces/file.txt")),
575 ]);
576
577 let result = format_value_default(&obj);
578 insta::assert_snapshot!(result);
579 }
580
581 #[test]
583 fn test_edge_case_13_quoted_keys() {
584 let obj = obj_multiline(vec![
585 entry("normal-key", scalar("value1")),
586 entry("key with spaces", scalar("value2")),
587 entry("123numeric", scalar("value3")),
588 ]);
589
590 let result = format_value_default(&obj);
591 insta::assert_snapshot!(result);
592 }
593
594 #[test]
596 fn test_edge_case_14_schema_declaration() {
597 let obj = obj_multiline(vec![
598 schema_entry(scalar("first.styx")),
599 entry("name", scalar("test")),
600 ]);
601
602 let result = format_value_default(&obj);
603 insta::assert_snapshot!(result);
604 }
605
606 #[test]
608 fn test_edge_case_15_nested_sequences() {
609 let inner_seq = seq_value(vec![scalar("a"), scalar("b")]);
610 let outer_seq = Value {
611 tag: None,
612 payload: Some(Payload::Sequence(Sequence {
613 items: vec![inner_seq, seq_value(vec![scalar("c"), scalar("d")])],
614 span: None,
615 })),
616 span: None,
617 };
618 let obj = obj_multiline(vec![entry("matrix", outer_seq)]);
619
620 let result = format_value_default(&obj);
621 insta::assert_snapshot!(result);
622 }
623
624 #[test]
626 fn test_edge_case_16_tagged_sequence() {
627 let tagged_seq = Value {
628 tag: Some(Tag {
629 name: "seq".to_string(),
630 span: None,
631 }),
632 payload: Some(Payload::Sequence(Sequence {
633 items: vec![tagged("string")],
634 span: None,
635 })),
636 span: None,
637 };
638 let obj = obj_multiline(vec![entry("items", tagged_seq)]);
639
640 let result = format_value_default(&obj);
641 insta::assert_snapshot!(result);
642 }
643
644 #[test]
646 fn test_edge_case_17_nested_doc_comments() {
647 let inner = Value {
648 tag: None,
649 payload: Some(Payload::Object(Object {
650 entries: vec![
651 entry_with_doc("host", scalar("localhost"), "The server hostname"),
652 entry_with_doc("port", scalar("8080"), "The server port"),
653 ],
654 separator: Separator::Newline,
655 span: None,
656 })),
657 span: None,
658 };
659 let obj = obj_multiline(vec![entry_with_doc(
660 "server",
661 inner,
662 "Server configuration",
663 )]);
664
665 let result = format_value_default(&obj);
666 insta::assert_snapshot!(result);
667 }
668
669 #[test]
671 fn test_edge_case_18_long_inline_stays_inline() {
672 let inner = obj_inline(vec![
673 entry("field1", scalar("value1")),
674 entry("field2", scalar("value2")),
675 entry("field3", scalar("value3")),
676 entry("field4", scalar("value4")),
677 ]);
678 let obj = obj_multiline(vec![entry("data", inner)]);
679
680 let result = format_value_default(&obj);
681 insta::assert_snapshot!(result);
682 }
683
684 #[test]
686 fn test_edge_case_19_multiline_single_field() {
687 let inner = obj_multiline(vec![entry("only", scalar("one"))]);
688 let obj = obj_multiline(vec![entry("wrapper", inner)]);
689
690 let result = format_value_default(&obj);
691 insta::assert_snapshot!(result);
692 }
693
694 #[test]
696 fn test_edge_case_20_full_schema_simulation() {
697 let meta = obj_multiline(vec![
700 entry("id", scalar("https://example.com/config")),
701 entry("version", scalar("2024-01-01")),
702 entry("description", scalar("\"A test schema\"")),
703 ]);
704
705 let _server_fields = obj_multiline(vec![
706 entry("host", tagged("string")),
707 entry("port", tagged("int")),
708 ]);
709 let server_schema = tagged_obj(
710 "object",
711 vec![
712 entry("host", tagged("string")),
713 entry("port", tagged("int")),
714 ],
715 Separator::Newline,
716 );
717
718 let root_schema = tagged_obj(
719 "object",
720 vec![
721 entry("name", tagged("string")),
722 entry("server", server_schema),
723 ],
724 Separator::Newline,
725 );
726
727 let schema = obj_multiline(vec![unit_entry(root_schema)]);
728
729 let root = obj_multiline(vec![entry("meta", meta), entry("schema", schema)]);
730
731 let result = format_value_default(&root);
732 insta::assert_snapshot!(result);
733 }
734
735 fn assert_matches_cst_formatter(value: &Value, description: &str) {
742 let value_output = format_value_default(value);
743 let cst_output = crate::format_source(&value_output, crate::FormatOptions::default());
744 assert_eq!(
745 value_output, cst_output,
746 "{}: ValueFormatter output should match CST formatter.\n\
747 ValueFormatter produced:\n{}\n\
748 CST formatter would produce:\n{}",
749 description, value_output, cst_output
750 );
751 }
752
753 #[test]
754 fn blank_line_01_two_scalars_at_root() {
755 let obj = obj_multiline(vec![
757 entry("name", scalar("Alice")),
758 entry("age", scalar("30")),
759 ]);
760 assert_matches_cst_formatter(&obj, "two scalars at root");
761 }
762
763 #[test]
764 fn blank_line_02_three_scalars_at_root() {
765 let obj = obj_multiline(vec![
766 entry("a", scalar("1")),
767 entry("b", scalar("2")),
768 entry("c", scalar("3")),
769 ]);
770 assert_matches_cst_formatter(&obj, "three scalars at root");
771 }
772
773 #[test]
774 fn blank_line_03_scalar_then_block() {
775 let block = obj_multiline(vec![
777 entry("host", scalar("localhost")),
778 entry("port", scalar("8080")),
779 ]);
780 let obj = obj_multiline(vec![entry("name", scalar("myapp")), entry("server", block)]);
781 assert_matches_cst_formatter(&obj, "scalar then block");
782 }
783
784 #[test]
785 fn blank_line_04_block_then_scalar() {
786 let block = obj_multiline(vec![entry("host", scalar("localhost"))]);
788 let obj = obj_multiline(vec![entry("server", block), entry("name", scalar("myapp"))]);
789 assert_matches_cst_formatter(&obj, "block then scalar");
790 }
791
792 #[test]
793 fn blank_line_05_two_blocks() {
794 let block1 = obj_multiline(vec![entry("a", scalar("1"))]);
796 let block2 = obj_multiline(vec![entry("b", scalar("2"))]);
797 let obj = obj_multiline(vec![entry("first", block1), entry("second", block2)]);
798 assert_matches_cst_formatter(&obj, "two blocks");
799 }
800
801 #[test]
802 fn blank_line_06_inline_objects_at_root() {
803 let inline1 = obj_inline(vec![entry("x", scalar("1"))]);
805 let inline2 = obj_inline(vec![entry("y", scalar("2"))]);
806 let obj = obj_multiline(vec![entry("point1", inline1), entry("point2", inline2)]);
807 assert_matches_cst_formatter(&obj, "inline objects at root");
808 }
809
810 #[test]
811 fn blank_line_07_scalar_inline_scalar() {
812 let inline = obj_inline(vec![entry("x", scalar("1"))]);
813 let obj = obj_multiline(vec![
814 entry("name", scalar("test")),
815 entry("point", inline),
816 entry("count", scalar("5")),
817 ]);
818 assert_matches_cst_formatter(&obj, "scalar inline scalar");
819 }
820
821 #[test]
822 fn blank_line_08_doc_comment_entries() {
823 let obj = obj_multiline(vec![
825 entry_with_doc("name", scalar("Alice"), "The user's name"),
826 entry_with_doc("age", scalar("30"), "Age in years"),
827 ]);
828 assert_matches_cst_formatter(&obj, "doc comment entries");
829 }
830
831 #[test]
832 fn blank_line_09_mixed_doc_and_plain() {
833 let obj = obj_multiline(vec![
834 entry("plain", scalar("1")),
835 entry_with_doc("documented", scalar("2"), "Has docs"),
836 entry("another_plain", scalar("3")),
837 ]);
838 assert_matches_cst_formatter(&obj, "mixed doc and plain");
839 }
840
841 #[test]
842 fn blank_line_10_schema_declaration_first() {
843 let obj = obj_multiline(vec![
845 schema_entry(scalar("schema.styx")),
846 entry("name", scalar("test")),
847 ]);
848 assert_matches_cst_formatter(&obj, "schema declaration first");
849 }
850
851 #[test]
852 fn blank_line_11_nested_blocks_dont_get_extra_blanks() {
853 let inner = obj_multiline(vec![entry("a", scalar("1")), entry("b", scalar("2"))]);
855 let obj = obj_multiline(vec![entry("wrapper", inner)]);
856 assert_matches_cst_formatter(&obj, "nested block internal");
857 }
858
859 #[test]
860 fn blank_line_12_deeply_nested() {
861 let level3 = obj_multiline(vec![entry("deep", scalar("value"))]);
862 let level2 = obj_multiline(vec![entry("inner", level3)]);
863 let obj = obj_multiline(vec![
864 entry("outer", level2),
865 entry("sibling", scalar("test")),
866 ]);
867 assert_matches_cst_formatter(&obj, "deeply nested");
868 }
869
870 #[test]
871 fn blank_line_13_tagged_block() {
872 let tagged_block = tagged_obj(
873 "object",
874 vec![entry("field", tagged("string"))],
875 Separator::Newline,
876 );
877 let obj = obj_multiline(vec![
878 entry("name", scalar("test")),
879 entry("schema", tagged_block),
880 ]);
881 assert_matches_cst_formatter(&obj, "tagged block");
882 }
883
884 #[test]
885 fn blank_line_14_sequence_of_scalars() {
886 let seq = seq_value(vec![scalar("a"), scalar("b"), scalar("c")]);
887 let obj = obj_multiline(vec![entry("items", seq), entry("count", scalar("3"))]);
888 assert_matches_cst_formatter(&obj, "sequence of scalars");
889 }
890
891 #[test]
892 fn blank_line_15_meta_then_schema_block() {
893 let meta = obj_multiline(vec![
895 entry("id", scalar("test")),
896 entry("version", scalar("1")),
897 ]);
898 let schema_content = tagged_obj(
899 "object",
900 vec![entry("name", tagged("string"))],
901 Separator::Newline,
902 );
903 let schema = obj_multiline(vec![unit_entry(schema_content)]);
904 let obj = obj_multiline(vec![entry("meta", meta), entry("schema", schema)]);
905 assert_matches_cst_formatter(&obj, "meta then schema block");
906 }
907
908 #[test]
909 fn blank_line_16_three_blocks() {
910 let b1 = obj_multiline(vec![entry("a", scalar("1"))]);
911 let b2 = obj_multiline(vec![entry("b", scalar("2"))]);
912 let b3 = obj_multiline(vec![entry("c", scalar("3"))]);
913 let obj = obj_multiline(vec![
914 entry("first", b1),
915 entry("second", b2),
916 entry("third", b3),
917 ]);
918 assert_matches_cst_formatter(&obj, "three blocks");
919 }
920
921 #[test]
922 fn blank_line_17_block_with_doc_comment() {
923 let block = obj_multiline(vec![entry("inner", scalar("value"))]);
924 let obj = obj_multiline(vec![
925 entry_with_doc("config", block, "Configuration section"),
926 entry("name", scalar("test")),
927 ]);
928 assert_matches_cst_formatter(&obj, "block with doc comment");
929 }
930
931 #[test]
932 fn blank_line_18_empty_inline_objects() {
933 let empty1 = obj_inline(vec![]);
934 let empty2 = obj_inline(vec![]);
935 let obj = obj_multiline(vec![entry("a", empty1), entry("b", empty2)]);
936 assert_matches_cst_formatter(&obj, "empty inline objects");
937 }
938
939 #[test]
940 fn blank_line_19_single_entry_block() {
941 let block = obj_multiline(vec![entry("only", scalar("one"))]);
942 let obj = obj_multiline(vec![entry("wrapper", block)]);
943 assert_matches_cst_formatter(&obj, "single entry block");
944 }
945
946 #[test]
947 fn blank_line_20_alternating_scalar_block() {
948 let b1 = obj_multiline(vec![entry("x", scalar("1"))]);
949 let b2 = obj_multiline(vec![entry("y", scalar("2"))]);
950 let obj = obj_multiline(vec![
951 entry("s1", scalar("a")),
952 entry("block1", b1),
953 entry("s2", scalar("b")),
954 entry("block2", b2),
955 entry("s3", scalar("c")),
956 ]);
957 assert_matches_cst_formatter(&obj, "alternating scalar block");
958 }
959}