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