facet_pretty/
shape.rs

1//! Pretty printer for Shape types as Rust-like code
2//!
3//! This module provides functionality to format a `Shape` as Rust source code,
4//! showing the type definition with its attributes.
5
6use 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, Def, EnumRepr, EnumType, Field, PointerType, Shape, StructKind, StructType, Type,
16    UserType, Variant,
17};
18use owo_colors::OwoColorize;
19
20/// Tokyo Night color scheme for syntax highlighting
21pub mod colors {
22    use owo_colors::Style;
23
24    /// Keywords: struct, enum, pub, etc. (purple)
25    pub fn keyword() -> Style {
26        Style::new().fg_rgb::<187, 154, 247>()
27    }
28
29    /// Type names and identifiers (light blue)
30    pub fn type_name() -> Style {
31        Style::new().fg_rgb::<192, 202, 245>()
32    }
33
34    /// Field names (cyan)
35    pub fn field_name() -> Style {
36        Style::new().fg_rgb::<125, 207, 255>()
37    }
38
39    /// Primitive types: u8, i32, bool, String, etc. (teal)
40    pub fn primitive() -> Style {
41        Style::new().fg_rgb::<115, 218, 202>()
42    }
43
44    /// Punctuation: {, }, (, ), :, etc. (gray-blue)
45    pub fn punctuation() -> Style {
46        Style::new().fg_rgb::<154, 165, 206>()
47    }
48
49    /// Attribute markers: #[...] (light cyan)
50    pub fn attribute() -> Style {
51        Style::new().fg_rgb::<137, 221, 255>()
52    }
53
54    /// Attribute content: derive, facet, repr (blue)
55    pub fn attribute_content() -> Style {
56        Style::new().fg_rgb::<122, 162, 247>()
57    }
58
59    /// String literals (green)
60    pub fn string() -> Style {
61        Style::new().fg_rgb::<158, 206, 106>()
62    }
63
64    /// Container types: Vec, Option, HashMap (orange)
65    pub fn container() -> Style {
66        Style::new().fg_rgb::<255, 158, 100>()
67    }
68
69    /// Doc comments (muted gray)
70    pub fn comment() -> Style {
71        Style::new().fg_rgb::<86, 95, 137>()
72    }
73}
74
75/// Configuration options for shape formatting
76#[derive(Clone, Debug, Default)]
77pub struct ShapeFormatConfig {
78    /// Whether to include doc comments in the output
79    pub show_doc_comments: bool,
80    /// Whether to include third-party (namespaced) attributes
81    pub show_third_party_attrs: bool,
82}
83
84impl ShapeFormatConfig {
85    /// Create a new config with default settings (no doc comments, no third-party attrs)
86    pub fn new() -> Self {
87        Self::default()
88    }
89
90    /// Enable doc comment display
91    pub fn with_doc_comments(mut self) -> Self {
92        self.show_doc_comments = true;
93        self
94    }
95
96    /// Enable third-party attribute display
97    pub fn with_third_party_attrs(mut self) -> Self {
98        self.show_third_party_attrs = true;
99        self
100    }
101
102    /// Enable all metadata (doc comments and third-party attrs)
103    pub fn with_all_metadata(mut self) -> Self {
104        self.show_doc_comments = true;
105        self.show_third_party_attrs = true;
106        self
107    }
108}
109
110/// A segment in a path through a type structure
111#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
112pub enum PathSegment {
113    /// A field name in a struct
114    Field(Cow<'static, str>),
115    /// A variant name in an enum
116    Variant(Cow<'static, str>),
117    /// An index in a list/array/tuple
118    Index(usize),
119    /// A key in a map (stored as formatted string representation)
120    Key(Cow<'static, str>),
121}
122
123/// A path to a location within a type structure
124pub type Path = Vec<PathSegment>;
125
126/// A byte span in formatted output (start, end)
127pub type Span = (usize, usize);
128
129/// Spans for a field or variant, tracking both key (name) and value (type) positions
130#[derive(Clone, Debug, Default, PartialEq, Eq)]
131pub struct FieldSpan {
132    /// Span of the field/variant name (e.g., "max_retries" in "max_retries: u8")
133    pub key: Span,
134    /// Span of the type annotation (e.g., "u8" in "max_retries: u8")
135    pub value: Span,
136}
137
138/// Result of formatting a shape with span tracking
139#[derive(Debug)]
140pub struct FormattedShape {
141    /// The formatted text (plain text, no ANSI colors)
142    pub text: String,
143    /// Map from paths to their field spans (key + value) in `text`
144    pub spans: BTreeMap<Path, FieldSpan>,
145}
146
147/// Strip ANSI escape codes from a string
148pub fn strip_ansi(s: &str) -> String {
149    let mut result = String::with_capacity(s.len());
150    let mut chars = s.chars().peekable();
151
152    while let Some(c) = chars.next() {
153        if c == '\x1b' {
154            // Skip escape sequence
155            if chars.peek() == Some(&'[') {
156                chars.next(); // consume '['
157                // Skip until we hit a letter (the terminator)
158                while let Some(&next) = chars.peek() {
159                    chars.next();
160                    if next.is_ascii_alphabetic() {
161                        break;
162                    }
163                }
164            }
165        } else {
166            result.push(c);
167        }
168    }
169    result
170}
171
172/// Format a Shape as Rust-like source code (plain text, no colors)
173///
174/// By default, this includes doc comments and third-party attributes.
175pub fn format_shape(shape: &Shape) -> String {
176    strip_ansi(&format_shape_colored(shape))
177}
178
179/// Format a Shape as Rust-like source code with config options (plain text, no colors)
180pub fn format_shape_with_config(shape: &Shape, config: &ShapeFormatConfig) -> String {
181    strip_ansi(&format_shape_colored_with_config(shape, config))
182}
183
184/// Format a Shape with span tracking for each field/variant
185/// Note: spans are computed on the plain text (no ANSI codes)
186pub fn format_shape_with_spans(shape: &Shape) -> FormattedShape {
187    let mut ctx = SpanTrackingContext::new();
188    format_shape_into_with_spans(shape, &mut ctx).expect("Formatting failed");
189    FormattedShape {
190        text: ctx.output,
191        spans: ctx.spans,
192    }
193}
194
195/// Format a Shape as Rust-like source code with ANSI colors (Tokyo Night theme)
196///
197/// By default, this includes doc comments and third-party attributes.
198pub fn format_shape_colored(shape: &Shape) -> String {
199    format_shape_colored_with_config(shape, &ShapeFormatConfig::default().with_all_metadata())
200}
201
202/// Format a Shape as Rust-like source code with ANSI colors and config options
203pub fn format_shape_colored_with_config(shape: &Shape, config: &ShapeFormatConfig) -> String {
204    let mut output = String::new();
205    format_shape_colored_into_with_config(shape, &mut output, config).expect("Formatting failed");
206    output
207}
208
209/// Format a Shape with ANSI colors into an existing String
210pub fn format_shape_colored_into(shape: &Shape, output: &mut String) -> core::fmt::Result {
211    format_shape_colored_into_with_config(shape, output, &ShapeFormatConfig::default())
212}
213
214/// Format a Shape with ANSI colors into an existing String with config options
215pub fn format_shape_colored_into_with_config(
216    shape: &Shape,
217    output: &mut String,
218    config: &ShapeFormatConfig,
219) -> core::fmt::Result {
220    let mut printed: BTreeSet<&'static str> = BTreeSet::new();
221    let mut queue: Vec<&Shape> = Vec::new();
222    queue.push(shape);
223
224    while let Some(current) = queue.pop() {
225        if !printed.insert(current.type_identifier) {
226            continue;
227        }
228
229        if printed.len() > 1 {
230            writeln!(output)?;
231            writeln!(output)?;
232        }
233
234        match current.def {
235            Def::Map(_) | Def::List(_) | Def::Option(_) | Def::Array(_) => {
236                printed.remove(current.type_identifier);
237                continue;
238            }
239            _ => {}
240        }
241
242        match &current.ty {
243            Type::User(user_type) => match user_type {
244                UserType::Struct(struct_type) => {
245                    format_struct_colored(current, struct_type, output, config)?;
246                    collect_nested_types(struct_type, &mut queue);
247                }
248                UserType::Enum(enum_type) => {
249                    format_enum_colored(current, enum_type, output, config)?;
250                    for variant in enum_type.variants {
251                        collect_nested_types(&variant.data, &mut queue);
252                    }
253                }
254                UserType::Union(_) | UserType::Opaque => {
255                    printed.remove(current.type_identifier);
256                }
257            },
258            _ => {
259                printed.remove(current.type_identifier);
260            }
261        }
262    }
263    Ok(())
264}
265
266fn format_struct_colored(
267    shape: &Shape,
268    struct_type: &StructType,
269    output: &mut String,
270    config: &ShapeFormatConfig,
271) -> core::fmt::Result {
272    // Write doc comments for the struct if enabled
273    if config.show_doc_comments {
274        write_doc_comments_colored(shape.doc, output, "")?;
275    }
276
277    // #[derive(Facet)]
278    write!(output, "{}", "#[".style(colors::attribute()))?;
279    write!(output, "{}", "derive".style(colors::attribute_content()))?;
280    write!(output, "{}", "(".style(colors::attribute()))?;
281    write!(output, "{}", "Facet".style(colors::attribute_content()))?;
282    writeln!(output, "{}", ")]".style(colors::attribute()))?;
283
284    // Write facet attributes if any
285    write_facet_attrs_colored(shape, output)?;
286
287    // Write third-party attributes if enabled
288    if config.show_third_party_attrs {
289        write_third_party_attrs_colored(shape.attributes, output, "")?;
290    }
291
292    match struct_type.kind {
293        StructKind::Struct => {
294            write!(output, "{} ", "struct".style(colors::keyword()))?;
295            write!(
296                output,
297                "{}",
298                shape.type_identifier.style(colors::type_name())
299            )?;
300            writeln!(output, " {}", "{".style(colors::punctuation()))?;
301
302            for (i, field) in struct_type.fields.iter().enumerate() {
303                // Blank line between fields (not before the first one)
304                if i > 0 {
305                    writeln!(output)?;
306                }
307                // Write doc comments for the field if enabled
308                if config.show_doc_comments {
309                    write_doc_comments_colored(field.doc, output, "    ")?;
310                }
311                // Write third-party attributes for the field if enabled
312                if config.show_third_party_attrs {
313                    write_field_third_party_attrs_colored(field, output, "    ")?;
314                }
315                write!(output, "    {}", field.name.style(colors::field_name()))?;
316                write!(output, "{} ", ":".style(colors::punctuation()))?;
317                write_type_name_colored(field.shape(), output)?;
318                writeln!(output, "{}", ",".style(colors::punctuation()))?;
319            }
320            write!(output, "{}", "}".style(colors::punctuation()))?;
321        }
322        StructKind::Tuple | StructKind::TupleStruct => {
323            write!(output, "{} ", "struct".style(colors::keyword()))?;
324            write!(
325                output,
326                "{}",
327                shape.type_identifier.style(colors::type_name())
328            )?;
329            write!(output, "{}", "(".style(colors::punctuation()))?;
330            for (i, field) in struct_type.fields.iter().enumerate() {
331                if i > 0 {
332                    write!(output, "{} ", ",".style(colors::punctuation()))?;
333                }
334                write_type_name_colored(field.shape(), output)?;
335            }
336            write!(
337                output,
338                "{}{}",
339                ")".style(colors::punctuation()),
340                ";".style(colors::punctuation())
341            )?;
342        }
343        StructKind::Unit => {
344            write!(output, "{} ", "struct".style(colors::keyword()))?;
345            write!(
346                output,
347                "{}",
348                shape.type_identifier.style(colors::type_name())
349            )?;
350            write!(output, "{}", ";".style(colors::punctuation()))?;
351        }
352    }
353    Ok(())
354}
355
356fn format_enum_colored(
357    shape: &Shape,
358    enum_type: &EnumType,
359    output: &mut String,
360    config: &ShapeFormatConfig,
361) -> core::fmt::Result {
362    // Write doc comments for the enum if enabled
363    if config.show_doc_comments {
364        write_doc_comments_colored(shape.doc, output, "")?;
365    }
366
367    // #[derive(Facet)]
368    write!(output, "{}", "#[".style(colors::attribute()))?;
369    write!(output, "{}", "derive".style(colors::attribute_content()))?;
370    write!(output, "{}", "(".style(colors::attribute()))?;
371    write!(output, "{}", "Facet".style(colors::attribute_content()))?;
372    writeln!(output, "{}", ")]".style(colors::attribute()))?;
373
374    // Write repr for the discriminant type
375    let repr_str = match enum_type.enum_repr {
376        EnumRepr::RustNPO => None,
377        EnumRepr::U8 => Some("u8"),
378        EnumRepr::U16 => Some("u16"),
379        EnumRepr::U32 => Some("u32"),
380        EnumRepr::U64 => Some("u64"),
381        EnumRepr::USize => Some("usize"),
382        EnumRepr::I8 => Some("i8"),
383        EnumRepr::I16 => Some("i16"),
384        EnumRepr::I32 => Some("i32"),
385        EnumRepr::I64 => Some("i64"),
386        EnumRepr::ISize => Some("isize"),
387    };
388
389    if let Some(repr) = repr_str {
390        write!(output, "{}", "#[".style(colors::attribute()))?;
391        write!(output, "{}", "repr".style(colors::attribute_content()))?;
392        write!(output, "{}", "(".style(colors::attribute()))?;
393        write!(output, "{}", repr.style(colors::primitive()))?;
394        writeln!(output, "{}", ")]".style(colors::attribute()))?;
395    }
396
397    // Write facet attributes if any
398    write_facet_attrs_colored(shape, output)?;
399
400    // Write third-party attributes if enabled
401    if config.show_third_party_attrs {
402        write_third_party_attrs_colored(shape.attributes, output, "")?;
403    }
404
405    // enum Name {
406    write!(output, "{} ", "enum".style(colors::keyword()))?;
407    write!(
408        output,
409        "{}",
410        shape.type_identifier.style(colors::type_name())
411    )?;
412    writeln!(output, " {}", "{".style(colors::punctuation()))?;
413
414    for (vi, variant) in enum_type.variants.iter().enumerate() {
415        // Blank line between variants (not before the first one)
416        if vi > 0 {
417            writeln!(output)?;
418        }
419        // Write doc comments for the variant if enabled
420        if config.show_doc_comments {
421            write_doc_comments_colored(variant.doc, output, "    ")?;
422        }
423        // Write third-party attributes for the variant if enabled
424        if config.show_third_party_attrs {
425            write_variant_third_party_attrs_colored(variant, output, "    ")?;
426        }
427
428        match variant.data.kind {
429            StructKind::Unit => {
430                write!(output, "    {}", variant.name.style(colors::type_name()))?;
431                writeln!(output, "{}", ",".style(colors::punctuation()))?;
432            }
433            StructKind::Tuple | StructKind::TupleStruct => {
434                write!(output, "    {}", variant.name.style(colors::type_name()))?;
435                write!(output, "{}", "(".style(colors::punctuation()))?;
436                for (i, field) in variant.data.fields.iter().enumerate() {
437                    if i > 0 {
438                        write!(output, "{} ", ",".style(colors::punctuation()))?;
439                    }
440                    write_type_name_colored(field.shape(), output)?;
441                }
442                write!(output, "{}", ")".style(colors::punctuation()))?;
443                writeln!(output, "{}", ",".style(colors::punctuation()))?;
444            }
445            StructKind::Struct => {
446                write!(output, "    {}", variant.name.style(colors::type_name()))?;
447                writeln!(output, " {}", "{".style(colors::punctuation()))?;
448                for (fi, field) in variant.data.fields.iter().enumerate() {
449                    // Blank line between variant fields (not before the first one)
450                    if fi > 0 {
451                        writeln!(output)?;
452                    }
453                    // Write doc comments for variant fields if enabled
454                    if config.show_doc_comments {
455                        write_doc_comments_colored(field.doc, output, "        ")?;
456                    }
457                    // Write third-party attributes for variant fields if enabled
458                    if config.show_third_party_attrs {
459                        write_field_third_party_attrs_colored(field, output, "        ")?;
460                    }
461                    write!(output, "        {}", field.name.style(colors::field_name()))?;
462                    write!(output, "{} ", ":".style(colors::punctuation()))?;
463                    write_type_name_colored(field.shape(), output)?;
464                    writeln!(output, "{}", ",".style(colors::punctuation()))?;
465                }
466                write!(output, "    {}", "}".style(colors::punctuation()))?;
467                writeln!(output, "{}", ",".style(colors::punctuation()))?;
468            }
469        }
470    }
471
472    write!(output, "{}", "}".style(colors::punctuation()))?;
473    Ok(())
474}
475
476fn write_facet_attrs_colored(shape: &Shape, output: &mut String) -> core::fmt::Result {
477    let mut attrs: Vec<String> = Vec::new();
478
479    if let Some(tag) = shape.get_tag_attr() {
480        if let Some(content) = shape.get_content_attr() {
481            attrs.push(alloc::format!(
482                "{}{}{}{}{}{}{}{}{}",
483                "tag".style(colors::attribute_content()),
484                " = ".style(colors::punctuation()),
485                "\"".style(colors::string()),
486                tag.style(colors::string()),
487                "\"".style(colors::string()),
488                ", ".style(colors::punctuation()),
489                "content".style(colors::attribute_content()),
490                " = ".style(colors::punctuation()),
491                format!("\"{content}\"").style(colors::string()),
492            ));
493        } else {
494            attrs.push(alloc::format!(
495                "{}{}{}",
496                "tag".style(colors::attribute_content()),
497                " = ".style(colors::punctuation()),
498                format!("\"{tag}\"").style(colors::string()),
499            ));
500        }
501    }
502
503    if shape.is_untagged() {
504        attrs.push(alloc::format!(
505            "{}",
506            "untagged".style(colors::attribute_content())
507        ));
508    }
509
510    if shape.has_deny_unknown_fields_attr() {
511        attrs.push(alloc::format!(
512            "{}",
513            "deny_unknown_fields".style(colors::attribute_content())
514        ));
515    }
516
517    if !attrs.is_empty() {
518        write!(output, "{}", "#[".style(colors::attribute()))?;
519        write!(output, "{}", "facet".style(colors::attribute_content()))?;
520        write!(output, "{}", "(".style(colors::attribute()))?;
521        write!(
522            output,
523            "{}",
524            attrs.join(&format!("{}", ", ".style(colors::punctuation())))
525        )?;
526        writeln!(output, "{}", ")]".style(colors::attribute()))?;
527    }
528
529    Ok(())
530}
531
532/// Write doc comments with the given indentation prefix
533fn write_doc_comments_colored(
534    doc: &[&str],
535    output: &mut String,
536    indent: &str,
537) -> core::fmt::Result {
538    for line in doc {
539        write!(output, "{indent}")?;
540        writeln!(output, "{}", format!("///{line}").style(colors::comment()))?;
541    }
542    Ok(())
543}
544
545/// Write third-party (namespaced) attributes from a Shape's attributes
546/// Groups attributes by namespace, e.g. `#[facet(args::named, args::short)]`
547fn write_third_party_attrs_colored(
548    attributes: &[Attr],
549    output: &mut String,
550    indent: &str,
551) -> core::fmt::Result {
552    // Group attributes by namespace
553    let mut by_namespace: BTreeMap<&'static str, Vec<&'static str>> = BTreeMap::new();
554    for attr in attributes {
555        if let Some(ns) = attr.ns {
556            by_namespace.entry(ns).or_default().push(attr.key);
557        }
558    }
559
560    // Write one line per namespace with all keys
561    for (ns, keys) in by_namespace {
562        write!(output, "{indent}")?;
563        write!(output, "{}", "#[".style(colors::attribute()))?;
564        write!(output, "{}", "facet".style(colors::attribute_content()))?;
565        write!(output, "{}", "(".style(colors::attribute()))?;
566
567        for (i, key) in keys.iter().enumerate() {
568            if i > 0 {
569                write!(output, "{}", ", ".style(colors::punctuation()))?;
570            }
571            write!(output, "{}", ns.style(colors::attribute_content()))?;
572            write!(output, "{}", "::".style(colors::punctuation()))?;
573            write!(output, "{}", key.style(colors::attribute_content()))?;
574        }
575
576        write!(output, "{}", ")".style(colors::attribute()))?;
577        writeln!(output, "{}", "]".style(colors::attribute()))?;
578    }
579    Ok(())
580}
581
582/// Write third-party attributes for a field
583fn write_field_third_party_attrs_colored(
584    field: &Field,
585    output: &mut String,
586    indent: &str,
587) -> core::fmt::Result {
588    write_third_party_attrs_colored(field.attributes, output, indent)
589}
590
591/// Write third-party attributes for a variant
592fn write_variant_third_party_attrs_colored(
593    variant: &Variant,
594    output: &mut String,
595    indent: &str,
596) -> core::fmt::Result {
597    write_third_party_attrs_colored(variant.attributes, output, indent)
598}
599
600fn write_type_name_colored(shape: &Shape, output: &mut String) -> core::fmt::Result {
601    match shape.def {
602        Def::Scalar => {
603            // Check if it's a primitive type
604            let id = shape.type_identifier;
605            if is_primitive_type(id) {
606                write!(output, "{}", id.style(colors::primitive()))?;
607            } else {
608                write!(output, "{}", id.style(colors::type_name()))?;
609            }
610        }
611        Def::Pointer(_) => {
612            if let Type::Pointer(PointerType::Reference(r)) = shape.ty
613                && let Def::Array(array_def) = r.target.def
614            {
615                write!(output, "{}", "&[".style(colors::punctuation()))?;
616                write_type_name_colored(array_def.t, output)?;
617                write!(
618                    output,
619                    "{}{}{}",
620                    "; ".style(colors::punctuation()),
621                    array_def.n.style(colors::primitive()),
622                    "]".style(colors::punctuation())
623                )?;
624                return Ok(());
625            }
626            write!(
627                output,
628                "{}",
629                shape.type_identifier.style(colors::type_name())
630            )?;
631        }
632        Def::List(list_def) => {
633            write!(output, "{}", "Vec".style(colors::container()))?;
634            write!(output, "{}", "<".style(colors::punctuation()))?;
635            write_type_name_colored(list_def.t, output)?;
636            write!(output, "{}", ">".style(colors::punctuation()))?;
637        }
638        Def::Array(array_def) => {
639            write!(output, "{}", "[".style(colors::punctuation()))?;
640            write_type_name_colored(array_def.t, output)?;
641            write!(
642                output,
643                "{}{}{}",
644                "; ".style(colors::punctuation()),
645                array_def.n.style(colors::primitive()),
646                "]".style(colors::punctuation())
647            )?;
648        }
649        Def::Map(map_def) => {
650            let map_name = if shape.type_identifier.contains("BTreeMap") {
651                "BTreeMap"
652            } else {
653                "HashMap"
654            };
655            write!(output, "{}", map_name.style(colors::container()))?;
656            write!(output, "{}", "<".style(colors::punctuation()))?;
657            write_type_name_colored(map_def.k, output)?;
658            write!(output, "{} ", ",".style(colors::punctuation()))?;
659            write_type_name_colored(map_def.v, output)?;
660            write!(output, "{}", ">".style(colors::punctuation()))?;
661        }
662        Def::Option(option_def) => {
663            write!(output, "{}", "Option".style(colors::container()))?;
664            write!(output, "{}", "<".style(colors::punctuation()))?;
665            write_type_name_colored(option_def.t, output)?;
666            write!(output, "{}", ">".style(colors::punctuation()))?;
667        }
668        _ => {
669            let id = shape.type_identifier;
670            if is_primitive_type(id) {
671                write!(output, "{}", id.style(colors::primitive()))?;
672            } else {
673                write!(output, "{}", id.style(colors::type_name()))?;
674            }
675        }
676    }
677    Ok(())
678}
679
680/// Check if a type identifier is a primitive type
681fn is_primitive_type(id: &str) -> bool {
682    matches!(
683        id,
684        "u8" | "u16"
685            | "u32"
686            | "u64"
687            | "u128"
688            | "usize"
689            | "i8"
690            | "i16"
691            | "i32"
692            | "i64"
693            | "i128"
694            | "isize"
695            | "f32"
696            | "f64"
697            | "bool"
698            | "char"
699            | "str"
700            | "&str"
701            | "String"
702    )
703}
704
705/// Context for tracking spans during formatting
706struct SpanTrackingContext {
707    output: String,
708    spans: BTreeMap<Path, FieldSpan>,
709    /// Current path prefix (for nested types)
710    current_type: Option<&'static str>,
711}
712
713impl SpanTrackingContext {
714    fn new() -> Self {
715        Self {
716            output: String::new(),
717            spans: BTreeMap::new(),
718            current_type: None,
719        }
720    }
721
722    fn len(&self) -> usize {
723        self.output.len()
724    }
725
726    fn record_field_span(&mut self, path: Path, key_span: Span, value_span: Span) {
727        self.spans.insert(
728            path,
729            FieldSpan {
730                key: key_span,
731                value: value_span,
732            },
733        );
734    }
735}
736
737/// Format a Shape with span tracking
738fn format_shape_into_with_spans(shape: &Shape, ctx: &mut SpanTrackingContext) -> core::fmt::Result {
739    // Track which types we've already printed to avoid duplicates
740    let mut printed: BTreeSet<&'static str> = BTreeSet::new();
741    // Queue of types to print
742    let mut queue: Vec<&Shape> = Vec::new();
743
744    // Start with the root shape
745    queue.push(shape);
746
747    while let Some(current) = queue.pop() {
748        // Skip if we've already printed this type
749        if !printed.insert(current.type_identifier) {
750            continue;
751        }
752
753        // Add separator between type definitions
754        if printed.len() > 1 {
755            writeln!(ctx.output)?;
756            writeln!(ctx.output)?;
757        }
758
759        // First check def for container types (Map, List, Option, Array)
760        // These have rich generic info even when ty is Opaque
761        match current.def {
762            Def::Map(_) | Def::List(_) | Def::Option(_) | Def::Array(_) => {
763                // Don't print container types as definitions, they're inline
764                printed.remove(current.type_identifier);
765                continue;
766            }
767            _ => {}
768        }
769
770        // Then check ty for user-defined types
771        match &current.ty {
772            Type::User(user_type) => match user_type {
773                UserType::Struct(struct_type) => {
774                    ctx.current_type = Some(current.type_identifier);
775                    format_struct_with_spans(current, struct_type, ctx)?;
776                    ctx.current_type = None;
777                    // Queue nested types from fields
778                    collect_nested_types(struct_type, &mut queue);
779                }
780                UserType::Enum(enum_type) => {
781                    ctx.current_type = Some(current.type_identifier);
782                    format_enum_with_spans(current, enum_type, ctx)?;
783                    ctx.current_type = None;
784                    // Queue nested types from variants
785                    for variant in enum_type.variants {
786                        collect_nested_types(&variant.data, &mut queue);
787                    }
788                }
789                UserType::Union(_) | UserType::Opaque => {
790                    // For union/opaque types, just show the type identifier
791                    // Don't actually print anything since we can't expand them
792                    printed.remove(current.type_identifier);
793                }
794            },
795            _ => {
796                // For non-user types (primitives, pointers, etc.), don't print
797                printed.remove(current.type_identifier);
798            }
799        }
800    }
801    Ok(())
802}
803
804fn format_struct_with_spans(
805    shape: &Shape,
806    struct_type: &StructType,
807    ctx: &mut SpanTrackingContext,
808) -> core::fmt::Result {
809    // Track start of the whole type definition
810    let type_start = ctx.len();
811
812    // Write #[derive(Facet)]
813    writeln!(ctx.output, "#[derive(Facet)]")?;
814
815    // Write facet attributes if any
816    write_facet_attrs(shape, &mut ctx.output)?;
817
818    // Write struct definition
819    match struct_type.kind {
820        StructKind::Struct => {
821            writeln!(ctx.output, "struct {} {{", shape.type_identifier)?;
822            for field in struct_type.fields {
823                write!(ctx.output, "    ")?;
824                // Track the span of the field name (key)
825                let key_start = ctx.len();
826                write!(ctx.output, "{}", field.name)?;
827                let key_end = ctx.len();
828                write!(ctx.output, ": ")?;
829                // Track the span of the type annotation (value)
830                let value_start = ctx.len();
831                write_type_name(field.shape(), &mut ctx.output)?;
832                let value_end = ctx.len();
833                ctx.record_field_span(
834                    vec![PathSegment::Field(Cow::Borrowed(field.name))],
835                    (key_start, key_end),
836                    (value_start, value_end),
837                );
838                writeln!(ctx.output, ",")?;
839            }
840            write!(ctx.output, "}}")?;
841        }
842        StructKind::Tuple | StructKind::TupleStruct => {
843            write!(ctx.output, "struct {}(", shape.type_identifier)?;
844            for (i, field) in struct_type.fields.iter().enumerate() {
845                if i > 0 {
846                    write!(ctx.output, ", ")?;
847                }
848                // For tuple structs, key and value span are the same (just the type)
849                let type_start = ctx.len();
850                write_type_name(field.shape(), &mut ctx.output)?;
851                let type_end = ctx.len();
852                // Use field name if available, otherwise use index as string
853                let field_name = if !field.name.is_empty() {
854                    field.name
855                } else {
856                    // Tuple fields don't have names, skip span tracking
857                    continue;
858                };
859                ctx.record_field_span(
860                    vec![PathSegment::Field(Cow::Borrowed(field_name))],
861                    (type_start, type_end), // key is the type itself for tuples
862                    (type_start, type_end),
863                );
864            }
865            write!(ctx.output, ");")?;
866        }
867        StructKind::Unit => {
868            write!(ctx.output, "struct {};", shape.type_identifier)?;
869        }
870    }
871
872    // Record span for the root (empty path) covering the whole type
873    let type_end = ctx.len();
874    ctx.record_field_span(vec![], (type_start, type_end), (type_start, type_end));
875
876    Ok(())
877}
878
879fn format_enum_with_spans(
880    shape: &Shape,
881    enum_type: &EnumType,
882    ctx: &mut SpanTrackingContext,
883) -> core::fmt::Result {
884    // Track start of the whole type definition
885    let type_start = ctx.len();
886
887    // Write #[derive(Facet)]
888    writeln!(ctx.output, "#[derive(Facet)]")?;
889
890    // Write repr for the discriminant type
891    let repr_str = match enum_type.enum_repr {
892        EnumRepr::RustNPO => None,
893        EnumRepr::U8 => Some("u8"),
894        EnumRepr::U16 => Some("u16"),
895        EnumRepr::U32 => Some("u32"),
896        EnumRepr::U64 => Some("u64"),
897        EnumRepr::USize => Some("usize"),
898        EnumRepr::I8 => Some("i8"),
899        EnumRepr::I16 => Some("i16"),
900        EnumRepr::I32 => Some("i32"),
901        EnumRepr::I64 => Some("i64"),
902        EnumRepr::ISize => Some("isize"),
903    };
904
905    if let Some(repr) = repr_str {
906        writeln!(ctx.output, "#[repr({repr})]")?;
907    }
908
909    // Write facet attributes if any
910    write_facet_attrs(shape, &mut ctx.output)?;
911
912    // Write enum definition
913    writeln!(ctx.output, "enum {} {{", shape.type_identifier)?;
914
915    for variant in enum_type.variants {
916        match variant.data.kind {
917            StructKind::Unit => {
918                write!(ctx.output, "    ")?;
919                // For unit variants, key and value are the same (just the variant name)
920                let name_start = ctx.len();
921                write!(ctx.output, "{}", variant.name)?;
922                let name_end = ctx.len();
923                ctx.record_field_span(
924                    vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
925                    (name_start, name_end),
926                    (name_start, name_end),
927                );
928                writeln!(ctx.output, ",")?;
929            }
930            StructKind::Tuple | StructKind::TupleStruct => {
931                write!(ctx.output, "    ")?;
932                let variant_name_start = ctx.len();
933                write!(ctx.output, "{}", variant.name)?;
934                let variant_name_end = ctx.len();
935                write!(ctx.output, "(")?;
936                let tuple_start = ctx.len();
937                for (i, field) in variant.data.fields.iter().enumerate() {
938                    if i > 0 {
939                        write!(ctx.output, ", ")?;
940                    }
941                    let type_start = ctx.len();
942                    write_type_name(field.shape(), &mut ctx.output)?;
943                    let type_end = ctx.len();
944                    // Track span for variant field
945                    if !field.name.is_empty() {
946                        ctx.record_field_span(
947                            vec![
948                                PathSegment::Variant(Cow::Borrowed(variant.name)),
949                                PathSegment::Field(Cow::Borrowed(field.name)),
950                            ],
951                            (type_start, type_end),
952                            (type_start, type_end),
953                        );
954                    }
955                }
956                write!(ctx.output, ")")?;
957                let tuple_end = ctx.len();
958                // Record variant span: key is the name, value is the tuple contents
959                ctx.record_field_span(
960                    vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
961                    (variant_name_start, variant_name_end),
962                    (tuple_start, tuple_end),
963                );
964                writeln!(ctx.output, ",")?;
965            }
966            StructKind::Struct => {
967                write!(ctx.output, "    ")?;
968                let variant_name_start = ctx.len();
969                write!(ctx.output, "{}", variant.name)?;
970                let variant_name_end = ctx.len();
971                writeln!(ctx.output, " {{")?;
972                let struct_start = ctx.len();
973                for field in variant.data.fields {
974                    write!(ctx.output, "        ")?;
975                    let key_start = ctx.len();
976                    write!(ctx.output, "{}", field.name)?;
977                    let key_end = ctx.len();
978                    write!(ctx.output, ": ")?;
979                    let value_start = ctx.len();
980                    write_type_name(field.shape(), &mut ctx.output)?;
981                    let value_end = ctx.len();
982                    ctx.record_field_span(
983                        vec![
984                            PathSegment::Variant(Cow::Borrowed(variant.name)),
985                            PathSegment::Field(Cow::Borrowed(field.name)),
986                        ],
987                        (key_start, key_end),
988                        (value_start, value_end),
989                    );
990                    writeln!(ctx.output, ",")?;
991                }
992                write!(ctx.output, "    }}")?;
993                let struct_end = ctx.len();
994                // Record variant span: key is the name, value is the struct body
995                ctx.record_field_span(
996                    vec![PathSegment::Variant(Cow::Borrowed(variant.name))],
997                    (variant_name_start, variant_name_end),
998                    (struct_start, struct_end),
999                );
1000                writeln!(ctx.output, ",")?;
1001            }
1002        }
1003    }
1004
1005    write!(ctx.output, "}}")?;
1006
1007    // Record span for the root (empty path) covering the whole type
1008    let type_end = ctx.len();
1009    ctx.record_field_span(vec![], (type_start, type_end), (type_start, type_end));
1010
1011    Ok(())
1012}
1013
1014/// Collect nested user-defined types from struct fields
1015fn collect_nested_types<'a>(struct_type: &'a StructType, queue: &mut Vec<&'a Shape>) {
1016    for field in struct_type.fields {
1017        collect_from_shape(field.shape(), queue);
1018    }
1019}
1020
1021/// Recursively collect user-defined types from a shape (handles containers)
1022fn collect_from_shape<'a>(shape: &'a Shape, queue: &mut Vec<&'a Shape>) {
1023    match shape.def {
1024        Def::List(list_def) => collect_from_shape(list_def.t, queue),
1025        Def::Array(array_def) => collect_from_shape(array_def.t, queue),
1026        Def::Map(map_def) => {
1027            collect_from_shape(map_def.k, queue);
1028            collect_from_shape(map_def.v, queue);
1029        }
1030        Def::Option(option_def) => collect_from_shape(option_def.t, queue),
1031        _ => {
1032            // Check if it's a user-defined type worth expanding
1033            if let Type::User(UserType::Struct(_) | UserType::Enum(_)) = &shape.ty {
1034                queue.push(shape);
1035            }
1036        }
1037    }
1038}
1039
1040// Plain text helpers for span tracking (ANSI codes would break byte offsets)
1041fn write_facet_attrs(shape: &Shape, output: &mut String) -> core::fmt::Result {
1042    let mut attrs: Vec<String> = Vec::new();
1043
1044    if let Some(tag) = shape.get_tag_attr() {
1045        if let Some(content) = shape.get_content_attr() {
1046            attrs.push(alloc::format!("tag = \"{tag}\", content = \"{content}\""));
1047        } else {
1048            attrs.push(alloc::format!("tag = \"{tag}\""));
1049        }
1050    }
1051
1052    if shape.is_untagged() {
1053        attrs.push("untagged".into());
1054    }
1055
1056    if shape.has_deny_unknown_fields_attr() {
1057        attrs.push("deny_unknown_fields".into());
1058    }
1059
1060    if !attrs.is_empty() {
1061        writeln!(output, "#[facet({})]", attrs.join(", "))?;
1062    }
1063
1064    Ok(())
1065}
1066
1067fn write_type_name(shape: &Shape, output: &mut String) -> core::fmt::Result {
1068    match shape.def {
1069        Def::Scalar => {
1070            write!(output, "{}", shape.type_identifier)?;
1071        }
1072        Def::Pointer(_) => {
1073            if let Type::Pointer(PointerType::Reference(r)) = shape.ty
1074                && let Def::Array(array_def) = r.target.def
1075            {
1076                write!(output, "&[")?;
1077                write_type_name(array_def.t, output)?;
1078                write!(output, "; {}]", array_def.n)?;
1079                return Ok(());
1080            }
1081            write!(output, "{}", shape.type_identifier)?;
1082        }
1083        Def::List(list_def) => {
1084            write!(output, "Vec<")?;
1085            write_type_name(list_def.t, output)?;
1086            write!(output, ">")?;
1087        }
1088        Def::Array(array_def) => {
1089            write!(output, "[")?;
1090            write_type_name(array_def.t, output)?;
1091            write!(output, "; {}]", array_def.n)?;
1092        }
1093        Def::Map(map_def) => {
1094            let map_name = if shape.type_identifier.contains("BTreeMap") {
1095                "BTreeMap"
1096            } else {
1097                "HashMap"
1098            };
1099            write!(output, "{map_name}<")?;
1100            write_type_name(map_def.k, output)?;
1101            write!(output, ", ")?;
1102            write_type_name(map_def.v, output)?;
1103            write!(output, ">")?;
1104        }
1105        Def::Option(option_def) => {
1106            write!(output, "Option<")?;
1107            write_type_name(option_def.t, output)?;
1108            write!(output, ">")?;
1109        }
1110        _ => {
1111            write!(output, "{}", shape.type_identifier)?;
1112        }
1113    }
1114    Ok(())
1115}
1116
1117#[cfg(test)]
1118mod tests {
1119    use super::*;
1120    use facet::Facet;
1121
1122    #[test]
1123    fn test_simple_struct() {
1124        #[derive(Facet)]
1125        struct Simple {
1126            name: String,
1127            count: u32,
1128        }
1129
1130        let output = format_shape(Simple::SHAPE);
1131        assert!(output.contains("struct Simple"));
1132        assert!(output.contains("name: String"));
1133        assert!(output.contains("count: u32"));
1134    }
1135
1136    #[test]
1137    fn test_enum_with_tag() {
1138        #[derive(Facet)]
1139        #[repr(C)]
1140        #[facet(tag = "type")]
1141        #[allow(dead_code)]
1142        enum Tagged {
1143            A { x: i32 },
1144            B { y: String },
1145        }
1146
1147        let output = format_shape(Tagged::SHAPE);
1148        assert!(output.contains("enum Tagged"));
1149        assert!(output.contains("#[facet(tag = \"type\")]"));
1150    }
1151
1152    #[test]
1153    fn test_nested_types() {
1154        #[derive(Facet)]
1155        #[allow(dead_code)]
1156        struct Inner {
1157            value: i32,
1158        }
1159
1160        #[derive(Facet)]
1161        #[allow(dead_code)]
1162        struct Outer {
1163            inner: Inner,
1164            name: String,
1165        }
1166
1167        let output = format_shape(Outer::SHAPE);
1168        // Should contain both Outer and Inner definitions
1169        assert!(output.contains("struct Outer"), "Missing Outer: {output}");
1170        assert!(
1171            output.contains("inner: Inner"),
1172            "Missing inner field: {output}"
1173        );
1174        assert!(
1175            output.contains("struct Inner"),
1176            "Missing Inner definition: {output}"
1177        );
1178        assert!(
1179            output.contains("value: i32"),
1180            "Missing value field: {output}"
1181        );
1182    }
1183
1184    #[test]
1185    fn test_nested_in_vec() {
1186        #[derive(Facet)]
1187        #[allow(dead_code)]
1188        struct Item {
1189            id: u32,
1190        }
1191
1192        #[derive(Facet)]
1193        #[allow(dead_code)]
1194        struct Container {
1195            items: Vec<Item>,
1196        }
1197
1198        let output = format_shape(Container::SHAPE);
1199        // Should contain both Container and Item definitions
1200        assert!(
1201            output.contains("struct Container"),
1202            "Missing Container: {output}"
1203        );
1204        assert!(
1205            output.contains("items: Vec<Item>"),
1206            "Missing items field: {output}"
1207        );
1208        assert!(
1209            output.contains("struct Item"),
1210            "Missing Item definition: {output}"
1211        );
1212    }
1213
1214    #[test]
1215    fn test_format_shape_with_spans() {
1216        #[derive(Facet)]
1217        #[allow(dead_code)]
1218        struct Config {
1219            name: String,
1220            max_retries: u8,
1221            enabled: bool,
1222        }
1223
1224        let result = format_shape_with_spans(Config::SHAPE);
1225
1226        // Check that spans were recorded for each field
1227        let name_path = vec![PathSegment::Field(Cow::Borrowed("name"))];
1228        let retries_path = vec![PathSegment::Field(Cow::Borrowed("max_retries"))];
1229        let enabled_path = vec![PathSegment::Field(Cow::Borrowed("enabled"))];
1230
1231        assert!(
1232            result.spans.contains_key(&name_path),
1233            "Missing span for 'name' field. Spans: {:?}",
1234            result.spans
1235        );
1236        assert!(
1237            result.spans.contains_key(&retries_path),
1238            "Missing span for 'max_retries' field. Spans: {:?}",
1239            result.spans
1240        );
1241        assert!(
1242            result.spans.contains_key(&enabled_path),
1243            "Missing span for 'enabled' field. Spans: {:?}",
1244            result.spans
1245        );
1246
1247        // Verify the span for max_retries points to "u8"
1248        let field_span = &result.spans[&retries_path];
1249        let spanned_text = &result.text[field_span.value.0..field_span.value.1];
1250        assert_eq!(spanned_text, "u8", "Expected 'u8', got '{spanned_text}'");
1251    }
1252
1253    #[test]
1254    fn test_format_enum_with_spans() {
1255        #[derive(Facet)]
1256        #[repr(u8)]
1257        #[allow(dead_code)]
1258        enum Status {
1259            Active,
1260            Pending,
1261            Error { code: i32, message: String },
1262        }
1263
1264        let result = format_shape_with_spans(Status::SHAPE);
1265
1266        // Check variant spans
1267        let active_path = vec![PathSegment::Variant(Cow::Borrowed("Active"))];
1268        let error_path = vec![PathSegment::Variant(Cow::Borrowed("Error"))];
1269        let error_code_path = vec![
1270            PathSegment::Variant(Cow::Borrowed("Error")),
1271            PathSegment::Field(Cow::Borrowed("code")),
1272        ];
1273
1274        assert!(
1275            result.spans.contains_key(&active_path),
1276            "Missing span for 'Active' variant. Spans: {:?}",
1277            result.spans
1278        );
1279        assert!(
1280            result.spans.contains_key(&error_path),
1281            "Missing span for 'Error' variant. Spans: {:?}",
1282            result.spans
1283        );
1284        assert!(
1285            result.spans.contains_key(&error_code_path),
1286            "Missing span for 'Error.code' field. Spans: {:?}",
1287            result.spans
1288        );
1289
1290        // Verify the span for code points to "i32"
1291        let field_span = &result.spans[&error_code_path];
1292        let spanned_text = &result.text[field_span.value.0..field_span.value.1];
1293        assert_eq!(spanned_text, "i32", "Expected 'i32', got '{spanned_text}'");
1294    }
1295
1296    #[test]
1297    fn test_format_with_doc_comments() {
1298        /// A configuration struct for the application.
1299        #[derive(Facet)]
1300        #[allow(dead_code)]
1301        struct Config {
1302            /// The name of the configuration.
1303            name: String,
1304            /// Maximum number of retries.
1305            max_retries: u8,
1306        }
1307
1308        // With doc comments (default)
1309        let output = format_shape(Config::SHAPE);
1310        assert!(
1311            output.contains("/// A configuration struct"),
1312            "Should contain struct doc comment: {output}"
1313        );
1314        assert!(
1315            output.contains("/// The name of the configuration"),
1316            "Should contain field doc comment: {output}"
1317        );
1318        assert!(
1319            output.contains("/// Maximum number of retries"),
1320            "Should contain field doc comment: {output}"
1321        );
1322
1323        // Without doc comments (explicit config)
1324        let config = ShapeFormatConfig::new();
1325        let output_without = format_shape_with_config(Config::SHAPE, &config);
1326        assert!(
1327            !output_without.contains("///"),
1328            "Should not contain doc comments when disabled: {output_without}"
1329        );
1330    }
1331
1332    #[test]
1333    fn test_format_enum_with_doc_comments() {
1334        /// Status of an operation.
1335        #[derive(Facet)]
1336        #[repr(u8)]
1337        #[allow(dead_code)]
1338        enum Status {
1339            /// The operation is active.
1340            Active,
1341            /// The operation failed with an error.
1342            Error {
1343                /// Error code.
1344                code: i32,
1345            },
1346        }
1347
1348        let config = ShapeFormatConfig::new().with_doc_comments();
1349        let output = format_shape_with_config(Status::SHAPE, &config);
1350
1351        assert!(
1352            output.contains("/// Status of an operation"),
1353            "Should contain enum doc comment: {output}"
1354        );
1355        assert!(
1356            output.contains("/// The operation is active"),
1357            "Should contain variant doc comment: {output}"
1358        );
1359        assert!(
1360            output.contains("/// The operation failed"),
1361            "Should contain variant doc comment: {output}"
1362        );
1363        assert!(
1364            output.contains("/// Error code"),
1365            "Should contain variant field doc comment: {output}"
1366        );
1367    }
1368}