1use alloc::borrow::Cow;
7use alloc::collections::BTreeMap;
8use alloc::collections::BTreeSet;
9use alloc::string::String;
10use alloc::vec;
11use alloc::vec::Vec;
12use core::fmt::Write;
13
14use facet_core::{
15 Attr, ConstTypeId, Def, EnumRepr, EnumType, Field, PointerType, Shape, StructKind, StructType,
16 Type, UserType, Variant,
17};
18use owo_colors::OwoColorize;
19
20pub mod colors {
29 use crate::color::Palette;
30 use owo_colors::Style;
31
32 pub const fn keyword() -> Style {
34 Style::new().fg_rgb::<
35 { Palette::MELANGE_DARK.accents[1].0 },
36 { Palette::MELANGE_DARK.accents[1].1 },
37 { Palette::MELANGE_DARK.accents[1].2 },
38 >()
39 }
40
41 pub const fn type_name() -> Style {
43 Style::new().fg_rgb::<
44 { Palette::MELANGE_DARK.type_name.0 },
45 { Palette::MELANGE_DARK.type_name.1 },
46 { Palette::MELANGE_DARK.type_name.2 },
47 >()
48 }
49
50 pub const fn field_name() -> Style {
52 Style::new().fg_rgb::<
53 { Palette::MELANGE_DARK.field_name.0 },
54 { Palette::MELANGE_DARK.field_name.1 },
55 { Palette::MELANGE_DARK.field_name.2 },
56 >()
57 }
58
59 pub const fn primitive() -> Style {
61 Style::new().fg_rgb::<
62 { Palette::MELANGE_DARK.type_name.0 },
63 { Palette::MELANGE_DARK.type_name.1 },
64 { Palette::MELANGE_DARK.type_name.2 },
65 >()
66 }
67
68 pub const fn punctuation() -> Style {
70 Style::new().fg_rgb::<
71 { Palette::MELANGE_DARK.punctuation.0 },
72 { Palette::MELANGE_DARK.punctuation.1 },
73 { Palette::MELANGE_DARK.punctuation.2 },
74 >()
75 }
76
77 pub const fn attribute() -> Style {
79 Style::new().fg_rgb::<
80 { Palette::MELANGE_DARK.comment.0 },
81 { Palette::MELANGE_DARK.comment.1 },
82 { Palette::MELANGE_DARK.comment.2 },
83 >()
84 }
85
86 pub const fn attribute_content() -> Style {
88 Style::new().fg_rgb::<
89 { Palette::MELANGE_DARK.comment.0 },
90 { Palette::MELANGE_DARK.comment.1 },
91 { Palette::MELANGE_DARK.comment.2 },
92 >()
93 }
94
95 pub const fn string() -> Style {
97 Style::new().fg_rgb::<
98 { Palette::MELANGE_DARK.string.0 },
99 { Palette::MELANGE_DARK.string.1 },
100 { Palette::MELANGE_DARK.string.2 },
101 >()
102 }
103
104 pub const fn container() -> Style {
106 Style::new().fg_rgb::<
107 { Palette::MELANGE_DARK.type_name.0 },
108 { Palette::MELANGE_DARK.type_name.1 },
109 { Palette::MELANGE_DARK.type_name.2 },
110 >()
111 }
112
113 pub const fn comment() -> Style {
115 Style::new().fg_rgb::<
116 { Palette::MELANGE_DARK.comment.0 },
117 { Palette::MELANGE_DARK.comment.1 },
118 { Palette::MELANGE_DARK.comment.2 },
119 >()
120 }
121
122 pub mod themed {
128 use crate::color::detected_palette;
129 use owo_colors::Style;
130
131 pub fn keyword() -> Style {
133 Style::new().color(detected_palette().accents[1])
134 }
135
136 pub fn type_name() -> Style {
138 Style::new().color(detected_palette().type_name)
139 }
140
141 pub fn field_name() -> Style {
143 Style::new().color(detected_palette().field_name)
144 }
145
146 pub fn primitive() -> Style {
148 Style::new().color(detected_palette().type_name)
149 }
150
151 pub fn punctuation() -> Style {
153 Style::new().color(detected_palette().punctuation)
154 }
155
156 pub fn attribute() -> Style {
158 Style::new().color(detected_palette().comment)
159 }
160
161 pub fn attribute_content() -> Style {
163 Style::new().color(detected_palette().comment)
164 }
165
166 pub fn string() -> Style {
168 Style::new().color(detected_palette().string)
169 }
170
171 pub fn container() -> Style {
173 Style::new().color(detected_palette().type_name)
174 }
175
176 pub fn comment() -> Style {
178 Style::new().color(detected_palette().comment)
179 }
180 }
181}
182
183#[derive(Clone, Debug, Default)]
185pub struct ShapeFormatConfig {
186 pub show_doc_comments: bool,
188 pub show_third_party_attrs: bool,
190 pub expand_nested_types: bool,
192}
193
194impl ShapeFormatConfig {
195 pub fn new() -> Self {
197 Self {
198 expand_nested_types: true,
199 ..Self::default()
200 }
201 }
202
203 pub const fn with_doc_comments(mut self) -> Self {
205 self.show_doc_comments = true;
206 self
207 }
208
209 pub const fn with_third_party_attrs(mut self) -> Self {
211 self.show_third_party_attrs = true;
212 self
213 }
214
215 pub const fn with_all_metadata(mut self) -> Self {
217 self.show_doc_comments = true;
218 self.show_third_party_attrs = true;
219 self
220 }
221
222 pub const fn without_nested_types(mut self) -> Self {
224 self.expand_nested_types = false;
225 self
226 }
227}
228
229#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
231pub enum PathSegment {
232 Field(Cow<'static, str>),
234 Variant(Cow<'static, str>),
236 Index(usize),
238 Key(Cow<'static, str>),
240}
241
242pub type Path = Vec<PathSegment>;
244
245pub type Span = (usize, usize);
247
248#[derive(Clone, Debug, Default, PartialEq, Eq)]
250pub struct FieldSpan {
251 pub key: Span,
253 pub value: Span,
255}
256
257#[derive(Debug)]
259pub struct FormattedShape {
260 pub text: String,
262 pub spans: BTreeMap<Path, FieldSpan>,
264 pub type_name_span: Option<Span>,
266 pub type_span: Option<Span>,
268 pub type_end_span: Option<Span>,
270}
271
272pub fn strip_ansi(s: &str) -> String {
274 let mut result = String::with_capacity(s.len());
275 let mut chars = s.chars().peekable();
276
277 while let Some(c) = chars.next() {
278 if c == '\x1b' {
279 if chars.peek() == Some(&'[') {
281 chars.next(); while let Some(&next) = chars.peek() {
284 chars.next();
285 if next.is_ascii_alphabetic() {
286 break;
287 }
288 }
289 }
290 } else {
291 result.push(c);
292 }
293 }
294 result
295}
296
297pub fn format_shape(shape: &Shape) -> String {
301 strip_ansi(&format_shape_colored(shape))
302}
303
304pub fn format_shape_with_config(shape: &Shape, config: &ShapeFormatConfig) -> String {
306 strip_ansi(&format_shape_colored_with_config(shape, config))
307}
308
309pub fn format_shape_with_spans(shape: &Shape) -> FormattedShape {
314 format_shape_with_spans_and_config(shape, &ShapeFormatConfig::default().with_all_metadata())
315}
316
317pub fn format_shape_with_spans_and_config(
320 shape: &Shape,
321 config: &ShapeFormatConfig,
322) -> FormattedShape {
323 let mut ctx = SpanTrackingContext::new(config);
324 format_shape_into_with_spans(shape, &mut ctx).expect("Formatting failed");
325 FormattedShape {
326 text: ctx.output,
327 spans: ctx.spans,
328 type_name_span: ctx.type_name_span,
329 type_span: ctx.type_span,
330 type_end_span: ctx.type_end_span,
331 }
332}
333
334pub fn format_shape_colored(shape: &Shape) -> String {
338 format_shape_colored_with_config(shape, &ShapeFormatConfig::default().with_all_metadata())
339}
340
341pub fn format_shape_colored_with_config(shape: &Shape, config: &ShapeFormatConfig) -> String {
343 let mut output = String::new();
344 format_shape_colored_into_with_config(shape, &mut output, config).expect("Formatting failed");
345 output
346}
347
348pub fn format_shape_colored_into(shape: &Shape, output: &mut String) -> core::fmt::Result {
350 format_shape_colored_into_with_config(shape, output, &ShapeFormatConfig::default())
351}
352
353pub fn format_shape_colored_into_with_config(
355 shape: &Shape,
356 output: &mut String,
357 config: &ShapeFormatConfig,
358) -> core::fmt::Result {
359 let mut printed: BTreeSet<ConstTypeId> = BTreeSet::new();
360 let mut queue: Vec<&Shape> = Vec::new();
361 queue.push(shape);
362
363 while let Some(current) = queue.pop() {
364 if !printed.insert(current.id) {
365 continue;
366 }
367
368 if printed.len() > 1 {
369 writeln!(output)?;
370 writeln!(output)?;
371 }
372
373 match current.def {
374 Def::Map(_) | Def::List(_) | Def::Option(_) | Def::Array(_) => {
375 printed.remove(¤t.id);
376 continue;
377 }
378 _ => {}
379 }
380
381 match ¤t.ty {
382 Type::User(user_type) => match user_type {
383 UserType::Struct(struct_type) => {
384 format_struct_colored(current, struct_type, output, config)?;
385 collect_nested_types(struct_type, &mut queue);
386 }
387 UserType::Enum(enum_type) => {
388 format_enum_colored(current, enum_type, output, config)?;
389 for variant in enum_type.variants {
390 collect_nested_types(&variant.data, &mut queue);
391 }
392 }
393 UserType::Union(_) | UserType::Opaque => {
394 printed.remove(¤t.id);
395 }
396 },
397 _ => {
398 printed.remove(¤t.id);
399 }
400 }
401 }
402 Ok(())
403}
404
405fn format_struct_colored(
406 shape: &Shape,
407 struct_type: &StructType,
408 output: &mut String,
409 config: &ShapeFormatConfig,
410) -> core::fmt::Result {
411 if config.show_doc_comments {
413 write_doc_comments_colored(shape.doc, output, "")?;
414 }
415
416 write!(output, "{}", "#[".style(colors::themed::attribute()))?;
418 write!(
419 output,
420 "{}",
421 "derive".style(colors::themed::attribute_content())
422 )?;
423 write!(output, "{}", "(".style(colors::themed::attribute()))?;
424 write!(
425 output,
426 "{}",
427 "Facet".style(colors::themed::attribute_content())
428 )?;
429 writeln!(output, "{}", ")]".style(colors::themed::attribute()))?;
430
431 write_facet_attrs_colored(shape, output)?;
433
434 if config.show_third_party_attrs {
436 write_third_party_attrs_colored(shape.attributes, output, "")?;
437 }
438
439 match struct_type.kind {
440 StructKind::Struct => {
441 write!(output, "{} ", "struct".style(colors::themed::keyword()))?;
442 write!(
443 output,
444 "{}",
445 shape.type_identifier.style(colors::themed::type_name())
446 )?;
447 writeln!(output, " {}", "{".style(colors::themed::punctuation()))?;
448
449 for (i, field) in struct_type.fields.iter().enumerate() {
450 if i > 0 {
452 writeln!(output)?;
453 }
454 if config.show_doc_comments {
456 write_doc_comments_colored(field.doc, output, " ")?;
457 }
458 if config.show_third_party_attrs {
460 write_field_third_party_attrs_colored(field, output, " ")?;
461 }
462 write!(
463 output,
464 " {}",
465 field.name.style(colors::themed::field_name())
466 )?;
467 write!(output, "{} ", ":".style(colors::themed::punctuation()))?;
468 write_type_name_colored(field.shape(), output)?;
469 writeln!(output, "{}", ",".style(colors::themed::punctuation()))?;
470 }
471 write!(output, "{}", "}".style(colors::themed::punctuation()))?;
472 }
473 StructKind::Tuple | StructKind::TupleStruct => {
474 write!(output, "{} ", "struct".style(colors::themed::keyword()))?;
475 write!(
476 output,
477 "{}",
478 shape.type_identifier.style(colors::themed::type_name())
479 )?;
480 write!(output, "{}", "(".style(colors::themed::punctuation()))?;
481 for (i, field) in struct_type.fields.iter().enumerate() {
482 if i > 0 {
483 write!(output, "{} ", ",".style(colors::themed::punctuation()))?;
484 }
485 write_type_name_colored(field.shape(), output)?;
486 }
487 write!(
488 output,
489 "{}{}",
490 ")".style(colors::themed::punctuation()),
491 ";".style(colors::themed::punctuation())
492 )?;
493 }
494 StructKind::Unit => {
495 write!(output, "{} ", "struct".style(colors::themed::keyword()))?;
496 write!(
497 output,
498 "{}",
499 shape.type_identifier.style(colors::themed::type_name())
500 )?;
501 write!(output, "{}", ";".style(colors::themed::punctuation()))?;
502 }
503 }
504 Ok(())
505}
506
507fn format_enum_colored(
508 shape: &Shape,
509 enum_type: &EnumType,
510 output: &mut String,
511 config: &ShapeFormatConfig,
512) -> core::fmt::Result {
513 if config.show_doc_comments {
515 write_doc_comments_colored(shape.doc, output, "")?;
516 }
517
518 write!(output, "{}", "#[".style(colors::themed::attribute()))?;
520 write!(
521 output,
522 "{}",
523 "derive".style(colors::themed::attribute_content())
524 )?;
525 write!(output, "{}", "(".style(colors::themed::attribute()))?;
526 write!(
527 output,
528 "{}",
529 "Facet".style(colors::themed::attribute_content())
530 )?;
531 writeln!(output, "{}", ")]".style(colors::themed::attribute()))?;
532
533 let repr_str = match enum_type.enum_repr {
535 EnumRepr::Rust => None,
536 EnumRepr::RustNPO => None,
537 EnumRepr::U8 => Some("u8"),
538 EnumRepr::U16 => Some("u16"),
539 EnumRepr::U32 => Some("u32"),
540 EnumRepr::U64 => Some("u64"),
541 EnumRepr::USize => Some("usize"),
542 EnumRepr::I8 => Some("i8"),
543 EnumRepr::I16 => Some("i16"),
544 EnumRepr::I32 => Some("i32"),
545 EnumRepr::I64 => Some("i64"),
546 EnumRepr::ISize => Some("isize"),
547 };
548
549 if let Some(repr) = repr_str {
550 write!(output, "{}", "#[".style(colors::themed::attribute()))?;
551 write!(
552 output,
553 "{}",
554 "repr".style(colors::themed::attribute_content())
555 )?;
556 write!(output, "{}", "(".style(colors::themed::attribute()))?;
557 write!(output, "{}", repr.style(colors::themed::primitive()))?;
558 writeln!(output, "{}", ")]".style(colors::themed::attribute()))?;
559 }
560
561 write_facet_attrs_colored(shape, output)?;
563
564 if config.show_third_party_attrs {
566 write_third_party_attrs_colored(shape.attributes, output, "")?;
567 }
568
569 write!(output, "{} ", "enum".style(colors::themed::keyword()))?;
571 write!(
572 output,
573 "{}",
574 shape.type_identifier.style(colors::themed::type_name())
575 )?;
576 writeln!(output, " {}", "{".style(colors::themed::punctuation()))?;
577
578 for (vi, variant) in enum_type.variants.iter().enumerate() {
579 if vi > 0 {
581 writeln!(output)?;
582 }
583 if config.show_doc_comments {
585 write_doc_comments_colored(variant.doc, output, " ")?;
586 }
587 if config.show_third_party_attrs {
589 write_variant_third_party_attrs_colored(variant, output, " ")?;
590 }
591
592 match variant.data.kind {
593 StructKind::Unit => {
594 write!(
595 output,
596 " {}",
597 variant.name.style(colors::themed::type_name())
598 )?;
599 writeln!(output, "{}", ",".style(colors::themed::punctuation()))?;
600 }
601 StructKind::Tuple | StructKind::TupleStruct => {
602 write!(
603 output,
604 " {}",
605 variant.name.style(colors::themed::type_name())
606 )?;
607 write!(output, "{}", "(".style(colors::themed::punctuation()))?;
608 for (i, field) in variant.data.fields.iter().enumerate() {
609 if i > 0 {
610 write!(output, "{} ", ",".style(colors::themed::punctuation()))?;
611 }
612 write_type_name_colored(field.shape(), output)?;
613 }
614 write!(output, "{}", ")".style(colors::themed::punctuation()))?;
615 writeln!(output, "{}", ",".style(colors::themed::punctuation()))?;
616 }
617 StructKind::Struct => {
618 write!(
619 output,
620 " {}",
621 variant.name.style(colors::themed::type_name())
622 )?;
623 writeln!(output, " {}", "{".style(colors::themed::punctuation()))?;
624 for (fi, field) in variant.data.fields.iter().enumerate() {
625 if fi > 0 {
627 writeln!(output)?;
628 }
629 if config.show_doc_comments {
631 write_doc_comments_colored(field.doc, output, " ")?;
632 }
633 if config.show_third_party_attrs {
635 write_field_third_party_attrs_colored(field, output, " ")?;
636 }
637 write!(
638 output,
639 " {}",
640 field.name.style(colors::themed::field_name())
641 )?;
642 write!(output, "{} ", ":".style(colors::themed::punctuation()))?;
643 write_type_name_colored(field.shape(), output)?;
644 writeln!(output, "{}", ",".style(colors::themed::punctuation()))?;
645 }
646 write!(output, " {}", "}".style(colors::themed::punctuation()))?;
647 writeln!(output, "{}", ",".style(colors::themed::punctuation()))?;
648 }
649 }
650 }
651
652 write!(output, "{}", "}".style(colors::themed::punctuation()))?;
653 Ok(())
654}
655
656fn write_facet_attrs_colored(shape: &Shape, output: &mut String) -> core::fmt::Result {
657 let mut attrs: Vec<String> = Vec::new();
658
659 if let Some(tag) = shape.get_tag_attr() {
660 if let Some(content) = shape.get_content_attr() {
661 attrs.push(alloc::format!(
662 "{}{}{}{}{}{}{}{}{}",
663 "tag".style(colors::themed::attribute_content()),
664 " = ".style(colors::themed::punctuation()),
665 "\"".style(colors::themed::string()),
666 tag.style(colors::themed::string()),
667 "\"".style(colors::themed::string()),
668 ", ".style(colors::themed::punctuation()),
669 "content".style(colors::themed::attribute_content()),
670 " = ".style(colors::themed::punctuation()),
671 format!("\"{content}\"").style(colors::themed::string()),
672 ));
673 } else {
674 attrs.push(alloc::format!(
675 "{}{}{}",
676 "tag".style(colors::themed::attribute_content()),
677 " = ".style(colors::themed::punctuation()),
678 format!("\"{tag}\"").style(colors::themed::string()),
679 ));
680 }
681 }
682
683 if shape.is_untagged() {
684 attrs.push(alloc::format!(
685 "{}",
686 "untagged".style(colors::themed::attribute_content())
687 ));
688 }
689
690 if shape.has_deny_unknown_fields_attr() {
691 attrs.push(alloc::format!(
692 "{}",
693 "deny_unknown_fields".style(colors::themed::attribute_content())
694 ));
695 }
696
697 if !attrs.is_empty() {
698 write!(output, "{}", "#[".style(colors::themed::attribute()))?;
699 write!(
700 output,
701 "{}",
702 "facet".style(colors::themed::attribute_content())
703 )?;
704 write!(output, "{}", "(".style(colors::themed::attribute()))?;
705 write!(
706 output,
707 "{}",
708 attrs.join(&format!("{}", ", ".style(colors::themed::punctuation())))
709 )?;
710 writeln!(output, "{}", ")]".style(colors::themed::attribute()))?;
711 }
712
713 Ok(())
714}
715
716fn write_doc_comments_colored(
718 doc: &[&str],
719 output: &mut String,
720 indent: &str,
721) -> core::fmt::Result {
722 for line in doc {
723 write!(output, "{indent}")?;
724 writeln!(
725 output,
726 "{}",
727 format!("///{line}").style(colors::themed::comment())
728 )?;
729 }
730 Ok(())
731}
732
733fn write_third_party_attrs_colored(
736 attributes: &[Attr],
737 output: &mut String,
738 indent: &str,
739) -> core::fmt::Result {
740 let mut by_namespace: BTreeMap<&'static str, Vec<&'static str>> = BTreeMap::new();
742 for attr in attributes {
743 if let Some(ns) = attr.ns {
744 by_namespace.entry(ns).or_default().push(attr.key);
745 }
746 }
747
748 for (ns, keys) in by_namespace {
750 write!(output, "{indent}")?;
751 write!(output, "{}", "#[".style(colors::themed::attribute()))?;
752 write!(
753 output,
754 "{}",
755 "facet".style(colors::themed::attribute_content())
756 )?;
757 write!(output, "{}", "(".style(colors::themed::attribute()))?;
758
759 for (i, key) in keys.iter().enumerate() {
760 if i > 0 {
761 write!(output, "{}", ", ".style(colors::themed::punctuation()))?;
762 }
763 write!(output, "{}", ns.style(colors::themed::attribute_content()))?;
764 write!(output, "{}", "::".style(colors::themed::punctuation()))?;
765 write!(output, "{}", key.style(colors::themed::attribute_content()))?;
766 }
767
768 write!(output, "{}", ")".style(colors::themed::attribute()))?;
769 writeln!(output, "{}", "]".style(colors::themed::attribute()))?;
770 }
771 Ok(())
772}
773
774fn write_field_third_party_attrs_colored(
776 field: &Field,
777 output: &mut String,
778 indent: &str,
779) -> core::fmt::Result {
780 write_third_party_attrs_colored(field.attributes, output, indent)
781}
782
783fn write_variant_third_party_attrs_colored(
785 variant: &Variant,
786 output: &mut String,
787 indent: &str,
788) -> core::fmt::Result {
789 write_third_party_attrs_colored(variant.attributes, output, indent)
790}
791
792fn write_type_name_colored(shape: &Shape, output: &mut String) -> core::fmt::Result {
793 match shape.def {
794 Def::Scalar => {
795 let id = shape.type_identifier;
797 if is_primitive_type(id) {
798 write!(output, "{}", id.style(colors::themed::primitive()))?;
799 } else {
800 write!(output, "{}", id.style(colors::themed::type_name()))?;
801 }
802 }
803 Def::Pointer(_) => {
804 if let Type::Pointer(PointerType::Reference(r)) = shape.ty
805 && let Def::Array(array_def) = r.target.def
806 {
807 write!(output, "{}", "&[".style(colors::themed::punctuation()))?;
808 write_type_name_colored(array_def.t, output)?;
809 write!(
810 output,
811 "{}{}{}",
812 "; ".style(colors::themed::punctuation()),
813 array_def.n.style(colors::themed::primitive()),
814 "]".style(colors::themed::punctuation())
815 )?;
816 return Ok(());
817 }
818 write!(
819 output,
820 "{}",
821 shape.type_identifier.style(colors::themed::type_name())
822 )?;
823 }
824 Def::List(list_def) => {
825 write!(output, "{}", "Vec".style(colors::themed::container()))?;
826 write!(output, "{}", "<".style(colors::themed::punctuation()))?;
827 write_type_name_colored(list_def.t, output)?;
828 write!(output, "{}", ">".style(colors::themed::punctuation()))?;
829 }
830 Def::Array(array_def) => {
831 write!(output, "{}", "[".style(colors::themed::punctuation()))?;
832 write_type_name_colored(array_def.t, output)?;
833 write!(
834 output,
835 "{}{}{}",
836 "; ".style(colors::themed::punctuation()),
837 array_def.n.style(colors::themed::primitive()),
838 "]".style(colors::themed::punctuation())
839 )?;
840 }
841 Def::Map(map_def) => {
842 let map_name = if shape.type_identifier.contains("BTreeMap") {
843 "BTreeMap"
844 } else {
845 "HashMap"
846 };
847 write!(output, "{}", map_name.style(colors::themed::container()))?;
848 write!(output, "{}", "<".style(colors::themed::punctuation()))?;
849 write_type_name_colored(map_def.k, output)?;
850 write!(output, "{} ", ",".style(colors::themed::punctuation()))?;
851 write_type_name_colored(map_def.v, output)?;
852 write!(output, "{}", ">".style(colors::themed::punctuation()))?;
853 }
854 Def::Option(option_def) => {
855 write!(output, "{}", "Option".style(colors::themed::container()))?;
856 write!(output, "{}", "<".style(colors::themed::punctuation()))?;
857 write_type_name_colored(option_def.t, output)?;
858 write!(output, "{}", ">".style(colors::themed::punctuation()))?;
859 }
860 _ => {
861 let id = shape.type_identifier;
862 if is_primitive_type(id) {
863 write!(output, "{}", id.style(colors::themed::primitive()))?;
864 } else {
865 write!(output, "{}", id.style(colors::themed::type_name()))?;
866 }
867 }
868 }
869 Ok(())
870}
871
872fn is_primitive_type(id: &str) -> bool {
874 matches!(
875 id,
876 "u8" | "u16"
877 | "u32"
878 | "u64"
879 | "u128"
880 | "usize"
881 | "i8"
882 | "i16"
883 | "i32"
884 | "i64"
885 | "i128"
886 | "isize"
887 | "f32"
888 | "f64"
889 | "bool"
890 | "char"
891 | "str"
892 | "&str"
893 | "String"
894 )
895}
896
897struct SpanTrackingContext<'a> {
899 output: String,
900 spans: BTreeMap<Path, FieldSpan>,
901 type_name_span: Option<Span>,
903 type_span: Option<Span>,
905 type_end_span: Option<Span>,
907 current_type: Option<&'static str>,
909 config: &'a ShapeFormatConfig,
911}
912
913impl<'a> SpanTrackingContext<'a> {
914 const fn new(config: &'a ShapeFormatConfig) -> Self {
915 Self {
916 output: String::new(),
917 spans: BTreeMap::new(),
918 type_name_span: None,
919 type_span: None,
920 type_end_span: None,
921 current_type: None,
922 config,
923 }
924 }
925
926 const fn len(&self) -> usize {
927 self.output.len()
928 }
929
930 fn record_field_span(&mut self, path: Path, key_span: Span, value_span: Span) {
931 self.spans.insert(
932 path,
933 FieldSpan {
934 key: key_span,
935 value: value_span,
936 },
937 );
938 }
939}
940
941fn format_shape_into_with_spans(shape: &Shape, ctx: &mut SpanTrackingContext) -> core::fmt::Result {
943 let mut printed: BTreeSet<ConstTypeId> = BTreeSet::new();
945 let mut queue: Vec<&Shape> = Vec::new();
947
948 queue.push(shape);
950
951 while let Some(current) = queue.pop() {
952 if !printed.insert(current.id) {
954 continue;
955 }
956
957 if printed.len() > 1 {
959 writeln!(ctx.output)?;
960 writeln!(ctx.output)?;
961 }
962
963 match current.def {
966 Def::Map(_) | Def::List(_) | Def::Option(_) | Def::Array(_) => {
967 printed.remove(¤t.id);
969 continue;
970 }
971 _ => {}
972 }
973
974 match ¤t.ty {
976 Type::User(user_type) => match user_type {
977 UserType::Struct(struct_type) => {
978 ctx.current_type = Some(current.type_identifier);
979 format_struct_with_spans(current, struct_type, ctx)?;
980 ctx.current_type = None;
981 if ctx.config.expand_nested_types {
983 collect_nested_types(struct_type, &mut queue);
984 }
985 }
986 UserType::Enum(enum_type) => {
987 ctx.current_type = Some(current.type_identifier);
988 format_enum_with_spans(current, enum_type, ctx)?;
989 ctx.current_type = None;
990 if ctx.config.expand_nested_types {
992 for variant in enum_type.variants {
993 collect_nested_types(&variant.data, &mut queue);
994 }
995 }
996 }
997 UserType::Union(_) | UserType::Opaque => {
998 printed.remove(¤t.id);
1001 }
1002 },
1003 _ => {
1004 printed.remove(¤t.id);
1006 }
1007 }
1008 }
1009 Ok(())
1010}
1011
1012fn format_struct_with_spans(
1013 shape: &Shape,
1014 struct_type: &StructType,
1015 ctx: &mut SpanTrackingContext,
1016) -> core::fmt::Result {
1017 let type_start = ctx.len();
1019
1020 if ctx.config.show_doc_comments {
1022 write_doc_comments(shape.doc, &mut ctx.output, "")?;
1023 }
1024
1025 writeln!(ctx.output, "#[derive(Facet)]")?;
1027
1028 write_facet_attrs(shape, &mut ctx.output)?;
1030
1031 if ctx.config.show_third_party_attrs {
1033 write_third_party_attrs(shape.attributes, &mut ctx.output, "")?;
1034 }
1035
1036 match struct_type.kind {
1038 StructKind::Struct => {
1039 write!(ctx.output, "struct ")?;
1040 let type_name_start = ctx.len();
1041 write!(ctx.output, "{}", shape.type_identifier)?;
1042 let type_name_end = ctx.len();
1043 ctx.type_name_span = Some((type_name_start, type_name_end));
1044 writeln!(ctx.output, " {{")?;
1045 for field in struct_type.fields {
1046 if ctx.config.show_doc_comments {
1048 write_doc_comments(field.doc, &mut ctx.output, " ")?;
1049 }
1050 if ctx.config.show_third_party_attrs {
1052 write_field_third_party_attrs(field, &mut ctx.output, " ")?;
1053 }
1054 write!(ctx.output, " ")?;
1055 let key_start = ctx.len();
1057 write!(ctx.output, "{}", field.name)?;
1058 let key_end = ctx.len();
1059 write!(ctx.output, ": ")?;
1060 let value_start = ctx.len();
1062 write_type_name(field.shape(), &mut ctx.output)?;
1063 let value_end = ctx.len();
1064 ctx.record_field_span(
1065 vec![PathSegment::Field(Cow::Borrowed(field.name))],
1066 (key_start, key_end),
1067 (value_start, value_end),
1068 );
1069 writeln!(ctx.output, ",")?;
1070 }
1071 let type_end_start = ctx.len();
1072 write!(ctx.output, "}}")?;
1073 let type_end_end = ctx.len();
1074 ctx.type_end_span = Some((type_end_start, type_end_end));
1075 }
1076 StructKind::Tuple | StructKind::TupleStruct => {
1077 write!(ctx.output, "struct ")?;
1078 let type_name_start = ctx.len();
1079 write!(ctx.output, "{}", shape.type_identifier)?;
1080 let type_name_end = ctx.len();
1081 ctx.type_name_span = Some((type_name_start, type_name_end));
1082 write!(ctx.output, "(")?;
1083 for (i, field) in struct_type.fields.iter().enumerate() {
1084 if i > 0 {
1085 write!(ctx.output, ", ")?;
1086 }
1087 let type_start = ctx.len();
1089 write_type_name(field.shape(), &mut ctx.output)?;
1090 let type_end = ctx.len();
1091 let field_name = if !field.name.is_empty() {
1093 field.name
1094 } else {
1095 continue;
1097 };
1098 ctx.record_field_span(
1099 vec![PathSegment::Field(Cow::Borrowed(field_name))],
1100 (type_start, type_end), (type_start, type_end),
1102 );
1103 }
1104 let type_end_start = ctx.len();
1105 write!(ctx.output, ");")?;
1106 let type_end_end = ctx.len();
1107 ctx.type_end_span = Some((type_end_start, type_end_end));
1108 }
1109 StructKind::Unit => {
1110 write!(ctx.output, "struct ")?;
1111 let type_name_start = ctx.len();
1112 write!(ctx.output, "{}", shape.type_identifier)?;
1113 let type_name_end = ctx.len();
1114 ctx.type_name_span = Some((type_name_start, type_name_end));
1115 let type_end_start = ctx.len();
1116 write!(ctx.output, ";")?;
1117 let type_end_end = ctx.len();
1118 ctx.type_end_span = Some((type_end_start, type_end_end));
1119 }
1120 }
1121
1122 let type_end = ctx.len();
1124 ctx.type_span = Some((type_start, type_end));
1125 ctx.record_field_span(vec![], (type_start, type_end), (type_start, type_end));
1126
1127 Ok(())
1128}
1129
1130fn format_enum_with_spans(
1131 shape: &Shape,
1132 enum_type: &EnumType,
1133 ctx: &mut SpanTrackingContext,
1134) -> core::fmt::Result {
1135 let type_start = ctx.len();
1137
1138 if ctx.config.show_doc_comments {
1140 write_doc_comments(shape.doc, &mut ctx.output, "")?;
1141 }
1142
1143 writeln!(ctx.output, "#[derive(Facet)]")?;
1145
1146 let repr_str = match enum_type.enum_repr {
1148 EnumRepr::Rust => None,
1149 EnumRepr::RustNPO => None,
1150 EnumRepr::U8 => Some("u8"),
1151 EnumRepr::U16 => Some("u16"),
1152 EnumRepr::U32 => Some("u32"),
1153 EnumRepr::U64 => Some("u64"),
1154 EnumRepr::USize => Some("usize"),
1155 EnumRepr::I8 => Some("i8"),
1156 EnumRepr::I16 => Some("i16"),
1157 EnumRepr::I32 => Some("i32"),
1158 EnumRepr::I64 => Some("i64"),
1159 EnumRepr::ISize => Some("isize"),
1160 };
1161
1162 if let Some(repr) = repr_str {
1163 writeln!(ctx.output, "#[repr({repr})]")?;
1164 }
1165
1166 write_facet_attrs(shape, &mut ctx.output)?;
1168
1169 if ctx.config.show_third_party_attrs {
1171 write_third_party_attrs(shape.attributes, &mut ctx.output, "")?;
1172 }
1173
1174 write!(ctx.output, "enum ")?;
1176 let type_name_start = ctx.len();
1177 write!(ctx.output, "{}", shape.type_identifier)?;
1178 let type_name_end = ctx.len();
1179 ctx.type_name_span = Some((type_name_start, type_name_end));
1180 writeln!(ctx.output, " {{")?;
1181
1182 for variant in enum_type.variants {
1183 if ctx.config.show_doc_comments {
1185 write_doc_comments(variant.doc, &mut ctx.output, " ")?;
1186 }
1187 if ctx.config.show_third_party_attrs {
1189 write_variant_third_party_attrs(variant, &mut ctx.output, " ")?;
1190 }
1191
1192 match variant.data.kind {
1193 StructKind::Unit => {
1194 write!(ctx.output, " ")?;
1195 let name_start = ctx.len();
1197 write!(ctx.output, "{}", variant.name)?;
1198 let name_end = ctx.len();
1199 ctx.record_field_span(
1200 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
1201 (name_start, name_end),
1202 (name_start, name_end),
1203 );
1204 writeln!(ctx.output, ",")?;
1205 }
1206 StructKind::Tuple | StructKind::TupleStruct => {
1207 write!(ctx.output, " ")?;
1208 let variant_name_start = ctx.len();
1209 write!(ctx.output, "{}", variant.name)?;
1210 let variant_name_end = ctx.len();
1211 write!(ctx.output, "(")?;
1212 let tuple_start = ctx.len();
1213 for (i, field) in variant.data.fields.iter().enumerate() {
1214 if i > 0 {
1215 write!(ctx.output, ", ")?;
1216 }
1217 let type_start = ctx.len();
1218 write_type_name(field.shape(), &mut ctx.output)?;
1219 let type_end = ctx.len();
1220 if !field.name.is_empty() {
1222 ctx.record_field_span(
1223 vec![
1224 PathSegment::Variant(Cow::Borrowed(variant.name)),
1225 PathSegment::Field(Cow::Borrowed(field.name)),
1226 ],
1227 (type_start, type_end),
1228 (type_start, type_end),
1229 );
1230 }
1231 }
1232 write!(ctx.output, ")")?;
1233 let tuple_end = ctx.len();
1234 ctx.record_field_span(
1236 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
1237 (variant_name_start, variant_name_end),
1238 (tuple_start, tuple_end),
1239 );
1240 writeln!(ctx.output, ",")?;
1241 }
1242 StructKind::Struct => {
1243 write!(ctx.output, " ")?;
1244 let variant_name_start = ctx.len();
1245 write!(ctx.output, "{}", variant.name)?;
1246 let variant_name_end = ctx.len();
1247 writeln!(ctx.output, " {{")?;
1248 let struct_start = ctx.len();
1249 for field in variant.data.fields {
1250 if ctx.config.show_doc_comments {
1252 write_doc_comments(field.doc, &mut ctx.output, " ")?;
1253 }
1254 if ctx.config.show_third_party_attrs {
1256 write_field_third_party_attrs(field, &mut ctx.output, " ")?;
1257 }
1258 write!(ctx.output, " ")?;
1259 let key_start = ctx.len();
1260 write!(ctx.output, "{}", field.name)?;
1261 let key_end = ctx.len();
1262 write!(ctx.output, ": ")?;
1263 let value_start = ctx.len();
1264 write_type_name(field.shape(), &mut ctx.output)?;
1265 let value_end = ctx.len();
1266 ctx.record_field_span(
1267 vec![
1268 PathSegment::Variant(Cow::Borrowed(variant.name)),
1269 PathSegment::Field(Cow::Borrowed(field.name)),
1270 ],
1271 (key_start, key_end),
1272 (value_start, value_end),
1273 );
1274 writeln!(ctx.output, ",")?;
1275 }
1276 write!(ctx.output, " }}")?;
1277 let struct_end = ctx.len();
1278 ctx.record_field_span(
1280 vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
1281 (variant_name_start, variant_name_end),
1282 (struct_start, struct_end),
1283 );
1284 writeln!(ctx.output, ",")?;
1285 }
1286 }
1287 }
1288
1289 let type_end_start = ctx.len();
1290 write!(ctx.output, "}}")?;
1291 let type_end_end = ctx.len();
1292 ctx.type_end_span = Some((type_end_start, type_end_end));
1293
1294 let type_end = ctx.len();
1296 ctx.type_span = Some((type_start, type_end));
1297 ctx.record_field_span(vec![], (type_start, type_end), (type_start, type_end));
1298
1299 Ok(())
1300}
1301
1302fn collect_nested_types<'a>(struct_type: &'a StructType, queue: &mut Vec<&'a Shape>) {
1304 for field in struct_type.fields {
1305 collect_from_shape(field.shape(), queue);
1306 }
1307}
1308
1309fn collect_from_shape<'a>(shape: &'a Shape, queue: &mut Vec<&'a Shape>) {
1311 match shape.def {
1312 Def::List(list_def) => collect_from_shape(list_def.t, queue),
1313 Def::Array(array_def) => collect_from_shape(array_def.t, queue),
1314 Def::Map(map_def) => {
1315 collect_from_shape(map_def.k, queue);
1316 collect_from_shape(map_def.v, queue);
1317 }
1318 Def::Option(option_def) => collect_from_shape(option_def.t, queue),
1319 _ => {
1320 if let Type::User(UserType::Struct(_) | UserType::Enum(_)) = &shape.ty {
1322 queue.push(shape);
1323 }
1324 }
1325 }
1326}
1327
1328fn write_facet_attrs(shape: &Shape, output: &mut String) -> core::fmt::Result {
1330 let mut attrs: Vec<String> = Vec::new();
1331
1332 if let Some(tag) = shape.get_tag_attr() {
1333 if let Some(content) = shape.get_content_attr() {
1334 attrs.push(alloc::format!("tag = \"{tag}\", content = \"{content}\""));
1335 } else {
1336 attrs.push(alloc::format!("tag = \"{tag}\""));
1337 }
1338 }
1339
1340 if shape.is_untagged() {
1341 attrs.push("untagged".into());
1342 }
1343
1344 if shape.has_deny_unknown_fields_attr() {
1345 attrs.push("deny_unknown_fields".into());
1346 }
1347
1348 if !attrs.is_empty() {
1349 writeln!(output, "#[facet({})]", attrs.join(", "))?;
1350 }
1351
1352 Ok(())
1353}
1354
1355fn write_doc_comments(doc: &[&str], output: &mut String, indent: &str) -> core::fmt::Result {
1357 for line in doc {
1358 write!(output, "{indent}")?;
1359 writeln!(output, "///{line}")?;
1360 }
1361 Ok(())
1362}
1363
1364fn write_third_party_attrs(
1366 attributes: &[Attr],
1367 output: &mut String,
1368 indent: &str,
1369) -> core::fmt::Result {
1370 let mut by_namespace: BTreeMap<&'static str, Vec<&'static str>> = BTreeMap::new();
1372 for attr in attributes {
1373 if let Some(ns) = attr.ns {
1374 by_namespace.entry(ns).or_default().push(attr.key);
1375 }
1376 }
1377
1378 for (ns, keys) in by_namespace {
1380 write!(output, "{indent}")?;
1381 write!(output, "#[facet(")?;
1382 for (i, key) in keys.iter().enumerate() {
1383 if i > 0 {
1384 write!(output, ", ")?;
1385 }
1386 write!(output, "{ns}::{key}")?;
1387 }
1388 writeln!(output, ")]")?;
1389 }
1390 Ok(())
1391}
1392
1393fn write_field_third_party_attrs(
1395 field: &Field,
1396 output: &mut String,
1397 indent: &str,
1398) -> core::fmt::Result {
1399 write_third_party_attrs(field.attributes, output, indent)
1400}
1401
1402fn write_variant_third_party_attrs(
1404 variant: &Variant,
1405 output: &mut String,
1406 indent: &str,
1407) -> core::fmt::Result {
1408 write_third_party_attrs(variant.attributes, output, indent)
1409}
1410
1411fn write_type_name(shape: &Shape, output: &mut String) -> core::fmt::Result {
1412 match shape.def {
1413 Def::Scalar => {
1414 write!(output, "{}", shape.type_identifier)?;
1415 }
1416 Def::Pointer(_) => {
1417 if let Type::Pointer(PointerType::Reference(r)) = shape.ty
1418 && let Def::Array(array_def) = r.target.def
1419 {
1420 write!(output, "&[")?;
1421 write_type_name(array_def.t, output)?;
1422 write!(output, "; {}]", array_def.n)?;
1423 return Ok(());
1424 }
1425 write!(output, "{}", shape.type_identifier)?;
1426 }
1427 Def::List(list_def) => {
1428 write!(output, "Vec<")?;
1429 write_type_name(list_def.t, output)?;
1430 write!(output, ">")?;
1431 }
1432 Def::Array(array_def) => {
1433 write!(output, "[")?;
1434 write_type_name(array_def.t, output)?;
1435 write!(output, "; {}]", array_def.n)?;
1436 }
1437 Def::Map(map_def) => {
1438 let map_name = if shape.type_identifier.contains("BTreeMap") {
1439 "BTreeMap"
1440 } else {
1441 "HashMap"
1442 };
1443 write!(output, "{map_name}<")?;
1444 write_type_name(map_def.k, output)?;
1445 write!(output, ", ")?;
1446 write_type_name(map_def.v, output)?;
1447 write!(output, ">")?;
1448 }
1449 Def::Option(option_def) => {
1450 write!(output, "Option<")?;
1451 write_type_name(option_def.t, output)?;
1452 write!(output, ">")?;
1453 }
1454 _ => {
1455 write!(output, "{}", shape.type_identifier)?;
1456 }
1457 }
1458 Ok(())
1459}
1460
1461#[cfg(test)]
1462mod tests {
1463 use super::*;
1464 use facet::Facet;
1465
1466 #[test]
1467 fn test_simple_struct() {
1468 #[derive(Facet)]
1469 struct Simple {
1470 name: String,
1471 count: u32,
1472 }
1473
1474 let output = format_shape(Simple::SHAPE);
1475 assert!(output.contains("struct Simple"));
1476 assert!(output.contains("name: String"));
1477 assert!(output.contains("count: u32"));
1478 }
1479
1480 #[test]
1481 fn test_enum_with_tag() {
1482 #[derive(Facet)]
1483 #[repr(C)]
1484 #[facet(tag = "type")]
1485 #[allow(dead_code)]
1486 enum Tagged {
1487 A { x: i32 },
1488 B { y: String },
1489 }
1490
1491 let output = format_shape(Tagged::SHAPE);
1492 assert!(output.contains("enum Tagged"));
1493 assert!(output.contains("#[facet(tag = \"type\")]"));
1494 }
1495
1496 #[test]
1497 fn test_nested_types() {
1498 #[derive(Facet)]
1499 #[allow(dead_code)]
1500 struct Inner {
1501 value: i32,
1502 }
1503
1504 #[derive(Facet)]
1505 #[allow(dead_code)]
1506 struct Outer {
1507 inner: Inner,
1508 name: String,
1509 }
1510
1511 let output = format_shape(Outer::SHAPE);
1512 assert!(output.contains("struct Outer"), "Missing Outer: {output}");
1514 assert!(
1515 output.contains("inner: Inner"),
1516 "Missing inner field: {output}"
1517 );
1518 assert!(
1519 output.contains("struct Inner"),
1520 "Missing Inner definition: {output}"
1521 );
1522 assert!(
1523 output.contains("value: i32"),
1524 "Missing value field: {output}"
1525 );
1526 }
1527
1528 #[test]
1529 fn test_nested_in_vec() {
1530 #[derive(Facet)]
1531 #[allow(dead_code)]
1532 struct Item {
1533 id: u32,
1534 }
1535
1536 #[derive(Facet)]
1537 #[allow(dead_code)]
1538 struct Container {
1539 items: Vec<Item>,
1540 }
1541
1542 let output = format_shape(Container::SHAPE);
1543 assert!(
1545 output.contains("struct Container"),
1546 "Missing Container: {output}"
1547 );
1548 assert!(
1549 output.contains("items: Vec<Item>"),
1550 "Missing items field: {output}"
1551 );
1552 assert!(
1553 output.contains("struct Item"),
1554 "Missing Item definition: {output}"
1555 );
1556 }
1557
1558 #[test]
1559 fn test_format_shape_with_spans() {
1560 #[derive(Facet)]
1561 #[allow(dead_code)]
1562 struct Config {
1563 name: String,
1564 max_retries: u8,
1565 enabled: bool,
1566 }
1567
1568 let result = format_shape_with_spans(Config::SHAPE);
1569
1570 let name_path = vec![PathSegment::Field(Cow::Borrowed("name"))];
1572 let retries_path = vec![PathSegment::Field(Cow::Borrowed("max_retries"))];
1573 let enabled_path = vec![PathSegment::Field(Cow::Borrowed("enabled"))];
1574
1575 assert!(
1576 result.spans.contains_key(&name_path),
1577 "Missing span for 'name' field. Spans: {:?}",
1578 result.spans
1579 );
1580 assert!(
1581 result.spans.contains_key(&retries_path),
1582 "Missing span for 'max_retries' field. Spans: {:?}",
1583 result.spans
1584 );
1585 assert!(
1586 result.spans.contains_key(&enabled_path),
1587 "Missing span for 'enabled' field. Spans: {:?}",
1588 result.spans
1589 );
1590
1591 let field_span = &result.spans[&retries_path];
1593 let spanned_text = &result.text[field_span.value.0..field_span.value.1];
1594 assert_eq!(spanned_text, "u8", "Expected 'u8', got '{spanned_text}'");
1595 }
1596
1597 #[test]
1598 fn test_format_enum_with_spans() {
1599 #[derive(Facet)]
1600 #[repr(u8)]
1601 #[allow(dead_code)]
1602 enum Status {
1603 Active,
1604 Pending,
1605 Error { code: i32, message: String },
1606 }
1607
1608 let result = format_shape_with_spans(Status::SHAPE);
1609
1610 let active_path = vec![PathSegment::Variant(Cow::Borrowed("Active"))];
1612 let error_path = vec![PathSegment::Variant(Cow::Borrowed("Error"))];
1613 let error_code_path = vec![
1614 PathSegment::Variant(Cow::Borrowed("Error")),
1615 PathSegment::Field(Cow::Borrowed("code")),
1616 ];
1617
1618 assert!(
1619 result.spans.contains_key(&active_path),
1620 "Missing span for 'Active' variant. Spans: {:?}",
1621 result.spans
1622 );
1623 assert!(
1624 result.spans.contains_key(&error_path),
1625 "Missing span for 'Error' variant. Spans: {:?}",
1626 result.spans
1627 );
1628 assert!(
1629 result.spans.contains_key(&error_code_path),
1630 "Missing span for 'Error.code' field. Spans: {:?}",
1631 result.spans
1632 );
1633
1634 let field_span = &result.spans[&error_code_path];
1636 let spanned_text = &result.text[field_span.value.0..field_span.value.1];
1637 assert_eq!(spanned_text, "i32", "Expected 'i32', got '{spanned_text}'");
1638 }
1639
1640 #[test]
1641 fn test_format_with_doc_comments() {
1642 #[derive(Facet)]
1644 #[allow(dead_code)]
1645 struct Config {
1646 name: String,
1648 max_retries: u8,
1650 }
1651
1652 let output = format_shape(Config::SHAPE);
1654 assert!(
1655 output.contains("/// A configuration struct"),
1656 "Should contain struct doc comment: {output}"
1657 );
1658 assert!(
1659 output.contains("/// The name of the configuration"),
1660 "Should contain field doc comment: {output}"
1661 );
1662 assert!(
1663 output.contains("/// Maximum number of retries"),
1664 "Should contain field doc comment: {output}"
1665 );
1666
1667 let config = ShapeFormatConfig::new();
1669 let output_without = format_shape_with_config(Config::SHAPE, &config);
1670 assert!(
1671 !output_without.contains("///"),
1672 "Should not contain doc comments when disabled: {output_without}"
1673 );
1674 }
1675
1676 #[test]
1677 fn test_format_enum_with_doc_comments() {
1678 #[derive(Facet)]
1680 #[repr(u8)]
1681 #[allow(dead_code)]
1682 enum Status {
1683 Active,
1685 Error {
1687 code: i32,
1689 },
1690 }
1691
1692 let config = ShapeFormatConfig::new().with_doc_comments();
1693 let output = format_shape_with_config(Status::SHAPE, &config);
1694
1695 assert!(
1696 output.contains("/// Status of an operation"),
1697 "Should contain enum doc comment: {output}"
1698 );
1699 assert!(
1700 output.contains("/// The operation is active"),
1701 "Should contain variant doc comment: {output}"
1702 );
1703 assert!(
1704 output.contains("/// The operation failed"),
1705 "Should contain variant doc comment: {output}"
1706 );
1707 assert!(
1708 output.contains("/// Error code"),
1709 "Should contain variant field doc comment: {output}"
1710 );
1711 }
1712}