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#[allow(dead_code)]
46struct CategorizedItems<'a> {
47 modules: Vec<&'a Item>,
48 structs: Vec<(&'a Id, &'a Item)>,
49 enums: Vec<(&'a Id, &'a Item)>,
50 unions: Vec<(&'a Id, &'a Item)>,
51 traits: Vec<(&'a Id, &'a Item)>,
52 functions: Vec<&'a Item>,
53 types: Vec<&'a Item>,
54 constants: Vec<&'a Item>,
55 statics: Vec<&'a Item>,
56 macros: Vec<&'a Item>,
57}
58
59#[allow(dead_code)]
60impl<'a> CategorizedItems<'a> {
61 const fn new() -> Self {
63 Self {
64 modules: Vec::new(),
65 structs: Vec::new(),
66 enums: Vec::new(),
67 unions: Vec::new(),
68 traits: Vec::new(),
69 functions: Vec::new(),
70 types: Vec::new(),
71 constants: Vec::new(),
72 statics: Vec::new(),
73 macros: Vec::new(),
74 }
75 }
76
77 const fn is_empty(&self) -> bool {
79 self.modules.is_empty()
80 && self.structs.is_empty()
81 && self.enums.is_empty()
82 && self.unions.is_empty()
83 && self.traits.is_empty()
84 && self.functions.is_empty()
85 && self.types.is_empty()
86 && self.constants.is_empty()
87 && self.statics.is_empty()
88 && self.macros.is_empty()
89 }
90
91 fn add_item(&mut self, id: &'a Id, item: &'a Item) {
93 match &item.inner {
94 ItemEnum::Module(_) => self.modules.push(item),
95 ItemEnum::Struct(_) => self.structs.push((id, item)),
96 ItemEnum::Enum(_) => self.enums.push((id, item)),
97 ItemEnum::Union(_) => self.unions.push((id, item)),
98 ItemEnum::Trait(_) => self.traits.push((id, item)),
99 ItemEnum::Function(_) => self.functions.push(item),
100 ItemEnum::TypeAlias(_) => self.types.push(item),
101 ItemEnum::Constant { .. } => self.constants.push(item),
102 ItemEnum::Static(_) => self.statics.push(item),
103 ItemEnum::Macro(_) | ItemEnum::ProcMacro(_) => self.macros.push(item),
104 _ => {},
105 }
106 }
107
108 fn add_reexport(&mut self, id: &'a Id, use_item: &'a Item, target: &Item) {
114 match &target.inner {
115 ItemEnum::Module(_) => self.modules.push(use_item),
116 ItemEnum::Struct(_) => self.structs.push((id, use_item)),
117 ItemEnum::Enum(_) => self.enums.push((id, use_item)),
118 ItemEnum::Union(_) => self.unions.push((id, use_item)),
119 ItemEnum::Trait(_) => self.traits.push((id, use_item)),
120 ItemEnum::Function(_) => self.functions.push(use_item),
121 ItemEnum::TypeAlias(_) => self.types.push(use_item),
122 ItemEnum::Constant { .. } => self.constants.push(use_item),
123 ItemEnum::Static(_) => self.statics.push(use_item),
124 ItemEnum::Macro(_) | ItemEnum::ProcMacro(_) => self.macros.push(use_item),
125 _ => {},
126 }
127 }
128
129 fn build_toc_entries(&self) -> Vec<TocEntry> {
134 let mut entries = Vec::new();
135
136 if let Some(e) = Self::build_section(&self.modules, "Modules", "modules", false) {
142 entries.push(e);
143 }
144
145 if let Some(e) = Self::build_section_with_ids(&self.structs, "Structs", "structs") {
147 entries.push(e);
148 }
149
150 if let Some(e) = Self::build_section_with_ids(&self.enums, "Enums", "enums") {
152 entries.push(e);
153 }
154
155 if let Some(e) = Self::build_section_with_ids(&self.unions, "Unions", "unions") {
157 entries.push(e);
158 }
159
160 if let Some(e) = Self::build_section_with_ids(&self.traits, "Traits", "traits") {
162 entries.push(e);
163 }
164
165 if let Some(e) = Self::build_section(&self.functions, "Functions", "functions", false) {
167 entries.push(e);
168 }
169
170 if let Some(e) = Self::build_section(&self.types, "Type Aliases", "type-aliases", false) {
172 entries.push(e);
173 }
174
175 if let Some(e) = Self::build_section(&self.constants, "Constants", "constants", false) {
177 entries.push(e);
178 }
179
180 if let Some(e) = Self::build_section(&self.statics, "Statics", "statics", false) {
182 entries.push(e);
183 }
184
185 if let Some(e) = Self::build_section(&self.macros, "Macros", "macros", true) {
187 entries.push(e);
188 }
189
190 entries
191 }
192
193 fn build_section(
197 items: &[&Item],
198 section: &str,
199 anchor: &str,
200 is_macro: bool,
201 ) -> Option<TocEntry> {
202 if items.is_empty() {
203 return None;
204 }
205
206 let children: Vec<TocEntry> = items
207 .iter()
208 .map(|item| {
209 let name = Self::get_item_name(item);
210 let display = if is_macro {
211 format!("`{name}!`")
212 } else {
213 format!("`{name}`")
214 };
215
216 TocEntry::new(display, AnchorUtils::slugify_anchor(name))
219 })
220 .collect();
221
222 Some(TocEntry::with_children(section, anchor, children))
223 }
224
225 fn build_section_with_ids(
229 items: &[(&Id, &Item)],
230 section: &str,
231 anchor: &str,
232 ) -> Option<TocEntry> {
233 if items.is_empty() {
234 return None;
235 }
236
237 let children: Vec<TocEntry> = items
238 .iter()
239 .map(|(_, item)| {
240 let name = Self::get_item_name(item);
241 TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
243 })
244 .collect();
245
246 Some(TocEntry::with_children(section, anchor, children))
247 }
248
249 fn build_quick_ref_entries(&self) -> Vec<QuickRefEntry> {
254 let mut entries = Vec::new();
255
256 Self::add_quick_ref_entries(&mut entries, &self.modules, "mod", false);
258 Self::add_quick_ref_entries_with_ids(&mut entries, &self.structs, "struct");
259 Self::add_quick_ref_entries_with_ids(&mut entries, &self.enums, "enum");
260 Self::add_quick_ref_entries_with_ids(&mut entries, &self.unions, "union");
261 Self::add_quick_ref_entries_with_ids(&mut entries, &self.traits, "trait");
262 Self::add_quick_ref_entries(&mut entries, &self.functions, "fn", false);
263 Self::add_quick_ref_entries(&mut entries, &self.types, "type", false);
264 Self::add_quick_ref_entries(&mut entries, &self.constants, "const", false);
265 Self::add_quick_ref_entries(&mut entries, &self.statics, "static", false);
266 Self::add_quick_ref_entries(&mut entries, &self.macros, "macro", true);
267
268 entries
269 }
270
271 fn add_quick_ref_entries(
275 entries: &mut Vec<QuickRefEntry>,
276 items: &[&Item],
277 kind: &'static str,
278 is_macro: bool,
279 ) {
280 for item in items {
281 let name = Self::get_item_name(item);
282 let summary = extract_summary(item.docs.as_deref());
283
284 let display_name = if is_macro {
285 format!("{name}!")
286 } else {
287 name.to_string()
288 };
289
290 entries.push(QuickRefEntry::new(
292 display_name,
293 kind,
294 AnchorUtils::slugify_anchor(name),
295 summary,
296 ));
297 }
298 }
299
300 fn add_quick_ref_entries_with_ids(
304 entries: &mut Vec<QuickRefEntry>,
305 items: &[(&Id, &Item)],
306 kind: &'static str,
307 ) {
308 for (_, item) in items {
309 let name = Self::get_item_name(item);
310 let summary = extract_summary(item.docs.as_deref());
311
312 entries.push(QuickRefEntry::new(
314 name,
315 kind,
316 AnchorUtils::slugify_anchor(name),
317 summary,
318 ));
319 }
320 }
321
322 fn get_item_name(item: &Item) -> &str {
324 if let ItemEnum::Use(use_item) = &item.inner {
325 &use_item.name
326 } else {
327 item.name.as_deref().unwrap_or("unnamed")
328 }
329 }
330
331 fn expand_glob_reexport(
343 &mut self,
344 use_item: &rustdoc_types::Use,
345 krate: &'a Crate,
346 view: &SingleCrateView<'_>,
347 seen_items: &mut HashSet<Id>,
348 ) {
349 let Some(target_id) = &use_item.id else {
350 return;
351 };
352 let Some(target_module) = krate.index.get(target_id) else {
353 return;
354 };
355 let ItemEnum::Module(module) = &target_module.inner else {
356 return;
357 };
358
359 for child_id in &module.items {
360 if !seen_items.insert(*child_id) {
361 continue; }
363
364 let Some(child) = krate.index.get(child_id) else {
365 continue;
366 };
367
368 if !view.should_include_item(child) {
369 continue;
370 }
371
372 self.add_item(child_id, child);
374 }
375 }
376}
377
378pub struct MultiCrateGenerator<'a> {
398 ctx: MultiCrateContext<'a>,
400
401 args: &'a Args,
403}
404
405impl<'a> MultiCrateGenerator<'a> {
406 #[must_use]
414 pub fn new(crates: &'a CrateCollection, args: &'a Args, config: RenderConfig) -> Self {
415 let ctx = MultiCrateContext::new(crates, args, config);
416 Self { ctx, args }
417 }
418
419 #[instrument(skip(self), fields(
429 crate_count = self.ctx.crates().names().len(),
430 output = %self.args.output.display(),
431 mdbook = !self.args.no_mdbook,
432 search_index = !self.args.no_search_index
433 ))]
434 pub fn generate(&self) -> Result<(), Error> {
435 info!("Starting multi-crate documentation generation");
436
437 FsErr::create_dir_all(&self.args.output).map_err(Error::CreateDir)?;
439
440 debug!(path = %self.args.output.display(), "Created output directory");
441
442 for crate_name in self.ctx.crates().names() {
444 let crate_dir = self.args.output.join(crate_name);
445 FsErr::create_dir_all(&crate_dir).map_err(Error::CreateDir)?;
446 }
447
448 let total_modules: usize = self
450 .ctx
451 .crates()
452 .iter()
453 .filter_map(|(name, _)| self.ctx.single_crate_view(name))
454 .map(|view| view.count_modules() + 1)
455 .sum();
456
457 debug!(total_modules, "Total modules to generate");
458 let progress = Arc::new(Self::create_progress_bar(total_modules)?);
459
460 self.ctx
462 .crates()
463 .names()
464 .par_iter()
465 .try_for_each(|crate_name| {
466 let span = info_span!("generate_crate", crate_name);
467 let _guard = span.enter();
468
469 let view = self
470 .ctx
471 .single_crate_view(crate_name)
472 .ok_or_else(|| Error::ItemNotFound((*crate_name).clone()))?;
473
474 self.generate_crate(&view, &progress)
475 })?;
476
477 if !self.args.no_mdbook {
479 info!("Generating SUMMARY.md for mdBook");
480 progress.set_message("Generating SUMMARY.md...");
481 let summary_gen = SummaryGenerator::new(
482 self.ctx.crates(),
483 &self.args.output,
484 !self.args.exclude_private,
485 );
486 summary_gen.generate()?;
487 }
488
489 if !self.args.no_search_index {
491 info!("Generating search_index.json");
492 progress.set_message("Generating search_index.json...");
493
494 let rendered_items = self.collect_rendered_items();
496
497 let search_gen = SearchIndexGenerator::new(
498 self.ctx.crates(),
499 !self.args.exclude_private,
500 rendered_items,
501 );
502 search_gen
503 .write(&self.args.output)
504 .map_err(Error::FileWrite)?;
505 }
506
507 progress.finish_with_message("Done!");
508 info!("Multi-crate documentation generation complete");
509 Ok(())
510 }
511
512 fn collect_rendered_items(&self) -> HashMap<String, HashSet<Id>> {
518 let mut result = HashMap::new();
519
520 for crate_name in self.ctx.crates().names() {
521 if let Some(view) = self.ctx.single_crate_view(crate_name) {
522 let mut ids = HashSet::new();
523 Self::collect_crate_items(&view, &mut ids);
524 result.insert(crate_name.clone(), ids);
525 }
526 }
527
528 result
529 }
530
531 fn collect_crate_items(view: &SingleCrateView, ids: &mut HashSet<Id>) {
533 let krate = view.krate();
534
535 let Some(root_item) = krate.index.get(&krate.root) else {
537 return;
538 };
539
540 Self::collect_module_items(view, root_item, ids);
542 }
543
544 fn collect_module_items(view: &SingleCrateView, item: &Item, ids: &mut HashSet<Id>) {
546 let krate = view.krate();
547
548 if let ItemEnum::Module(module) = &item.inner {
549 for item_id in &module.items {
550 if let Some(child) = krate.index.get(item_id) {
551 if !view.should_include_item(child) {
552 continue;
553 }
554
555 match &child.inner {
556 ItemEnum::Struct(_)
558 | ItemEnum::Enum(_)
559 | ItemEnum::Trait(_)
560 | ItemEnum::Function(_)
561 | ItemEnum::TypeAlias(_)
562 | ItemEnum::Constant { .. }
563 | ItemEnum::Macro(_) => {
564 ids.insert(*item_id);
565 },
566
567 ItemEnum::Module(_) => {
569 ids.insert(*item_id);
570 Self::collect_module_items(view, child, ids);
571 },
572
573 ItemEnum::Use(use_item) if !use_item.is_glob => {
575 let target_exists =
577 use_item.id.as_ref().is_some_and(|target_id| {
578 krate.index.contains_key(target_id)
579 || view.lookup_item_across_crates(target_id).is_some()
580 }) || view.resolve_external_path(&use_item.source).is_some();
581
582 if target_exists {
583 ids.insert(*item_id);
584 }
585 },
586
587 _ => {},
588 }
589 }
590 }
591 }
592 }
593
594 #[instrument(skip(self, view, progress), fields(crate_name = %view.crate_name()))]
596 fn generate_crate(
597 &self,
598 view: &SingleCrateView,
599 progress: &Arc<ProgressBar>,
600 ) -> Result<(), Error> {
601 debug!("Starting crate generation");
602
603 let crate_name = view.crate_name();
604 let crate_dir = self.args.output.join(crate_name);
605
606 let root_item = view
610 .krate()
611 .index
612 .get(&view.krate().root)
613 .ok_or_else(|| Error::ItemNotFound(view.krate().root.0.to_string()))?;
614
615 let file_path = format!("{crate_name}/index.md");
617 let renderer = MultiCrateModuleRenderer::new(view, &file_path, true);
618 let content = renderer.render(root_item);
619
620 let index_path = crate_dir.join("index.md");
621 FsErr::write(&index_path, content).map_err(Error::FileWrite)?;
622 progress.inc(1);
623
624 if let ItemEnum::Module(module) = &root_item.inner {
626 for item_id in &module.items {
627 if let Some(item) = view.krate().index.get(item_id)
628 && let ItemEnum::Module(_) = &item.inner
629 && view.should_include_item(item)
630 {
631 Self::generate_module(view, item, &crate_dir, vec![], &Arc::clone(progress))?;
632 }
633 }
634 }
635
636 debug!("Crate generation complete");
637 Ok(())
638 }
639
640 fn generate_module(
642 view: &SingleCrateView,
643 item: &Item,
644 parent_dir: &Path,
645 module_path: Vec<String>,
646 progress: &Arc<ProgressBar>,
647 ) -> Result<(), Error> {
648 let name = item.name.as_deref().unwrap_or("unnamed");
649
650 let module_dir = parent_dir.join(name);
652 FsErr::create_dir_all(&module_dir).map_err(Error::CreateDir)?;
653
654 let mut current_path = module_path;
656 current_path.push(name.to_string());
657
658 let file_path = format!("{}/{}/index.md", view.crate_name(), current_path.join("/"));
660
661 let breadcrumb_gen = BreadcrumbGenerator::new(¤t_path, view.crate_name());
663 let breadcrumbs = breadcrumb_gen.generate();
664
665 let renderer = MultiCrateModuleRenderer::new(view, &file_path, false);
667 let module_content = renderer.render(item);
668
669 let content = format!("{breadcrumbs}{module_content}");
671
672 let file_path_on_disk = module_dir.join("index.md");
674 FsErr::write(&file_path_on_disk, content).map_err(Error::FileWrite)?;
675 progress.inc(1);
676
677 if let ItemEnum::Module(module) = &item.inner {
679 for sub_id in &module.items {
680 if let Some(sub_item) = view.krate().index.get(sub_id)
681 && let ItemEnum::Module(_) = &sub_item.inner
682 && view.should_include_item(sub_item)
683 {
684 Self::generate_module(
685 view,
686 sub_item,
687 &module_dir,
688 current_path.clone(),
689 &Arc::clone(progress),
690 )?;
691 }
692 }
693 }
694
695 Ok(())
696 }
697
698 fn create_progress_bar(total: usize) -> Result<ProgressBar, Error> {
704 let progress = ProgressBar::new(total as u64);
705
706 let style =
707 ProgressStyle::with_template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
708 .map_err(Error::ProgressBarTemplate)?
709 .progress_chars("=>-");
710
711 progress.set_style(style);
712 Ok(progress)
713 }
714}
715
716struct MultiCrateModuleRenderer<'a> {
724 view: &'a SingleCrateView<'a>,
726
727 file_path: &'a str,
729
730 is_root: bool,
732
733 type_renderer: TypeRenderer<'a>,
735}
736
737impl<'a> MultiCrateModuleRenderer<'a> {
738 const fn new(view: &'a SingleCrateView<'a>, file_path: &'a str, is_root: bool) -> Self {
740 Self {
741 view,
742 file_path,
743 is_root,
744 type_renderer: TypeRenderer::new(view.krate()),
745 }
746 }
747
748 fn process_docs(&self, item: &Item) -> Option<String> {
755 self.view.process_docs(item, self.file_path)
756 }
757
758 fn maybe_render_source_location(&self, item: &Item) -> String {
764 if self.view.render_config().include_source.source_locations {
765 let source_config = self.view.source_path_config_for_file(self.file_path);
766
767 RendererInternals::render_source_location(item.span.as_ref(), source_config.as_ref())
768 } else {
769 String::new()
770 }
771 }
772
773 fn render(&self, item: &Item) -> String {
775 let mut md = String::new();
776
777 let name = item.name.as_deref().unwrap_or("unnamed");
779 if self.is_root {
780 _ = writeln!(md, "# Crate `{name}`\n");
781 } else {
782 _ = writeln!(md, "# Module `{name}`\n");
783 }
784
785 if let Some(docs) = self.view.process_docs(item, self.file_path) {
787 _ = writeln!(md, "{docs}\n");
788 }
789
790 if let ItemEnum::Module(module) = &item.inner {
792 self.render_module_contents(&mut md, module, item);
793 }
794
795 md
796 }
797
798 #[expect(
800 clippy::too_many_lines,
801 reason = "Inherently complex. Will probably grow more."
802 )]
803 fn render_module_contents(
804 &self,
805 md: &mut String,
806 module: &rustdoc_types::Module,
807 _parent: &Item,
808 ) {
809 let krate = self.view.krate();
810 let mut seen_items: HashSet<Id> = HashSet::new();
811
812 let mut modules: Vec<&Item> = Vec::new();
814 let mut structs: Vec<(&Id, &Item)> = Vec::new();
815 let mut enums: Vec<(&Id, &Item)> = Vec::new();
816 let mut traits: Vec<(&Id, &Item)> = Vec::new();
817 let mut functions: Vec<&Item> = Vec::new();
818 let mut types: Vec<&Item> = Vec::new();
819 let mut constants: Vec<&Item> = Vec::new();
820 let mut macros: Vec<&Item> = Vec::new();
821
822 for item_id in &module.items {
823 if !seen_items.insert(*item_id) {
825 continue;
826 }
827
828 if let Some(item) = krate.index.get(item_id) {
829 if !self.view.should_include_item(item) {
830 continue;
831 }
832
833 match &item.inner {
834 ItemEnum::Module(_) => modules.push(item),
835
836 ItemEnum::Struct(_) => structs.push((item_id, item)),
837
838 ItemEnum::Enum(_) => enums.push((item_id, item)),
839
840 ItemEnum::Trait(_) => traits.push((item_id, item)),
841
842 ItemEnum::Function(_) => functions.push(item),
843
844 ItemEnum::TypeAlias(_) => types.push(item),
845
846 ItemEnum::Constant { .. } => constants.push(item),
847
848 ItemEnum::Macro(_) => macros.push(item),
849
850 ItemEnum::Use(use_item) => {
852 if use_item.is_glob {
853 self.expand_glob_reexport(
855 &mut modules,
856 &mut structs,
857 &mut enums,
858 &mut traits,
859 &mut functions,
860 &mut types,
861 &mut constants,
862 &mut macros,
863 use_item,
864 &mut seen_items,
865 );
866 } else {
867 let target_item = use_item.id.as_ref().map_or_else(
869 || {
870 self.view
872 .resolve_external_path(&use_item.source)
873 .map(|(_, item, _)| item)
874 },
875 |target_id| {
876 krate.index.get(target_id).or_else(|| {
878 self.view
879 .lookup_item_across_crates(target_id)
880 .map(|(_, item)| item)
881 })
882 },
883 );
884
885 if let Some(target_item) = target_item {
886 match &target_item.inner {
887 ItemEnum::Module(_) => modules.push(item),
888
889 ItemEnum::Struct(_) => structs.push((item_id, item)),
890
891 ItemEnum::Enum(_) => enums.push((item_id, item)),
892
893 ItemEnum::Trait(_) => traits.push((item_id, item)),
894
895 ItemEnum::Function(_) => functions.push(item),
896
897 ItemEnum::TypeAlias(_) => types.push(item),
898
899 ItemEnum::Constant { .. } => constants.push(item),
900
901 ItemEnum::Macro(_) => macros.push(item),
902
903 _ => {},
904 }
905 }
906 }
907 },
908 _ => {},
909 }
910 }
911 }
912
913 let is_empty = modules.is_empty()
915 && structs.is_empty()
916 && enums.is_empty()
917 && traits.is_empty()
918 && functions.is_empty()
919 && types.is_empty()
920 && constants.is_empty()
921 && macros.is_empty();
922
923 if is_empty && self.is_root {
924 let crate_name = self.view.crate_name();
926 if crate_name.ends_with("_derive") || crate_name.ends_with("-derive") {
927 let parent_crate = crate_name
929 .strip_suffix("_derive")
930 .or_else(|| crate_name.strip_suffix("-derive"))
931 .unwrap_or(crate_name);
932
933 _ = writeln!(md, "## Overview\n");
934 _ = writeln!(
935 md,
936 "This is a **procedural macro crate** that provides derive macros."
937 );
938 _ = writeln!(md);
939 _ = writeln!(
940 md,
941 "The macros from this crate are typically re-exported from the parent crate \
942 [`{parent_crate}`](../{parent_crate}/index.md) for convenience. \
943 You should generally depend on the parent crate rather than this one directly."
944 );
945 _ = writeln!(md);
946 _ = writeln!(md, "### Usage\n");
947 _ = writeln!(md, "```toml");
948 _ = writeln!(md, "[dependencies]");
949 _ = writeln!(
950 md,
951 "{parent_crate} = {{ version = \"*\", features = [\"derive\"] }}"
952 );
953 _ = writeln!(md, "```");
954 } else if crate_name.ends_with("_impl") || crate_name.ends_with("-impl") {
955 let parent_crate = crate_name
956 .strip_suffix("_impl")
957 .or_else(|| crate_name.strip_suffix("-impl"))
958 .unwrap_or(crate_name);
959
960 _ = writeln!(md, "## Overview\n");
961 _ = writeln!(
962 md,
963 "This is an **implementation detail crate** with no public API."
964 );
965 _ = writeln!(md);
966 _ = writeln!(
967 md,
968 "The functionality from this crate is re-exported through \
969 [`{parent_crate}`](../{parent_crate}/index.md). \
970 You should depend on the parent crate instead."
971 );
972 } else {
973 _ = writeln!(md, "*This crate has no public items to document.*");
974 }
975
976 return;
977 }
978
979 let config = self.view.render_config();
981 let toc_gen = TocGenerator::new(config.toc_threshold);
982 let toc_entries = Self::build_toc_entries(
983 &modules, &structs, &enums, &traits, &functions, &types, &constants, ¯os,
984 );
985
986 if let Some(toc) = toc_gen.generate(&toc_entries) {
987 _ = write!(md, "{}", &toc);
988 }
989
990 if config.quick_reference {
992 let quick_ref_entries = Self::build_quick_ref_entries(
993 &modules, &structs, &enums, &traits, &functions, &types, &constants, ¯os,
994 );
995
996 if !quick_ref_entries.is_empty() {
997 let quick_ref_gen = QuickRefGenerator::new();
998
999 _ = write!(md, "{}", &quick_ref_gen.generate(&quick_ref_entries));
1000 }
1001 }
1002
1003 Self::render_modules_section(md, &modules, self.view.krate());
1005 self.render_structs_section(md, &structs);
1006 self.render_enums_section(md, &enums);
1007 self.render_traits_section(md, &traits);
1008 self.render_functions_section(md, &functions);
1009 self.render_type_aliases_section(md, &types);
1010 self.render_constants_section(md, &constants);
1011 self.render_macros_section(md, ¯os);
1012 }
1013
1014 fn render_modules_section(md: &mut String, modules: &[&Item], krate: &Crate) {
1016 if modules.is_empty() {
1017 return;
1018 }
1019
1020 _ = writeln!(md, "## Modules\n");
1021
1022 for item in modules {
1023 let (name, summary) = Self::get_item_name_and_summary_with_fallback(item, Some(krate));
1024 if summary.is_empty() {
1025 _ = writeln!(md, "- [`{name}`]({name}/index.md)");
1026 } else {
1027 _ = writeln!(md, "- [`{name}`]({name}/index.md) — {summary}");
1028 }
1029 }
1030
1031 _ = writeln!(md);
1032 }
1033
1034 fn render_structs_section(&self, md: &mut String, structs: &[(&Id, &Item)]) {
1036 if structs.is_empty() {
1037 return;
1038 }
1039
1040 _ = writeln!(md, "## Structs\n");
1041
1042 for (item_id, item) in structs {
1043 self.render_struct(md, **item_id, item);
1044 }
1045 }
1046
1047 fn render_enums_section(&self, md: &mut String, enums: &[(&Id, &Item)]) {
1049 if enums.is_empty() {
1050 return;
1051 }
1052
1053 _ = writeln!(md, "## Enums\n");
1054
1055 for (item_id, item) in enums {
1056 self.render_enum(md, **item_id, item);
1057 }
1058 }
1059
1060 fn render_traits_section(&self, md: &mut String, traits: &[(&Id, &Item)]) {
1062 if traits.is_empty() {
1063 return;
1064 }
1065
1066 _ = writeln!(md, "## Traits\n");
1067
1068 for (item_id, item) in traits {
1069 self.render_trait(md, **item_id, item);
1070 }
1071 }
1072
1073 fn render_functions_section(&self, md: &mut String, functions: &[&Item]) {
1075 if functions.is_empty() {
1076 return;
1077 }
1078
1079 _ = writeln!(md, "## Functions\n");
1080
1081 for item in functions {
1082 self.render_function(md, item);
1083 }
1084 }
1085
1086 fn render_type_aliases_section(&self, md: &mut String, types: &[&Item]) {
1088 if types.is_empty() {
1089 return;
1090 }
1091
1092 _ = writeln!(md, "## Type Aliases\n");
1093
1094 for item in types {
1095 self.render_type_alias(md, item);
1096 }
1097 }
1098
1099 fn render_constants_section(&self, md: &mut String, constants: &[&Item]) {
1101 if constants.is_empty() {
1102 return;
1103 }
1104
1105 _ = writeln!(md, "## Constants\n");
1106
1107 for item in constants {
1108 self.render_constant(md, item);
1109 }
1110 }
1111
1112 fn render_macros_section(&self, md: &mut String, macros: &[&Item]) {
1114 if macros.is_empty() {
1115 return;
1116 }
1117
1118 _ = writeln!(md, "## Macros\n");
1119
1120 for item in macros {
1121 self.render_macro(md, item);
1122 }
1123 }
1124
1125 #[expect(
1130 dead_code,
1131 reason = "Kept for API consistency, use _with_fallback for docs"
1132 )]
1133 fn get_item_name_and_summary(item: &Item) -> (String, String) {
1134 Self::get_item_name_and_summary_with_fallback(item, None)
1135 }
1136
1137 fn get_item_name_and_summary_with_fallback(
1142 item: &Item,
1143 krate: Option<&Crate>,
1144 ) -> (String, String) {
1145 if let ItemEnum::Use(use_item) = &item.inner {
1146 let name = use_item.name.clone();
1148
1149 let summary = if let Some(docs) = &item.docs
1151 && !docs.trim().is_empty()
1152 {
1153 docs.lines().next().unwrap_or("").to_string()
1154 } else if let Some(krate) = krate
1155 && let Some(target_id) = &use_item.id
1156 && let Some(target_item) = krate.index.get(target_id)
1157 && let Some(target_docs) = &target_item.docs
1158 {
1159 target_docs.lines().next().unwrap_or("").to_string()
1161 } else {
1162 String::new()
1163 };
1164
1165 (name, summary)
1166 } else {
1167 let name = item.name.clone().unwrap_or_else(|| "unnamed".to_string());
1168 let docs = item.docs.as_deref().unwrap_or("");
1169 let summary = docs.lines().next().unwrap_or("").to_string();
1170
1171 (name, summary)
1172 }
1173 }
1174
1175 fn get_item_name(item: &Item) -> &str {
1180 if let ItemEnum::Use(use_item) = &item.inner {
1181 &use_item.name
1182 } else {
1183 item.name.as_deref().unwrap_or("unnamed")
1184 }
1185 }
1186
1187 #[expect(
1191 clippy::too_many_arguments,
1192 reason = "Matches categorization structure"
1193 )]
1194 fn build_toc_entries(
1195 modules: &[&Item],
1196 structs: &[(&Id, &Item)],
1197 enums: &[(&Id, &Item)],
1198 traits: &[(&Id, &Item)],
1199 functions: &[&Item],
1200 types: &[&Item],
1201 constants: &[&Item],
1202 macros: &[&Item],
1203 ) -> Vec<TocEntry> {
1204 let mut entries = Vec::new();
1205
1206 if !modules.is_empty() {
1208 let children: Vec<TocEntry> = modules
1209 .iter()
1210 .map(|item| {
1211 let name = Self::get_item_name(item);
1212 TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1213 })
1214 .collect();
1215
1216 entries.push(TocEntry::with_children("Modules", "modules", children));
1217 }
1218
1219 if !structs.is_empty() {
1221 let children: Vec<TocEntry> = structs
1222 .iter()
1223 .map(|(_, item)| {
1224 let name = Self::get_item_name(item);
1225 TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1226 })
1227 .collect();
1228 entries.push(TocEntry::with_children("Structs", "structs", children));
1229 }
1230
1231 if !enums.is_empty() {
1233 let children: Vec<TocEntry> = enums
1234 .iter()
1235 .map(|(_, item)| {
1236 let name = Self::get_item_name(item);
1237 TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1238 })
1239 .collect();
1240
1241 entries.push(TocEntry::with_children("Enums", "enums", children));
1242 }
1243
1244 if !traits.is_empty() {
1246 let children: Vec<TocEntry> = traits
1247 .iter()
1248 .map(|(_, item)| {
1249 let name = Self::get_item_name(item);
1250 TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1251 })
1252 .collect();
1253
1254 entries.push(TocEntry::with_children("Traits", "traits", children));
1255 }
1256
1257 if !functions.is_empty() {
1259 let children: Vec<TocEntry> = functions
1260 .iter()
1261 .map(|item| {
1262 let name = Self::get_item_name(item);
1263 TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1264 })
1265 .collect();
1266
1267 entries.push(TocEntry::with_children("Functions", "functions", children));
1268 }
1269
1270 if !types.is_empty() {
1272 let children: Vec<TocEntry> = types
1273 .iter()
1274 .map(|item| {
1275 let name = Self::get_item_name(item);
1276 TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1277 })
1278 .collect();
1279
1280 entries.push(TocEntry::with_children(
1281 "Type Aliases",
1282 "type-aliases",
1283 children,
1284 ));
1285 }
1286
1287 if !constants.is_empty() {
1289 let children: Vec<TocEntry> = constants
1290 .iter()
1291 .map(|item| {
1292 let name = Self::get_item_name(item);
1293 TocEntry::new(format!("`{name}`"), AnchorUtils::slugify_anchor(name))
1294 })
1295 .collect();
1296
1297 entries.push(TocEntry::with_children("Constants", "constants", children));
1298 }
1299
1300 if !macros.is_empty() {
1302 let children: Vec<TocEntry> = macros
1303 .iter()
1304 .map(|item| {
1305 let name = Self::get_item_name(item);
1306 TocEntry::new(format!("`{name}!`"), AnchorUtils::slugify_anchor(name))
1307 })
1308 .collect();
1309
1310 entries.push(TocEntry::with_children("Macros", "macros", children));
1311 }
1312
1313 entries
1314 }
1315
1316 #[expect(
1320 clippy::too_many_arguments,
1321 reason = "Matches categorization structure"
1322 )]
1323 fn build_quick_ref_entries(
1324 modules: &[&Item],
1325 structs: &[(&Id, &Item)],
1326 enums: &[(&Id, &Item)],
1327 traits: &[(&Id, &Item)],
1328 functions: &[&Item],
1329 types: &[&Item],
1330 constants: &[&Item],
1331 macros: &[&Item],
1332 ) -> Vec<QuickRefEntry> {
1333 let mut entries = Vec::new();
1334
1335 for item in modules {
1337 let name = Self::get_item_name(item);
1338 let summary = extract_summary(item.docs.as_deref());
1339 let anchor = AnchorUtils::slugify_anchor(name);
1340
1341 entries.push(QuickRefEntry::new(name, "mod", anchor, summary));
1342 }
1343
1344 for (_, item) in structs {
1346 let name = Self::get_item_name(item);
1347 let summary = extract_summary(item.docs.as_deref());
1348 let anchor = AnchorUtils::slugify_anchor(name);
1349
1350 entries.push(QuickRefEntry::new(name, "struct", anchor, summary));
1351 }
1352
1353 for (_, item) in enums {
1355 let name = Self::get_item_name(item);
1356 let summary = extract_summary(item.docs.as_deref());
1357 let anchor = AnchorUtils::slugify_anchor(name);
1358
1359 entries.push(QuickRefEntry::new(name, "enum", anchor, summary));
1360 }
1361
1362 for (_, item) in traits {
1364 let name = Self::get_item_name(item);
1365 let summary = extract_summary(item.docs.as_deref());
1366 let anchor = AnchorUtils::slugify_anchor(name);
1367
1368 entries.push(QuickRefEntry::new(name, "trait", anchor, summary));
1369 }
1370
1371 for item in functions {
1373 let name = Self::get_item_name(item);
1374 let summary = extract_summary(item.docs.as_deref());
1375 let anchor = AnchorUtils::slugify_anchor(name);
1376
1377 entries.push(QuickRefEntry::new(name, "fn", anchor, summary));
1378 }
1379
1380 for item in types {
1382 let name = Self::get_item_name(item);
1383 let summary = extract_summary(item.docs.as_deref());
1384 let anchor = AnchorUtils::slugify_anchor(name);
1385
1386 entries.push(QuickRefEntry::new(name, "type", anchor, summary));
1387 }
1388
1389 for item in constants {
1391 let name = Self::get_item_name(item);
1392 let summary = extract_summary(item.docs.as_deref());
1393 let anchor = AnchorUtils::slugify_anchor(name);
1394
1395 entries.push(QuickRefEntry::new(name, "const", anchor, summary));
1396 }
1397
1398 for item in macros {
1400 let name = Self::get_item_name(item);
1401 let summary = extract_summary(item.docs.as_deref());
1402 let anchor = AnchorUtils::slugify_anchor(name);
1403
1404 entries.push(QuickRefEntry::new(
1405 format!("{name}!"),
1406 "macro",
1407 anchor,
1408 summary,
1409 ));
1410 }
1411
1412 entries
1413 }
1414
1415 fn render_struct(&self, md: &mut String, item_id: Id, item: &Item) {
1417 let current_krate = self.view.krate();
1418
1419 let (name, actual_item, actual_id, source_crate_name): (&str, &Item, Id, Option<&str>) =
1422 if let ItemEnum::Use(use_item) = &item.inner {
1423 let name = use_item.name.as_str();
1424
1425 if let Some(ref target_id) = use_item.id {
1426 if let Some(target) = current_krate.index.get(target_id) {
1428 (name, target, *target_id, None)
1430 } else if let Some((src_crate, target)) =
1431 self.view.lookup_item_across_crates(target_id)
1432 {
1433 let is_external = src_crate != self.view.crate_name();
1435 (
1436 name,
1437 target,
1438 *target_id,
1439 if is_external { Some(src_crate) } else { None },
1440 )
1441 } else {
1442 return;
1443 }
1444 } else {
1445 if let Some((src_crate, target, target_id)) =
1447 self.view.resolve_external_path(&use_item.source)
1448 {
1449 (name, target, target_id, Some(src_crate))
1450 } else {
1451 return;
1452 }
1453 }
1454 } else {
1455 (
1456 item.name.as_deref().unwrap_or("unnamed"),
1457 item,
1458 item_id,
1459 None,
1460 )
1461 };
1462
1463 if let ItemEnum::Struct(s) = &actual_item.inner {
1464 let render_krate = source_crate_name
1467 .and_then(|name| self.view.get_crate(name))
1468 .unwrap_or(current_krate);
1469
1470 let type_renderer = if source_crate_name.is_some() {
1472 TypeRenderer::new(render_krate)
1473 } else {
1474 self.type_renderer
1476 };
1477
1478 RendererInternals::render_struct_definition(md, name, s, render_krate, &type_renderer);
1480
1481 _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1483
1484 if let Some(src_crate) = source_crate_name {
1486 _ = writeln!(md, "*Re-exported from `{src_crate}`*\n");
1487 }
1488
1489 RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1491
1492 if let StructKind::Plain { fields, .. } = &s.kind {
1494 RendererInternals::render_struct_fields(
1495 md,
1496 fields,
1497 render_krate,
1498 &type_renderer,
1499 |field| self.view.process_docs(field, self.file_path),
1500 );
1501 }
1502
1503 self.render_impl_blocks(md, actual_id, source_crate_name);
1505 }
1506 }
1507
1508 fn render_enum(&self, md: &mut String, item_id: Id, item: &Item) {
1510 let current_krate = self.view.krate();
1511
1512 let (name, actual_item, actual_id, source_crate_name): (&str, &Item, Id, Option<&str>) =
1515 if let ItemEnum::Use(use_item) = &item.inner {
1516 let name = use_item.name.as_str();
1517
1518 if let Some(ref target_id) = use_item.id {
1519 if let Some(target) = current_krate.index.get(target_id) {
1521 (name, target, *target_id, None)
1523 } else if let Some((src_crate, target)) =
1524 self.view.lookup_item_across_crates(target_id)
1525 {
1526 let is_external = src_crate != self.view.crate_name();
1528 (
1529 name,
1530 target,
1531 *target_id,
1532 if is_external { Some(src_crate) } else { None },
1533 )
1534 } else {
1535 return;
1536 }
1537 } else {
1538 if let Some((src_crate, target, target_id)) =
1540 self.view.resolve_external_path(&use_item.source)
1541 {
1542 (name, target, target_id, Some(src_crate))
1543 } else {
1544 return;
1545 }
1546 }
1547 } else {
1548 (
1549 item.name.as_deref().unwrap_or("unnamed"),
1550 item,
1551 item_id,
1552 None,
1553 )
1554 };
1555
1556 if let ItemEnum::Enum(e) = &actual_item.inner {
1557 let render_krate = source_crate_name
1560 .and_then(|name| self.view.get_crate(name))
1561 .unwrap_or(current_krate);
1562
1563 let type_renderer = if source_crate_name.is_some() {
1565 TypeRenderer::new(render_krate)
1566 } else {
1567 self.type_renderer
1569 };
1570
1571 RendererInternals::render_enum_definition(md, name, e, render_krate, &type_renderer);
1573
1574 _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1576
1577 if let Some(src_crate) = source_crate_name {
1579 _ = writeln!(md, "*Re-exported from `{src_crate}`*\n");
1580 }
1581
1582 RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1584
1585 RendererInternals::render_enum_variants_docs(
1587 md,
1588 &e.variants,
1589 render_krate,
1590 |variant| self.view.process_docs(variant, self.file_path),
1591 );
1592
1593 self.render_impl_blocks(md, actual_id, source_crate_name);
1595 }
1596 }
1597
1598 fn render_trait(&self, md: &mut String, item_id: Id, item: &Item) {
1600 let current_krate = self.view.krate();
1601
1602 let (name, actual_item, actual_id): (&str, &Item, Id) = if let ItemEnum::Use(use_item) =
1604 &item.inner
1605 {
1606 let name = use_item.name.as_str();
1607
1608 if let Some(ref target_id) = use_item.id {
1609 if let Some(target) = current_krate.index.get(target_id) {
1611 (name, target, *target_id)
1612 } else if let Some((_, target)) = self.view.lookup_item_across_crates(target_id) {
1613 (name, target, *target_id)
1614 } else {
1615 return;
1616 }
1617 } else {
1618 if let Some((_, target, target_id)) =
1620 self.view.resolve_external_path(&use_item.source)
1621 {
1622 (name, target, target_id)
1623 } else {
1624 return;
1625 }
1626 }
1627 } else {
1628 (item.name.as_deref().unwrap_or("unnamed"), item, item_id)
1629 };
1630
1631 if let ItemEnum::Trait(t) = &actual_item.inner {
1632 TraitRenderer::render_trait_definition(md, name, t, &self.type_renderer);
1634
1635 _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1637
1638 RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1640
1641 let items = CategorizedTraitItems::categorize_trait_items(&t.items, current_krate);
1643
1644 if !items.associated_types.is_empty() {
1646 _ = writeln!(md, "#### Associated Types\n");
1647
1648 for type_item in &items.associated_types {
1649 TraitRenderer::render_trait_item(md, type_item, &self.type_renderer, |m| {
1650 self.view.process_docs(m, self.file_path)
1651 });
1652 }
1653 }
1654
1655 if !items.associated_consts.is_empty() {
1657 _ = writeln!(md, "#### Associated Constants\n");
1658
1659 for const_item in &items.associated_consts {
1660 TraitRenderer::render_trait_item(md, const_item, &self.type_renderer, |m| {
1661 self.view.process_docs(m, self.file_path)
1662 });
1663 }
1664 }
1665
1666 if !items.required_methods.is_empty() {
1668 _ = writeln!(md, "#### Required Methods\n");
1669
1670 for method in &items.required_methods {
1671 TraitRenderer::render_trait_item(md, method, &self.type_renderer, |m| {
1672 self.view.process_docs(m, self.file_path)
1673 });
1674 }
1675 }
1676
1677 if !items.provided_methods.is_empty() {
1679 _ = writeln!(md, "#### Provided Methods\n");
1680
1681 for method in &items.provided_methods {
1682 TraitRenderer::render_trait_item(md, method, &self.type_renderer, |m| {
1683 self.view.process_docs(m, self.file_path)
1684 });
1685 }
1686 }
1687
1688 self.render_trait_implementors(md, actual_id);
1690 }
1691 }
1692
1693 fn render_trait_implementors(&self, md: &mut String, trait_id: Id) {
1699 let krate = self.view.krate();
1700
1701 let Some(trait_item) = krate.index.get(&trait_id) else {
1703 return;
1704 };
1705 let ItemEnum::Trait(trait_data) = &trait_item.inner else {
1706 return;
1707 };
1708
1709 let mut implementors: BTreeSet<String> = BTreeSet::new();
1710
1711 for impl_id in &trait_data.implementations {
1713 let Some(impl_item) = krate.index.get(impl_id) else {
1714 continue;
1715 };
1716 let ItemEnum::Impl(impl_block) = &impl_item.inner else {
1717 continue;
1718 };
1719
1720 let for_type = self.type_renderer.render_type(&impl_block.for_);
1721
1722 let entry = self
1723 .type_renderer
1724 .get_type_id(&impl_block.for_)
1725 .and_then(|type_id| LinkResolver::create_link(self.view, type_id, self.file_path))
1726 .unwrap_or_else(|| format!("`{for_type}`"));
1727
1728 implementors.insert(entry);
1729 }
1730
1731 if !implementors.is_empty() {
1732 _ = write!(md, "#### Implementors\n\n");
1733 for implementor in implementors {
1734 _ = writeln!(md, "- {implementor}");
1735 }
1736 md.push('\n');
1737 }
1738 }
1739
1740 fn render_function(&self, md: &mut String, item: &Item) {
1743 let Some((name, actual_item)) = self.resolve_reexport(item) else {
1744 return;
1745 };
1746
1747 let ItemEnum::Function(f) = &actual_item.inner else {
1748 return;
1749 };
1750
1751 RendererInternals::render_function_definition(md, name, f, &self.type_renderer);
1752 _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1753
1754 RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1755 }
1756
1757 fn render_constant(&self, md: &mut String, item: &Item) {
1760 let Some((name, actual_item)) = self.resolve_reexport(item) else {
1761 return;
1762 };
1763
1764 let ItemEnum::Constant { type_, const_ } = &actual_item.inner else {
1765 return; };
1767
1768 RendererInternals::render_constant_definition(md, name, type_, const_, &self.type_renderer);
1769 _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1770
1771 RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1772 }
1773
1774 fn render_type_alias(&self, md: &mut String, item: &Item) {
1777 let Some((name, actual_item)) = self.resolve_reexport(item) else {
1778 return;
1779 };
1780
1781 let ItemEnum::TypeAlias(ta) = &actual_item.inner else {
1782 return; };
1784
1785 RendererInternals::render_type_alias_definition(md, name, ta, &self.type_renderer);
1786 _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1787
1788 RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1789 }
1790
1791 fn render_macro(&self, md: &mut String, item: &Item) {
1794 let Some((name, actual_item)) = self.resolve_reexport(item) else {
1795 return;
1796 };
1797
1798 if !matches!(
1800 &actual_item.inner,
1801 ItemEnum::Macro(_) | ItemEnum::ProcMacro(_)
1802 ) {
1803 return;
1804 }
1805
1806 RendererInternals::render_macro_heading(md, name);
1807 _ = write!(md, "{}", self.maybe_render_source_location(actual_item));
1808
1809 RendererInternals::append_docs(md, self.view.process_docs(actual_item, self.file_path));
1810 }
1811
1812 fn resolve_reexport<'b>(&'b self, item: &'b Item) -> Option<(&'b str, &'b Item)> {
1813 let ItemEnum::Use(use_item) = &item.inner else {
1814 return Some((item.name.as_deref().unwrap_or("unnamed"), item));
1816 };
1817
1818 let name = use_item.name.as_str();
1819 let current_crate = self.view.krate();
1820
1821 if let Some(ref target_id) = use_item.id {
1822 if let Some(target) = current_crate.index.get(target_id) {
1824 return Some((name, target));
1825 }
1826
1827 if let Some((_, target)) = self.view.lookup_item_across_crates(target_id) {
1829 return Some((name, target));
1830 }
1831 } else if let Some((_, target, _)) = self.view.resolve_external_path(&use_item.source) {
1832 return Some((name, target));
1834 }
1835
1836 #[cfg(feature = "trace")]
1838 tracing::error!(
1839 "Cannot resolve link. Logging it for investigation: {:?}",
1840 item.clone()
1841 );
1842
1843 None
1844 }
1845
1846 #[allow(clippy::too_many_arguments)]
1848 fn expand_glob_reexport<'b>(
1849 &self,
1850 modules: &mut Vec<&'b Item>,
1851 structs: &mut Vec<(&'b Id, &'b Item)>,
1852 enums: &mut Vec<(&'b Id, &'b Item)>,
1853 traits: &mut Vec<(&'b Id, &'b Item)>,
1854 functions: &mut Vec<&'b Item>,
1855 types: &mut Vec<&'b Item>,
1856 constants: &mut Vec<&'b Item>,
1857 macros: &mut Vec<&'b Item>,
1858 use_item: &rustdoc_types::Use,
1859 seen_items: &mut HashSet<Id>,
1860 ) where
1861 'a: 'b,
1862 {
1863 let krate = self.view.krate();
1864
1865 let Some(target_id) = &use_item.id else {
1866 return;
1867 };
1868 let Some(target_module) = krate.index.get(target_id) else {
1869 return;
1870 };
1871 let ItemEnum::Module(module) = &target_module.inner else {
1872 return;
1873 };
1874
1875 for child_id in &module.items {
1876 if !seen_items.insert(*child_id) {
1877 continue; }
1879
1880 let Some(child) = krate.index.get(child_id) else {
1881 continue;
1882 };
1883
1884 if !self.view.should_include_item(child) {
1885 continue;
1886 }
1887
1888 match &child.inner {
1889 ItemEnum::Module(_) => modules.push(child),
1890
1891 ItemEnum::Struct(_) => structs.push((child_id, child)),
1892
1893 ItemEnum::Enum(_) => enums.push((child_id, child)),
1894
1895 ItemEnum::Trait(_) => traits.push((child_id, child)),
1896
1897 ItemEnum::Function(_) => functions.push(child),
1898
1899 ItemEnum::TypeAlias(_) => types.push(child),
1900
1901 ItemEnum::Constant { .. } => constants.push(child),
1902
1903 ItemEnum::Macro(_) => macros.push(child),
1904 _ => {},
1905 }
1906 }
1907 }
1908
1909 fn render_impl_blocks(&self, md: &mut String, item_id: Id, source_crate_name: Option<&str>) {
1918 let current_krate = self.view.krate();
1919
1920 let render_krate = source_crate_name
1922 .and_then(|name| self.view.get_crate(name))
1923 .unwrap_or(current_krate);
1924
1925 let type_renderer = if source_crate_name.is_some() {
1927 TypeRenderer::new(render_krate)
1928 } else {
1929 self.type_renderer
1930 };
1931
1932 let impls = if source_crate_name.is_some() {
1934 self.view.get_impls_from_crate(item_id, render_krate)
1936 } else {
1937 self.view.get_all_impls(item_id)
1939 };
1940
1941 if impls.is_empty() {
1942 return;
1943 }
1944
1945 let (inherent, trait_impls): (Vec<&Impl>, Vec<&Impl>) = impls
1947 .into_iter()
1948 .partition(|i: &&Impl| -> bool { i.trait_.is_none() });
1949
1950 let inherent: Vec<_> = inherent.into_iter().filter(|i| !i.is_synthetic).collect();
1952
1953 let include_blanket = self.view.include_blanket_impls();
1956 let mut trait_impls: Vec<_> = trait_impls
1957 .into_iter()
1958 .filter(|i| !i.is_synthetic)
1959 .filter(|i| include_blanket || !ImplUtils::is_blanket_impl(i))
1960 .collect();
1961
1962 trait_impls.sort_by(|a, b| {
1964 let key_a = RendererInternals::impl_sort_key(a, &type_renderer);
1965 let key_b = RendererInternals::impl_sort_key(b, &type_renderer);
1966 key_a.cmp(&key_b)
1967 });
1968
1969 trait_impls.dedup_by(|a, b| {
1971 RendererInternals::impl_sort_key(a, &type_renderer)
1972 == RendererInternals::impl_sort_key(b, &type_renderer)
1973 });
1974
1975 if !inherent.is_empty() {
1977 _ = write!(md, "#### Implementations\n\n");
1978
1979 for impl_block in inherent {
1980 let type_name = type_renderer.render_type(&impl_block.for_);
1982
1983 RendererInternals::render_impl_items(
1984 md,
1985 impl_block,
1986 render_krate,
1987 &type_renderer,
1988 &Some(|item: &Item| self.process_docs(item)),
1989 &Some(|id: Id| LinkResolver::create_link(self.view, id, self.file_path)),
1990 Some(type_name.as_ref()),
1991 ImplContext::Inherent,
1992 self.view.render_config().full_method_docs,
1993 );
1994 }
1995 }
1996
1997 if !trait_impls.is_empty() {
1999 _ = write!(md, "#### Trait Implementations\n\n");
2000
2001 for impl_block in trait_impls {
2002 let for_type = type_renderer.render_type(&impl_block.for_);
2004
2005 let impl_ctx = impl_block.trait_.as_ref().map_or(
2008 ImplContext::Inherent,
2009 |t| ImplContext::Trait(PathUtils::short_name(&t.path)),
2010 );
2011
2012 if let Some(trait_path) = &impl_block.trait_ {
2013 let trait_name = trait_path
2015 .path
2016 .split("::")
2017 .last()
2018 .unwrap_or(&trait_path.path);
2019
2020 let signature_generics = ImplUtils::extract_impl_signature_generics(impl_block);
2025 let generics = if signature_generics.is_empty() {
2026 String::new()
2027 } else {
2028 let filtered: Vec<_> = impl_block
2030 .generics
2031 .params
2032 .iter()
2033 .filter(|p| signature_generics.contains(&p.name))
2034 .cloned()
2035 .collect();
2036 if filtered.is_empty() {
2037 String::new()
2038 } else {
2039 type_renderer.render_generics(&filtered)
2040 }
2041 };
2042
2043 let unsafe_str = if impl_block.is_unsafe { "unsafe " } else { "" };
2045 let negative_str = if impl_block.is_negative { "!" } else { "" };
2046
2047 _ = writeln!(
2048 md,
2049 "##### `{unsafe_str}impl{generics} {negative_str}{trait_name} for {for_type}`\n"
2050 );
2051 }
2052
2053 RendererInternals::render_impl_items(
2054 md,
2055 impl_block,
2056 render_krate,
2057 &type_renderer,
2058 &Some(|item: &Item| self.process_docs(item)),
2059 &Some(|id: Id| LinkResolver::create_link(self.view, id, self.file_path)),
2060 Some(for_type.as_ref()),
2061 impl_ctx,
2062 self.view.render_config().full_method_docs,
2063 );
2064 }
2065 }
2066 }
2067}
2068
2069#[cfg(test)]
2070mod tests {
2071 use super::*;
2072
2073 mod get_item_name_tests {
2074 use rustdoc_types::{Item, ItemEnum, Use, Visibility};
2075
2076 use super::*;
2077
2078 fn make_item(name: Option<&str>) -> Item {
2080 Item {
2081 id: Id(0),
2082 crate_id: 0,
2083 name: name.map(ToString::to_string),
2084 span: None,
2085 visibility: Visibility::Public,
2086 docs: None,
2087 links: std::collections::HashMap::new(),
2088 attrs: Vec::new(),
2089 deprecation: None,
2090 inner: ItemEnum::Module(rustdoc_types::Module {
2091 is_crate: false,
2092 items: Vec::new(),
2093 is_stripped: false,
2094 }),
2095 }
2096 }
2097
2098 fn make_use_item(use_name: &str) -> Item {
2100 Item {
2101 id: Id(0),
2102 crate_id: 0,
2103 name: None, span: None,
2105 visibility: Visibility::Public,
2106 docs: None,
2107 links: std::collections::HashMap::new(),
2108 attrs: Vec::new(),
2109 deprecation: None,
2110 inner: ItemEnum::Use(Use {
2111 source: "some::path".to_string(),
2112 name: use_name.to_string(),
2113 id: None,
2114 is_glob: false,
2115 }),
2116 }
2117 }
2118
2119 #[test]
2120 fn regular_item_with_name() {
2121 let item = make_item(Some("MyStruct"));
2122
2123 assert_eq!(MultiCrateModuleRenderer::get_item_name(&item), "MyStruct");
2124 }
2125
2126 #[test]
2127 fn regular_item_without_name_returns_unnamed() {
2128 let item = make_item(None);
2129
2130 assert_eq!(MultiCrateModuleRenderer::get_item_name(&item), "unnamed");
2131 }
2132
2133 #[test]
2134 fn use_item_returns_reexport_name() {
2135 let item = make_use_item("Parser");
2136
2137 assert_eq!(MultiCrateModuleRenderer::get_item_name(&item), "Parser");
2138 }
2139
2140 #[test]
2141 fn use_item_ignores_item_name() {
2142 let mut item = make_use_item("CorrectName");
2144 item.name = Some("WrongName".to_string());
2145
2146 assert_eq!(
2147 MultiCrateModuleRenderer::get_item_name(&item),
2148 "CorrectName"
2149 );
2150 }
2151 }
2152}