cargo_docs_md/generator/
render_shared.rs

1//! Shared rendering functions for documentation generation.
2//!
3//! This module contains standalone rendering functions that can be used by both
4//! single-crate ([`ItemRenderer`](super::ItemRenderer)) and multi-crate
5//! ([`MultiCrateModuleRenderer`](crate::multi_crate::generator)) renderers.
6//!
7//! These functions handle the core markdown generation logic without being tied
8//! to a specific rendering context, avoiding code duplication between the two modes.
9
10use std::borrow::Cow;
11use std::fmt::Write;
12
13use rustdoc_types::{Crate, Id, Impl, Item, ItemEnum, StructKind, VariantKind, Visibility};
14
15use crate::generator::context::RenderContext;
16use crate::types::TypeRenderer;
17
18/// Render a struct definition code block to markdown.
19///
20/// Produces a heading with the struct name and generics, followed by a Rust
21/// code block showing the struct definition.
22///
23/// # Arguments
24///
25/// * `md` - Output markdown string
26/// * `name` - The struct name (may differ from item.name for re-exports)
27/// * `s` - The struct data from rustdoc
28/// * `krate` - The crate containing field definitions
29/// * `type_renderer` - Type renderer for generics and field types
30pub fn render_struct_definition(
31    md: &mut String,
32    name: &str,
33    s: &rustdoc_types::Struct,
34    krate: &Crate,
35    type_renderer: &TypeRenderer,
36) {
37    let generics = type_renderer.render_generics(&s.generics.params);
38    let where_clause = type_renderer.render_where_clause(&s.generics.where_predicates);
39
40    _ = write!(md, "### `{name}{generics}`\n\n");
41
42    md.push_str("```rust\n");
43    match &s.kind {
44        StructKind::Unit => {
45            _ = writeln!(md, "struct {name}{generics}{where_clause};");
46        },
47
48        StructKind::Tuple(fields) => {
49            let field_types: Vec<Cow<str>> = fields
50                .iter()
51                .filter_map(|id| id.as_ref())
52                .filter_map(|id| krate.index.get(id))
53                .filter_map(|item| {
54                    if let ItemEnum::StructField(ty) = &item.inner {
55                        Some(type_renderer.render_type(ty))
56                    } else {
57                        None
58                    }
59                })
60                .collect();
61            _ = writeln!(
62                md,
63                "struct {}{}({}){};",
64                name,
65                generics,
66                field_types.join(", "),
67                where_clause
68            );
69        },
70
71        StructKind::Plain {
72            fields,
73            has_stripped_fields,
74        } => {
75            _ = writeln!(md, "struct {name}{generics}{where_clause} {{");
76
77            for field_id in fields {
78                if let Some(field) = krate.index.get(field_id) {
79                    let field_name = field.name.as_deref().unwrap_or("_");
80                    if let ItemEnum::StructField(ty) = &field.inner {
81                        let vis = match &field.visibility {
82                            Visibility::Public => "pub ",
83                            _ => "",
84                        };
85                        _ = writeln!(
86                            md,
87                            "    {}{}: {},",
88                            vis,
89                            field_name,
90                            type_renderer.render_type(ty)
91                        );
92                    }
93                }
94            }
95
96            if *has_stripped_fields {
97                _ = writeln!(md, "    // [REDACTED: Private Fields]");
98            }
99
100            md.push_str("}\n");
101        },
102    }
103    md.push_str("```\n\n");
104}
105
106/// Render documented struct fields to markdown.
107///
108/// Produces a "Fields" section with each documented field as a bullet point
109/// showing the field name, type, and documentation.
110///
111/// # Arguments
112///
113/// * `md` - Output markdown string
114/// * `fields` - Field IDs from the struct
115/// * `krate` - Crate containing field definitions
116/// * `type_renderer` - Type renderer for field types
117/// * `process_docs` - Closure to process documentation with intra-doc link resolution
118pub fn render_struct_fields<F>(
119    md: &mut String,
120    fields: &[Id],
121    krate: &Crate,
122    type_renderer: &TypeRenderer,
123    process_docs: F,
124) where
125    F: Fn(&Item) -> Option<String>,
126{
127    let documented_fields: Vec<_> = fields
128        .iter()
129        .filter_map(|id| krate.index.get(id))
130        .filter(|f| f.docs.is_some())
131        .collect();
132
133    if !documented_fields.is_empty() {
134        md.push_str("#### Fields\n\n");
135        for field in documented_fields {
136            let field_name = field.name.as_deref().unwrap_or("_");
137            if let ItemEnum::StructField(ty) = &field.inner {
138                _ = write!(
139                    md,
140                    "- **`{}`**: `{}`",
141                    field_name,
142                    type_renderer.render_type(ty)
143                );
144
145                if let Some(docs) = process_docs(field) {
146                    _ = write!(md, "\n\n  {}", docs.replace('\n', "\n  "));
147                }
148
149                md.push_str("\n\n");
150            }
151        }
152    }
153}
154
155/// Render an enum definition code block to markdown.
156///
157/// Produces a heading with the enum name and generics, followed by a Rust
158/// code block showing the enum definition with all variants.
159///
160/// # Arguments
161///
162/// * `md` - Output markdown string
163/// * `name` - The enum name (may differ from item.name for re-exports)
164/// * `e` - The enum data from rustdoc
165/// * `krate` - The crate containing variant definitions
166/// * `type_renderer` - Type renderer for generics and variant types
167pub fn render_enum_definition(
168    md: &mut String,
169    name: &str,
170    e: &rustdoc_types::Enum,
171    krate: &Crate,
172    type_renderer: &TypeRenderer,
173) {
174    let generics = type_renderer.render_generics(&e.generics.params);
175    let where_clause = type_renderer.render_where_clause(&e.generics.where_predicates);
176
177    _ = write!(md, "### `{name}{generics}`\n\n");
178
179    md.push_str("```rust\n");
180    _ = writeln!(md, "enum {name}{generics}{where_clause} {{");
181
182    for variant_id in &e.variants {
183        if let Some(variant) = krate.index.get(variant_id) {
184            render_enum_variant(md, variant, krate, type_renderer);
185        }
186    }
187    md.push_str("}\n");
188    md.push_str("```\n\n");
189}
190
191/// Render a single enum variant within the definition code block.
192///
193/// Handles all three variant kinds: plain, tuple, and struct variants.
194pub fn render_enum_variant(
195    md: &mut String,
196    variant: &Item,
197    krate: &Crate,
198    type_renderer: &TypeRenderer,
199) {
200    let variant_name = variant.name.as_deref().unwrap_or("_");
201
202    if let ItemEnum::Variant(v) = &variant.inner {
203        match &v.kind {
204            VariantKind::Plain => {
205                _ = writeln!(md, "    {variant_name},");
206            },
207
208            VariantKind::Tuple(fields) => {
209                let field_types: Vec<Cow<str>> = fields
210                    .iter()
211                    .filter_map(|id| id.as_ref())
212                    .filter_map(|id| krate.index.get(id))
213                    .filter_map(|item| {
214                        if let ItemEnum::StructField(ty) = &item.inner {
215                            Some(type_renderer.render_type(ty))
216                        } else {
217                            None
218                        }
219                    })
220                    .collect();
221
222                _ = writeln!(md, "    {}({}),", variant_name, field_types.join(", "));
223            },
224
225            VariantKind::Struct { fields, .. } => {
226                _ = writeln!(md, "    {variant_name} {{");
227
228                for field_id in fields {
229                    if let Some(field) = krate.index.get(field_id) {
230                        let field_name = field.name.as_deref().unwrap_or("_");
231                        if let ItemEnum::StructField(ty) = &field.inner {
232                            _ = writeln!(
233                                md,
234                                "        {}: {},",
235                                field_name,
236                                type_renderer.render_type(ty)
237                            );
238                        }
239                    }
240                }
241                md.push_str("    },\n");
242            },
243        }
244    }
245}
246
247/// Render documented enum variants to markdown.
248///
249/// Produces a "Variants" section with each documented variant as a bullet point.
250///
251/// # Arguments
252///
253/// * `md` - Output markdown string
254/// * `variants` - Variant IDs from the enum
255/// * `krate` - Crate containing variant definitions
256/// * `process_docs` - Closure to process documentation with intra-doc link resolution
257pub fn render_enum_variants_docs<F>(
258    md: &mut String,
259    variants: &[Id],
260    krate: &Crate,
261    process_docs: F,
262) where
263    F: Fn(&Item) -> Option<String>,
264{
265    let documented_variants: Vec<_> = variants
266        .iter()
267        .filter_map(|id| krate.index.get(id))
268        .filter(|v| v.docs.is_some())
269        .collect();
270
271    if !documented_variants.is_empty() {
272        md.push_str("#### Variants\n\n");
273        for variant in documented_variants {
274            let variant_name = variant.name.as_deref().unwrap_or("_");
275            _ = write!(md, "- **`{variant_name}`**");
276
277            if let Some(docs) = process_docs(variant) {
278                _ = write!(md, "\n\n  {}", docs.replace('\n', "\n  "));
279            }
280
281            md.push_str("\n\n");
282        }
283    }
284}
285
286/// Render a trait definition code block to markdown.
287///
288/// Produces a heading with the trait name and generics, followed by a Rust
289/// code block showing the trait signature with supertraits.
290///
291/// # Arguments
292///
293/// * `md` - Output markdown string
294/// * `name` - The trait name
295/// * `t` - The trait data from rustdoc
296/// * `type_renderer` - Type renderer for generics and bounds
297pub fn render_trait_definition(
298    md: &mut String,
299    name: &str,
300    t: &rustdoc_types::Trait,
301    type_renderer: &TypeRenderer,
302) {
303    let generics = type_renderer.render_generics(&t.generics.params);
304    let where_clause = type_renderer.render_where_clause(&t.generics.where_predicates);
305
306    _ = write!(md, "### `{name}{generics}`\n\n");
307
308    md.push_str("```rust\n");
309
310    let bounds = if t.bounds.is_empty() {
311        String::new()
312    } else {
313        let bound_strs: Vec<Cow<str>> = t
314            .bounds
315            .iter()
316            .map(|b| type_renderer.render_generic_bound(b))
317            .collect();
318        format!(": {}", bound_strs.join(" + "))
319    };
320
321    _ = writeln!(md, "trait {name}{generics}{bounds}{where_clause} {{ ... }}");
322    md.push_str("```\n\n");
323}
324
325/// Render a single trait item (method, associated type, or constant).
326///
327/// Each item is rendered as a bullet point with its signature in backticks.
328/// For methods, the first line of documentation is included.
329///
330/// # Arguments
331///
332/// * `md` - Output markdown string
333/// * `item` - The trait item (function, assoc type, or assoc const)
334/// * `type_renderer` - Type renderer for types
335/// * `process_docs` - Closure to process documentation with intra-doc link resolution
336pub fn render_trait_item<F>(
337    md: &mut String,
338    item: &Item,
339    type_renderer: &TypeRenderer,
340    process_docs: F,
341) where
342    F: Fn(&Item) -> Option<String>,
343{
344    let name = item.name.as_deref().unwrap_or("_");
345
346    match &item.inner {
347        ItemEnum::Function(f) => {
348            let generics = type_renderer.render_generics(&f.generics.params);
349
350            let params: Vec<String> = f
351                .sig
352                .inputs
353                .iter()
354                .map(|(param_name, ty)| format!("{param_name}: {}", type_renderer.render_type(ty)))
355                .collect();
356
357            let ret = f
358                .sig
359                .output
360                .as_ref()
361                .map(|ty| format!(" -> {}", type_renderer.render_type(ty)))
362                .unwrap_or_default();
363
364            _ = write!(
365                md,
366                "- `fn {}{}({}){}`",
367                name,
368                generics,
369                params.join(", "),
370                ret
371            );
372
373            if let Some(docs) = process_docs(item)
374                && let Some(first_line) = docs.lines().next()
375            {
376                _ = write!(md, "\n\n  {first_line}");
377            }
378
379            md.push_str("\n\n");
380        },
381
382        ItemEnum::AssocType { bounds, type_, .. } => {
383            let bounds_str = if bounds.is_empty() {
384                String::new()
385            } else {
386                format!(": {}", bounds.len())
387            };
388            let default_str = type_
389                .as_ref()
390                .map(|ty| format!(" = {}", type_renderer.render_type(ty)))
391                .unwrap_or_default();
392
393            _ = write!(md, "- `type {name}{bounds_str}{default_str}`\n\n");
394        },
395
396        ItemEnum::AssocConst { type_, .. } => {
397            _ = write!(
398                md,
399                "- `const {name}: {}`\n\n",
400                type_renderer.render_type(type_)
401            );
402        },
403
404        _ => {
405            _ = write!(md, "- `{name}`\n\n");
406        },
407    }
408}
409
410/// Render a function definition to markdown.
411///
412/// Produces a heading with the function name, followed by a Rust code block
413/// showing the full signature with modifiers (const, async, unsafe).
414///
415/// # Arguments
416///
417/// * `md` - Output markdown string
418/// * `name` - The function name
419/// * `f` - The function data from rustdoc
420/// * `type_renderer` - Type renderer for parameter and return types
421pub fn render_function_definition(
422    md: &mut String,
423    name: &str,
424    f: &rustdoc_types::Function,
425    type_renderer: &TypeRenderer,
426) {
427    let generics = type_renderer.render_generics(&f.generics.params);
428    let where_clause = type_renderer.render_where_clause(&f.generics.where_predicates);
429
430    let params: Vec<String> = f
431        .sig
432        .inputs
433        .iter()
434        .map(|(param_name, ty)| format!("{param_name}: {}", type_renderer.render_type(ty)))
435        .collect();
436
437    let ret = f
438        .sig
439        .output
440        .as_ref()
441        .map(|ty| format!(" -> {}", type_renderer.render_type(ty)))
442        .unwrap_or_default();
443
444    let is_async = if f.header.is_async { "async " } else { "" };
445    let is_const = if f.header.is_const { "const " } else { "" };
446    let is_unsafe = if f.header.is_unsafe { "unsafe " } else { "" };
447
448    _ = write!(md, "### `{name}`\n\n");
449    md.push_str("```rust\n");
450
451    _ = writeln!(
452        md,
453        "{}{}{}fn {}{}({}){}{}",
454        is_const,
455        is_async,
456        is_unsafe,
457        name,
458        generics,
459        params.join(", "),
460        ret,
461        where_clause
462    );
463
464    md.push_str("```\n\n");
465}
466
467/// Render a constant definition to markdown.
468///
469/// Produces a heading with the constant name, followed by a Rust code block
470/// showing `const NAME: Type = value;`.
471///
472/// # Arguments
473///
474/// * `md` - Output markdown string
475/// * `name` - The constant name
476/// * `type_` - The constant's type
477/// * `const_` - The constant data including value
478/// * `type_renderer` - Type renderer for the type
479pub fn render_constant_definition(
480    md: &mut String,
481    name: &str,
482    type_: &rustdoc_types::Type,
483    const_: &rustdoc_types::Constant,
484    type_renderer: &TypeRenderer,
485) {
486    _ = write!(md, "### `{name}`\n\n");
487
488    md.push_str("```rust\n");
489
490    let value = const_
491        .value
492        .as_ref()
493        .map(|v| format!(" = {v}"))
494        .unwrap_or_default();
495
496    _ = writeln!(
497        md,
498        "const {name}: {}{value};",
499        type_renderer.render_type(type_)
500    );
501
502    md.push_str("```\n\n");
503}
504
505/// Render a type alias definition to markdown.
506///
507/// Produces a heading with the alias name and generics, followed by a Rust
508/// code block showing `type Name<T> = TargetType;`.
509///
510/// # Arguments
511///
512/// * `md` - Output markdown string
513/// * `name` - The type alias name
514/// * `ta` - The type alias data from rustdoc
515/// * `type_renderer` - Type renderer for generics and the aliased type
516pub fn render_type_alias_definition(
517    md: &mut String,
518    name: &str,
519    ta: &rustdoc_types::TypeAlias,
520    type_renderer: &TypeRenderer,
521) {
522    let generics = type_renderer.render_generics(&ta.generics.params);
523    let where_clause = type_renderer.render_where_clause(&ta.generics.where_predicates);
524
525    _ = write!(md, "### `{name}{generics}`\n\n");
526    md.push_str("```rust\n");
527
528    _ = writeln!(
529        md,
530        "type {name}{generics}{where_clause} = {};",
531        type_renderer.render_type(&ta.type_)
532    );
533    md.push_str("```\n\n");
534}
535
536/// Render a macro definition to markdown.
537///
538/// Produces a heading with the macro name and `!` suffix.
539/// Note: We don't show macro rules since rustdoc JSON doesn't provide them.
540///
541/// # Arguments
542///
543/// * `md` - Output markdown string
544/// * `name` - The macro name
545pub fn render_macro_heading(md: &mut String, name: &str) {
546    _ = write!(md, "### `{name}!`\n\n");
547}
548
549/// Render the items within an impl block.
550///
551/// This renders all methods, associated constants, and associated types
552/// within an impl block as bullet points.
553///
554/// # Arguments
555///
556/// * `md` - Output markdown string
557/// * `impl_block` - The impl block to render items from
558/// * `krate` - The crate containing item definitions
559/// * `type_renderer` - Type renderer for types
560/// * `process_docs` - Optional closure to process documentation
561/// * `create_type_link` - Optional closure to create links for types `(id -> Option<markdown_link>)`
562pub fn render_impl_items<F, L>(
563    md: &mut String,
564    impl_block: &Impl,
565    krate: &Crate,
566    type_renderer: &TypeRenderer,
567    process_docs: &Option<F>,
568    create_type_link: &Option<L>,
569) where
570    F: Fn(&Item) -> Option<String>,
571    L: Fn(rustdoc_types::Id) -> Option<String>,
572{
573    for item_id in &impl_block.items {
574        if let Some(item) = krate.index.get(item_id) {
575            let name = item.name.as_deref().unwrap_or("_");
576
577            match &item.inner {
578                ItemEnum::Function(f) => {
579                    render_impl_function(md, name, f, *type_renderer);
580
581                    // Add type links if link creator is provided
582                    if let Some(link_creator) = create_type_link {
583                        render_function_type_links_inline(md, f, *type_renderer, link_creator);
584                    }
585
586                    // First line of docs as summary (with blank line before)
587                    if let Some(pf) = process_docs
588                        && let Some(docs) = pf(item)
589                        && let Some(first_line) = docs.lines().next()
590                    {
591                        _ = write!(md, "\n\n  {first_line}");
592                    }
593                    md.push_str("\n\n");
594                },
595
596                ItemEnum::AssocConst { type_, .. } => {
597                    _ = writeln!(
598                        md,
599                        "- `const {name}: {}`\n",
600                        type_renderer.render_type(type_)
601                    );
602                },
603
604                ItemEnum::AssocType { type_, .. } => {
605                    if let Some(ty) = type_ {
606                        _ = writeln!(md, "- `type {name} = {}`\n", type_renderer.render_type(ty));
607                    } else {
608                        _ = writeln!(md, "- `type {name}`\n");
609                    }
610                },
611
612                _ => {},
613            }
614        }
615    }
616}
617
618/// Render type links for a function signature inline (for impl methods).
619///
620/// This is a helper that collects types from function signatures and
621/// creates links for resolvable types, outputting them on the same line.
622fn render_function_type_links_inline<L>(
623    md: &mut String,
624    f: &rustdoc_types::Function,
625    type_renderer: TypeRenderer,
626    create_link: &L,
627) where
628    L: Fn(rustdoc_types::Id) -> Option<String>,
629{
630    use std::collections::HashSet;
631
632    let mut all_types = Vec::new();
633
634    // Collect from parameters
635    for (_, ty) in &f.sig.inputs {
636        all_types.extend(type_renderer.collect_linkable_types(ty));
637    }
638
639    // Collect from return type
640    if let Some(output) = &f.sig.output {
641        all_types.extend(type_renderer.collect_linkable_types(output));
642    }
643
644    // Deduplicate by name (keep first occurrence)
645    let mut seen = HashSet::new();
646    let unique_types: Vec<_> = all_types
647        .into_iter()
648        .filter(|(name, _)| seen.insert(name.clone()))
649        .collect();
650
651    // Create links for resolvable types
652    let links: Vec<String> = unique_types
653        .iter()
654        .filter_map(|(_, id)| create_link(*id))
655        .collect();
656
657    // Output inline if we have links
658    if !links.is_empty() {
659        _ = write!(md, " — {}", links.join(", "));
660    }
661}
662
663/// Render a function signature within an impl block.
664///
665/// Renders as a bullet point with the full signature including modifiers.
666fn render_impl_function(
667    md: &mut String,
668    name: &str,
669    f: &rustdoc_types::Function,
670    type_renderer: TypeRenderer,
671) {
672    let generics = type_renderer.render_generics(&f.generics.params);
673
674    let params: Vec<String> = f
675        .sig
676        .inputs
677        .iter()
678        .map(|(param_name, ty)| format!("{param_name}: {}", type_renderer.render_type(ty)))
679        .collect();
680
681    let ret = f
682        .sig
683        .output
684        .as_ref()
685        .map(|ty| format!(" -> {}", type_renderer.render_type(ty)))
686        .unwrap_or_default();
687
688    let is_async = if f.header.is_async { "async " } else { "" };
689    let is_const = if f.header.is_const { "const " } else { "" };
690    let is_unsafe = if f.header.is_unsafe { "unsafe " } else { "" };
691
692    _ = write!(
693        md,
694        "- `{}{}{}fn {}{}({}){}`",
695        is_const,
696        is_async,
697        is_unsafe,
698        name,
699        generics,
700        params.join(", "),
701        ret
702    );
703}
704
705/// Append processed documentation to markdown.
706///
707/// Helper function to add documentation with consistent formatting.
708pub fn append_docs(md: &mut String, docs: Option<String>) {
709    if let Some(docs) = docs {
710        md.push_str(&docs);
711        md.push_str("\n\n");
712    }
713}
714
715/// Generate a sort key for an impl block for deterministic ordering.
716///
717/// Combines trait name, generic params, and for-type to create a unique key.
718#[must_use]
719pub fn impl_sort_key(impl_block: &Impl, type_renderer: &TypeRenderer) -> String {
720    let trait_name = impl_block
721        .trait_
722        .as_ref()
723        .map(|t| t.path.clone())
724        .unwrap_or_default();
725    let for_type = type_renderer.render_type(&impl_block.for_);
726    let generics = type_renderer.render_generics(&impl_block.generics.params);
727    format!("{trait_name}{generics}::{for_type}")
728}
729
730/// Check if a render context can resolve documentation.
731///
732/// This trait provides a unified way to process docs from different contexts.
733pub trait DocsProcessor {
734    /// Process documentation for an item, resolving intra-doc links.
735    fn process_item_docs(&self, item: &Item) -> Option<String>;
736}
737
738impl<T: RenderContext + ?Sized> DocsProcessor for (&T, &str) {
739    fn process_item_docs(&self, item: &Item) -> Option<String> {
740        self.0.process_docs(item, self.1)
741    }
742}