rustdoc_md/
lib.rs

1use rustdoc_types::{
2    Abi, AssocItemConstraintKind, Crate, Enum, GenericArg, GenericArgs, GenericBound,
3    GenericParamDefKind, Generics, Id, Impl, Item, ItemEnum, MacroKind, Module,
4    PreciseCapturingArg, Struct, StructKind, Term, Trait, TraitBoundModifier, Type, Union,
5    VariantKind, Visibility, WherePredicate,
6};
7
8pub fn rustdoc_json_to_markdown(data: Crate) -> String {
9    let mut output = String::new();
10
11    // Add crate header and basic info
12    output.push_str("# Crate Documentation\n\n");
13
14    if let Some(version) = &data.crate_version {
15        output.push_str(&format!("**Version:** {}\n\n", version));
16    }
17
18    output.push_str(&format!("**Format Version:** {}\n\n", data.format_version));
19
20    // Process the root module to start
21    let root_id = data.root;
22    if let Some(root_item) = data.index.get(&root_id) {
23        if let ItemEnum::Module(module) = &root_item.inner {
24            if let Some(name) = &root_item.name {
25                output.push_str(&format!("# Module `{}`\n\n", name));
26            } else if module.is_crate {
27                output.push_str("# Crate Root\n\n");
28            }
29
30            // Add root documentation if available
31            if let Some(docs) = &root_item.docs {
32                output.push_str(&format!("{}\n\n", docs));
33            }
34
35            // Process all items in the module with consistent heading levels
36            // starting at level 2 for top-level categories
37            process_items(&mut output, &module.items, &data, 2);
38        }
39    }
40
41    output
42}
43
44fn process_items(output: &mut String, item_ids: &[Id], data: &Crate, level: usize) {
45    // Cap heading level at 6 (maximum valid Markdown heading level)
46    let heading_level = std::cmp::min(level, 6);
47
48    // Group items by kind for better organization
49    let mut modules = Vec::new();
50    let mut types = Vec::new();
51    let mut traits = Vec::new();
52    let mut functions = Vec::new();
53    let mut constants = Vec::new();
54    let mut macros = Vec::new();
55    let mut reexports = Vec::new(); // New category for re-exports
56    let mut other_items = Vec::new();
57
58    for &id in item_ids {
59        if let Some(item) = data.index.get(&id) {
60            match &item.inner {
61                ItemEnum::Module(_) => modules.push(id),
62                ItemEnum::Struct(_)
63                | ItemEnum::Enum(_)
64                | ItemEnum::Union(_)
65                | ItemEnum::TypeAlias(_) => types.push(id),
66                ItemEnum::Trait(_) | ItemEnum::TraitAlias(_) => traits.push(id),
67                ItemEnum::Function(_) => functions.push(id),
68                ItemEnum::Constant { .. } | ItemEnum::Static(_) => constants.push(id),
69                ItemEnum::Macro(_) | ItemEnum::ProcMacro(_) => macros.push(id),
70                ItemEnum::Use(_) => reexports.push(id), // Categorize re-exports
71                _ => other_items.push(id),
72            }
73        }
74    }
75
76    // Process each group in order
77    if !modules.is_empty() {
78        output.push_str(&format!("{} Modules\n\n", "#".repeat(heading_level)));
79        for id in modules {
80            process_item(output, data.index.get(&id).unwrap(), data, level + 1);
81        }
82    }
83
84    if !types.is_empty() {
85        output.push_str(&format!("{} Types\n\n", "#".repeat(heading_level)));
86        for id in types {
87            process_item(output, data.index.get(&id).unwrap(), data, level + 1);
88        }
89    }
90
91    if !traits.is_empty() {
92        output.push_str(&format!("{} Traits\n\n", "#".repeat(heading_level)));
93        for id in traits {
94            process_item(output, data.index.get(&id).unwrap(), data, level + 1);
95        }
96    }
97
98    if !functions.is_empty() {
99        output.push_str(&format!("{} Functions\n\n", "#".repeat(heading_level)));
100        for id in functions {
101            process_item(output, data.index.get(&id).unwrap(), data, level + 1);
102        }
103    }
104
105    if !constants.is_empty() {
106        output.push_str(&format!(
107            "{} Constants and Statics\n\n",
108            "#".repeat(heading_level)
109        ));
110        for id in constants {
111            process_item(output, data.index.get(&id).unwrap(), data, level + 1);
112        }
113    }
114
115    if !macros.is_empty() {
116        output.push_str(&format!("{} Macros\n\n", "#".repeat(heading_level)));
117        for id in macros {
118            process_item(output, data.index.get(&id).unwrap(), data, level + 1);
119        }
120    }
121
122    if !reexports.is_empty() {
123        output.push_str(&format!("{} Re-exports\n\n", "#".repeat(heading_level)));
124        for id in reexports {
125            process_item(output, data.index.get(&id).unwrap(), data, level + 1);
126        }
127    }
128
129    if !other_items.is_empty() {
130        output.push_str(&format!("{} Other Items\n\n", "#".repeat(heading_level)));
131        for id in other_items {
132            process_item(output, data.index.get(&id).unwrap(), data, level + 1);
133        }
134    }
135}
136
137fn process_item(output: &mut String, item: &Item, data: &Crate, level: usize) {
138    // Cap heading level at 6 (maximum valid Markdown heading level)
139    let heading_level = std::cmp::min(level, 6);
140    let heading = "#".repeat(heading_level);
141
142    // Add item heading with name and kind
143    match &item.inner {
144        // Check for re-exports first, regardless of whether they have a name
145        ItemEnum::Use(use_item) => {
146            // Extract the meaningful part of the source path
147            let source_name = use_item
148                .source
149                .split("::")
150                .last()
151                .unwrap_or(&use_item.source);
152
153            // Format the heading based on the type of re-export
154            if use_item.is_glob {
155                output.push_str(&format!(
156                    "{} Re-export `{}::*`\n\n",
157                    heading, use_item.source
158                ));
159            } else if let Some(name) = &item.name {
160                if name != source_name {
161                    output.push_str(&format!(
162                        "{} Re-export `{}` as `{}`\n\n",
163                        heading, source_name, name
164                    ));
165                } else {
166                    output.push_str(&format!("{} Re-export `{}`\n\n", heading, name));
167                }
168            } else {
169                output.push_str(&format!("{} Re-export `{}`\n\n", heading, source_name));
170            }
171        }
172        _ => {
173            // Handle all other items as before
174            if let Some(name) = &item.name {
175                match &item.inner {
176                    // For modules, always use a consistent level (level 2) to ensure they stand out
177                    ItemEnum::Module(_) => output.push_str(&format!("## Module `{}`\n\n", name)),
178                    ItemEnum::Struct(_) => {
179                        output.push_str(&format!("{} Struct `{}`\n\n", heading, name))
180                    }
181                    ItemEnum::Enum(_) => {
182                        output.push_str(&format!("{} Enum `{}`\n\n", heading, name))
183                    }
184                    ItemEnum::Union(_) => {
185                        output.push_str(&format!("{} Union `{}`\n\n", heading, name))
186                    }
187                    ItemEnum::Trait(_) => {
188                        output.push_str(&format!("{} Trait `{}`\n\n", heading, name))
189                    }
190                    ItemEnum::TraitAlias(_) => {
191                        output.push_str(&format!("{} Trait Alias `{}`\n\n", heading, name))
192                    }
193                    ItemEnum::Function(_) => {
194                        output.push_str(&format!("{} Function `{}`\n\n", heading, name))
195                    }
196                    ItemEnum::TypeAlias(_) => {
197                        output.push_str(&format!("{} Type Alias `{}`\n\n", heading, name))
198                    }
199                    ItemEnum::Constant { .. } => {
200                        output.push_str(&format!("{} Constant `{}`\n\n", heading, name))
201                    }
202                    ItemEnum::Static(_) => {
203                        output.push_str(&format!("{} Static `{}`\n\n", heading, name))
204                    }
205                    ItemEnum::Macro(_) => {
206                        output.push_str(&format!("{} Macro `{}`\n\n", heading, name))
207                    }
208                    ItemEnum::ProcMacro(_) => {
209                        output.push_str(&format!("{} Procedural Macro `{}`\n\n", heading, name))
210                    }
211                    ItemEnum::ExternCrate {
212                        name: crate_name, ..
213                    } => output.push_str(&format!("{} Extern Crate `{}`\n\n", heading, crate_name)),
214                    _ => output.push_str(&format!("{} `{}`\n\n", heading, name)),
215                }
216            } else {
217                // Special case for impl blocks and other nameless items
218                match &item.inner {
219                    ItemEnum::Impl(impl_) => {
220                        if let Some(trait_) = &impl_.trait_ {
221                            // For trait impls, show "Implementation of TraitName for Type"
222                            output.push_str(&format!(
223                                "{} Implementation of `{}` for `{}`\n\n",
224                                heading,
225                                trait_.path,
226                                format_type(&impl_.for_, data)
227                            ));
228                        } else {
229                            // For inherent impls, show "Implementation for Type"
230                            output.push_str(&format!(
231                                "{} Implementation for `{}`\n\n",
232                                heading,
233                                format_type(&impl_.for_, data)
234                            ));
235                        }
236                    }
237                    _ => {
238                        // For other items without names
239                        output.push_str(&format!("{} Unnamed Item\n\n", heading));
240                    }
241                }
242            }
243        }
244    }
245
246    // Add item attributes if present
247    if !item.attrs.is_empty() {
248        output.push_str("**Attributes:**\n\n");
249        for attr in &item.attrs {
250            output.push_str(&format!("- `{:?}`\n", attr));
251        }
252        output.push('\n');
253    }
254
255    // Add deprecation info if present
256    if let Some(deprecation) = &item.deprecation {
257        output.push_str("**⚠️ Deprecated");
258        if let Some(since) = &deprecation.since {
259            output.push_str(&format!(" since {}", since));
260        }
261        output.push_str("**");
262
263        if let Some(note) = &deprecation.note {
264            output.push_str(&format!(": {}", note));
265        }
266        output.push_str("\n\n");
267    }
268
269    // Add documentation if available
270    if let Some(docs) = &item.docs {
271        output.push_str(&format!("{}\n\n", docs));
272    }
273
274    // Add code block with item signature
275    output.push_str("```rust\n");
276    format_item_signature(output, item, data);
277    output.push_str("\n```\n\n");
278
279    // Process additional details based on item kind
280    match &item.inner {
281        ItemEnum::Module(module) => process_module_details(output, module, data, level + 1),
282        ItemEnum::Struct(struct_) => process_struct_details(output, struct_, data, level + 1),
283        ItemEnum::Enum(enum_) => process_enum_details(output, enum_, data, level + 1),
284        ItemEnum::Union(union_) => process_union_details(output, union_, data, level + 1),
285        ItemEnum::Trait(trait_) => process_trait_details(output, trait_, data, level + 1),
286        ItemEnum::Impl(impl_) => process_impl_details(output, impl_, data, level + 1),
287        _ => {}
288    }
289}
290
291fn format_item_signature(output: &mut String, item: &Item, data: &Crate) {
292    // Format visibility
293    match &item.visibility {
294        Visibility::Public => output.push_str("pub "),
295        Visibility::Crate => output.push_str("pub(crate) "),
296        Visibility::Restricted { path, .. } => output.push_str(&format!("pub(in {}) ", path)),
297        Visibility::Default => {}
298    }
299
300    // Format item based on its kind
301    match &item.inner {
302        ItemEnum::Module(_) => {
303            if let Some(name) = &item.name {
304                output.push_str(&format!("mod {} {{ /* ... */ }}", name));
305            }
306        }
307        ItemEnum::Struct(struct_) => {
308            if let Some(name) = &item.name {
309                output.push_str(&format!("struct {}", name));
310                format_generics(output, &struct_.generics, data);
311
312                match &struct_.kind {
313                    StructKind::Unit => output.push(';'),
314                    StructKind::Tuple(fields) => {
315                        output.push('(');
316                        for (i, field_opt) in fields.iter().enumerate() {
317                            if let Some(field_id) = field_opt {
318                                if let Some(field_item) = data.index.get(field_id) {
319                                    if let ItemEnum::StructField(field_type) = &field_item.inner {
320                                        // Field visibility if needed
321                                        match &field_item.visibility {
322                                            Visibility::Public => output.push_str("pub "),
323                                            Visibility::Crate => output.push_str("pub(crate) "),
324                                            Visibility::Restricted { path, .. } => {
325                                                output.push_str(&format!("pub(in {}) ", path))
326                                            }
327                                            Visibility::Default => {}
328                                        }
329                                        output.push_str(&format_type(field_type, data));
330                                    }
331                                }
332                                if i < fields.len() - 1 {
333                                    output.push_str(", ");
334                                }
335                            } else {
336                                // For stripped fields
337                                output.push_str("/* private field */");
338                                if i < fields.len() - 1 {
339                                    output.push_str(", ");
340                                }
341                            }
342                        }
343                        output.push_str(");");
344                    }
345                    StructKind::Plain {
346                        fields,
347                        has_stripped_fields,
348                    } => {
349                        output.push_str(" {\n");
350                        for &field_id in fields {
351                            if let Some(field_item) = data.index.get(&field_id) {
352                                if let Some(field_name) = &field_item.name {
353                                    if let ItemEnum::StructField(field_type) = &field_item.inner {
354                                        // Field visibility
355                                        match &field_item.visibility {
356                                            Visibility::Public => output.push_str("    pub "),
357                                            Visibility::Crate => output.push_str("    pub(crate) "),
358                                            Visibility::Restricted { path, .. } => {
359                                                output.push_str(&format!("    pub(in {}) ", path))
360                                            }
361                                            Visibility::Default => output.push_str("    "),
362                                        }
363                                        output.push_str(&format!(
364                                            "{}: {},\n",
365                                            field_name,
366                                            format_type(field_type, data)
367                                        ));
368                                    }
369                                }
370                            }
371                        }
372                        if *has_stripped_fields {
373                            output.push_str("    // Some fields omitted\n");
374                        }
375                        output.push('}');
376                    }
377                }
378            }
379        }
380        ItemEnum::Enum(enum_) => {
381            if let Some(name) = &item.name {
382                output.push_str(&format!("enum {}", name));
383                format_generics(output, &enum_.generics, data);
384                output.push_str(" {\n");
385
386                for &variant_id in &enum_.variants {
387                    if let Some(variant_item) = data.index.get(&variant_id) {
388                        if let Some(variant_name) = &variant_item.name {
389                            output.push_str(&format!("    {}", variant_name));
390
391                            if let ItemEnum::Variant(variant) = &variant_item.inner {
392                                match &variant.kind {
393                                    VariantKind::Plain => {}
394                                    VariantKind::Tuple(fields) => {
395                                        output.push('(');
396                                        for (i, field_opt) in fields.iter().enumerate() {
397                                            if let Some(field_id) = field_opt {
398                                                if let Some(field_item) = data.index.get(field_id) {
399                                                    if let ItemEnum::StructField(field_type) =
400                                                        &field_item.inner
401                                                    {
402                                                        output.push_str(&format_type(
403                                                            field_type, data,
404                                                        ));
405                                                    }
406                                                }
407                                                if i < fields.len() - 1 {
408                                                    output.push_str(", ");
409                                                }
410                                            } else {
411                                                // For stripped fields
412                                                output.push_str("/* private field */");
413                                                if i < fields.len() - 1 {
414                                                    output.push_str(", ");
415                                                }
416                                            }
417                                        }
418                                        output.push(')');
419                                    }
420                                    VariantKind::Struct {
421                                        fields,
422                                        has_stripped_fields,
423                                    } => {
424                                        output.push_str(" {\n");
425                                        for &field_id in fields {
426                                            if let Some(field_item) = data.index.get(&field_id) {
427                                                if let Some(field_name) = &field_item.name {
428                                                    if let ItemEnum::StructField(field_type) =
429                                                        &field_item.inner
430                                                    {
431                                                        output.push_str(&format!(
432                                                            "        {}: {},\n",
433                                                            field_name,
434                                                            format_type(field_type, data)
435                                                        ));
436                                                    }
437                                                }
438                                            }
439                                        }
440                                        if *has_stripped_fields {
441                                            output.push_str("        // Some fields omitted\n");
442                                        }
443                                        output.push_str("    }");
444                                    }
445                                }
446
447                                if let Some(discriminant) = &variant.discriminant {
448                                    output.push_str(&format!(" = {}", discriminant.expr));
449                                }
450                            }
451
452                            output.push_str(",\n");
453                        }
454                    }
455                }
456
457                if enum_.has_stripped_variants {
458                    output.push_str("    // Some variants omitted\n");
459                }
460
461                output.push('}');
462            }
463        }
464        ItemEnum::Union(union_) => {
465            if let Some(name) = &item.name {
466                output.push_str(&format!("union {}", name));
467                format_generics(output, &union_.generics, data);
468                output.push_str(" {\n");
469
470                for &field_id in &union_.fields {
471                    if let Some(field_item) = data.index.get(&field_id) {
472                        if let Some(field_name) = &field_item.name {
473                            if let ItemEnum::StructField(field_type) = &field_item.inner {
474                                match &field_item.visibility {
475                                    Visibility::Public => output.push_str("    pub "),
476                                    Visibility::Crate => output.push_str("    pub(crate) "),
477                                    Visibility::Restricted { path, .. } => {
478                                        output.push_str(&format!("    pub(in {}) ", path))
479                                    }
480                                    Visibility::Default => output.push_str("    "),
481                                }
482                                output.push_str(&format!(
483                                    "{}: {},\n",
484                                    field_name,
485                                    format_type(field_type, data)
486                                ));
487                            }
488                        }
489                    }
490                }
491
492                if union_.has_stripped_fields {
493                    output.push_str("    // Some fields omitted\n");
494                }
495
496                output.push('}');
497            }
498        }
499        ItemEnum::Function(function) => {
500            // Function header
501            if function.header.is_const {
502                output.push_str("const ");
503            }
504            if function.header.is_unsafe {
505                output.push_str("unsafe ");
506            }
507            if function.header.is_async {
508                output.push_str("async ");
509            }
510
511            // ABI
512            match &function.header.abi {
513                Abi::Rust => {}
514                Abi::C { unwind } => {
515                    if *unwind {
516                        output.push_str("extern \"C-unwind\" ");
517                    } else {
518                        output.push_str("extern \"C\" ");
519                    }
520                }
521                Abi::Cdecl { unwind } => {
522                    if *unwind {
523                        output.push_str("extern \"cdecl-unwind\" ");
524                    } else {
525                        output.push_str("extern \"cdecl\" ");
526                    }
527                }
528                Abi::Stdcall { unwind } => {
529                    if *unwind {
530                        output.push_str("extern \"stdcall-unwind\" ");
531                    } else {
532                        output.push_str("extern \"stdcall\" ");
533                    }
534                }
535                Abi::Fastcall { unwind } => {
536                    if *unwind {
537                        output.push_str("extern \"fastcall-unwind\" ");
538                    } else {
539                        output.push_str("extern \"fastcall\" ");
540                    }
541                }
542                Abi::Aapcs { unwind } => {
543                    if *unwind {
544                        output.push_str("extern \"aapcs-unwind\" ");
545                    } else {
546                        output.push_str("extern \"aapcs\" ");
547                    }
548                }
549                Abi::Win64 { unwind } => {
550                    if *unwind {
551                        output.push_str("extern \"win64-unwind\" ");
552                    } else {
553                        output.push_str("extern \"win64\" ");
554                    }
555                }
556                Abi::SysV64 { unwind } => {
557                    if *unwind {
558                        output.push_str("extern \"sysv64-unwind\" ");
559                    } else {
560                        output.push_str("extern \"sysv64\" ");
561                    }
562                }
563                Abi::System { unwind } => {
564                    if *unwind {
565                        output.push_str("extern \"system-unwind\" ");
566                    } else {
567                        output.push_str("extern \"system\" ");
568                    }
569                }
570                Abi::Other(abi) => {
571                    output.push_str(&format!("extern \"{}\" ", abi));
572                }
573            }
574
575            // Function name
576            if let Some(name) = &item.name {
577                output.push_str(&format!("fn {}", name));
578
579                // Generic parameters
580                format_generics(output, &function.generics, data);
581
582                // Parameters
583                output.push('(');
584                for (i, (param_name, param_type)) in function.sig.inputs.iter().enumerate() {
585                    output.push_str(&format!(
586                        "{}: {}",
587                        param_name,
588                        format_type(param_type, data)
589                    ));
590                    if i < function.sig.inputs.len() - 1 || function.sig.is_c_variadic {
591                        output.push_str(", ");
592                    }
593                }
594
595                // Variadic
596                if function.sig.is_c_variadic {
597                    output.push_str("...");
598                }
599
600                output.push(')');
601
602                // Return type
603                if let Some(return_type) = &function.sig.output {
604                    output.push_str(&format!(" -> {}", format_type(return_type, data)));
605                }
606
607                // Where clause
608                format_where_clause(output, &function.generics.where_predicates, data);
609
610                // Function body indication
611                if function.has_body {
612                    output.push_str(" { /* ... */ }");
613                } else {
614                    output.push(';');
615                }
616            }
617        }
618        ItemEnum::Trait(trait_) => {
619            // Trait modifiers
620            if trait_.is_auto {
621                output.push_str("auto ");
622            }
623            if trait_.is_unsafe {
624                output.push_str("unsafe ");
625            }
626
627            // Trait definition
628            if let Some(name) = &item.name {
629                output.push_str(&format!("trait {}", name));
630                format_generics(output, &trait_.generics, data);
631
632                // Trait bounds
633                if !trait_.bounds.is_empty() {
634                    output.push_str(": ");
635                    format_bounds(output, &trait_.bounds, data);
636                }
637
638                // Where clause
639                format_where_clause(output, &trait_.generics.where_predicates, data);
640
641                output.push_str(" {\n    /* Associated items */\n}");
642            }
643        }
644        ItemEnum::TraitAlias(trait_alias) => {
645            if let Some(name) = &item.name {
646                output.push_str(&format!("trait {}", name));
647                format_generics(output, &trait_alias.generics, data);
648                output.push_str(" = ");
649                format_bounds(output, &trait_alias.params, data);
650                format_where_clause(output, &trait_alias.generics.where_predicates, data);
651                output.push(';');
652            }
653        }
654        ItemEnum::Impl(impl_) => {
655            // Impl modifiers
656            if impl_.is_unsafe {
657                output.push_str("unsafe ");
658            }
659
660            output.push_str("impl");
661
662            // Generics
663            format_generics(output, &impl_.generics, data);
664
665            // Trait reference if this is a trait impl
666            if let Some(trait_) = &impl_.trait_ {
667                if impl_.is_negative {
668                    output.push_str(" !");
669                } else {
670                    output.push(' ');
671                }
672
673                output.push_str(&trait_.path);
674                if let Some(args) = &trait_.args {
675                    let mut args_str = String::new();
676                    format_generic_args(&mut args_str, args, data);
677                    output.push_str(&args_str);
678                }
679
680                output.push_str(" for ");
681            }
682
683            // For type
684            output.push_str(&format_type(&impl_.for_, data));
685
686            // Where clause
687            format_where_clause(output, &impl_.generics.where_predicates, data);
688
689            output.push_str(" {\n    /* Associated items */\n}");
690
691            // Add note if this is a compiler-generated impl
692            if impl_.is_synthetic {
693                output.push_str("\n// Note: This impl is compiler-generated");
694            }
695        }
696        ItemEnum::TypeAlias(type_alias) => {
697            if let Some(name) = &item.name {
698                output.push_str(&format!("type {}", name));
699                format_generics(output, &type_alias.generics, data);
700                format_where_clause(output, &type_alias.generics.where_predicates, data);
701                output.push_str(&format!(" = {};", format_type(&type_alias.type_, data)));
702            }
703        }
704        ItemEnum::Constant { type_, const_ } => {
705            if let Some(name) = &item.name {
706                output.push_str(&format!(
707                    "const {}: {} = {};",
708                    name,
709                    format_type(type_, data),
710                    const_.expr
711                ));
712            }
713        }
714        ItemEnum::Static(static_) => {
715            if let Some(name) = &item.name {
716                output.push_str("static ");
717                if static_.is_mutable {
718                    output.push_str("mut ");
719                }
720                if static_.is_unsafe {
721                    output.push_str("/* unsafe */ ");
722                }
723                output.push_str(&format!(
724                    "{}: {} = {};",
725                    name,
726                    format_type(&static_.type_, data),
727                    static_.expr
728                ));
729            }
730        }
731        ItemEnum::Macro(macro_body) => {
732            if let Some(name) = &item.name {
733                output.push_str(&format!(
734                    "macro_rules! {} {{\n    /* {} */\n}}",
735                    name, macro_body
736                ));
737            }
738        }
739        ItemEnum::ProcMacro(proc_macro) => {
740            if let Some(name) = &item.name {
741                output.push_str("#[proc_macro");
742                match proc_macro.kind {
743                    MacroKind::Bang => output.push(']'),
744
745                    MacroKind::Attr => output.push_str("_attribute]"),
746                    MacroKind::Derive => {
747                        output.push_str("_derive]");
748                        if !proc_macro.helpers.is_empty() {
749                            output.push_str("\n// Helpers: ");
750                            for (i, helper) in proc_macro.helpers.iter().enumerate() {
751                                output.push_str(&format!("#[{}]", helper));
752                                if i < proc_macro.helpers.len() - 1 {
753                                    output.push_str(", ");
754                                }
755                            }
756                        }
757                    }
758                }
759                output.push_str(&format!(
760                    "\npub fn {}(/* ... */) -> /* ... */ {{\n    /* ... */\n}}",
761                    name
762                ));
763            }
764        }
765        ItemEnum::ExternCrate { name, rename } => {
766            output.push_str(&format!("extern crate {}", name));
767            if let Some(rename_val) = rename {
768                output.push_str(&format!(" as {}", rename_val));
769            }
770            output.push(';');
771        }
772        ItemEnum::Use(use_item) => {
773            output.push_str(&format!("use {}", use_item.source));
774            if use_item.is_glob {
775                output.push_str("::*");
776            } else if use_item.name
777                != use_item
778                    .source
779                    .split("::")
780                    .last()
781                    .unwrap_or(&use_item.source)
782            {
783                output.push_str(&format!(" as {}", use_item.name));
784            }
785            output.push(';');
786        }
787        ItemEnum::StructField(field_type) => {
788            // For struct fields, just output the type
789            if let Some(name) = &item.name {
790                match &item.visibility {
791                    Visibility::Public => output.push_str("pub "),
792                    Visibility::Crate => output.push_str("pub(crate) "),
793                    Visibility::Restricted { path, .. } => {
794                        output.push_str(&format!("pub(in {}) ", path))
795                    }
796                    Visibility::Default => {}
797                }
798                output.push_str(&format!("{}: {}", name, format_type(field_type, data)));
799            } else {
800                output.push_str(&format_type(field_type, data));
801            }
802        }
803        ItemEnum::Variant(variant) => {
804            // For enum variants
805            if let Some(name) = &item.name {
806                output.push_str(name);
807
808                match &variant.kind {
809                    VariantKind::Plain => {}
810                    VariantKind::Tuple(fields) => {
811                        output.push('(');
812                        for (i, field_opt) in fields.iter().enumerate() {
813                            if let Some(field_id) = field_opt {
814                                if let Some(field_item) = data.index.get(field_id) {
815                                    if let ItemEnum::StructField(field_type) = &field_item.inner {
816                                        output.push_str(&format_type(field_type, data));
817                                    }
818                                }
819                                if i < fields.len() - 1 {
820                                    output.push_str(", ");
821                                }
822                            } else {
823                                // For stripped fields
824                                output.push_str("/* private field */");
825                                if i < fields.len() - 1 {
826                                    output.push_str(", ");
827                                }
828                            }
829                        }
830                        output.push(')');
831                    }
832                    VariantKind::Struct {
833                        fields,
834                        has_stripped_fields,
835                    } => {
836                        output.push_str(" {\n");
837                        for &field_id in fields {
838                            if let Some(field_item) = data.index.get(&field_id) {
839                                if let Some(field_name) = &field_item.name {
840                                    if let ItemEnum::StructField(field_type) = &field_item.inner {
841                                        output.push_str(&format!(
842                                            "    {}: {},\n",
843                                            field_name,
844                                            format_type(field_type, data)
845                                        ));
846                                    }
847                                }
848                            }
849                        }
850                        if *has_stripped_fields {
851                            output.push_str("    // Some fields omitted\n");
852                        }
853                        output.push('}');
854                    }
855                }
856
857                if let Some(discriminant) = &variant.discriminant {
858                    output.push_str(&format!(" = {}", discriminant.expr));
859                }
860            }
861        }
862        ItemEnum::Primitive(primitive) => {
863            output.push_str(&format!("// Primitive type: {}", primitive.name));
864        }
865        ItemEnum::ExternType => {
866            if let Some(name) = &item.name {
867                output.push_str(&format!("extern {{ type {}; }}", name));
868            }
869        }
870        ItemEnum::AssocConst { type_, value } => {
871            if let Some(name) = &item.name {
872                output.push_str(&format!("const {}: {}", name, format_type(type_, data)));
873                if let Some(val) = value {
874                    output.push_str(&format!(" = {}", val));
875                }
876                output.push(';');
877            }
878        }
879        ItemEnum::AssocType {
880            generics,
881            bounds,
882            type_,
883        } => {
884            if let Some(name) = &item.name {
885                output.push_str(&format!("type {}", name));
886                format_generics(output, generics, data);
887
888                if !bounds.is_empty() {
889                    output.push_str(": ");
890                    format_bounds(output, bounds, data);
891                }
892
893                if let Some(ty) = type_ {
894                    output.push_str(&format!(" = {}", format_type(ty, data)));
895                }
896
897                format_where_clause(output, &generics.where_predicates, data);
898                output.push(';');
899            }
900        }
901    }
902}
903
904fn format_generics(output: &mut String, generics: &Generics, data: &Crate) {
905    if generics.params.is_empty() {
906        return;
907    }
908
909    output.push('<');
910    for (i, param) in generics.params.iter().enumerate() {
911        match &param.kind {
912            GenericParamDefKind::Lifetime { outlives } => {
913                output.push_str(&format!("'{}", param.name));
914                if !outlives.is_empty() {
915                    output.push_str(": ");
916                    for (j, lifetime) in outlives.iter().enumerate() {
917                        output.push_str(&format!("'{}", lifetime));
918                        if j < outlives.len() - 1 {
919                            output.push_str(" + ");
920                        }
921                    }
922                }
923            }
924            GenericParamDefKind::Type {
925                bounds,
926                default,
927                is_synthetic,
928            } => {
929                // If synthetic, add a note
930                if *is_synthetic {
931                    output.push_str("/* synthetic */ ");
932                }
933
934                output.push_str(&param.name);
935                if !bounds.is_empty() {
936                    output.push_str(": ");
937                    format_bounds(output, bounds, data);
938                }
939                if let Some(default_type) = default {
940                    output.push_str(&format!(" = {}", format_type(default_type, data)));
941                }
942            }
943            GenericParamDefKind::Const { type_, default } => {
944                output.push_str(&format!(
945                    "const {}: {}",
946                    param.name,
947                    format_type(type_, data)
948                ));
949                if let Some(default_value) = default {
950                    output.push_str(&format!(" = {}", default_value));
951                }
952            }
953        }
954
955        if i < generics.params.len() - 1 {
956            output.push_str(", ");
957        }
958    }
959    output.push('>');
960}
961
962fn format_where_clause(output: &mut String, predicates: &[WherePredicate], data: &Crate) {
963    if predicates.is_empty() {
964        return;
965    }
966
967    output.push_str("\nwhere\n    ");
968    for (i, predicate) in predicates.iter().enumerate() {
969        match predicate {
970            WherePredicate::BoundPredicate {
971                type_,
972                bounds,
973                generic_params,
974            } => {
975                if !generic_params.is_empty() {
976                    output.push_str("for<");
977                    for (j, param) in generic_params.iter().enumerate() {
978                        match &param.kind {
979                            GenericParamDefKind::Lifetime { .. } => {
980                                output.push_str(&format!("'{}", param.name));
981                            }
982                            _ => output.push_str(&param.name),
983                        }
984
985                        if j < generic_params.len() - 1 {
986                            output.push_str(", ");
987                        }
988                    }
989                    output.push_str("> ");
990                }
991
992                output.push_str(&format_type(type_, data));
993
994                if !bounds.is_empty() {
995                    output.push_str(": ");
996                    format_bounds(output, bounds, data);
997                }
998            }
999            WherePredicate::LifetimePredicate { lifetime, outlives } => {
1000                output.push_str(&format!("'{}", lifetime));
1001                if !outlives.is_empty() {
1002                    output.push_str(": ");
1003                    for (j, outlive) in outlives.iter().enumerate() {
1004                        output.push_str(&format!("'{}", outlive));
1005                        if j < outlives.len() - 1 {
1006                            output.push_str(" + ");
1007                        }
1008                    }
1009                }
1010            }
1011            WherePredicate::EqPredicate { lhs, rhs } => {
1012                output.push_str(&format_type(lhs, data));
1013                output.push_str(" = ");
1014                match rhs {
1015                    Term::Type(type_) => output.push_str(&format_type(type_, data)),
1016                    Term::Constant(constant) => output.push_str(&constant.expr),
1017                }
1018            }
1019        }
1020
1021        if i < predicates.len() - 1 {
1022            output.push_str(",\n    ");
1023        }
1024    }
1025}
1026
1027fn format_bounds(output: &mut String, bounds: &[GenericBound], data: &Crate) {
1028    for (i, bound) in bounds.iter().enumerate() {
1029        match bound {
1030            GenericBound::TraitBound {
1031                trait_,
1032                generic_params,
1033                modifier,
1034            } => {
1035                match modifier {
1036                    TraitBoundModifier::None => {}
1037                    TraitBoundModifier::Maybe => output.push('?'),
1038                    TraitBoundModifier::MaybeConst => output.push_str("~const "),
1039                }
1040
1041                if !generic_params.is_empty() {
1042                    output.push_str("for<");
1043                    for (j, param) in generic_params.iter().enumerate() {
1044                        match &param.kind {
1045                            GenericParamDefKind::Lifetime { .. } => {
1046                                output.push_str(&format!("'{}", param.name));
1047                            }
1048                            _ => output.push_str(&param.name),
1049                        }
1050
1051                        if j < generic_params.len() - 1 {
1052                            output.push_str(", ");
1053                        }
1054                    }
1055                    output.push_str("> ");
1056                }
1057
1058                output.push_str(&trait_.path);
1059                if let Some(args) = &trait_.args {
1060                    let mut args_str = String::new();
1061                    format_generic_args(&mut args_str, args, data);
1062                    output.push_str(&args_str);
1063                }
1064            }
1065            GenericBound::Outlives(lifetime) => {
1066                output.push_str(&format!("'{}", lifetime));
1067            }
1068            GenericBound::Use(args) => {
1069                output.push_str("use<");
1070                for (i, arg) in args.iter().enumerate() {
1071                    match arg {
1072                        PreciseCapturingArg::Lifetime(lifetime) => {
1073                            output.push_str(&format!("'{}", lifetime))
1074                        }
1075                        PreciseCapturingArg::Param(param) => output.push_str(param),
1076                    }
1077
1078                    if i < args.len() - 1 {
1079                        output.push_str(", ");
1080                    }
1081                }
1082                output.push('>');
1083            }
1084        }
1085
1086        if i < bounds.len() - 1 {
1087            output.push_str(" + ");
1088        }
1089    }
1090}
1091
1092fn format_generic_args(output: &mut String, args: &GenericArgs, data: &Crate) {
1093    match args {
1094        GenericArgs::AngleBracketed { args, constraints } => {
1095            if args.is_empty() && constraints.is_empty() {
1096                return;
1097            }
1098
1099            output.push('<');
1100
1101            // Format args
1102            for (i, arg) in args.iter().enumerate() {
1103                match arg {
1104                    GenericArg::Lifetime(lifetime) => output.push_str(&format!("'{}", lifetime)),
1105                    GenericArg::Type(type_) => output.push_str(&format_type(type_, data)),
1106                    GenericArg::Const(constant) => output.push_str(&constant.expr),
1107                    GenericArg::Infer => output.push('_'),
1108                }
1109
1110                if i < args.len() - 1 || !constraints.is_empty() {
1111                    output.push_str(", ");
1112                }
1113            }
1114
1115            // Format constraints
1116            for (i, constraint) in constraints.iter().enumerate() {
1117                output.push_str(&constraint.name.to_string());
1118
1119                // Format constraint args if present
1120                if let Some(args) = &constraint.args {
1121                    let mut args_str = String::new();
1122                    format_generic_args(&mut args_str, &args, data);
1123                    if !args_str.is_empty() && args_str != "<>" {
1124                        output.push_str(&args_str);
1125                    }
1126                }
1127
1128                match &constraint.binding {
1129                    AssocItemConstraintKind::Equality(term) => {
1130                        output.push_str(" = ");
1131                        match term {
1132                            Term::Type(type_) => output.push_str(&format_type(type_, data)),
1133                            Term::Constant(constant) => output.push_str(&constant.expr),
1134                        }
1135                    }
1136                    AssocItemConstraintKind::Constraint(bounds) => {
1137                        output.push_str(": ");
1138                        format_bounds(output, bounds, data);
1139                    }
1140                }
1141
1142                if i < constraints.len() - 1 {
1143                    output.push_str(", ");
1144                }
1145            }
1146
1147            output.push('>');
1148        }
1149        GenericArgs::Parenthesized {
1150            inputs,
1151            output: output_type,
1152        } => {
1153            output.push('(');
1154
1155            for (i, input) in inputs.iter().enumerate() {
1156                output.push_str(&format_type(input, data));
1157                if i < inputs.len() - 1 {
1158                    output.push_str(", ");
1159                }
1160            }
1161
1162            output.push(')');
1163
1164            if let Some(output_ty) = output_type {
1165                output.push_str(&format!(" -> {}", format_type(output_ty, data)));
1166            }
1167        }
1168        GenericArgs::ReturnTypeNotation => {
1169            output.push_str("::method(..)");
1170        }
1171    }
1172}
1173
1174fn format_type(ty: &Type, data: &Crate) -> String {
1175    let mut output = String::new();
1176
1177    match ty {
1178        Type::ResolvedPath(path) => {
1179            output.push_str(&path.path);
1180            if let Some(args) = &path.args {
1181                let mut args_str = String::new();
1182                format_generic_args(&mut args_str, args, data);
1183                output.push_str(&args_str);
1184            }
1185        }
1186        Type::DynTrait(dyn_trait) => {
1187            output.push_str("dyn ");
1188
1189            for (i, trait_) in dyn_trait.traits.iter().enumerate() {
1190                // Higher-rank bounds if necessary
1191                if !trait_.generic_params.is_empty() {
1192                    output.push_str("for<");
1193                    for (j, param) in trait_.generic_params.iter().enumerate() {
1194                        match &param.kind {
1195                            GenericParamDefKind::Lifetime { .. } => {
1196                                output.push_str(&format!("'{}", param.name));
1197                            }
1198                            _ => output.push_str(&param.name),
1199                        }
1200
1201                        if j < trait_.generic_params.len() - 1 {
1202                            output.push_str(", ");
1203                        }
1204                    }
1205                    output.push_str("> ");
1206                }
1207
1208                output.push_str(&trait_.trait_.path);
1209                if let Some(args) = &trait_.trait_.args {
1210                    let mut args_str = String::new();
1211                    format_generic_args(&mut args_str, args, data);
1212                    output.push_str(&args_str);
1213                }
1214
1215                if i < dyn_trait.traits.len() - 1 {
1216                    output.push_str(" + ");
1217                }
1218            }
1219
1220            // Lifetime bound if present
1221            if let Some(lifetime) = &dyn_trait.lifetime {
1222                output.push_str(&format!(" + '{}", lifetime));
1223            }
1224        }
1225        Type::Generic(name) => {
1226            output.push_str(name);
1227        }
1228        Type::Primitive(name) => {
1229            output.push_str(name);
1230        }
1231        Type::FunctionPointer(fn_ptr) => {
1232            // For clarity about the parameters
1233            if !fn_ptr.generic_params.is_empty() {
1234                output.push_str("for<");
1235                for (j, param) in fn_ptr.generic_params.iter().enumerate() {
1236                    match &param.kind {
1237                        GenericParamDefKind::Lifetime { .. } => {
1238                            output.push_str(&format!("'{}", param.name));
1239                        }
1240                        _ => output.push_str(&param.name),
1241                    }
1242
1243                    if j < fn_ptr.generic_params.len() - 1 {
1244                        output.push_str(", ");
1245                    }
1246                }
1247                output.push_str("> ");
1248            }
1249
1250            // Function header (const, unsafe, extern, etc.)
1251            if fn_ptr.header.is_const {
1252                output.push_str("const ");
1253            }
1254            if fn_ptr.header.is_unsafe {
1255                output.push_str("unsafe ");
1256            }
1257
1258            // ABI
1259            match &fn_ptr.header.abi {
1260                Abi::Rust => {}
1261                Abi::C { unwind } => {
1262                    if *unwind {
1263                        output.push_str("extern \"C-unwind\" ");
1264                    } else {
1265                        output.push_str("extern \"C\" ");
1266                    }
1267                }
1268                Abi::Cdecl { unwind } => {
1269                    if *unwind {
1270                        output.push_str("extern \"cdecl-unwind\" ");
1271                    } else {
1272                        output.push_str("extern \"cdecl\" ");
1273                    }
1274                }
1275                Abi::Stdcall { unwind } => {
1276                    if *unwind {
1277                        output.push_str("extern \"stdcall-unwind\" ");
1278                    } else {
1279                        output.push_str("extern \"stdcall\" ");
1280                    }
1281                }
1282                Abi::Fastcall { unwind } => {
1283                    if *unwind {
1284                        output.push_str("extern \"fastcall-unwind\" ");
1285                    } else {
1286                        output.push_str("extern \"fastcall\" ");
1287                    }
1288                }
1289                Abi::Aapcs { unwind } => {
1290                    if *unwind {
1291                        output.push_str("extern \"aapcs-unwind\" ");
1292                    } else {
1293                        output.push_str("extern \"aapcs\" ");
1294                    }
1295                }
1296                Abi::Win64 { unwind } => {
1297                    if *unwind {
1298                        output.push_str("extern \"win64-unwind\" ");
1299                    } else {
1300                        output.push_str("extern \"win64\" ");
1301                    }
1302                }
1303                Abi::SysV64 { unwind } => {
1304                    if *unwind {
1305                        output.push_str("extern \"sysv64-unwind\" ");
1306                    } else {
1307                        output.push_str("extern \"sysv64\" ");
1308                    }
1309                }
1310                Abi::System { unwind } => {
1311                    if *unwind {
1312                        output.push_str("extern \"system-unwind\" ");
1313                    } else {
1314                        output.push_str("extern \"system\" ");
1315                    }
1316                }
1317                Abi::Other(abi) => {
1318                    output.push_str(&format!("extern \"{}\" ", abi));
1319                }
1320            }
1321
1322            output.push_str("fn(");
1323
1324            // Parameters
1325            for (i, (_, param_type)) in fn_ptr.sig.inputs.iter().enumerate() {
1326                output.push_str(&format_type(param_type, data));
1327                if i < fn_ptr.sig.inputs.len() - 1 || fn_ptr.sig.is_c_variadic {
1328                    output.push_str(", ");
1329                }
1330            }
1331
1332            // Variadic
1333            if fn_ptr.sig.is_c_variadic {
1334                output.push_str("...");
1335            }
1336
1337            output.push(')');
1338
1339            // Return type
1340            if let Some(return_type) = &fn_ptr.sig.output {
1341                output.push_str(&format!(" -> {}", format_type(return_type, data)));
1342            }
1343        }
1344        Type::Tuple(types) => {
1345            if types.is_empty() {
1346                output.push_str("()");
1347            } else {
1348                output.push('(');
1349                for (i, ty) in types.iter().enumerate() {
1350                    output.push_str(&format_type(ty, data));
1351                    if i < types.len() - 1 {
1352                        output.push_str(", ");
1353                    }
1354                }
1355                output.push(')');
1356            }
1357        }
1358        Type::Slice(ty) => {
1359            output.push_str(&format!("[{}]", format_type(ty, data)));
1360        }
1361        Type::Array { type_, len } => {
1362            output.push_str(&format!("[{}; {}]", format_type(type_, data), len));
1363        }
1364        Type::Pat {
1365            type_,
1366            __pat_unstable_do_not_use,
1367        } => {
1368            output.push_str(&format!(
1369                "{} is {}",
1370                format_type(type_, data),
1371                __pat_unstable_do_not_use
1372            ));
1373        }
1374        Type::ImplTrait(bounds) => {
1375            output.push_str("impl ");
1376
1377            let mut bounds_str = String::new();
1378            format_bounds(&mut bounds_str, bounds, data);
1379            output.push_str(&bounds_str);
1380        }
1381        Type::Infer => {
1382            output.push('_');
1383        }
1384        Type::RawPointer { is_mutable, type_ } => {
1385            if *is_mutable {
1386                output.push_str("*mut ");
1387            } else {
1388                output.push_str("*const ");
1389            }
1390            output.push_str(&format_type(type_, data));
1391        }
1392        Type::BorrowedRef {
1393            lifetime,
1394            is_mutable,
1395            type_,
1396        } => {
1397            output.push('&');
1398            if let Some(lt) = lifetime {
1399                output.push_str(&format!("'{} ", lt));
1400            }
1401            if *is_mutable {
1402                output.push_str("mut ");
1403            }
1404            output.push_str(&format_type(type_, data));
1405        }
1406        Type::QualifiedPath {
1407            name,
1408            args,
1409            self_type,
1410            trait_,
1411        } => {
1412            output.push('<');
1413            output.push_str(&format_type(self_type, data));
1414
1415            if let Some(trait_path) = trait_ {
1416                output.push_str(&format!(" as {}", trait_path.path));
1417                if let Some(trait_args) = &trait_path.args {
1418                    let mut args_str = String::new();
1419                    format_generic_args(&mut args_str, trait_args, data);
1420                    output.push_str(&args_str);
1421                }
1422            }
1423
1424            output.push_str(&format!(">::{}", name));
1425
1426            if let Some(args) = args {
1427                let mut args_str = String::new();
1428                format_generic_args(&mut args_str, args, data);
1429                if args_str != "<>" && !args_str.is_empty() {
1430                    output.push_str(&args_str);
1431                }
1432            }
1433        }
1434    }
1435
1436    output
1437}
1438
1439fn process_module_details(output: &mut String, module: &Module, data: &Crate, _level: usize) {
1440    if module.is_stripped {
1441        output.push_str(
1442            "> **Note:** This module is marked as stripped. Some items may be omitted.\n\n",
1443        );
1444    }
1445
1446    // Reset level when entering a module to avoid excessive nesting
1447    // This ensures that module contents are always at a reasonable heading level
1448    process_items(output, &module.items, data, 3);
1449}
1450
1451fn process_struct_details(output: &mut String, struct_: &Struct, data: &Crate, level: usize) {
1452    // Cap heading level at 6 (maximum valid Markdown heading level)
1453    let heading_level = std::cmp::min(level, 6);
1454    // Detail fields based on struct kind
1455    match &struct_.kind {
1456        StructKind::Unit => {
1457            // Nothing to detail for unit structs
1458        }
1459        StructKind::Tuple(fields) => {
1460            // Use heading_level for Fields section (since level is already incremented in process_item)
1461            output.push_str(&format!("{} Fields\n\n", "#".repeat(heading_level)));
1462            output.push_str("| Index | Type | Documentation |\n");
1463            output.push_str("|-------|------|---------------|\n");
1464
1465            for (i, field_opt) in fields.iter().enumerate() {
1466                if let Some(field_id) = field_opt {
1467                    if let Some(field_item) = data.index.get(field_id) {
1468                        if let ItemEnum::StructField(field_type) = &field_item.inner {
1469                            let docs = field_item
1470                                .docs
1471                                .as_deref()
1472                                .unwrap_or("")
1473                                .replace("\n", "<br>");
1474                            output.push_str(&format!(
1475                                "| {} | `{}` | {} |\n",
1476                                i,
1477                                format_type(field_type, data),
1478                                docs
1479                            ));
1480                        }
1481                    }
1482                } else {
1483                    output.push_str(&format!("| {} | `private` | *Private field* |\n", i));
1484                }
1485            }
1486            output.push('\n');
1487        }
1488        StructKind::Plain {
1489            fields,
1490            has_stripped_fields,
1491        } => {
1492            // Use heading_level for Fields section
1493            output.push_str(&format!("{} Fields\n\n", "#".repeat(heading_level)));
1494            output.push_str("| Name | Type | Documentation |\n");
1495            output.push_str("|------|------|---------------|\n");
1496
1497            for &field_id in fields {
1498                if let Some(field_item) = data.index.get(&field_id) {
1499                    if let Some(field_name) = &field_item.name {
1500                        if let ItemEnum::StructField(field_type) = &field_item.inner {
1501                            let docs = field_item
1502                                .docs
1503                                .as_deref()
1504                                .unwrap_or("")
1505                                .replace("\n", "<br>");
1506                            output.push_str(&format!(
1507                                "| `{}` | `{}` | {} |\n",
1508                                field_name,
1509                                format_type(field_type, data),
1510                                docs
1511                            ));
1512                        }
1513                    }
1514                }
1515            }
1516
1517            if *has_stripped_fields {
1518                output.push_str("| *private fields* | ... | *Some fields have been omitted* |\n");
1519            }
1520
1521            output.push('\n');
1522        }
1523    }
1524
1525    // Process impls
1526    if !struct_.impls.is_empty() {
1527        // Use heading_level for Implementations section
1528        output.push_str(&format!(
1529            "{} Implementations\n\n",
1530            "#".repeat(heading_level)
1531        ));
1532
1533        // Group impls by trait
1534        let mut trait_impls: std::collections::HashMap<String, Vec<Id>> =
1535            std::collections::HashMap::new();
1536        let mut inherent_impls: Vec<Id> = Vec::new();
1537
1538        for &impl_id in &struct_.impls {
1539            if let Some(impl_item) = data.index.get(&impl_id) {
1540                if let ItemEnum::Impl(impl_) = &impl_item.inner {
1541                    if let Some(trait_) = &impl_.trait_ {
1542                        let trait_name = trait_.path.clone();
1543                        trait_impls.entry(trait_name).or_default().push(impl_id);
1544                    } else {
1545                        // Inherent impl
1546                        inherent_impls.push(impl_id);
1547                    }
1548                }
1549            }
1550        }
1551
1552        // First list inherent impls
1553        if !inherent_impls.is_empty() {
1554            // Use level+1 for Methods (one level deeper than Implementations)
1555            output.push_str(&format!(
1556                "{} Methods\n\n",
1557                "#".repeat(std::cmp::min(heading_level + 1, 6))
1558            ));
1559            for &impl_id in &inherent_impls {
1560                if let Some(impl_item) = data.index.get(&impl_id) {
1561                    if let ItemEnum::Impl(impl_) = &impl_item.inner {
1562                        for &item_id in &impl_.items {
1563                            if let Some(method_item) = data.index.get(&item_id) {
1564                                if let ItemEnum::Function(_) = &method_item.inner {
1565                                    // Format method signature
1566                                    let mut method_signature = String::new();
1567                                    format_item_signature(&mut method_signature, method_item, data);
1568
1569                                    // Output with proper code block formatting
1570                                    output.push_str("- ```rust\n  ");
1571                                    output.push_str(&method_signature.trim());
1572                                    output.push_str("\n  ```");
1573
1574                                    // Add documentation if available
1575                                    if let Some(docs) = &method_item.docs {
1576                                        if let Some(first_line) = docs.lines().next() {
1577                                            if !first_line.trim().is_empty() {
1578                                                output.push_str(&format!("\n  {}", first_line));
1579                                            }
1580                                        }
1581                                    }
1582                                    output.push_str("\n\n");
1583                                }
1584                            }
1585                        }
1586                    }
1587                }
1588            }
1589        }
1590
1591        // Then list trait impls
1592        if !trait_impls.is_empty() {
1593            // Use level+1 for Trait Implementations (one level deeper than Implementations)
1594            output.push_str(&format!(
1595                "{} Trait Implementations\n\n",
1596                "#".repeat(std::cmp::min(heading_level + 1, 6))
1597            ));
1598            // Sort trait implementations alphabetically for deterministic output
1599            let mut sorted_trait_impls: Vec<_> = trait_impls.into_iter().collect();
1600            sorted_trait_impls.sort_by(|a, b| a.0.cmp(&b.0));
1601            for (trait_name, impls) in sorted_trait_impls {
1602                output.push_str(&format!("- **{}**\n", trait_name));
1603                for &impl_id in &impls {
1604                    if let Some(impl_item) = data.index.get(&impl_id) {
1605                        if let ItemEnum::Impl(impl_) = &impl_item.inner {
1606                            for &item_id in &impl_.items {
1607                                if let Some(method_item) = data.index.get(&item_id) {
1608                                    if let ItemEnum::Function(_) = &method_item.inner {
1609                                        // Format method signature
1610                                        let mut method_signature = String::new();
1611                                        format_item_signature(
1612                                            &mut method_signature,
1613                                            method_item,
1614                                            data,
1615                                        );
1616
1617                                        // Output with proper code block formatting
1618                                        output.push_str("  - ```rust\n    ");
1619                                        output.push_str(&method_signature.trim());
1620                                        output.push_str("\n    ```");
1621
1622                                        // Add documentation if available
1623                                        if let Some(docs) = &method_item.docs {
1624                                            if let Some(first_line) = docs.lines().next() {
1625                                                if !first_line.trim().is_empty() {
1626                                                    output
1627                                                        .push_str(&format!("\n    {}", first_line));
1628                                                }
1629                                            }
1630                                        }
1631                                        output.push_str("\n\n");
1632                                    }
1633                                }
1634                            }
1635                        }
1636                    }
1637                }
1638            }
1639        }
1640    }
1641}
1642
1643fn process_enum_details(output: &mut String, enum_: &Enum, data: &Crate, level: usize) {
1644    // Cap heading level at 6 (maximum valid Markdown heading level)
1645    let heading_level = std::cmp::min(level, 6);
1646    // Detail variants with proper nesting
1647    output.push_str(&format!("{} Variants\n\n", "#".repeat(heading_level)));
1648
1649    for &variant_id in &enum_.variants {
1650        if let Some(variant_item) = data.index.get(&variant_id) {
1651            if let Some(variant_name) = &variant_item.name {
1652                // Use heading_level + 1 for individual variants (capped at 6)
1653                let variant_heading_level = std::cmp::min(heading_level + 1, 6);
1654                output.push_str(&format!(
1655                    "{} `{}`\n\n",
1656                    "#".repeat(variant_heading_level),
1657                    variant_name
1658                ));
1659
1660                // Add variant docs if available
1661                if let Some(docs) = &variant_item.docs {
1662                    output.push_str(&format!("{}\n\n", docs));
1663                }
1664
1665                if let ItemEnum::Variant(variant) = &variant_item.inner {
1666                    match &variant.kind {
1667                        VariantKind::Plain => {
1668                            // Nothing additional to display for plain variants
1669                            if let Some(discriminant) = &variant.discriminant {
1670                                output.push_str(&format!(
1671                                    "Discriminant: `{}`\n\n",
1672                                    discriminant.expr
1673                                ));
1674                            }
1675                        }
1676                        VariantKind::Tuple(fields) => {
1677                            output.push_str("Fields:\n\n");
1678                            output.push_str("| Index | Type | Documentation |\n");
1679                            output.push_str("|-------|------|---------------|\n");
1680
1681                            for (i, field_opt) in fields.iter().enumerate() {
1682                                if let Some(field_id) = field_opt {
1683                                    if let Some(field_item) = data.index.get(field_id) {
1684                                        if let ItemEnum::StructField(field_type) = &field_item.inner
1685                                        {
1686                                            let docs = field_item
1687                                                .docs
1688                                                .as_deref()
1689                                                .unwrap_or("")
1690                                                .replace("\n", "<br>");
1691                                            output.push_str(&format!(
1692                                                "| {} | `{}` | {} |\n",
1693                                                i,
1694                                                format_type(field_type, data),
1695                                                docs
1696                                            ));
1697                                        }
1698                                    }
1699                                } else {
1700                                    output.push_str(&format!(
1701                                        "| {} | `private` | *Private field* |\n",
1702                                        i
1703                                    ));
1704                                }
1705                            }
1706                            output.push('\n');
1707                        }
1708                        VariantKind::Struct {
1709                            fields,
1710                            has_stripped_fields,
1711                        } => {
1712                            output.push_str("Fields:\n\n");
1713                            output.push_str("| Name | Type | Documentation |\n");
1714                            output.push_str("|------|------|---------------|\n");
1715
1716                            for &field_id in fields {
1717                                if let Some(field_item) = data.index.get(&field_id) {
1718                                    if let Some(field_name) = &field_item.name {
1719                                        if let ItemEnum::StructField(field_type) = &field_item.inner
1720                                        {
1721                                            let docs = field_item
1722                                                .docs
1723                                                .as_deref()
1724                                                .unwrap_or("")
1725                                                .replace("\n", "<br>");
1726                                            output.push_str(&format!(
1727                                                "| `{}` | `{}` | {} |\n",
1728                                                field_name,
1729                                                format_type(field_type, data),
1730                                                docs
1731                                            ));
1732                                        }
1733                                    }
1734                                }
1735                            }
1736
1737                            if *has_stripped_fields {
1738                                output.push_str("| *private fields* | ... | *Some fields have been omitted* |\n");
1739                            }
1740
1741                            output.push('\n');
1742                        }
1743                    }
1744
1745                    if let Some(discriminant) = &variant.discriminant {
1746                        output
1747                            .push_str(&format!("Discriminant value: `{}`\n\n", discriminant.value));
1748                    }
1749                }
1750            }
1751        }
1752    }
1753
1754    if enum_.has_stripped_variants {
1755        output.push_str(
1756            "*Note: Some variants have been omitted because they are private or hidden.*\n\n",
1757        );
1758    }
1759
1760    // Process impls (same as for struct)
1761    if !enum_.impls.is_empty() {
1762        output.push_str(&format!(
1763            "{} Implementations\n\n",
1764            "#".repeat(heading_level)
1765        ));
1766
1767        // Group impls by trait
1768        let mut trait_impls: std::collections::HashMap<String, Vec<Id>> =
1769            std::collections::HashMap::new();
1770        let mut inherent_impls: Vec<Id> = Vec::new();
1771
1772        for &impl_id in &enum_.impls {
1773            if let Some(impl_item) = data.index.get(&impl_id) {
1774                if let ItemEnum::Impl(impl_) = &impl_item.inner {
1775                    if let Some(trait_) = &impl_.trait_ {
1776                        let trait_name = trait_.path.clone();
1777                        trait_impls.entry(trait_name).or_default().push(impl_id);
1778                    } else {
1779                        // Inherent impl
1780                        inherent_impls.push(impl_id);
1781                    }
1782                }
1783            }
1784        }
1785
1786        // First list inherent impls
1787        if !inherent_impls.is_empty() {
1788            let methods_level = std::cmp::min(heading_level + 1, 6);
1789            output.push_str(&format!("{} Methods\n\n", "#".repeat(methods_level)));
1790            for &impl_id in &inherent_impls {
1791                if let Some(impl_item) = data.index.get(&impl_id) {
1792                    if let ItemEnum::Impl(impl_) = &impl_item.inner {
1793                        for &item_id in &impl_.items {
1794                            if let Some(method_item) = data.index.get(&item_id) {
1795                                if let ItemEnum::Function(_) = &method_item.inner {
1796                                    // Format method signature
1797                                    let mut method_signature = String::new();
1798                                    format_item_signature(&mut method_signature, method_item, data);
1799
1800                                    // Output with proper code block formatting
1801                                    output.push_str("- ```rust\n  ");
1802                                    output.push_str(&method_signature.trim());
1803                                    output.push_str("\n  ```");
1804
1805                                    // Add documentation if available
1806                                    if let Some(docs) = &method_item.docs {
1807                                        if let Some(first_line) = docs.lines().next() {
1808                                            if !first_line.trim().is_empty() {
1809                                                output.push_str(&format!("\n  {}", first_line));
1810                                            }
1811                                        }
1812                                    }
1813                                    output.push_str("\n\n");
1814                                }
1815                            }
1816                        }
1817                    }
1818                }
1819            }
1820        }
1821
1822        // Then list trait impls
1823        if !trait_impls.is_empty() {
1824            let trait_impl_level = std::cmp::min(heading_level + 1, 6);
1825            output.push_str(&format!(
1826                "{} Trait Implementations\n\n",
1827                "#".repeat(trait_impl_level)
1828            ));
1829            // Sort trait implementations alphabetically for deterministic output
1830            let mut sorted_trait_impls: Vec<_> = trait_impls.into_iter().collect();
1831            sorted_trait_impls.sort_by(|a, b| a.0.cmp(&b.0));
1832            for (trait_name, impls) in sorted_trait_impls {
1833                output.push_str(&format!("- **{}**\n", trait_name));
1834                for &impl_id in &impls {
1835                    if let Some(impl_item) = data.index.get(&impl_id) {
1836                        if let ItemEnum::Impl(impl_) = &impl_item.inner {
1837                            for &item_id in &impl_.items {
1838                                if let Some(method_item) = data.index.get(&item_id) {
1839                                    if let ItemEnum::Function(_) = &method_item.inner {
1840                                        // Format method signature
1841                                        let mut method_signature = String::new();
1842                                        format_item_signature(
1843                                            &mut method_signature,
1844                                            method_item,
1845                                            data,
1846                                        );
1847
1848                                        // Output with proper code block formatting
1849                                        output.push_str("  - ```rust\n    ");
1850                                        output.push_str(&method_signature.trim());
1851                                        output.push_str("\n    ```");
1852
1853                                        // Add documentation if available
1854                                        if let Some(docs) = &method_item.docs {
1855                                            if let Some(first_line) = docs.lines().next() {
1856                                                if !first_line.trim().is_empty() {
1857                                                    output
1858                                                        .push_str(&format!("\n    {}", first_line));
1859                                                }
1860                                            }
1861                                        }
1862                                        output.push_str("\n\n");
1863                                    }
1864                                }
1865                            }
1866                        }
1867                    }
1868                }
1869            }
1870        }
1871    }
1872}
1873
1874fn process_union_details(output: &mut String, union_: &Union, data: &Crate, level: usize) {
1875    // Cap heading level at 6 (maximum valid Markdown heading level)
1876    let heading_level = std::cmp::min(level, 6);
1877    // Detail fields
1878    output.push_str(&format!("{} Fields\n\n", "#".repeat(heading_level)));
1879    output.push_str("| Name | Type | Documentation |\n");
1880    output.push_str("|------|------|---------------|\n");
1881
1882    for &field_id in &union_.fields {
1883        if let Some(field_item) = data.index.get(&field_id) {
1884            if let Some(field_name) = &field_item.name {
1885                if let ItemEnum::StructField(field_type) = &field_item.inner {
1886                    let docs = field_item
1887                        .docs
1888                        .as_deref()
1889                        .unwrap_or("")
1890                        .replace("\n", "<br>");
1891                    output.push_str(&format!(
1892                        "| `{}` | `{}` | {} |\n",
1893                        field_name,
1894                        format_type(field_type, data),
1895                        docs
1896                    ));
1897                }
1898            }
1899        }
1900    }
1901
1902    if union_.has_stripped_fields {
1903        output.push_str("| *private fields* | ... | *Some fields have been omitted* |\n");
1904    }
1905
1906    output.push('\n');
1907
1908    // Process impls
1909    if !union_.impls.is_empty() {
1910        output.push_str(&format!(
1911            "{} Implementations\n\n",
1912            "#".repeat(heading_level)
1913        ));
1914
1915        // Group impls by trait
1916        let mut trait_impls: std::collections::HashMap<String, Vec<Id>> =
1917            std::collections::HashMap::new();
1918        let mut inherent_impls: Vec<Id> = Vec::new();
1919
1920        for &impl_id in &union_.impls {
1921            if let Some(impl_item) = data.index.get(&impl_id) {
1922                if let ItemEnum::Impl(impl_) = &impl_item.inner {
1923                    if let Some(trait_) = &impl_.trait_ {
1924                        let trait_name = trait_.path.clone();
1925                        trait_impls.entry(trait_name).or_default().push(impl_id);
1926                    } else {
1927                        // Inherent impl
1928                        inherent_impls.push(impl_id);
1929                    }
1930                }
1931            }
1932        }
1933
1934        // First list inherent impls
1935        if !inherent_impls.is_empty() {
1936            let methods_level = std::cmp::min(heading_level + 1, 6);
1937            output.push_str(&format!("{} Methods\n\n", "#".repeat(methods_level)));
1938            for &impl_id in &inherent_impls {
1939                if let Some(impl_item) = data.index.get(&impl_id) {
1940                    if let ItemEnum::Impl(impl_) = &impl_item.inner {
1941                        for &item_id in &impl_.items {
1942                            if let Some(method_item) = data.index.get(&item_id) {
1943                                if let ItemEnum::Function(_) = &method_item.inner {
1944                                    if let Some(name) = &method_item.name {
1945                                        output.push_str(&format!("- `{}`: ", name));
1946                                        if let Some(docs) = &method_item.docs {
1947                                            let first_line = docs.lines().next().unwrap_or("");
1948                                            output.push_str(first_line);
1949                                        }
1950                                        output.push('\n');
1951                                    }
1952                                }
1953                            }
1954                        }
1955                    }
1956                }
1957            }
1958            output.push('\n');
1959        }
1960
1961        // Then list trait impls
1962        if !trait_impls.is_empty() {
1963            let trait_impl_level = std::cmp::min(heading_level + 1, 6);
1964            output.push_str(&format!(
1965                "{} Trait Implementations\n\n",
1966                "#".repeat(trait_impl_level)
1967            ));
1968            // Sort trait implementations alphabetically for deterministic output
1969            let mut sorted_trait_impls: Vec<_> = trait_impls.into_iter().collect();
1970            sorted_trait_impls.sort_by(|a, b| a.0.cmp(&b.0));
1971            for (trait_name, impls) in sorted_trait_impls {
1972                output.push_str(&format!("- **{}**\n", trait_name));
1973                for &impl_id in &impls {
1974                    if let Some(impl_item) = data.index.get(&impl_id) {
1975                        if let ItemEnum::Impl(impl_) = &impl_item.inner {
1976                            for &item_id in &impl_.items {
1977                                if let Some(method_item) = data.index.get(&item_id) {
1978                                    if let Some(name) = &method_item.name {
1979                                        output.push_str(&format!("  - `{}`: ", name));
1980                                        if let Some(docs) = &method_item.docs {
1981                                            let first_line = docs.lines().next().unwrap_or("");
1982                                            output.push_str(first_line);
1983                                        }
1984                                        output.push('\n');
1985                                    }
1986                                }
1987                            }
1988                        }
1989                    }
1990                }
1991            }
1992            output.push('\n');
1993        }
1994    }
1995}
1996
1997fn process_trait_details(output: &mut String, trait_: &Trait, data: &Crate, level: usize) {
1998    // Cap heading level at 6 (maximum valid Markdown heading level)
1999    let heading_level = std::cmp::min(level, 6);
2000    // Special traits info
2001    if trait_.is_auto {
2002        output.push_str("> This is an auto trait.\n\n");
2003    }
2004    if trait_.is_unsafe {
2005        output.push_str("> This trait is unsafe to implement.\n\n");
2006    }
2007    if !trait_.is_dyn_compatible {
2008        output.push_str(
2009            "> This trait is not object-safe and cannot be used in dynamic trait objects.\n\n",
2010        );
2011    }
2012
2013    // Associated items
2014    if !trait_.items.is_empty() {
2015        // Group items by kind
2016        let mut required_methods = Vec::new();
2017        let mut provided_methods = Vec::new();
2018        let mut assoc_types = Vec::new();
2019        let mut assoc_consts = Vec::new();
2020
2021        for &item_id in &trait_.items {
2022            if let Some(item) = data.index.get(&item_id) {
2023                match &item.inner {
2024                    ItemEnum::Function(function) => {
2025                        if function.has_body {
2026                            provided_methods.push(item_id);
2027                        } else {
2028                            required_methods.push(item_id);
2029                        }
2030                    }
2031                    ItemEnum::AssocType { .. } => assoc_types.push(item_id),
2032                    ItemEnum::AssocConst { value, .. } => {
2033                        if value.is_some() {
2034                            // Has a default value
2035                            provided_methods.push(item_id);
2036                        } else {
2037                            assoc_consts.push(item_id);
2038                        }
2039                    }
2040                    _ => {}
2041                }
2042            }
2043        }
2044
2045        // Required items
2046        if !required_methods.is_empty() || !assoc_types.is_empty() || !assoc_consts.is_empty() {
2047            output.push_str(&format!("{} Required Items\n\n", "#".repeat(heading_level)));
2048
2049            if !assoc_types.is_empty() {
2050                output.push_str(&format!(
2051                    "{} Associated Types\n\n",
2052                    "#".repeat(heading_level + 1)
2053                ));
2054                for &type_id in &assoc_types {
2055                    if let Some(type_item) = data.index.get(&type_id) {
2056                        if let Some(name) = &type_item.name {
2057                            output.push_str(&format!("- `{}`", name));
2058                            if let Some(docs) = &type_item.docs {
2059                                if let Some(first_line) = docs.lines().next() {
2060                                    if !first_line.trim().is_empty() {
2061                                        output.push_str(&format!(": {}", first_line));
2062                                    }
2063                                }
2064                            }
2065                            output.push('\n');
2066                        }
2067                    }
2068                }
2069                output.push('\n');
2070            }
2071
2072            if !assoc_consts.is_empty() {
2073                output.push_str(&format!(
2074                    "{} Associated Constants\n\n",
2075                    "#".repeat(heading_level + 1)
2076                ));
2077                for &const_id in &assoc_consts {
2078                    if let Some(const_item) = data.index.get(&const_id) {
2079                        if let Some(name) = &const_item.name {
2080                            output.push_str(&format!("- `{}`", name));
2081                            if let Some(docs) = &const_item.docs {
2082                                if let Some(first_line) = docs.lines().next() {
2083                                    if !first_line.trim().is_empty() {
2084                                        output.push_str(&format!(": {}", first_line));
2085                                    }
2086                                }
2087                            }
2088                            output.push('\n');
2089                        }
2090                    }
2091                }
2092                output.push('\n');
2093            }
2094
2095            if !required_methods.is_empty() {
2096                output.push_str(&format!(
2097                    "{} Required Methods\n\n",
2098                    "#".repeat(heading_level + 1)
2099                ));
2100                for &method_id in &required_methods {
2101                    if let Some(method_item) = data.index.get(&method_id) {
2102                        if let Some(name) = &method_item.name {
2103                            output.push_str(&format!("- `{}`", name));
2104                            if let Some(docs) = &method_item.docs {
2105                                if let Some(first_line) = docs.lines().next() {
2106                                    if !first_line.trim().is_empty() {
2107                                        output.push_str(&format!(": {}", first_line));
2108                                    }
2109                                }
2110                            }
2111                            output.push('\n');
2112                        }
2113                    }
2114                }
2115                output.push('\n');
2116            }
2117        }
2118
2119        // Provided items
2120        if !provided_methods.is_empty() {
2121            output.push_str(&format!(
2122                "{} Provided Methods\n\n",
2123                "#".repeat(heading_level)
2124            ));
2125            for &method_id in &provided_methods {
2126                if let Some(method_item) = data.index.get(&method_id) {
2127                    if let ItemEnum::Function(_) = &method_item.inner {
2128                        // Format method signature
2129                        let mut method_signature = String::new();
2130                        format_item_signature(&mut method_signature, method_item, data);
2131
2132                        // Output with proper code block formatting
2133                        output.push_str("- ```rust\n  ");
2134                        output.push_str(&method_signature.trim());
2135                        output.push_str("\n  ```");
2136
2137                        // Add documentation if available
2138                        if let Some(docs) = &method_item.docs {
2139                            if let Some(first_line) = docs.lines().next() {
2140                                if !first_line.trim().is_empty() {
2141                                    output.push_str(&format!("\n  {}", first_line));
2142                                }
2143                            }
2144                        }
2145                        output.push_str("\n\n");
2146                    }
2147                }
2148            }
2149        }
2150    }
2151
2152    // Implementations
2153    if !trait_.implementations.is_empty() {
2154        output.push_str(&format!(
2155            "{} Implementations\n\n",
2156            "#".repeat(heading_level)
2157        ));
2158        output.push_str("This trait is implemented for the following types:\n\n");
2159
2160        for &impl_id in &trait_.implementations {
2161            if let Some(impl_item) = data.index.get(&impl_id) {
2162                if let ItemEnum::Impl(impl_) = &impl_item.inner {
2163                    output.push_str(&format!("- `{}`", format_type(&impl_.for_, data)));
2164                    // Add generics if present
2165                    if !impl_.generics.params.is_empty() {
2166                        let mut generics_str = String::new();
2167                        format_generics(&mut generics_str, &impl_.generics, data);
2168                        if generics_str != "<>" {
2169                            output.push_str(" with ");
2170                            output.push_str(&generics_str);
2171                        }
2172                    }
2173                    output.push('\n');
2174                }
2175            }
2176        }
2177        output.push('\n');
2178    }
2179}
2180
2181fn process_impl_details(output: &mut String, impl_: &Impl, data: &Crate, level: usize) {
2182    // Cap heading level at 6 (maximum valid Markdown heading level)
2183    let heading_level = std::cmp::min(level, 6);
2184    // List all items in the impl
2185    if !impl_.items.is_empty() {
2186        output.push_str(&format!(
2187            "{} Associated Items\n\n",
2188            "#".repeat(heading_level)
2189        ));
2190
2191        // Group by kind
2192        let mut methods = Vec::new();
2193        let mut assoc_types = Vec::new();
2194        let mut assoc_consts = Vec::new();
2195
2196        for &item_id in &impl_.items {
2197            if let Some(item) = data.index.get(&item_id) {
2198                match &item.inner {
2199                    ItemEnum::Function(_) => methods.push(item_id),
2200                    ItemEnum::AssocType { .. } => assoc_types.push(item_id),
2201                    ItemEnum::AssocConst { .. } => assoc_consts.push(item_id),
2202                    _ => {}
2203                }
2204            }
2205        }
2206
2207        if !assoc_types.is_empty() {
2208            output.push_str(&format!(
2209                "{} Associated Types\n\n",
2210                "#".repeat(heading_level + 1)
2211            ));
2212            for &type_id in &assoc_types {
2213                process_item(output, data.index.get(&type_id).unwrap(), data, level + 1);
2214            }
2215        }
2216
2217        if !assoc_consts.is_empty() {
2218            output.push_str(&format!(
2219                "{} Associated Constants\n\n",
2220                "#".repeat(heading_level + 1)
2221            ));
2222            for &const_id in &assoc_consts {
2223                process_item(output, data.index.get(&const_id).unwrap(), data, level + 1);
2224            }
2225        }
2226
2227        if !methods.is_empty() {
2228            output.push_str(&format!("{} Methods\n\n", "#".repeat(heading_level + 1)));
2229            for &method_id in &methods {
2230                process_item(output, data.index.get(&method_id).unwrap(), data, level + 1);
2231            }
2232        }
2233    }
2234
2235    // If this is a trait impl, list the provided trait methods that aren't overridden
2236    if impl_.trait_.is_some() && !impl_.provided_trait_methods.is_empty() {
2237        output.push_str(&format!(
2238            "{} Provided Trait Methods\n\n",
2239            "#".repeat(heading_level)
2240        ));
2241        output.push_str("The following methods are available through the trait but not explicitly implemented:\n\n");
2242
2243        for provided_method in &impl_.provided_trait_methods {
2244            output.push_str(&format!("- `{}`\n", provided_method));
2245        }
2246
2247        output.push('\n');
2248    }
2249
2250    // If this is a blanket impl, mention it
2251    if let Some(blanket_type) = &impl_.blanket_impl {
2252        output.push_str(&format!(
2253            "This is a blanket implementation for all types that match: `{}`\n\n",
2254            format_type(blanket_type, data)
2255        ));
2256    }
2257}