cargo_docs_md/multi_crate/
generator.rs

1//! Multi-crate documentation generator.
2//!
3//! This module provides [`MultiCrateGenerator`] which orchestrates
4//! documentation generation across multiple crates with cross-crate linking.
5
6use std::collections::{BTreeSet, HashMap, HashSet};
7use std::fmt::Write;
8use std::path::Path;
9use std::sync::Arc;
10
11use fs_err as FsErr;
12use indicatif::{ProgressBar, ProgressStyle};
13use rayon::prelude::*;
14use rustdoc_types::{Crate, Id, Impl, Item, ItemEnum, StructKind};
15use tracing::{debug, info, info_span, instrument};
16
17use crate::error::Error;
18use crate::generator::breadcrumbs::BreadcrumbGenerator;
19use crate::generator::config::RenderConfig;
20use crate::generator::impls::ImplUtils;
21use crate::generator::quick_ref::{QuickRefEntry, QuickRefGenerator, extract_summary};
22use crate::generator::render_shared::{CategorizedTraitItems, RendererInternals, TraitRenderer};
23use crate::generator::toc::{TocEntry, TocGenerator};
24use crate::generator::{ItemAccess, ItemFilter, LinkResolver};
25use crate::multi_crate::context::SingleCrateView;
26use crate::multi_crate::search::SearchIndexGenerator;
27use crate::multi_crate::summary::SummaryGenerator;
28use crate::multi_crate::{CrateCollection, MultiCrateContext};
29use crate::types::TypeRenderer;
30use crate::linker::ImplContext;
31use crate::utils::PathUtils;
32use crate::{AnchorUtils, Args};
33
34// Note: CategorizedItems for multi-crate rendering is defined in generator/module.rs
35// and is used there for the single-crate rendering path. The struct below was written
36// for potential future use in multi-crate rendering but is not yet integrated.
37
38/// Categorized module items for rendering.
39///
40/// Collects items by category during module traversal, eliminating the need
41/// for 8 separate vector parameters in TOC/QuickRef generation.
42///
43/// Note: This is currently unused - kept for potential future multi-crate
44/// rendering improvements.
45#[allow(dead_code)]
46struct CategorizedItems<'a> {
47    modules: Vec<&'a Item>,
48    structs: Vec<(&'a Id, &'a Item)>,
49    enums: Vec<(&'a Id, &'a Item)>,
50    unions: Vec<(&'a Id, &'a Item)>,
51    traits: Vec<(&'a Id, &'a Item)>,
52    functions: Vec<&'a Item>,
53    types: Vec<&'a Item>,
54    constants: Vec<&'a Item>,
55    statics: Vec<&'a Item>,
56    macros: Vec<&'a Item>,
57}
58
59#[allow(dead_code)]
60impl<'a> CategorizedItems<'a> {
61    /// Create empty categorized items collection.
62    const fn new() -> Self {
63        Self {
64            modules: Vec::new(),
65            structs: Vec::new(),
66            enums: Vec::new(),
67            unions: Vec::new(),
68            traits: Vec::new(),
69            functions: Vec::new(),
70            types: Vec::new(),
71            constants: Vec::new(),
72            statics: Vec::new(),
73            macros: Vec::new(),
74        }
75    }
76
77    /// Check if all categories are empty.
78    const fn is_empty(&self) -> bool {
79        self.modules.is_empty()
80            && self.structs.is_empty()
81            && self.enums.is_empty()
82            && self.unions.is_empty()
83            && self.traits.is_empty()
84            && self.functions.is_empty()
85            && self.types.is_empty()
86            && self.constants.is_empty()
87            && self.statics.is_empty()
88            && self.macros.is_empty()
89    }
90
91    /// Add an item to the appropriate category based on its type.
92    fn add_item(&mut self, id: &'a Id, item: &'a Item) {
93        match &item.inner {
94            ItemEnum::Module(_) => self.modules.push(item),
95            ItemEnum::Struct(_) => self.structs.push((id, item)),
96            ItemEnum::Enum(_) => self.enums.push((id, item)),
97            ItemEnum::Union(_) => self.unions.push((id, item)),
98            ItemEnum::Trait(_) => self.traits.push((id, item)),
99            ItemEnum::Function(_) => self.functions.push(item),
100            ItemEnum::TypeAlias(_) => self.types.push(item),
101            ItemEnum::Constant { .. } => self.constants.push(item),
102            ItemEnum::Static(_) => self.statics.push(item),
103            ItemEnum::Macro(_) | ItemEnum::ProcMacro(_) => self.macros.push(item),
104            _ => {},
105        }
106    }
107
108    /// Add an item by category based on target item type (for re-exports).
109    ///
110    /// The `id` is the Use item's ID, and `target` is the resolved target item
111    /// used to determine the category. The `use_item` is stored (not target)
112    /// because `get_item_name` handles Use items specially.
113    fn add_reexport(&mut self, id: &'a Id, use_item: &'a Item, target: &Item) {
114        match &target.inner {
115            ItemEnum::Module(_) => self.modules.push(use_item),
116            ItemEnum::Struct(_) => self.structs.push((id, use_item)),
117            ItemEnum::Enum(_) => self.enums.push((id, use_item)),
118            ItemEnum::Union(_) => self.unions.push((id, use_item)),
119            ItemEnum::Trait(_) => self.traits.push((id, use_item)),
120            ItemEnum::Function(_) => self.functions.push(use_item),
121            ItemEnum::TypeAlias(_) => self.types.push(use_item),
122            ItemEnum::Constant { .. } => self.constants.push(use_item),
123            ItemEnum::Static(_) => self.statics.push(use_item),
124            ItemEnum::Macro(_) | ItemEnum::ProcMacro(_) => self.macros.push(use_item),
125            _ => {},
126        }
127    }
128
129    /// Build TOC entries from categorized items.
130    ///
131    /// Preserves the standard rustdoc section order:
132    /// Modules → Structs → Enums → Unions → Traits → Functions → Type Aliases → Constants → Statics → Macros
133    fn build_toc_entries(&self) -> Vec<TocEntry> {
134        let mut entries = Vec::new();
135
136        // Build sections in standard rustdoc order
137        // Note: We use direct method calls instead of an array literal because
138        // Rust cannot coerce &Vec<T> to &[T] inside array initializers.
139
140        // Modules (simple items)
141        if let Some(e) = Self::build_section(&self.modules, "Modules", "modules", false) {
142            entries.push(e);
143        }
144
145        // Structs (items with IDs)
146        if let Some(e) = Self::build_section_with_ids(&self.structs, "Structs", "structs") {
147            entries.push(e);
148        }
149
150        // Enums (items with IDs)
151        if let Some(e) = Self::build_section_with_ids(&self.enums, "Enums", "enums") {
152            entries.push(e);
153        }
154
155        // Unions (items with IDs)
156        if let Some(e) = Self::build_section_with_ids(&self.unions, "Unions", "unions") {
157            entries.push(e);
158        }
159
160        // Traits (items with IDs)
161        if let Some(e) = Self::build_section_with_ids(&self.traits, "Traits", "traits") {
162            entries.push(e);
163        }
164
165        // Functions (simple items)
166        if let Some(e) = Self::build_section(&self.functions, "Functions", "functions", false) {
167            entries.push(e);
168        }
169
170        // Type Aliases (simple items)
171        if let Some(e) = Self::build_section(&self.types, "Type Aliases", "type-aliases", false) {
172            entries.push(e);
173        }
174
175        // Constants (simple items)
176        if let Some(e) = Self::build_section(&self.constants, "Constants", "constants", false) {
177            entries.push(e);
178        }
179
180        // Statics (simple items)
181        if let Some(e) = Self::build_section(&self.statics, "Statics", "statics", false) {
182            entries.push(e);
183        }
184
185        // Macros (simple items, with ! suffix)
186        if let Some(e) = Self::build_section(&self.macros, "Macros", "macros", true) {
187            entries.push(e);
188        }
189
190        entries
191    }
192
193    /// Build a TOC section for items without IDs.
194    ///
195    /// Uses `slugify_anchor()` for anchor generation to match heading anchors.
196    fn build_section(
197        items: &[&Item],
198        section: &str,
199        anchor: &str,
200        is_macro: bool,
201    ) -> Option<TocEntry> {
202        if items.is_empty() {
203            return None;
204        }
205
206        let children: Vec<TocEntry> = items
207            .iter()
208            .map(|item| {
209                let name = Self::get_item_name(item);
210                let display = if is_macro {
211                    format!("`{name}!`")
212                } else {
213                    format!("`{name}`")
214                };
215
216                // Use slugify_anchor for consistent anchor generation
217                // e.g., "my_type" → "my-type" to match heading anchors
218                TocEntry::new(display, AnchorUtils::slugify_anchor(name))
219            })
220            .collect();
221
222        Some(TocEntry::with_children(section, anchor, children))
223    }
224
225    /// Build a TOC section for items with IDs.
226    ///
227    /// Uses `slugify_anchor()` for anchor generation to match heading anchors.
228    fn build_section_with_ids(
229        items: &[(&Id, &Item)],
230        section: &str,
231        anchor: &str,
232    ) -> Option<TocEntry> {
233        if items.is_empty() {
234            return None;
235        }
236
237        let children: Vec<TocEntry> = items
238            .iter()
239            .map(|(_, item)| {
240                let name = Self::get_item_name(item);
241                // Use slugify_anchor for consistent anchor generation
242                TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
243            })
244            .collect();
245
246        Some(TocEntry::with_children(section, anchor, children))
247    }
248
249    /// Build Quick Reference entries from categorized items.
250    ///
251    /// Preserves the standard rustdoc section order:
252    /// Modules → Structs → Enums → Unions → Traits → Functions → Type Aliases → Constants → Statics → Macros
253    fn build_quick_ref_entries(&self) -> Vec<QuickRefEntry> {
254        let mut entries = Vec::new();
255
256        // Add entries in standard rustdoc order
257        Self::add_quick_ref_entries(&mut entries, &self.modules, "mod", false);
258        Self::add_quick_ref_entries_with_ids(&mut entries, &self.structs, "struct");
259        Self::add_quick_ref_entries_with_ids(&mut entries, &self.enums, "enum");
260        Self::add_quick_ref_entries_with_ids(&mut entries, &self.unions, "union");
261        Self::add_quick_ref_entries_with_ids(&mut entries, &self.traits, "trait");
262        Self::add_quick_ref_entries(&mut entries, &self.functions, "fn", false);
263        Self::add_quick_ref_entries(&mut entries, &self.types, "type", false);
264        Self::add_quick_ref_entries(&mut entries, &self.constants, "const", false);
265        Self::add_quick_ref_entries(&mut entries, &self.statics, "static", false);
266        Self::add_quick_ref_entries(&mut entries, &self.macros, "macro", true);
267
268        entries
269    }
270
271    /// Add quick ref entries for items without IDs.
272    ///
273    /// Uses `slugify_anchor()` for anchor generation to match heading anchors.
274    fn add_quick_ref_entries(
275        entries: &mut Vec<QuickRefEntry>,
276        items: &[&Item],
277        kind: &'static str,
278        is_macro: bool,
279    ) {
280        for item in items {
281            let name = Self::get_item_name(item);
282            let summary = extract_summary(item.docs.as_deref());
283
284            let display_name = if is_macro {
285                format!("{name}!")
286            } else {
287                name.to_string()
288            };
289
290            // Use slugify_anchor for consistent anchor generation
291            entries.push(QuickRefEntry::new(
292                display_name,
293                kind,
294                AnchorUtils::slugify_anchor(name),
295                summary,
296            ));
297        }
298    }
299
300    /// Add quick ref entries for items with IDs.
301    ///
302    /// Uses `slugify_anchor()` for anchor generation to match heading anchors.
303    fn add_quick_ref_entries_with_ids(
304        entries: &mut Vec<QuickRefEntry>,
305        items: &[(&Id, &Item)],
306        kind: &'static str,
307    ) {
308        for (_, item) in items {
309            let name = Self::get_item_name(item);
310            let summary = extract_summary(item.docs.as_deref());
311
312            // Use slugify_anchor for consistent anchor generation
313            entries.push(QuickRefEntry::new(
314                name,
315                kind,
316                AnchorUtils::slugify_anchor(name),
317                summary,
318            ));
319        }
320    }
321
322    /// Get the display name for an item, handling re-exports.
323    fn get_item_name(item: &Item) -> &str {
324        if let ItemEnum::Use(use_item) = &item.inner {
325            &use_item.name
326        } else {
327            item.name.as_deref().unwrap_or("unnamed")
328        }
329    }
330
331    /// Expand a glob re-export into this collection.
332    ///
333    /// Iterates through items in the target module and adds them to the
334    /// appropriate category vectors. Uses `seen_items` to avoid duplicates.
335    ///
336    /// # Arguments
337    ///
338    /// * `use_item` - The glob Use item to expand
339    /// * `krate` - The crate containing the target module
340    /// * `view` - The single-crate view for visibility filtering
341    /// * `seen_items` - Set of already-processed item IDs (mutated)
342    fn expand_glob_reexport(
343        &mut self,
344        use_item: &rustdoc_types::Use,
345        krate: &'a Crate,
346        view: &SingleCrateView<'_>,
347        seen_items: &mut HashSet<Id>,
348    ) {
349        let Some(target_id) = &use_item.id else {
350            return;
351        };
352        let Some(target_module) = krate.index.get(target_id) else {
353            return;
354        };
355        let ItemEnum::Module(module) = &target_module.inner else {
356            return;
357        };
358
359        for child_id in &module.items {
360            if !seen_items.insert(*child_id) {
361                continue; // Already processed
362            }
363
364            let Some(child) = krate.index.get(child_id) else {
365                continue;
366            };
367
368            if !view.should_include_item(child) {
369                continue;
370            }
371
372            // Add the child item to the appropriate category
373            self.add_item(child_id, child);
374        }
375    }
376}
377
378/// Generator for multi-crate documentation.
379///
380/// Produces a directory structure with one subdirectory per crate,
381/// each containing nested markdown files with cross-crate linking.
382///
383/// # Output Structure
384///
385/// ```text
386/// output/
387/// ├── tracing/
388/// │   ├── index.md
389/// │   └── span/
390/// │       └── index.md
391/// ├── tracing_core/
392/// │   ├── index.md
393/// │   └── subscriber/
394/// │       └── index.md
395/// └── SUMMARY.md        # If --mdbook enabled
396/// ```
397pub struct MultiCrateGenerator<'a> {
398    /// Multi-crate context with unified registry.
399    ctx: MultiCrateContext<'a>,
400
401    /// CLI arguments.
402    args: &'a Args,
403}
404
405impl<'a> MultiCrateGenerator<'a> {
406    /// Create a new multi-crate generator.
407    ///
408    /// # Arguments
409    ///
410    /// * `crates` - Collection of parsed crates
411    /// * `args` - CLI arguments
412    /// * `config` - Rendering configuration options
413    #[must_use]
414    pub fn new(crates: &'a CrateCollection, args: &'a Args, config: RenderConfig) -> Self {
415        let ctx = MultiCrateContext::new(crates, args, config);
416        Self { ctx, args }
417    }
418
419    /// Generate documentation for all crates.
420    ///
421    /// Creates the output directory structure, generates docs for each crate
422    /// in parallel using rayon, and optionally generates SUMMARY.md for
423    /// mdBook compatibility.
424    ///
425    /// # Errors
426    ///
427    /// Returns an error if any file operation fails.
428    #[instrument(skip(self), fields(
429        crate_count = self.ctx.crates().names().len(),
430        output = %self.args.output.display(),
431        mdbook = !self.args.no_mdbook,
432        search_index = !self.args.no_search_index
433    ))]
434    pub fn generate(&self) -> Result<(), Error> {
435        info!("Starting multi-crate documentation generation");
436
437        // Create output directory
438        FsErr::create_dir_all(&self.args.output).map_err(Error::CreateDir)?;
439
440        debug!(path = %self.args.output.display(), "Created output directory");
441
442        // Pre-create crate directories to avoid race conditions in parallel generation
443        for crate_name in self.ctx.crates().names() {
444            let crate_dir = self.args.output.join(crate_name);
445            FsErr::create_dir_all(&crate_dir).map_err(Error::CreateDir)?;
446        }
447
448        // Count total modules across all crates for progress bar
449        let total_modules: usize = self
450            .ctx
451            .crates()
452            .iter()
453            .filter_map(|(name, _)| self.ctx.single_crate_view(name))
454            .map(|view| view.count_modules() + 1)
455            .sum();
456
457        debug!(total_modules, "Total modules to generate");
458        let progress = Arc::new(Self::create_progress_bar(total_modules)?);
459
460        // Generate crates in parallel
461        self.ctx
462            .crates()
463            .names()
464            .par_iter()
465            .try_for_each(|crate_name| {
466                let span = info_span!("generate_crate", crate_name);
467                let _guard = span.enter();
468
469                let view = self
470                    .ctx
471                    .single_crate_view(crate_name)
472                    .ok_or_else(|| Error::ItemNotFound((*crate_name).clone()))?;
473
474                self.generate_crate(&view, &progress)
475            })?;
476
477        // Generate SUMMARY.md if requested (sequential - single file)
478        if !self.args.no_mdbook {
479            info!("Generating SUMMARY.md for mdBook");
480            progress.set_message("Generating SUMMARY.md...");
481            let summary_gen = SummaryGenerator::new(
482                self.ctx.crates(),
483                &self.args.output,
484                !self.args.exclude_private,
485            );
486            summary_gen.generate()?;
487        }
488
489        // Generate search index if requested (sequential - single file)
490        if !self.args.no_search_index {
491            info!("Generating search_index.json");
492            progress.set_message("Generating search_index.json...");
493
494            // Collect the IDs of all rendered items to filter the search index
495            let rendered_items = self.collect_rendered_items();
496
497            let search_gen = SearchIndexGenerator::new(
498                self.ctx.crates(),
499                !self.args.exclude_private,
500                rendered_items,
501            );
502            search_gen
503                .write(&self.args.output)
504                .map_err(Error::FileWrite)?;
505        }
506
507        progress.finish_with_message("Done!");
508        info!("Multi-crate documentation generation complete");
509        Ok(())
510    }
511
512    /// Collect the IDs of all items that would be rendered.
513    ///
514    /// This walks the module tree for each crate using the same visibility
515    /// rules as rendering, collecting the IDs of items that will have
516    /// documentation generated for them.
517    fn collect_rendered_items(&self) -> HashMap<String, HashSet<Id>> {
518        let mut result = HashMap::new();
519
520        for crate_name in self.ctx.crates().names() {
521            if let Some(view) = self.ctx.single_crate_view(crate_name) {
522                let mut ids = HashSet::new();
523                Self::collect_crate_items(&view, &mut ids);
524                result.insert(crate_name.clone(), ids);
525            }
526        }
527
528        result
529    }
530
531    /// Collect rendered item IDs for a single crate.
532    fn collect_crate_items(view: &SingleCrateView, ids: &mut HashSet<Id>) {
533        let krate = view.krate();
534
535        // Get root item
536        let Some(root_item) = krate.index.get(&krate.root) else {
537            return;
538        };
539
540        // Collect root module items
541        Self::collect_module_items(view, root_item, ids);
542    }
543
544    /// Recursively collect rendered item IDs from a module.
545    fn collect_module_items(view: &SingleCrateView, item: &Item, ids: &mut HashSet<Id>) {
546        let krate = view.krate();
547
548        if let ItemEnum::Module(module) = &item.inner {
549            for item_id in &module.items {
550                if let Some(child) = krate.index.get(item_id) {
551                    if !view.should_include_item(child) {
552                        continue;
553                    }
554
555                    match &child.inner {
556                        // Documentable items - add their IDs
557                        ItemEnum::Struct(_)
558                        | ItemEnum::Enum(_)
559                        | ItemEnum::Trait(_)
560                        | ItemEnum::Function(_)
561                        | ItemEnum::TypeAlias(_)
562                        | ItemEnum::Constant { .. }
563                        | ItemEnum::Macro(_) => {
564                            ids.insert(*item_id);
565                        },
566
567                        // Modules - add ID and recurse
568                        ItemEnum::Module(_) => {
569                            ids.insert(*item_id);
570                            Self::collect_module_items(view, child, ids);
571                        },
572
573                        // Re-exports - add the Use item ID (not the target)
574                        ItemEnum::Use(use_item) if !use_item.is_glob => {
575                            // Verify target exists (same logic as rendering)
576                            let target_exists =
577                                use_item.id.as_ref().is_some_and(|target_id| {
578                                    krate.index.contains_key(target_id)
579                                        || view.lookup_item_across_crates(target_id).is_some()
580                                }) || view.resolve_external_path(&use_item.source).is_some();
581
582                            if target_exists {
583                                ids.insert(*item_id);
584                            }
585                        },
586
587                        _ => {},
588                    }
589                }
590            }
591        }
592    }
593
594    /// Generate documentation for a single crate.
595    #[instrument(skip(self, view, progress), fields(crate_name = %view.crate_name()))]
596    fn generate_crate(
597        &self,
598        view: &SingleCrateView,
599        progress: &Arc<ProgressBar>,
600    ) -> Result<(), Error> {
601        debug!("Starting crate generation");
602
603        let crate_name = view.crate_name();
604        let crate_dir = self.args.output.join(crate_name);
605
606        // Crate directory already created in generate() to avoid race conditions
607
608        // Get root item
609        let root_item = view
610            .krate()
611            .index
612            .get(&view.krate().root)
613            .ok_or_else(|| Error::ItemNotFound(view.krate().root.0.to_string()))?;
614
615        // Generate root index.md
616        let file_path = format!("{crate_name}/index.md");
617        let renderer = MultiCrateModuleRenderer::new(view, &file_path, true);
618        let content = renderer.render(root_item);
619
620        let index_path = crate_dir.join("index.md");
621        FsErr::write(&index_path, content).map_err(Error::FileWrite)?;
622        progress.inc(1);
623
624        // Generate submodules
625        if let ItemEnum::Module(module) = &root_item.inner {
626            for item_id in &module.items {
627                if let Some(item) = view.krate().index.get(item_id)
628                    && let ItemEnum::Module(_) = &item.inner
629                    && view.should_include_item(item)
630                {
631                    Self::generate_module(view, item, &crate_dir, vec![], &Arc::clone(progress))?;
632                }
633            }
634        }
635
636        debug!("Crate generation complete");
637        Ok(())
638    }
639
640    /// Generate a module directory with index.md and child modules.
641    fn generate_module(
642        view: &SingleCrateView,
643        item: &Item,
644        parent_dir: &Path,
645        module_path: Vec<String>,
646        progress: &Arc<ProgressBar>,
647    ) -> Result<(), Error> {
648        let name = item.name.as_deref().unwrap_or("unnamed");
649
650        // Create module directory
651        let module_dir = parent_dir.join(name);
652        FsErr::create_dir_all(&module_dir).map_err(Error::CreateDir)?;
653
654        // Build module path for file and breadcrumbs
655        let mut current_path = module_path;
656        current_path.push(name.to_string());
657
658        // File path relative to output root (includes crate name)
659        let file_path = format!("{}/{}/index.md", view.crate_name(), current_path.join("/"));
660
661        // Generate breadcrumbs
662        let breadcrumb_gen = BreadcrumbGenerator::new(&current_path, view.crate_name());
663        let breadcrumbs = breadcrumb_gen.generate();
664
665        // Generate module content
666        let renderer = MultiCrateModuleRenderer::new(view, &file_path, false);
667        let module_content = renderer.render(item);
668
669        // Combine breadcrumbs + content
670        let content = format!("{breadcrumbs}{module_content}");
671
672        // Write index.md
673        let file_path_on_disk = module_dir.join("index.md");
674        FsErr::write(&file_path_on_disk, content).map_err(Error::FileWrite)?;
675        progress.inc(1);
676
677        // Recurse into child modules
678        if let ItemEnum::Module(module) = &item.inner {
679            for sub_id in &module.items {
680                if let Some(sub_item) = view.krate().index.get(sub_id)
681                    && let ItemEnum::Module(_) = &sub_item.inner
682                    && view.should_include_item(sub_item)
683                {
684                    Self::generate_module(
685                        view,
686                        sub_item,
687                        &module_dir,
688                        current_path.clone(),
689                        &Arc::clone(progress),
690                    )?;
691                }
692            }
693        }
694
695        Ok(())
696    }
697
698    /// Create a progress bar.
699    ///
700    /// # Errors
701    ///
702    /// Returns an error if the progress bar template is invalid.
703    fn create_progress_bar(total: usize) -> Result<ProgressBar, Error> {
704        let progress = ProgressBar::new(total as u64);
705
706        let style =
707            ProgressStyle::with_template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
708                .map_err(Error::ProgressBarTemplate)?
709                .progress_chars("=>-");
710
711        progress.set_style(style);
712        Ok(progress)
713    }
714}
715
716/// Module renderer for multi-crate context.
717///
718/// Wraps the standard module rendering with multi-crate link resolution.
719///
720/// This renderer handles special cases that aren't covered by the standard
721/// `ModuleRenderer`, particularly re-exports (`pub use`) which need to
722/// resolve items across crate boundaries.
723struct MultiCrateModuleRenderer<'a> {
724    /// Single-crate view for this crate (implements `RenderContext`).
725    view: &'a SingleCrateView<'a>,
726
727    /// Current file path for link resolution.
728    file_path: &'a str,
729
730    /// Whether this is the crate root.
731    is_root: bool,
732
733    /// Cached type renderer to avoid repeated construction.
734    type_renderer: TypeRenderer<'a>,
735}
736
737impl<'a> MultiCrateModuleRenderer<'a> {
738    /// Create a new multi-crate module renderer.
739    const fn new(view: &'a SingleCrateView<'a>, file_path: &'a str, is_root: bool) -> Self {
740        Self {
741            view,
742            file_path,
743            is_root,
744            type_renderer: TypeRenderer::new(view.krate()),
745        }
746    }
747
748    /// Process documentation for an item.
749    ///
750    /// Delegates to the view's `process_docs` method which handles:
751    /// - Stripping duplicate titles
752    /// - Converting doc links to markdown links
753    /// - Processing code blocks
754    fn process_docs(&self, item: &Item) -> Option<String> {
755        self.view.process_docs(item, self.file_path)
756    }
757
758    /// Render source location if enabled in config.
759    ///
760    /// Returns the source location string if `source_locations` is enabled,
761    /// otherwise returns an empty string. Uses the source path config to
762    /// generate clickable links to the `.source_*` directory when available.
763    fn maybe_render_source_location(&self, item: &Item) -> String {
764        if self.view.render_config().include_source.source_locations {
765            let source_config = self.view.source_path_config_for_file(self.file_path);
766
767            RendererInternals::render_source_location(item.span.as_ref(), source_config.as_ref())
768        } else {
769            String::new()
770        }
771    }
772
773    /// Render a module item to markdown.
774    fn render(&self, item: &Item) -> String {
775        let mut md = String::new();
776
777        // Module title
778        let name = item.name.as_deref().unwrap_or("unnamed");
779        if self.is_root {
780            _ = writeln!(md, "# Crate `{name}`\n");
781        } else {
782            _ = writeln!(md, "# Module `{name}`\n");
783        }
784
785        // Module documentation - use RenderContext trait method
786        if let Some(docs) = self.view.process_docs(item, self.file_path) {
787            _ = writeln!(md, "{docs}\n");
788        }
789
790        // Module contents
791        if let ItemEnum::Module(module) = &item.inner {
792            self.render_module_contents(&mut md, module, item);
793        }
794
795        md
796    }
797
798    /// Render module contents (items, types, functions, etc.).
799    #[expect(
800        clippy::too_many_lines,
801        reason = "Inherently complex. Will probably grow more."
802    )]
803    fn render_module_contents(
804        &self,
805        md: &mut String,
806        module: &rustdoc_types::Module,
807        _parent: &Item,
808    ) {
809        let krate = self.view.krate();
810        let mut seen_items: HashSet<Id> = HashSet::new();
811
812        // Collect items by category (with IDs for impl block rendering)
813        let mut modules: Vec<&Item> = Vec::new();
814        let mut structs: Vec<(&Id, &Item)> = Vec::new();
815        let mut enums: Vec<(&Id, &Item)> = Vec::new();
816        let mut traits: Vec<(&Id, &Item)> = Vec::new();
817        let mut functions: Vec<&Item> = Vec::new();
818        let mut types: Vec<&Item> = Vec::new();
819        let mut constants: Vec<&Item> = Vec::new();
820        let mut macros: Vec<&Item> = Vec::new();
821
822        for item_id in &module.items {
823            // Skip if already processed (from glob expansion)
824            if !seen_items.insert(*item_id) {
825                continue;
826            }
827
828            if let Some(item) = krate.index.get(item_id) {
829                if !self.view.should_include_item(item) {
830                    continue;
831                }
832
833                match &item.inner {
834                    ItemEnum::Module(_) => modules.push(item),
835
836                    ItemEnum::Struct(_) => structs.push((item_id, item)),
837
838                    ItemEnum::Enum(_) => enums.push((item_id, item)),
839
840                    ItemEnum::Trait(_) => traits.push((item_id, item)),
841
842                    ItemEnum::Function(_) => functions.push(item),
843
844                    ItemEnum::TypeAlias(_) => types.push(item),
845
846                    ItemEnum::Constant { .. } => constants.push(item),
847
848                    ItemEnum::Macro(_) => macros.push(item),
849
850                    // Handle re-exports
851                    ItemEnum::Use(use_item) => {
852                        if use_item.is_glob {
853                            // Glob re-export: expand target module's items
854                            self.expand_glob_reexport(
855                                &mut modules,
856                                &mut structs,
857                                &mut enums,
858                                &mut traits,
859                                &mut functions,
860                                &mut types,
861                                &mut constants,
862                                &mut macros,
863                                use_item,
864                                &mut seen_items,
865                            );
866                        } else {
867                            // Specific re-export: resolve target and categorize
868                            let target_item = use_item.id.as_ref().map_or_else(
869                                || {
870                                    // No ID (external re-export) - resolve by path
871                                    self.view
872                                        .resolve_external_path(&use_item.source)
873                                        .map(|(_, item, _)| item)
874                                },
875                                |target_id| {
876                                    // Has ID - try local crate first, then search all crates
877                                    krate.index.get(target_id).or_else(|| {
878                                        self.view
879                                            .lookup_item_across_crates(target_id)
880                                            .map(|(_, item)| item)
881                                    })
882                                },
883                            );
884
885                            if let Some(target_item) = target_item {
886                                match &target_item.inner {
887                                    ItemEnum::Module(_) => modules.push(item),
888
889                                    ItemEnum::Struct(_) => structs.push((item_id, item)),
890
891                                    ItemEnum::Enum(_) => enums.push((item_id, item)),
892
893                                    ItemEnum::Trait(_) => traits.push((item_id, item)),
894
895                                    ItemEnum::Function(_) => functions.push(item),
896
897                                    ItemEnum::TypeAlias(_) => types.push(item),
898
899                                    ItemEnum::Constant { .. } => constants.push(item),
900
901                                    ItemEnum::Macro(_) => macros.push(item),
902
903                                    _ => {},
904                                }
905                            }
906                        }
907                    },
908                    _ => {},
909                }
910            }
911        }
912
913        // Check if crate/module is empty
914        let is_empty = modules.is_empty()
915            && structs.is_empty()
916            && enums.is_empty()
917            && traits.is_empty()
918            && functions.is_empty()
919            && types.is_empty()
920            && constants.is_empty()
921            && macros.is_empty();
922
923        if is_empty && self.is_root {
924            // Empty crate - likely a proc-macro or re-export crate
925            let crate_name = self.view.crate_name();
926            if crate_name.ends_with("_derive") || crate_name.ends_with("-derive") {
927                // Derive macro crate - try to link to parent crate
928                let parent_crate = crate_name
929                    .strip_suffix("_derive")
930                    .or_else(|| crate_name.strip_suffix("-derive"))
931                    .unwrap_or(crate_name);
932
933                _ = writeln!(md, "## Overview\n");
934                _ = writeln!(
935                    md,
936                    "This is a **procedural macro crate** that provides derive macros."
937                );
938                _ = writeln!(md);
939                _ = writeln!(
940                    md,
941                    "The macros from this crate are typically re-exported from the parent crate \
942                     [`{parent_crate}`](../{parent_crate}/index.md) for convenience. \
943                     You should generally depend on the parent crate rather than this one directly."
944                );
945                _ = writeln!(md);
946                _ = writeln!(md, "### Usage\n");
947                _ = writeln!(md, "```toml");
948                _ = writeln!(md, "[dependencies]");
949                _ = writeln!(
950                    md,
951                    "{parent_crate} = {{ version = \"*\", features = [\"derive\"] }}"
952                );
953                _ = writeln!(md, "```");
954            } else if crate_name.ends_with("_impl") || crate_name.ends_with("-impl") {
955                let parent_crate = crate_name
956                    .strip_suffix("_impl")
957                    .or_else(|| crate_name.strip_suffix("-impl"))
958                    .unwrap_or(crate_name);
959
960                _ = writeln!(md, "## Overview\n");
961                _ = writeln!(
962                    md,
963                    "This is an **implementation detail crate** with no public API."
964                );
965                _ = writeln!(md);
966                _ = writeln!(
967                    md,
968                    "The functionality from this crate is re-exported through \
969                     [`{parent_crate}`](../{parent_crate}/index.md). \
970                     You should depend on the parent crate instead."
971                );
972            } else {
973                _ = writeln!(md, "*This crate has no public items to document.*");
974            }
975
976            return;
977        }
978
979        // === Table of Contents (if above threshold) ===
980        let config = self.view.render_config();
981        let toc_gen = TocGenerator::new(config.toc_threshold);
982        let toc_entries = Self::build_toc_entries(
983            &modules, &structs, &enums, &traits, &functions, &types, &constants, &macros,
984        );
985
986        if let Some(toc) = toc_gen.generate(&toc_entries) {
987            _ = write!(md, "{}", &toc);
988        }
989
990        // === Quick Reference (if enabled) ===
991        if config.quick_reference {
992            let quick_ref_entries = Self::build_quick_ref_entries(
993                &modules, &structs, &enums, &traits, &functions, &types, &constants, &macros,
994            );
995
996            if !quick_ref_entries.is_empty() {
997                let quick_ref_gen = QuickRefGenerator::new();
998
999                _ = write!(md, "{}", &quick_ref_gen.generate(&quick_ref_entries));
1000            }
1001        }
1002
1003        // Render sections with full detail
1004        Self::render_modules_section(md, &modules, self.view.krate());
1005        self.render_structs_section(md, &structs);
1006        self.render_enums_section(md, &enums);
1007        self.render_traits_section(md, &traits);
1008        self.render_functions_section(md, &functions);
1009        self.render_type_aliases_section(md, &types);
1010        self.render_constants_section(md, &constants);
1011        self.render_macros_section(md, &macros);
1012    }
1013
1014    /// Render modules section (links to subdirectories).
1015    fn render_modules_section(md: &mut String, modules: &[&Item], krate: &Crate) {
1016        if modules.is_empty() {
1017            return;
1018        }
1019
1020        _ = writeln!(md, "## Modules\n");
1021
1022        for item in modules {
1023            let (name, summary) = Self::get_item_name_and_summary_with_fallback(item, Some(krate));
1024            if summary.is_empty() {
1025                _ = writeln!(md, "- [`{name}`]({name}/index.md)");
1026            } else {
1027                _ = writeln!(md, "- [`{name}`]({name}/index.md) — {summary}");
1028            }
1029        }
1030
1031        _ = writeln!(md);
1032    }
1033
1034    /// Render structs section with full detail.
1035    fn render_structs_section(&self, md: &mut String, structs: &[(&Id, &Item)]) {
1036        if structs.is_empty() {
1037            return;
1038        }
1039
1040        _ = writeln!(md, "## Structs\n");
1041
1042        for (item_id, item) in structs {
1043            self.render_struct(md, **item_id, item);
1044        }
1045    }
1046
1047    /// Render enums section with full detail.
1048    fn render_enums_section(&self, md: &mut String, enums: &[(&Id, &Item)]) {
1049        if enums.is_empty() {
1050            return;
1051        }
1052
1053        _ = writeln!(md, "## Enums\n");
1054
1055        for (item_id, item) in enums {
1056            self.render_enum(md, **item_id, item);
1057        }
1058    }
1059
1060    /// Render traits section with full detail.
1061    fn render_traits_section(&self, md: &mut String, traits: &[(&Id, &Item)]) {
1062        if traits.is_empty() {
1063            return;
1064        }
1065
1066        _ = writeln!(md, "## Traits\n");
1067
1068        for (item_id, item) in traits {
1069            self.render_trait(md, **item_id, item);
1070        }
1071    }
1072
1073    /// Render functions section with full detail.
1074    fn render_functions_section(&self, md: &mut String, functions: &[&Item]) {
1075        if functions.is_empty() {
1076            return;
1077        }
1078
1079        _ = writeln!(md, "## Functions\n");
1080
1081        for item in functions {
1082            self.render_function(md, item);
1083        }
1084    }
1085
1086    /// Render type aliases section with full detail.
1087    fn render_type_aliases_section(&self, md: &mut String, types: &[&Item]) {
1088        if types.is_empty() {
1089            return;
1090        }
1091
1092        _ = writeln!(md, "## Type Aliases\n");
1093
1094        for item in types {
1095            self.render_type_alias(md, item);
1096        }
1097    }
1098
1099    /// Render constants section with full detail.
1100    fn render_constants_section(&self, md: &mut String, constants: &[&Item]) {
1101        if constants.is_empty() {
1102            return;
1103        }
1104
1105        _ = writeln!(md, "## Constants\n");
1106
1107        for item in constants {
1108            self.render_constant(md, item);
1109        }
1110    }
1111
1112    /// Render macros section with full detail.
1113    fn render_macros_section(&self, md: &mut String, macros: &[&Item]) {
1114        if macros.is_empty() {
1115            return;
1116        }
1117
1118        _ = writeln!(md, "## Macros\n");
1119
1120        for item in macros {
1121            self.render_macro(md, item);
1122        }
1123    }
1124
1125    /// Get name and summary for an item, handling re-exports.
1126    ///
1127    /// For re-exports (Use items), if the Use item has no docs, falls back to
1128    /// the target item's docs when a crate reference is provided.
1129    #[expect(
1130        dead_code,
1131        reason = "Kept for API consistency, use _with_fallback for docs"
1132    )]
1133    fn get_item_name_and_summary(item: &Item) -> (String, String) {
1134        Self::get_item_name_and_summary_with_fallback(item, None)
1135    }
1136
1137    /// Get name and summary for an item with optional fallback lookup.
1138    ///
1139    /// When `krate` is provided and the item is a re-export without docs,
1140    /// looks up the target item's docs as a fallback.
1141    fn get_item_name_and_summary_with_fallback(
1142        item: &Item,
1143        krate: Option<&Crate>,
1144    ) -> (String, String) {
1145        if let ItemEnum::Use(use_item) = &item.inner {
1146            // For re-exports, the name is always from the Use item
1147            let name = use_item.name.clone();
1148
1149            // First try the Use item's own docs
1150            let summary = if let Some(docs) = &item.docs
1151                && !docs.trim().is_empty()
1152            {
1153                docs.lines().next().unwrap_or("").to_string()
1154            } else if let Some(krate) = krate
1155                && let Some(target_id) = &use_item.id
1156                && let Some(target_item) = krate.index.get(target_id)
1157                && let Some(target_docs) = &target_item.docs
1158            {
1159                // Fall back to target item's docs
1160                target_docs.lines().next().unwrap_or("").to_string()
1161            } else {
1162                String::new()
1163            };
1164
1165            (name, summary)
1166        } else {
1167            let name = item.name.clone().unwrap_or_else(|| "unnamed".to_string());
1168            let docs = item.docs.as_deref().unwrap_or("");
1169            let summary = docs.lines().next().unwrap_or("").to_string();
1170
1171            (name, summary)
1172        }
1173    }
1174
1175    /// Get the display name for an item, handling re-exports.
1176    ///
1177    /// For `Use` items (re-exports), the name is stored in `use_item.name`.
1178    /// For all other items, the name is in `item.name`.
1179    fn get_item_name(item: &Item) -> &str {
1180        if let ItemEnum::Use(use_item) = &item.inner {
1181            &use_item.name
1182        } else {
1183            item.name.as_deref().unwrap_or("unnamed")
1184        }
1185    }
1186
1187    /// Build TOC entries from categorized module items.
1188    ///
1189    /// Uses `slugify_anchor()` for anchor generation to match heading anchors.
1190    #[expect(
1191        clippy::too_many_arguments,
1192        reason = "Matches categorization structure"
1193    )]
1194    fn build_toc_entries(
1195        modules: &[&Item],
1196        structs: &[(&Id, &Item)],
1197        enums: &[(&Id, &Item)],
1198        traits: &[(&Id, &Item)],
1199        functions: &[&Item],
1200        types: &[&Item],
1201        constants: &[&Item],
1202        macros: &[&Item],
1203    ) -> Vec<TocEntry> {
1204        let mut entries = Vec::new();
1205
1206        // Modules section
1207        if !modules.is_empty() {
1208            let children: Vec<TocEntry> = modules
1209                .iter()
1210                .map(|item| {
1211                    let name = Self::get_item_name(item);
1212                    TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1213                })
1214                .collect();
1215
1216            entries.push(TocEntry::with_children("Modules", "modules", children));
1217        }
1218
1219        // Structs section
1220        if !structs.is_empty() {
1221            let children: Vec<TocEntry> = structs
1222                .iter()
1223                .map(|(_, item)| {
1224                    let name = Self::get_item_name(item);
1225                    TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1226                })
1227                .collect();
1228            entries.push(TocEntry::with_children("Structs", "structs", children));
1229        }
1230
1231        // Enums section
1232        if !enums.is_empty() {
1233            let children: Vec<TocEntry> = enums
1234                .iter()
1235                .map(|(_, item)| {
1236                    let name = Self::get_item_name(item);
1237                    TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1238                })
1239                .collect();
1240
1241            entries.push(TocEntry::with_children("Enums", "enums", children));
1242        }
1243
1244        // Traits section
1245        if !traits.is_empty() {
1246            let children: Vec<TocEntry> = traits
1247                .iter()
1248                .map(|(_, item)| {
1249                    let name = Self::get_item_name(item);
1250                    TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1251                })
1252                .collect();
1253
1254            entries.push(TocEntry::with_children("Traits", "traits", children));
1255        }
1256
1257        // Functions section
1258        if !functions.is_empty() {
1259            let children: Vec<TocEntry> = functions
1260                .iter()
1261                .map(|item| {
1262                    let name = Self::get_item_name(item);
1263                    TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1264                })
1265                .collect();
1266
1267            entries.push(TocEntry::with_children("Functions", "functions", children));
1268        }
1269
1270        // Type Aliases section
1271        if !types.is_empty() {
1272            let children: Vec<TocEntry> = types
1273                .iter()
1274                .map(|item| {
1275                    let name = Self::get_item_name(item);
1276                    TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1277                })
1278                .collect();
1279
1280            entries.push(TocEntry::with_children(
1281                "Type Aliases",
1282                "type-aliases",
1283                children,
1284            ));
1285        }
1286
1287        // Constants section
1288        if !constants.is_empty() {
1289            let children: Vec<TocEntry> = constants
1290                .iter()
1291                .map(|item| {
1292                    let name = Self::get_item_name(item);
1293                    TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1294                })
1295                .collect();
1296
1297            entries.push(TocEntry::with_children("Constants", "constants", children));
1298        }
1299
1300        // Macros section
1301        if !macros.is_empty() {
1302            let children: Vec<TocEntry> = macros
1303                .iter()
1304                .map(|item| {
1305                    let name = Self::get_item_name(item);
1306                    TocEntry::new(format!("`{name}!`"), AnchorUtils::slugify_anchor(name))
1307                })
1308                .collect();
1309
1310            entries.push(TocEntry::with_children("Macros", "macros", children));
1311        }
1312
1313        entries
1314    }
1315
1316    /// Build Quick Reference entries from categorized module items.
1317    ///
1318    /// Uses `slugify_anchor()` for anchor generation to match heading anchors.
1319    #[expect(
1320        clippy::too_many_arguments,
1321        reason = "Matches categorization structure"
1322    )]
1323    fn build_quick_ref_entries(
1324        modules: &[&Item],
1325        structs: &[(&Id, &Item)],
1326        enums: &[(&Id, &Item)],
1327        traits: &[(&Id, &Item)],
1328        functions: &[&Item],
1329        types: &[&Item],
1330        constants: &[&Item],
1331        macros: &[&Item],
1332    ) -> Vec<QuickRefEntry> {
1333        let mut entries = Vec::new();
1334
1335        // Modules
1336        for item in modules {
1337            let name = Self::get_item_name(item);
1338            let summary = extract_summary(item.docs.as_deref());
1339            let anchor = AnchorUtils::slugify_anchor(name);
1340
1341            entries.push(QuickRefEntry::new(name, "mod", anchor, summary));
1342        }
1343
1344        // Structs
1345        for (_, item) in structs {
1346            let name = Self::get_item_name(item);
1347            let summary = extract_summary(item.docs.as_deref());
1348            let anchor = AnchorUtils::slugify_anchor(name);
1349
1350            entries.push(QuickRefEntry::new(name, "struct", anchor, summary));
1351        }
1352
1353        // Enums
1354        for (_, item) in enums {
1355            let name = Self::get_item_name(item);
1356            let summary = extract_summary(item.docs.as_deref());
1357            let anchor = AnchorUtils::slugify_anchor(name);
1358
1359            entries.push(QuickRefEntry::new(name, "enum", anchor, summary));
1360        }
1361
1362        // Traits
1363        for (_, item) in traits {
1364            let name = Self::get_item_name(item);
1365            let summary = extract_summary(item.docs.as_deref());
1366            let anchor = AnchorUtils::slugify_anchor(name);
1367
1368            entries.push(QuickRefEntry::new(name, "trait", anchor, summary));
1369        }
1370
1371        // Functions
1372        for item in functions {
1373            let name = Self::get_item_name(item);
1374            let summary = extract_summary(item.docs.as_deref());
1375            let anchor = AnchorUtils::slugify_anchor(name);
1376
1377            entries.push(QuickRefEntry::new(name, "fn", anchor, summary));
1378        }
1379
1380        // Type aliases
1381        for item in types {
1382            let name = Self::get_item_name(item);
1383            let summary = extract_summary(item.docs.as_deref());
1384            let anchor = AnchorUtils::slugify_anchor(name);
1385
1386            entries.push(QuickRefEntry::new(name, "type", anchor, summary));
1387        }
1388
1389        // Constants
1390        for item in constants {
1391            let name = Self::get_item_name(item);
1392            let summary = extract_summary(item.docs.as_deref());
1393            let anchor = AnchorUtils::slugify_anchor(name);
1394
1395            entries.push(QuickRefEntry::new(name, "const", anchor, summary));
1396        }
1397
1398        // Macros
1399        for item in macros {
1400            let name = Self::get_item_name(item);
1401            let summary = extract_summary(item.docs.as_deref());
1402            let anchor = AnchorUtils::slugify_anchor(name);
1403
1404            entries.push(QuickRefEntry::new(
1405                format!("{name}!"),
1406                "macro",
1407                anchor,
1408                summary,
1409            ));
1410        }
1411
1412        entries
1413    }
1414
1415    /// Render a struct definition to markdown.
1416    fn render_struct(&self, md: &mut String, item_id: Id, item: &Item) {
1417        let current_krate = self.view.krate();
1418
1419        // Handle re-exports: use the target item for rendering
1420        // source_crate_name is set when the target is from another crate
1421        let (name, actual_item, actual_id, source_crate_name): (&str, &Item, Id, Option<&str>) =
1422            if let ItemEnum::Use(use_item) = &item.inner {
1423                let name = use_item.name.as_str();
1424
1425                if let Some(ref target_id) = use_item.id {
1426                    // Has ID - try local crate first, then search all crates
1427                    if let Some(target) = current_krate.index.get(target_id) {
1428                        // Found in local crate
1429                        (name, target, *target_id, None)
1430                    } else if let Some((src_crate, target)) =
1431                        self.view.lookup_item_across_crates(target_id)
1432                    {
1433                        // Found in another crate - capture the source crate name
1434                        let is_external = src_crate != self.view.crate_name();
1435                        (
1436                            name,
1437                            target,
1438                            *target_id,
1439                            if is_external { Some(src_crate) } else { None },
1440                        )
1441                    } else {
1442                        return;
1443                    }
1444                } else {
1445                    // No ID - try to resolve by path (external re-export)
1446                    if let Some((src_crate, target, target_id)) =
1447                        self.view.resolve_external_path(&use_item.source)
1448                    {
1449                        (name, target, target_id, Some(src_crate))
1450                    } else {
1451                        return;
1452                    }
1453                }
1454            } else {
1455                (
1456                    item.name.as_deref().unwrap_or("unnamed"),
1457                    item,
1458                    item_id,
1459                    None,
1460                )
1461            };
1462
1463        if let ItemEnum::Struct(s) = &actual_item.inner {
1464            // Get the appropriate crate context for rendering
1465            // Use source crate for field lookups if this is a cross-crate re-export
1466            let render_krate = source_crate_name
1467                .and_then(|name| self.view.get_crate(name))
1468                .unwrap_or(current_krate);
1469
1470            // Create TypeRenderer for the appropriate crate
1471            let type_renderer = if source_crate_name.is_some() {
1472                TypeRenderer::new(render_krate)
1473            } else {
1474                // Use cached renderer for local items
1475                self.type_renderer
1476            };
1477
1478            // Struct definition (heading + code block)
1479            RendererInternals::render_struct_definition(md, name, s, render_krate, &type_renderer);
1480
1481            // Source location (if enabled)
1482            _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1483
1484            // Add re-export annotation for external re-exports
1485            if let Some(src_crate) = source_crate_name {
1486                _ = writeln!(md, "*Re-exported from `{src_crate}`*\n");
1487            }
1488
1489            // Documentation
1490            RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1491
1492            // Fields documentation - use source crate for field type lookups
1493            if let StructKind::Plain { fields, .. } = &s.kind {
1494                RendererInternals::render_struct_fields(
1495                    md,
1496                    fields,
1497                    render_krate,
1498                    &type_renderer,
1499                    |field| self.view.process_docs(field, self.file_path),
1500                );
1501            }
1502
1503            // Impl blocks - pass source crate for cross-crate re-exports
1504            self.render_impl_blocks(md, actual_id, source_crate_name);
1505        }
1506    }
1507
1508    /// Render an enum definition to markdown.
1509    fn render_enum(&self, md: &mut String, item_id: Id, item: &Item) {
1510        let current_krate = self.view.krate();
1511
1512        // Handle re-exports: use the target item for rendering
1513        // source_crate_name is set when the target is from another crate
1514        let (name, actual_item, actual_id, source_crate_name): (&str, &Item, Id, Option<&str>) =
1515            if let ItemEnum::Use(use_item) = &item.inner {
1516                let name = use_item.name.as_str();
1517
1518                if let Some(ref target_id) = use_item.id {
1519                    // Has ID - try local crate first, then search all crates
1520                    if let Some(target) = current_krate.index.get(target_id) {
1521                        // Found in local crate
1522                        (name, target, *target_id, None)
1523                    } else if let Some((src_crate, target)) =
1524                        self.view.lookup_item_across_crates(target_id)
1525                    {
1526                        // Found in another crate - capture the source crate name
1527                        let is_external = src_crate != self.view.crate_name();
1528                        (
1529                            name,
1530                            target,
1531                            *target_id,
1532                            if is_external { Some(src_crate) } else { None },
1533                        )
1534                    } else {
1535                        return;
1536                    }
1537                } else {
1538                    // No ID - try to resolve by path (external re-export)
1539                    if let Some((src_crate, target, target_id)) =
1540                        self.view.resolve_external_path(&use_item.source)
1541                    {
1542                        (name, target, target_id, Some(src_crate))
1543                    } else {
1544                        return;
1545                    }
1546                }
1547            } else {
1548                (
1549                    item.name.as_deref().unwrap_or("unnamed"),
1550                    item,
1551                    item_id,
1552                    None,
1553                )
1554            };
1555
1556        if let ItemEnum::Enum(e) = &actual_item.inner {
1557            // Get the appropriate crate context for rendering
1558            // Use source crate for variant lookups if this is a cross-crate re-export
1559            let render_krate = source_crate_name
1560                .and_then(|name| self.view.get_crate(name))
1561                .unwrap_or(current_krate);
1562
1563            // Create TypeRenderer for the appropriate crate
1564            let type_renderer = if source_crate_name.is_some() {
1565                TypeRenderer::new(render_krate)
1566            } else {
1567                // Use cached renderer for local items
1568                self.type_renderer
1569            };
1570
1571            // Enum definition (heading + code block with variants)
1572            RendererInternals::render_enum_definition(md, name, e, render_krate, &type_renderer);
1573
1574            // Source location (if enabled)
1575            _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1576
1577            // Add re-export annotation for external re-exports
1578            if let Some(src_crate) = source_crate_name {
1579                _ = writeln!(md, "*Re-exported from `{src_crate}`*\n");
1580            }
1581
1582            // Documentation
1583            RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1584
1585            // Variants documentation - use source crate for variant type lookups
1586            RendererInternals::render_enum_variants_docs(
1587                md,
1588                &e.variants,
1589                render_krate,
1590                |variant| self.view.process_docs(variant, self.file_path),
1591            );
1592
1593            // Impl blocks - pass source crate for cross-crate re-exports
1594            self.render_impl_blocks(md, actual_id, source_crate_name);
1595        }
1596    }
1597
1598    /// Render a trait definition to markdown.
1599    fn render_trait(&self, md: &mut String, item_id: Id, item: &Item) {
1600        let current_krate = self.view.krate();
1601
1602        // Handle re-exports: resolve to actual trait item
1603        let (name, actual_item, actual_id): (&str, &Item, Id) = if let ItemEnum::Use(use_item) =
1604            &item.inner
1605        {
1606            let name = use_item.name.as_str();
1607
1608            if let Some(ref target_id) = use_item.id {
1609                // Has ID - try local crate first, then search all crates
1610                if let Some(target) = current_krate.index.get(target_id) {
1611                    (name, target, *target_id)
1612                } else if let Some((_, target)) = self.view.lookup_item_across_crates(target_id) {
1613                    (name, target, *target_id)
1614                } else {
1615                    return;
1616                }
1617            } else {
1618                // No ID - try to resolve by path (external re-export)
1619                if let Some((_, target, target_id)) =
1620                    self.view.resolve_external_path(&use_item.source)
1621                {
1622                    (name, target, target_id)
1623                } else {
1624                    return;
1625                }
1626            }
1627        } else {
1628            (item.name.as_deref().unwrap_or("unnamed"), item, item_id)
1629        };
1630
1631        if let ItemEnum::Trait(t) = &actual_item.inner {
1632            // Trait definition (heading + code block)
1633            TraitRenderer::render_trait_definition(md, name, t, &self.type_renderer);
1634
1635            // Source location (if enabled)
1636            _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1637
1638            // Documentation
1639            RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1640
1641            // Categorize trait items
1642            let items = CategorizedTraitItems::categorize_trait_items(&t.items, current_krate);
1643
1644            // Associated Types
1645            if !items.associated_types.is_empty() {
1646                _ = writeln!(md, "#### Associated Types\n");
1647
1648                for type_item in &items.associated_types {
1649                    TraitRenderer::render_trait_item(md, type_item, &self.type_renderer, |m| {
1650                        self.view.process_docs(m, self.file_path)
1651                    });
1652                }
1653            }
1654
1655            // Associated Constants
1656            if !items.associated_consts.is_empty() {
1657                _ = writeln!(md, "#### Associated Constants\n");
1658
1659                for const_item in &items.associated_consts {
1660                    TraitRenderer::render_trait_item(md, const_item, &self.type_renderer, |m| {
1661                        self.view.process_docs(m, self.file_path)
1662                    });
1663                }
1664            }
1665
1666            // Required Methods
1667            if !items.required_methods.is_empty() {
1668                _ = writeln!(md, "#### Required Methods\n");
1669
1670                for method in &items.required_methods {
1671                    TraitRenderer::render_trait_item(md, method, &self.type_renderer, |m| {
1672                        self.view.process_docs(m, self.file_path)
1673                    });
1674                }
1675            }
1676
1677            // Provided Methods
1678            if !items.provided_methods.is_empty() {
1679                _ = writeln!(md, "#### Provided Methods\n");
1680
1681                for method in &items.provided_methods {
1682                    TraitRenderer::render_trait_item(md, method, &self.type_renderer, |m| {
1683                        self.view.process_docs(m, self.file_path)
1684                    });
1685                }
1686            }
1687
1688            // Implementors section
1689            self.render_trait_implementors(md, actual_id);
1690        }
1691    }
1692
1693    /// Render the implementors section for a trait.
1694    ///
1695    /// Uses `Trait.implementations` field for direct lookup instead of scanning
1696    /// all items in the crate index, providing O(k) performance where k is the
1697    /// number of implementors.
1698    fn render_trait_implementors(&self, md: &mut String, trait_id: Id) {
1699        let krate = self.view.krate();
1700
1701        // Get the trait item to access its implementations list
1702        let Some(trait_item) = krate.index.get(&trait_id) else {
1703            return;
1704        };
1705        let ItemEnum::Trait(trait_data) = &trait_item.inner else {
1706            return;
1707        };
1708
1709        let mut implementors: BTreeSet<String> = BTreeSet::new();
1710
1711        // Use Trait.implementations for direct lookup instead of scanning all items
1712        for impl_id in &trait_data.implementations {
1713            let Some(impl_item) = krate.index.get(impl_id) else {
1714                continue;
1715            };
1716            let ItemEnum::Impl(impl_block) = &impl_item.inner else {
1717                continue;
1718            };
1719
1720            let for_type = self.type_renderer.render_type(&impl_block.for_);
1721
1722            let entry = self
1723                .type_renderer
1724                .get_type_id(&impl_block.for_)
1725                .and_then(|type_id| LinkResolver::create_link(self.view, type_id, self.file_path))
1726                .unwrap_or_else(|| format!("`{for_type}`"));
1727
1728            implementors.insert(entry);
1729        }
1730
1731        if !implementors.is_empty() {
1732            _ = write!(md, "#### Implementors\n\n");
1733            for implementor in implementors {
1734                _ = writeln!(md, "- {implementor}");
1735            }
1736            md.push('\n');
1737        }
1738    }
1739
1740    /// Render a function definition to markdown.
1741    /// FIX: Handle re-exports: Resolve to actual function item.
1742    fn render_function(&self, md: &mut String, item: &Item) {
1743        let Some((name, actual_item)) = self.resolve_reexport(item) else {
1744            return;
1745        };
1746
1747        let ItemEnum::Function(f) = &actual_item.inner else {
1748            return;
1749        };
1750
1751        RendererInternals::render_function_definition(md, name, f, &self.type_renderer);
1752        _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1753
1754        RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1755    }
1756
1757    /// Render a constant definition to markdown.
1758    /// Handles re-exports by resolving to the actual constant item.
1759    fn render_constant(&self, md: &mut String, item: &Item) {
1760        let Some((name, actual_item)) = self.resolve_reexport(item) else {
1761            return;
1762        };
1763
1764        let ItemEnum::Constant { type_, const_ } = &actual_item.inner else {
1765            return; // Not a constant after resolution
1766        };
1767
1768        RendererInternals::render_constant_definition(md, name, type_, const_, &self.type_renderer);
1769        _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1770
1771        RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1772    }
1773
1774    /// Render a type alias to markdown.
1775    /// Handles re-exports by resolving to the actual type alias item.
1776    fn render_type_alias(&self, md: &mut String, item: &Item) {
1777        let Some((name, actual_item)) = self.resolve_reexport(item) else {
1778            return;
1779        };
1780
1781        let ItemEnum::TypeAlias(ta) = &actual_item.inner else {
1782            return; // Not a type alias after resolution
1783        };
1784
1785        RendererInternals::render_type_alias_definition(md, name, ta, &self.type_renderer);
1786        _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1787
1788        RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1789    }
1790
1791    /// Render a macro to markdown.
1792    /// Handles re-exports by resolving to the actual macro item.
1793    fn render_macro(&self, md: &mut String, item: &Item) {
1794        let Some((name, actual_item)) = self.resolve_reexport(item) else {
1795            return;
1796        };
1797
1798        // Verify it's actually a macro after resolution
1799        if !matches!(
1800            &actual_item.inner,
1801            ItemEnum::Macro(_) | ItemEnum::ProcMacro(_)
1802        ) {
1803            return;
1804        }
1805
1806        RendererInternals::render_macro_heading(md, name);
1807        _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1808
1809        RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1810    }
1811
1812    fn resolve_reexport<'b>(&'b self, item: &'b Item) -> Option<(&'b str, &'b Item)> {
1813        let ItemEnum::Use(use_item) = &item.inner else {
1814            // Not a re-export - return the item itself
1815            return Some((item.name.as_deref().unwrap_or("unnamed"), item));
1816        };
1817
1818        let name = use_item.name.as_str();
1819        let current_crate = self.view.krate();
1820
1821        if let Some(ref target_id) = use_item.id {
1822            // Has ID - try local crate first, then search all crates
1823            if let Some(target) = current_crate.index.get(target_id) {
1824                return Some((name, target));
1825            }
1826
1827            // Cross crate search if not in local crate
1828            if let Some((_, target)) = self.view.lookup_item_across_crates(target_id) {
1829                return Some((name, target));
1830            }
1831        } else if let Some((_, target, _)) = self.view.resolve_external_path(&use_item.source) {
1832            // No ID - try to resolve by path (external re-export)
1833            return Some((name, target));
1834        }
1835
1836        // Cannot resolve
1837        #[cfg(feature = "trace")]
1838        tracing::error!(
1839            "Cannot resolve link. Logging it for investigation: {:?}",
1840            item.clone()
1841        );
1842
1843        None
1844    }
1845
1846    /// Expand a glob re-export into the category vectors.
1847    #[allow(clippy::too_many_arguments)]
1848    fn expand_glob_reexport<'b>(
1849        &self,
1850        modules: &mut Vec<&'b Item>,
1851        structs: &mut Vec<(&'b Id, &'b Item)>,
1852        enums: &mut Vec<(&'b Id, &'b Item)>,
1853        traits: &mut Vec<(&'b Id, &'b Item)>,
1854        functions: &mut Vec<&'b Item>,
1855        types: &mut Vec<&'b Item>,
1856        constants: &mut Vec<&'b Item>,
1857        macros: &mut Vec<&'b Item>,
1858        use_item: &rustdoc_types::Use,
1859        seen_items: &mut HashSet<Id>,
1860    ) where
1861        'a: 'b,
1862    {
1863        let krate = self.view.krate();
1864
1865        let Some(target_id) = &use_item.id else {
1866            return;
1867        };
1868        let Some(target_module) = krate.index.get(target_id) else {
1869            return;
1870        };
1871        let ItemEnum::Module(module) = &target_module.inner else {
1872            return;
1873        };
1874
1875        for child_id in &module.items {
1876            if !seen_items.insert(*child_id) {
1877                continue; // Already processed
1878            }
1879
1880            let Some(child) = krate.index.get(child_id) else {
1881                continue;
1882            };
1883
1884            if !self.view.should_include_item(child) {
1885                continue;
1886            }
1887
1888            match &child.inner {
1889                ItemEnum::Module(_) => modules.push(child),
1890
1891                ItemEnum::Struct(_) => structs.push((child_id, child)),
1892
1893                ItemEnum::Enum(_) => enums.push((child_id, child)),
1894
1895                ItemEnum::Trait(_) => traits.push((child_id, child)),
1896
1897                ItemEnum::Function(_) => functions.push(child),
1898
1899                ItemEnum::TypeAlias(_) => types.push(child),
1900
1901                ItemEnum::Constant { .. } => constants.push(child),
1902
1903                ItemEnum::Macro(_) => macros.push(child),
1904                _ => {},
1905            }
1906        }
1907    }
1908
1909    /// Render impl blocks for a type, including cross-crate impls.
1910    ///
1911    /// # Arguments
1912    ///
1913    /// * `md` - The markdown output buffer
1914    /// * `item_id` - The ID of the item to render impl blocks for
1915    /// * `source_crate_name` - Optional source crate name for cross-crate re-exports.
1916    ///   When provided, impls are looked up from the source crate.
1917    fn render_impl_blocks(&self, md: &mut String, item_id: Id, source_crate_name: Option<&str>) {
1918        let current_krate = self.view.krate();
1919
1920        // Get the appropriate crate for impl lookups
1921        let render_krate = source_crate_name
1922            .and_then(|name| self.view.get_crate(name))
1923            .unwrap_or(current_krate);
1924
1925        // Create TypeRenderer for the appropriate crate
1926        let type_renderer = if source_crate_name.is_some() {
1927            TypeRenderer::new(render_krate)
1928        } else {
1929            self.type_renderer
1930        };
1931
1932        // Get impls - for cross-crate items, look up from source crate
1933        let impls = if source_crate_name.is_some() {
1934            // For cross-crate re-exports, get impls from the source crate
1935            self.view.get_impls_from_crate(item_id, render_krate)
1936        } else {
1937            // For local items, use the normal lookup
1938            self.view.get_all_impls(item_id)
1939        };
1940
1941        if impls.is_empty() {
1942            return;
1943        }
1944
1945        // Partition into inherent vs trait impls
1946        let (inherent, trait_impls): (Vec<&Impl>, Vec<&Impl>) = impls
1947            .into_iter()
1948            .partition(|i: &&Impl| -> bool { i.trait_.is_none() });
1949
1950        // Filter out synthetic impls
1951        let inherent: Vec<_> = inherent.into_iter().filter(|i| !i.is_synthetic).collect();
1952
1953        // Filter out synthetic impls and optionally blanket impls
1954        // Respect the --include-blanket-impls flag
1955        let include_blanket = self.view.include_blanket_impls();
1956        let mut trait_impls: Vec<_> = trait_impls
1957            .into_iter()
1958            .filter(|i| !i.is_synthetic)
1959            .filter(|i| include_blanket || !ImplUtils::is_blanket_impl(i))
1960            .collect();
1961
1962        // Sort trait impls by trait name + generics for deterministic output
1963        trait_impls.sort_by(|a, b| {
1964            let key_a = RendererInternals::impl_sort_key(a, &type_renderer);
1965            let key_b = RendererInternals::impl_sort_key(b, &type_renderer);
1966            key_a.cmp(&key_b)
1967        });
1968
1969        // Deduplicate trait impls with same key (can happen with cross-crate impls)
1970        trait_impls.dedup_by(|a, b| {
1971            RendererInternals::impl_sort_key(a, &type_renderer)
1972                == RendererInternals::impl_sort_key(b, &type_renderer)
1973        });
1974
1975        // Render inherent implementations
1976        if !inherent.is_empty() {
1977            _ = write!(md, "#### Implementations\n\n");
1978
1979            for impl_block in inherent {
1980                // Extract type name for method anchor generation
1981                let type_name = type_renderer.render_type(&impl_block.for_);
1982
1983                RendererInternals::render_impl_items(
1984                    md,
1985                    impl_block,
1986                    render_krate,
1987                    &type_renderer,
1988                    &Some(|item: &Item| self.process_docs(item)),
1989                    &Some(|id: Id| LinkResolver::create_link(self.view, id, self.file_path)),
1990                    Some(type_name.as_ref()),
1991                    ImplContext::Inherent,
1992                    self.view.render_config().full_method_docs,
1993                );
1994            }
1995        }
1996
1997        // Render trait implementations
1998        if !trait_impls.is_empty() {
1999            _ = write!(md, "#### Trait Implementations\n\n");
2000
2001            for impl_block in trait_impls {
2002                // Extract type name for method anchor generation
2003                let for_type = type_renderer.render_type(&impl_block.for_);
2004
2005                // Determine impl context for anchor generation:
2006                // - For trait impls, include the trait name to avoid duplicate anchors
2007                let impl_ctx = impl_block.trait_.as_ref().map_or(
2008                    ImplContext::Inherent,
2009                    |t| ImplContext::Trait(PathUtils::short_name(&t.path)),
2010                );
2011
2012                if let Some(trait_path) = &impl_block.trait_ {
2013                    // Build trait name with generic args
2014                    let trait_name = trait_path
2015                        .path
2016                        .split("::")
2017                        .last()
2018                        .unwrap_or(&trait_path.path);
2019
2020                    // Only show generic parameters that appear in the signature (for_ type
2021                    // or trait path). For blanket impls like `impl<T: Sized> IntoEither for T`,
2022                    // when rendered for `Either<L, R>`, the `T` only appears in the where clause.
2023                    // We don't want to show orphaned generics like `impl<T> IntoEither for Either<L, R>`.
2024                    let signature_generics = ImplUtils::extract_impl_signature_generics(impl_block);
2025                    let generics = if signature_generics.is_empty() {
2026                        String::new()
2027                    } else {
2028                        // Filter to params that appear in the signature
2029                        let filtered: Vec<_> = impl_block
2030                            .generics
2031                            .params
2032                            .iter()
2033                            .filter(|p| signature_generics.contains(&p.name))
2034                            .cloned()
2035                            .collect();
2036                        if filtered.is_empty() {
2037                            String::new()
2038                        } else {
2039                            type_renderer.render_generics(&filtered)
2040                        }
2041                    };
2042
2043                    // Include unsafe/negative markers like single-crate mode
2044                    let unsafe_str = if impl_block.is_unsafe { "unsafe " } else { "" };
2045                    let negative_str = if impl_block.is_negative { "!" } else { "" };
2046
2047                    _ = writeln!(
2048                        md,
2049                        "##### `{unsafe_str}impl{generics} {negative_str}{trait_name} for {for_type}`\n"
2050                    );
2051                }
2052
2053                RendererInternals::render_impl_items(
2054                    md,
2055                    impl_block,
2056                    render_krate,
2057                    &type_renderer,
2058                    &Some(|item: &Item| self.process_docs(item)),
2059                    &Some(|id: Id| LinkResolver::create_link(self.view, id, self.file_path)),
2060                    Some(for_type.as_ref()),
2061                    impl_ctx,
2062                    self.view.render_config().full_method_docs,
2063                );
2064            }
2065        }
2066    }
2067}
2068
2069#[cfg(test)]
2070mod tests {
2071    use super::*;
2072
2073    mod get_item_name_tests {
2074        use rustdoc_types::{Item, ItemEnum, Use, Visibility};
2075
2076        use super::*;
2077
2078        /// Create a regular item with the given name.
2079        fn make_item(name: Option<&str>) -> Item {
2080            Item {
2081                id: Id(0),
2082                crate_id: 0,
2083                name: name.map(ToString::to_string),
2084                span: None,
2085                visibility: Visibility::Public,
2086                docs: None,
2087                links: std::collections::HashMap::new(),
2088                attrs: Vec::new(),
2089                deprecation: None,
2090                inner: ItemEnum::Module(rustdoc_types::Module {
2091                    is_crate: false,
2092                    items: Vec::new(),
2093                    is_stripped: false,
2094                }),
2095            }
2096        }
2097
2098        /// Create a Use item (re-export) with the given name.
2099        fn make_use_item(use_name: &str) -> Item {
2100            Item {
2101                id: Id(0),
2102                crate_id: 0,
2103                name: None, // Use items don't have item.name set
2104                span: None,
2105                visibility: Visibility::Public,
2106                docs: None,
2107                links: std::collections::HashMap::new(),
2108                attrs: Vec::new(),
2109                deprecation: None,
2110                inner: ItemEnum::Use(Use {
2111                    source: "some::path".to_string(),
2112                    name: use_name.to_string(),
2113                    id: None,
2114                    is_glob: false,
2115                }),
2116            }
2117        }
2118
2119        #[test]
2120        fn regular_item_with_name() {
2121            let item = make_item(Some("MyStruct"));
2122
2123            assert_eq!(MultiCrateModuleRenderer::get_item_name(&item), "MyStruct");
2124        }
2125
2126        #[test]
2127        fn regular_item_without_name_returns_unnamed() {
2128            let item = make_item(None);
2129
2130            assert_eq!(MultiCrateModuleRenderer::get_item_name(&item), "unnamed");
2131        }
2132
2133        #[test]
2134        fn use_item_returns_reexport_name() {
2135            let item = make_use_item("Parser");
2136
2137            assert_eq!(MultiCrateModuleRenderer::get_item_name(&item), "Parser");
2138        }
2139
2140        #[test]
2141        fn use_item_ignores_item_name() {
2142            // Even if item.name were somehow set, we should use use_item.name
2143            let mut item = make_use_item("CorrectName");
2144            item.name = Some("WrongName".to_string());
2145
2146            assert_eq!(
2147                MultiCrateModuleRenderer::get_item_name(&item),
2148                "CorrectName"
2149            );
2150        }
2151    }
2152}