1use 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#[derive(Clone, Copy, Debug)]
50struct ResolvedItem<'a> {
51 name: &'a str,
53
54 item: &'a Item,
56
57 id: Id,
59
60 source_crate: Option<&'a str>,
62}
63
64struct 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 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 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 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 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 fn build_toc_entries(&self) -> Vec<TocEntry> {
155 let mut entries = Vec::new();
156
157 if let Some(e) = Self::build_section(&self.modules, "Modules", "modules", false) {
163 entries.push(e);
164 }
165
166 if let Some(e) = Self::build_section_with_ids(&self.structs, "Structs", "structs") {
168 entries.push(e);
169 }
170
171 if let Some(e) = Self::build_section_with_ids(&self.enums, "Enums", "enums") {
173 entries.push(e);
174 }
175
176 if let Some(e) = Self::build_section_with_ids(&self.unions, "Unions", "unions") {
178 entries.push(e);
179 }
180
181 if let Some(e) = Self::build_section_with_ids(&self.traits, "Traits", "traits") {
183 entries.push(e);
184 }
185
186 if let Some(e) = Self::build_section(&self.functions, "Functions", "functions", false) {
188 entries.push(e);
189 }
190
191 if let Some(e) = Self::build_section(&self.types, "Type Aliases", "type-aliases", false) {
193 entries.push(e);
194 }
195
196 if let Some(e) = Self::build_section(&self.constants, "Constants", "constants", false) {
198 entries.push(e);
199 }
200
201 if let Some(e) = Self::build_section(&self.statics, "Statics", "statics", false) {
203 entries.push(e);
204 }
205
206 if let Some(e) = Self::build_section(&self.macros, "Macros", "macros", true) {
208 entries.push(e);
209 }
210
211 entries
212 }
213
214 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 TocEntry::new(display, AnchorUtils::slugify_anchor(name))
240 })
241 .collect();
242
243 Some(TocEntry::with_children(section, anchor, children))
244 }
245
246 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 TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
264 })
265 .collect();
266
267 Some(TocEntry::with_children(section, anchor, children))
268 }
269
270 fn build_quick_ref_entries(&self) -> Vec<QuickRefEntry> {
275 let mut entries = Vec::new();
276
277 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 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 entries.push(QuickRefEntry::new(
313 display_name,
314 kind,
315 AnchorUtils::slugify_anchor(name),
316 summary,
317 ));
318 }
319 }
320
321 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 entries.push(QuickRefEntry::new(
335 name,
336 kind,
337 AnchorUtils::slugify_anchor(name),
338 summary,
339 ));
340 }
341 }
342
343 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 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; }
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 self.add_item(child_id, child);
395 }
396 }
397}
398
399pub struct MultiCrateGenerator<'a> {
419 ctx: MultiCrateContext<'a>,
421
422 args: &'a Args,
424}
425
426impl<'a> MultiCrateGenerator<'a> {
427 #[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 #[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 FsErr::create_dir_all(&self.args.output).map_err(Error::CreateDir)?;
460
461 debug!(path = %self.args.output.display(), "Created output directory");
462
463 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 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 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 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 if self.args.search_index {
512 info!("Generating search_index.json");
513 progress.set_message("Generating search_index.json...");
514
515 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 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 fn collect_crate_items(view: &SingleCrateView, ids: &mut HashSet<Id>) {
554 let krate = view.krate();
555
556 let Some(root_item) = krate.index.get(&krate.root) else {
558 return;
559 };
560
561 Self::collect_module_items(view, root_item, ids);
563 }
564
565 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 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 ItemEnum::Module(_) => {
590 ids.insert(*item_id);
591 Self::collect_module_items(view, child, ids);
592 },
593
594 ItemEnum::Use(use_item) if !use_item.is_glob => {
596 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 #[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 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 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 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 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 let module_dir = parent_dir.join(name);
673 FsErr::create_dir_all(&module_dir).map_err(Error::CreateDir)?;
674
675 let mut current_path = module_path;
677 current_path.push(name.to_string());
678
679 let file_path = format!("{}/{}/index.md", view.crate_name(), current_path.join("/"));
681
682 let breadcrumb_gen = BreadcrumbGenerator::new(¤t_path, view.crate_name());
684 let breadcrumbs = breadcrumb_gen.generate();
685
686 let renderer = MultiCrateModuleRenderer::new(view, &file_path, false);
688 let module_content = renderer.render(item);
689
690 let content = format!("{breadcrumbs}{module_content}");
692
693 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 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 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
737struct MultiCrateModuleRenderer<'a> {
745 view: &'a SingleCrateView<'a>,
747
748 file_path: &'a str,
750
751 is_root: bool,
753
754 type_renderer: TypeRenderer<'a>,
756}
757
758impl<'a> MultiCrateModuleRenderer<'a> {
759 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 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 fn render(&self, item: &Item) -> String {
786 let mut md = String::new();
787
788 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 if let Some(docs) = self.view.process_docs(item, self.file_path) {
798 _ = writeln!(md, "{docs}\n");
799 }
800
801 if let ItemEnum::Module(module) = &item.inner {
803 self.render_module_contents(&mut md, module, item);
804 }
805
806 md
807 }
808
809 #[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 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 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 ItemEnum::Use(use_item) => {
863 if use_item.is_glob {
864 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 let target_item = use_item.id.as_ref().map_or_else(
880 || {
881 self.view
883 .resolve_external_path(&use_item.source)
884 .map(|(_, item, _)| item)
885 },
886 |target_id| {
887 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 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 let crate_name = self.view.crate_name();
937 if crate_name.ends_with("_derive") || crate_name.ends_with("-derive") {
938 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 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, ¯os,
995 );
996
997 if let Some(toc) = toc_gen.generate(&toc_entries) {
998 _ = write!(md, "{}", &toc);
999 }
1000
1001 if config.quick_reference {
1003 let quick_ref_entries = Self::build_quick_ref_entries(
1004 &modules, &structs, &enums, &traits, &functions, &types, &constants, ¯os,
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 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, ¯os);
1023 }
1024
1025 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 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 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 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 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 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 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 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 #[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 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 let name = use_item.name.clone();
1159
1160 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 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 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 #[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 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 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 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 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 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 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 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 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 #[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 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 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 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 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 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 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 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 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 fn render_struct(&self, md: &mut String, item_id: Id, item: &Item) {
1428 let current_krate = self.view.krate();
1429
1430 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 if let Some(target) = current_krate.index.get(target_id) {
1439 (name, target, *target_id, None)
1441 } else if let Some((src_crate, target)) =
1442 self.view.lookup_item_across_crates(target_id)
1443 {
1444 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 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 let render_krate = source_crate_name
1478 .and_then(|name| self.view.get_crate(name))
1479 .unwrap_or(current_krate);
1480
1481 let type_renderer = if source_crate_name.is_some() {
1483 TypeRenderer::new(render_krate)
1484 } else {
1485 self.type_renderer
1487 };
1488
1489 RendererInternals::render_struct_definition(md, name, s, render_krate, &type_renderer);
1491
1492 _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1494
1495 if let Some(src_crate) = source_crate_name {
1497 _ = writeln!(md, "*Re-exported from `{src_crate}`*\n");
1498 }
1499
1500 RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1502
1503 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 self.render_impl_blocks(md, actual_id, source_crate_name);
1516 }
1517 }
1518
1519 fn render_enum(&self, md: &mut String, item_id: Id, item: &Item) {
1521 let current_krate = self.view.krate();
1522
1523 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 if let Some(target) = current_krate.index.get(target_id) {
1532 (name, target, *target_id, None)
1534 } else if let Some((src_crate, target)) =
1535 self.view.lookup_item_across_crates(target_id)
1536 {
1537 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 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 let render_krate = source_crate_name
1571 .and_then(|name| self.view.get_crate(name))
1572 .unwrap_or(current_krate);
1573
1574 let type_renderer = if source_crate_name.is_some() {
1576 TypeRenderer::new(render_krate)
1577 } else {
1578 self.type_renderer
1580 };
1581
1582 RendererInternals::render_enum_definition(md, name, e, render_krate, &type_renderer);
1584
1585 _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1587
1588 if let Some(src_crate) = source_crate_name {
1590 _ = writeln!(md, "*Re-exported from `{src_crate}`*\n");
1591 }
1592
1593 RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1595
1596 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 self.render_impl_blocks(md, actual_id, source_crate_name);
1606 }
1607 }
1608
1609 fn render_trait(&self, md: &mut String, item_id: Id, item: &Item) {
1611 let current_krate = self.view.krate();
1612
1613 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 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 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 TraitRenderer::render_trait_definition(md, name, t, &self.type_renderer);
1645
1646 _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1648
1649 RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1651
1652 let items = CategorizedTraitItems::categorize_trait_items(&t.items, current_krate);
1654
1655 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 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 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 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 self.render_trait_implementors(md, actual_id);
1701 }
1702 }
1703
1704 fn render_trait_implementors(&self, md: &mut String, trait_id: Id) {
1710 let krate = self.view.krate();
1711
1712 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 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 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 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; };
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 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; };
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 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 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 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 if let Some(target) = current_crate.index.get(target_id) {
1835 return Some((name, target));
1836 }
1837
1838 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 return Some((name, target));
1845 }
1846
1847 #[cfg(feature = "trace")]
1849 tracing::error!(
1850 "Cannot resolve link. Logging it for investigation: {:?}",
1851 item.clone()
1852 );
1853
1854 None
1855 }
1856
1857 #[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; }
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 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 let render_krate = source_crate_name
1933 .and_then(|name| self.view.get_crate(name))
1934 .unwrap_or(current_krate);
1935
1936 let type_renderer = if source_crate_name.is_some() {
1938 TypeRenderer::new(render_krate)
1939 } else {
1940 self.type_renderer
1941 };
1942
1943 let impls = if source_crate_name.is_some() {
1945 self.view.get_impls_from_crate(item_id, render_krate)
1947 } else {
1948 self.view.get_all_impls(item_id)
1950 };
1951
1952 if impls.is_empty() {
1953 return;
1954 }
1955
1956 let (inherent, trait_impls): (Vec<&Impl>, Vec<&Impl>) = impls
1958 .into_iter()
1959 .partition(|i: &&Impl| -> bool { i.trait_.is_none() });
1960
1961 let inherent: Vec<_> = inherent.into_iter().filter(|i| !i.is_synthetic).collect();
1963
1964 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 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 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 if !inherent.is_empty() {
1988 _ = write!(md, "#### Implementations\n\n");
1989
1990 for impl_block in inherent {
1991 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 if !trait_impls.is_empty() {
2009 _ = write!(md, "#### Trait Implementations\n\n");
2010
2011 for impl_block in trait_impls {
2012 let for_type = type_renderer.render_type(&impl_block.for_);
2014
2015 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 let trait_name = trait_path
2025 .path
2026 .split("::")
2027 .last()
2028 .unwrap_or(&trait_path.path);
2029
2030 let signature_generics = ImplUtils::extract_impl_signature_generics(impl_block);
2035 let generics = if signature_generics.is_empty() {
2036 String::new()
2037 } else {
2038 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 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 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 fn make_use_item(use_name: &str) -> Item {
2109 Item {
2110 id: Id(0),
2111 crate_id: 0,
2112 name: None, 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 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}