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