#![allow(clippy::uninlined_format_args)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::cognitive_complexity)]
use anyhow::{Context, Result, bail}; use cargo_manifest::{FeatureSet, Manifest as CargoManifest, StringOrBool}; use graph::{Edge, IdGraph, ResolvedModule};
use rustdoc_json::Builder;
use rustdoc_types::{
Abi, Attribute, Constant, Crate, Discriminant, Enum, Function, GenericArg, GenericArgs,
GenericBound, GenericParamDef, Generics, Id, Impl, Item, ItemEnum, ItemKind, Path, PolyTrait,
Primitive, Struct, StructKind, Term, Trait, Type, Union, Variant, VariantKind, WherePredicate,
};
use std::collections::{HashMap, HashSet}; use std::fmt::Write as FmtWrite; use std::hash::{Hash, Hasher};
use std::path::Path as FilePath; use tracing::{debug, info, trace, warn};
use std::fs;
use std::io::BufReader;
use pulldown_cmark::{Event, Parser as CmarkParser, Tag, TagEnd}; use pulldown_cmark_to_cmark::cmark;
pub const NIGHTLY_RUST_VERSION: &str = "nightly-2025-11-27";
pub mod cratesio;
#[doc(hidden)]
pub mod graph;
#[derive(Debug, Clone, Default)]
pub struct CrateExtra {
pub readme_content: Option<String>,
pub examples_readme_content: Option<String>,
pub examples: Vec<(String, String)>, }
#[derive(Debug, Default)]
pub struct CrateExtraReader {
read_readme: bool,
read_examples: bool,
}
impl CrateExtraReader {
pub fn new() -> Self {
Self {
read_readme: true,
read_examples: true,
}
}
pub fn no_readme(mut self) -> Self {
self.read_readme = false;
self
}
pub fn no_examples(mut self) -> Self {
self.read_examples = false;
self
}
pub fn read(&self, manifest: &CargoManifest, package_dir: &FilePath) -> Result<CrateExtra> {
let mut extra = CrateExtra::default();
if self.read_readme {
let readme_path_from_manifest = manifest
.package
.as_ref()
.and_then(|p| p.readme.as_ref())
.and_then(|r| r.as_ref().as_local());
let readme_file_to_read = match readme_path_from_manifest {
Some(StringOrBool::String(readme_filename)) => {
Some(package_dir.join(readme_filename))
}
Some(StringOrBool::Bool(true)) | None => {
let default_readme_md = package_dir.join("README.md");
if default_readme_md.exists() {
Some(default_readme_md)
} else {
Some(package_dir.join("README"))
}
}
Some(StringOrBool::Bool(false)) => {
info!("README explicitly disabled in Cargo.toml.");
None
}
};
if let Some(path) = readme_file_to_read {
if path.exists() {
match fs::read_to_string(&path) {
Ok(content) => extra.readme_content = Some(content),
Err(_) => warn!("Failed to read README at {}", path.display()),
}
} else {
info!(
"README file specified in Cargo.toml not found: {}",
path.display()
);
}
} else if readme_path_from_manifest.is_none() {
info!("No README specified in Cargo.toml and no default README found.");
}
}
if self.read_examples {
let examples_dir = package_dir.join("examples");
if examples_dir.is_dir() {
let ex_readme_md_path = examples_dir.join("README.md");
let ex_readme_path = examples_dir.join("README");
if let Some(path) = ex_readme_md_path
.exists()
.then_some(ex_readme_md_path)
.or_else(|| ex_readme_path.exists().then_some(ex_readme_path))
&& let Ok(content) = fs::read_to_string(path)
{
extra.examples_readme_content = Some(content);
}
if let Ok(entries) = fs::read_dir(&examples_dir) {
let mut found_examples = Vec::new();
for entry in entries.flatten() {
let path = entry.path();
if path.is_file()
&& path.extension().is_some_and(|ext| ext == "rs")
&& let Some(filename_str) = path.file_name().and_then(|n| n.to_str())
&& let Ok(content) = fs::read_to_string(&path)
{
found_examples.push((filename_str.to_string(), content));
}
}
if !found_examples.is_empty() {
found_examples.sort_by(|a, b| a.0.cmp(&b.0));
extra.examples = found_examples;
}
}
}
}
Ok(extra)
}
}
#[derive(Debug, Clone, Default)]
struct CrateManifestData {
description: Option<String>,
homepage: Option<String>,
repository: Option<String>,
categories: Vec<String>,
license: Option<String>,
rust_version: Option<String>,
edition: Option<String>,
features: FeatureSet, }
impl CrateManifestData {
fn from_cargo_manifest(manifest: &CargoManifest) -> Self {
let package_data = manifest.package.as_ref();
CrateManifestData {
description: package_data
.and_then(|p| p.description.as_ref())
.and_then(|d| d.as_ref().as_local())
.cloned(),
homepage: package_data
.and_then(|p| p.homepage.as_ref())
.and_then(|h| h.as_ref().as_local())
.cloned(),
repository: package_data
.and_then(|p| p.repository.as_ref())
.and_then(|r| r.as_ref().as_local())
.cloned(),
categories: package_data
.and_then(|p| p.categories.as_ref())
.and_then(|c| c.as_ref().as_local())
.cloned()
.unwrap_or_default(),
license: package_data
.and_then(|p| p.license.as_ref())
.and_then(|l| l.as_ref().as_local())
.cloned(),
rust_version: package_data
.and_then(|p| p.rust_version.as_ref())
.and_then(|rv| rv.as_ref().as_local())
.cloned(),
edition: package_data
.and_then(|p| p.edition.as_ref())
.and_then(|e| e.as_ref().as_local())
.map(|e| e.as_str().to_string()),
features: manifest.features.clone().unwrap_or_default(),
}
}
}
pub fn run_rustdoc(
crate_dir: &FilePath,
crate_name: &str,
features: Option<&str>,
no_default_features: bool,
target: Option<&str>,
allow_rustup: bool,
) -> Result<Crate> {
let manifest_path = crate_dir.join("Cargo.toml");
if !manifest_path.exists() {
bail!(
"Cargo.toml not found in unpacked crate at {}",
manifest_path.display()
);
}
if allow_rustup {
rustup_toolchain::install(NIGHTLY_RUST_VERSION).unwrap();
}
info!("Generating rustdoc JSON using rustdoc-json crate...");
let crate_name_underscore = crate_name.replace('-', "_");
let json_output_path = crate_dir
.join("target/doc")
.join(format!("{}.json", crate_name_underscore));
let mut builder = Builder::default()
.manifest_path(manifest_path)
.toolchain(NIGHTLY_RUST_VERSION) .target_dir(crate_dir.join("target")) .package(crate_name);
if let Some(features_str) = features {
let feature_list: Vec<String> = features_str.split_whitespace().map(String::from).collect();
if !feature_list.is_empty() {
info!("Enabling features: {:?}", feature_list);
builder = builder.features(feature_list);
}
}
if no_default_features {
info!("Disabling default features.");
builder = builder.no_default_features(true);
}
if let Some(target_str) = target {
info!("Setting target: {}", target_str);
builder = builder.target(target_str.to_string());
}
match builder.build() {
Ok(s) => {
info!("Generated rustdoc JSON at: {}", s.display());
}
Err(e) => {
eprintln!("--- rustdoc-json build failed ---");
eprintln!("{:?}", e);
if json_output_path.exists()
&& let Ok(content) = std::fs::read_to_string(&json_output_path)
{
eprintln!(
"\n--- Potential content of {}: ---\n{}",
json_output_path.display(),
content
);
}
bail!("rustdoc-json failed: {}", e);
}
}
info!("Parsing rustdoc JSON: {}", json_output_path.display());
let file = fs::File::open(&json_output_path)
.with_context(|| format!("Failed to open JSON file: {}", json_output_path.display()))?;
let reader = BufReader::new(file);
let krate_data: Crate = serde_json::from_reader(reader)
.with_context(|| format!("Failed to parse JSON file: {}", json_output_path.display()))?;
info!(
"Loaded rustdoc JSON for {} v{}",
crate_name,
krate_data.crate_version.as_deref().unwrap_or("?")
);
Ok(krate_data)
}
pub(crate) fn get_type_id(ty: &Type) -> Option<Id> {
match ty {
Type::ResolvedPath(p) => Some(p.id),
Type::Generic(_) => None, Type::Primitive(_) => None,
Type::FunctionPointer(_) => None, Type::Tuple(_) => None,
Type::Slice(inner) => get_type_id(inner), Type::Array { type_, .. } => get_type_id(type_), Type::Pat { type_, .. } => get_type_id(type_), Type::Infer => None,
Type::RawPointer { type_, .. } => get_type_id(type_), Type::BorrowedRef { type_, .. } => get_type_id(type_), Type::QualifiedPath { self_type, .. } => get_type_id(self_type), Type::ImplTrait(_) => None,
Type::DynTrait(_) => None,
}
}
fn attribute_to_string(attr: &Attribute) -> String {
match attr {
Attribute::NonExhaustive => "#[non_exhaustive]".to_string(),
Attribute::MustUse { reason } => {
if let Some(r) = reason {
format!("#[must_use = \"{}\"]", r)
} else {
"#[must_use]".to_string()
}
}
Attribute::MacroExport => "#[macro_export]".to_string(),
Attribute::ExportName(name) => format!("#[export_name = \"{}\"]", name),
Attribute::LinkSection(section) => format!("#[link_section = \"{}\"]", section),
Attribute::AutomaticallyDerived => "#[automatically_derived]".to_string(),
Attribute::Repr(repr) => format!("#[repr({:?})]", repr),
Attribute::NoMangle => "#[no_mangle]".to_string(),
Attribute::TargetFeature { enable } => {
format!("#[target_feature(enable = \"{}\")]", enable.join("\", \""))
}
Attribute::Other(s) => s.clone(),
}
}
fn format_attributes(attrs: &[Attribute]) -> String {
let filtered_attrs: Vec<String> = attrs
.iter()
.map(attribute_to_string)
.filter(|attr| !attr.starts_with("#[derive("))
.collect();
if filtered_attrs.is_empty() {
String::new()
} else {
filtered_attrs
.iter()
.map(|attr| format!("{}\n", attr))
.collect::<String>()
}
}
fn format_attributes_inline(attrs: &[Attribute]) -> String {
let filtered_attrs: Vec<String> = attrs
.iter()
.map(attribute_to_string)
.filter(|attr| !attr.starts_with("#[derive("))
.collect();
if filtered_attrs.is_empty() {
String::new()
} else {
format!("{} ", filtered_attrs.join(" "))
}
}
fn has_docs(item: &Item) -> bool {
item.docs.as_ref().is_some_and(|d| !d.trim().is_empty())
}
fn adjust_markdown_headers(markdown: &str, base_level: usize) -> String {
let parser = CmarkParser::new(markdown);
let transformed_events = parser.map(|event| match event {
Event::Start(Tag::Heading {
level,
id,
classes,
attrs,
}) => {
let old_level_usize = match level {
pulldown_cmark::HeadingLevel::H1 => 1,
pulldown_cmark::HeadingLevel::H2 => 2,
pulldown_cmark::HeadingLevel::H3 => 3,
pulldown_cmark::HeadingLevel::H4 => 4,
pulldown_cmark::HeadingLevel::H5 => 5,
pulldown_cmark::HeadingLevel::H6 => 6,
};
let new_level_usize = std::cmp::min(old_level_usize + base_level, 6);
let new_level = pulldown_cmark::HeadingLevel::try_from(new_level_usize)
.unwrap_or(pulldown_cmark::HeadingLevel::H6);
Event::Start(pulldown_cmark::Tag::Heading {
level: new_level,
id,
classes,
attrs,
})
}
Event::End(TagEnd::Heading(level)) => {
let old_level_usize = match level {
pulldown_cmark::HeadingLevel::H1 => 1,
pulldown_cmark::HeadingLevel::H2 => 2,
pulldown_cmark::HeadingLevel::H3 => 3,
pulldown_cmark::HeadingLevel::H4 => 4,
pulldown_cmark::HeadingLevel::H5 => 5,
pulldown_cmark::HeadingLevel::H6 => 6,
};
let new_level_usize = std::cmp::min(old_level_usize + base_level, 6);
let new_level = pulldown_cmark::HeadingLevel::try_from(new_level_usize)
.unwrap_or(pulldown_cmark::HeadingLevel::H6);
Event::End(pulldown_cmark::TagEnd::Heading(new_level))
}
_ => event,
});
let mut out_buf = String::with_capacity(markdown.len() + 128); cmark(transformed_events, &mut out_buf).expect("Markdown formatting failed");
out_buf
}
fn indent_string(s: &str, amount: usize) -> String {
let prefix = " ".repeat(amount);
s.lines()
.map(|line| format!("{}{}", prefix, line))
.collect::<Vec<_>>()
.join("\n")
}
fn clean_trait_path(path_str: &str) -> String {
path_str
.replace("core::marker::", "")
.replace("core::ops::", "") .replace("core::fmt::", "")
.replace("core::cmp::", "")
.replace("core::clone::", "")
.replace("core::hash::", "")
.replace("core::panic::unwind_safe::", "") .replace("core::", "") .replace("alloc::string::", "") .replace("alloc::vec::", "")
.replace("alloc::boxed::", "")
.replace("alloc::borrow::", "") .replace("alloc::", "") .replace("std::", "") }
fn format_id_path_canonical(id: &Id, krate: &Crate) -> String {
krate
.paths
.get(id)
.map(|p| p.path.join("::"))
.unwrap_or_else(|| {
krate
.index
.get(id)
.and_then(|item| item.name.as_deref())
.map_or_else(|| format!("{{id:{}}}", id.0), |name| name.to_string())
})
}
fn format_path(path: &Path, krate: &Crate) -> String {
let base_path = format_id_path_canonical(&path.id, krate);
let cleaned_base_path = clean_trait_path(&base_path); if let Some(args) = path.args.as_ref() {
let args_str = format_generic_args(args, krate);
if !args_str.is_empty() {
format!("{}<{}>", cleaned_base_path, args_str) } else {
cleaned_base_path }
} else {
cleaned_base_path }
}
fn format_poly_trait(poly_trait: &PolyTrait, krate: &Crate) -> String {
let hrtb = if poly_trait.generic_params.is_empty() {
"".to_string()
} else {
format!(
"for<{}> ",
poly_trait
.generic_params
.iter()
.map(|p| format_generic_param_def(p, krate)) .collect::<Vec<_>>()
.join(", ")
)
};
format!("{}{}", hrtb, format_path(&poly_trait.trait_, krate)) }
fn format_type(ty: &Type, krate: &Crate) -> String {
match ty {
Type::ResolvedPath(p) => format_path(p, krate),
Type::DynTrait(dt) => {
let lifetime_bound = dt
.lifetime
.as_ref()
.map(|lt| format!(" + {}", lt)) .unwrap_or_default();
format!(
"dyn {}{}",
dt.traits
.iter()
.map(|pt| format_poly_trait(pt, krate))
.collect::<Vec<_>>()
.join(" + "),
lifetime_bound
)
}
Type::Generic(name) => name.clone(),
Type::Primitive(name) => name.clone(),
Type::FunctionPointer(fp) => {
let hrtb = if fp.generic_params.is_empty() {
"".to_string()
} else {
format!(
"for<{}> ",
fp.generic_params
.iter()
.map(|p| format_generic_param_def(p, krate))
.collect::<Vec<_>>()
.join(", ")
)
};
let abi = if !matches!(fp.header.abi, Abi::Rust) {
format!("extern \"{:?}\" ", fp.header.abi) } else {
"".to_string()
};
let unsafe_kw = if fp.header.is_unsafe { "unsafe " } else { "" };
format!(
"{}{}{}fn({}){}",
hrtb,
unsafe_kw,
abi,
fp.sig
.inputs
.iter()
.map(|(_name, type_)| format_type(type_, krate)) .collect::<Vec<_>>()
.join(", "),
fp.sig
.output
.as_ref()
.map(|t| format!(" -> {}", format_type(t, krate)))
.unwrap_or_default()
)
}
Type::Tuple(types) => {
if types.is_empty() {
"()".to_string()
} else {
format!(
"({})",
types
.iter()
.map(|t| format_type(t, krate))
.collect::<Vec<_>>()
.join(", ")
)
}
}
Type::Slice(inner) => format!("[{}]", format_type(inner, krate)),
Type::Array { type_, len } => format!("[{}; {}]", format_type(type_, krate), len),
Type::Pat { type_, .. } => format!("pat {}", format_type(type_, krate)), Type::ImplTrait(bounds) => {
format!(
"impl {}",
bounds
.iter()
.map(|b| format_generic_bound(b, krate))
.collect::<Vec<_>>()
.join(" + ")
)
}
Type::Infer => "_".to_string(),
Type::RawPointer { is_mutable, type_ } => {
format!(
"*{}{}",
if *is_mutable { "mut " } else { "const " },
format_type(type_, krate)
)
}
Type::BorrowedRef {
lifetime,
is_mutable,
type_,
} => format!(
"&{}{}{}",
lifetime
.as_ref()
.map(|lt| format!("{} ", lt)) .unwrap_or_default(),
if *is_mutable { "mut " } else { "" },
format_type(type_, krate)
),
Type::QualifiedPath {
name,
args,
self_type,
trait_,
} => {
let self_type_str = format_type(self_type, krate);
let trait_str = trait_
.as_ref()
.map(|t| format_path(t, krate)) .unwrap_or("_".to_string());
let args_str = args
.as_ref()
.map(|a| format_generic_args(a, krate))
.unwrap_or_default();
format!(
"<{} as {}>::{}{}",
self_type_str,
trait_str,
name,
if args_str.is_empty() {
"".to_string()
} else {
format!("<{}>", args_str)
}
)
}
}
}
fn format_generic_args(args: &GenericArgs, krate: &Crate) -> String {
match args {
GenericArgs::AngleBracketed {
args, constraints, ..
} => {
let arg_strs: Vec<String> = args.iter().map(|a| format_generic_arg(a, krate)).collect();
let constraint_strs: Vec<String> = constraints
.iter()
.map(|c| match c {
rustdoc_types::AssocItemConstraint {
name,
args: assoc_args,
binding: rustdoc_types::AssocItemConstraintKind::Equality(term),
} => {
let assoc_args_str = assoc_args
.as_ref()
.map(|a| format_generic_args(a, krate))
.unwrap_or_default();
format!(
"{}{}{}{} = {}",
name,
if assoc_args_str.is_empty() { "" } else { "<" },
assoc_args_str,
if assoc_args_str.is_empty() { "" } else { ">" },
format_term(term, krate)
)
}
rustdoc_types::AssocItemConstraint {
name,
args: assoc_args,
binding: rustdoc_types::AssocItemConstraintKind::Constraint(bounds),
} => {
let assoc_args_str = assoc_args
.as_ref()
.map(|a| format_generic_args(a, krate))
.unwrap_or_default();
format!(
"{}{}{}{}: {}",
name,
if assoc_args_str.is_empty() { "" } else { "<" },
assoc_args_str,
if assoc_args_str.is_empty() { "" } else { ">" },
bounds
.iter()
.map(|bnd| format_generic_bound(bnd, krate))
.collect::<Vec<_>>()
.join(" + ")
)
}
})
.collect();
let mut all_strs = arg_strs;
all_strs.extend(constraint_strs);
all_strs.join(", ")
}
GenericArgs::Parenthesized { inputs, output, .. } => {
format!(
"({}) -> {}",
inputs
.iter()
.map(|t| format_type(t, krate))
.collect::<Vec<_>>()
.join(", "),
output
.as_ref()
.map_or("()".to_string(), |t| format_type(t, krate))
)
}
GenericArgs::ReturnTypeNotation => String::new(),
}
}
fn format_const_expr(constant: &Constant) -> String {
if let Some(v) = &constant.value
&& v != &constant.expr
{
return format!("{} /* = {} */", constant.expr, v);
}
constant.expr.clone()
}
fn format_discriminant_expr(discr: &Discriminant) -> String {
if discr.value != discr.expr {
format!("{} /* = {} */", discr.expr, discr.value)
} else {
discr.expr.clone()
}
}
fn format_generic_arg(arg: &GenericArg, krate: &Crate) -> String {
match arg {
GenericArg::Lifetime(lt) => lt.to_string(), GenericArg::Type(ty) => format_type(ty, krate),
GenericArg::Const(c) => format_const_expr(c),
GenericArg::Infer => "_".to_string(),
}
}
fn format_generic_bound(bound: &GenericBound, krate: &Crate) -> String {
match bound {
GenericBound::TraitBound {
trait_, generic_params, modifier,
..
} => {
let hrtb = if generic_params.is_empty() {
"".to_string()
} else {
format!(
"for<{}> ",
generic_params
.iter()
.map(|p| format_generic_param_def(p, krate)) .collect::<Vec<_>>()
.join(", ")
)
};
let mod_str = match modifier {
rustdoc_types::TraitBoundModifier::None => "",
rustdoc_types::TraitBoundModifier::Maybe => "?",
rustdoc_types::TraitBoundModifier::MaybeConst => "?const ", };
format!("{}{}{}", hrtb, mod_str, format_path(trait_, krate)) }
GenericBound::Outlives(lifetime) => lifetime.to_string(), GenericBound::Use(args) => {
format!(
"use<{}>",
args.iter()
.map(|a| match a {
rustdoc_types::PreciseCapturingArg::Lifetime(lt) => format!("'{}", lt),
rustdoc_types::PreciseCapturingArg::Param(id_str) => id_str.clone(), })
.collect::<Vec<_>>()
.join(", ")
)
}
}
}
fn format_term(term: &Term, krate: &Crate) -> String {
match term {
Term::Type(t) => format_type(t, krate),
Term::Constant(c) => format_const_expr(c),
}
}
fn format_generic_param_def(p: &GenericParamDef, krate: &Crate) -> String {
match &p.kind {
rustdoc_types::GenericParamDefKind::Lifetime { .. } => p.name.to_string(), rustdoc_types::GenericParamDefKind::Type {
bounds,
default,
is_synthetic,
..
} => {
format!(
"{}{}{}{}",
if *is_synthetic { "impl " } else { "" },
p.name,
if bounds.is_empty() {
"".to_string()
} else {
format!(
": {}",
bounds
.iter()
.map(|b| format_generic_bound(b, krate))
.collect::<Vec<_>>()
.join(" + ")
)
},
default
.as_ref()
.map(|t| format!(" = {}", format_type(t, krate)))
.unwrap_or_default()
)
}
rustdoc_types::GenericParamDefKind::Const { type_, default, .. } => {
format!(
"const {}: {}{}",
p.name,
format_type(type_, krate),
default
.as_deref()
.map(|d| format!(" = {}", d))
.unwrap_or_default()
)
}
}
}
fn format_generics_full(generics: &Generics, krate: &Crate) -> String {
if generics.params.is_empty() && generics.where_predicates.is_empty() {
return String::new();
}
let mut s = String::new();
let params_str = if !generics.params.is_empty() {
format!(
"<{}>",
generics
.params
.iter()
.map(|p| format_generic_param_def(p, krate))
.collect::<Vec<_>>()
.join(", ")
)
} else {
String::new()
};
let where_clause = format_generics_where_only(&generics.where_predicates, krate);
if !params_str.is_empty() {
write!(s, "{}", params_str).unwrap();
}
if !where_clause.is_empty() {
if !params_str.is_empty() && where_clause.contains('\n') {
write!(s, "\n {}", where_clause).unwrap();
} else {
write!(s, " {}", where_clause).unwrap(); }
}
s
}
fn format_generics_params_only(params: &[GenericParamDef], krate: &Crate) -> String {
if params.is_empty() {
return String::new();
}
format!(
"<{}>",
params
.iter()
.map(|p| format_generic_param_def(p, krate))
.collect::<Vec<_>>()
.join(", ")
)
}
fn format_generics_where_only(predicates: &[WherePredicate], krate: &Crate) -> String {
if predicates.is_empty() {
return String::new();
}
let clauses: Vec<String> = predicates
.iter()
.map(|p| match p {
WherePredicate::BoundPredicate {
type_,
bounds,
generic_params,
..
} => {
let hrtb = if generic_params.is_empty() {
"".to_string()
} else {
format!(
"for<{}> ",
generic_params
.iter()
.map(|gp| format_generic_param_def(gp, krate))
.collect::<Vec<_>>()
.join(", ")
)
};
format!(
"{}{}: {}",
hrtb,
format_type(type_, krate),
bounds
.iter()
.map(|b| format_generic_bound(b, krate))
.collect::<Vec<_>>()
.join(" + ")
)
}
WherePredicate::LifetimePredicate {
lifetime, outlives, ..
} => {
format!(
"{}: {}",
lifetime,
outlives
.iter()
.map(|lt| lt.to_string()) .collect::<Vec<_>>()
.join(" + ")
)
}
WherePredicate::EqPredicate { lhs, rhs, .. } => {
format!("{} == {}", format_type(lhs, krate), format_term(rhs, krate))
}
})
.collect();
let total_len = clauses.iter().map(|s| s.len()).sum::<usize>();
let is_multiline = clauses.len() > 1 || total_len > 60;
if is_multiline {
format!("where\n {}", clauses.join(",\n ")) } else {
format!("where {}", clauses.join(", "))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum TraitImplCategory {
Simple,
GenericOrComplex,
Auto,
Blanket,
}
#[derive(Debug, Clone)]
struct FormattedTraitImpl {
trait_id: Id,
trait_generics: Generics,
is_unsafe_impl: bool,
is_negative: bool,
category: TraitImplCategory,
formatted_markdown_list_entry: String,
impl_id: Option<Id>,
}
impl PartialEq for FormattedTraitImpl {
fn eq(&self, other: &Self) -> bool {
self.trait_id == other.trait_id
&& self.trait_generics == other.trait_generics && self.is_unsafe_impl == other.is_unsafe_impl
&& self.is_negative == other.is_negative
&& self.category == other.category }
}
impl Eq for FormattedTraitImpl {}
impl Hash for FormattedTraitImpl {
fn hash<H: Hasher>(&self, state: &mut H) {
self.trait_id.hash(state);
self.trait_generics.hash(state); self.is_unsafe_impl.hash(state);
self.is_negative.hash(state);
self.category.hash(state); }
}
fn generic_args_to_generics(args_opt: Option<Box<GenericArgs>>, krate: &Crate) -> Generics {
let mut params = Vec::new();
let mut where_predicates = Vec::new();
if let Some(args_box) = args_opt
&& let GenericArgs::AngleBracketed {
args, constraints, ..
} = *args_box
{
for arg in args {
match arg {
GenericArg::Type(t) => {
let name = match t {
Type::Generic(g_name) => g_name,
_ => format_type(&t, krate), };
params.push(GenericParamDef {
name,
kind: rustdoc_types::GenericParamDefKind::Type {
bounds: vec![], default: None,
is_synthetic: false,
},
});
}
GenericArg::Lifetime(lt_name) => {
params.push(GenericParamDef {
name: lt_name,
kind: rustdoc_types::GenericParamDefKind::Lifetime { outlives: vec![] },
});
}
GenericArg::Const(c) => {
params.push(GenericParamDef {
name: c.expr,
kind: rustdoc_types::GenericParamDefKind::Const {
type_: Type::Infer, default: None,
},
});
}
GenericArg::Infer => {
params.push(GenericParamDef {
name: "_".to_string(),
kind: rustdoc_types::GenericParamDefKind::Type {
bounds: vec![],
default: None,
is_synthetic: true,
},
});
}
}
}
for constraint in constraints {
match constraint {
rustdoc_types::AssocItemConstraint {
name: assoc_name,
args: assoc_args, binding: rustdoc_types::AssocItemConstraintKind::Equality(term),
} => {
let lhs_type = Type::QualifiedPath {
name: assoc_name,
args: assoc_args.clone(),
self_type: Box::new(Type::Generic("Self".to_string())), trait_: None, };
where_predicates.push(WherePredicate::EqPredicate {
lhs: lhs_type,
rhs: term,
});
}
rustdoc_types::AssocItemConstraint {
name: assoc_name,
args: assoc_args,
binding: rustdoc_types::AssocItemConstraintKind::Constraint(bounds),
} => {
let for_type = Type::QualifiedPath {
name: assoc_name,
args: assoc_args.clone(),
self_type: Box::new(Type::Generic("Self".to_string())),
trait_: None,
};
where_predicates.push(WherePredicate::BoundPredicate {
type_: for_type,
bounds,
generic_params: vec![], });
}
}
}
}
Generics {
params,
where_predicates,
}
}
fn get_trait_for_type_generics(item: &Item) -> Option<&Generics> {
match item.inner {
ItemEnum::Struct(ref s) => Some(&s.generics),
ItemEnum::Enum(ref e) => Some(&e.generics),
ItemEnum::Union(ref u) => Some(&u.generics),
_ => None,
}
}
fn trait_impl_has_associated_items(imp: &Impl, krate: &Crate) -> bool {
imp.items.iter().any(|id| {
if let Some(item) = krate.index.get(id) {
matches!(
item.inner,
ItemEnum::AssocType { .. } | ItemEnum::AssocConst { .. }
)
} else {
false
}
})
}
fn is_passthrough_generic_impl_check(imp: &Impl, trait_path: &Path, krate: &Crate) -> bool {
let trait_path_is_simple = trait_path.args.as_ref().is_none_or(|ga| {
matches!(ga.as_ref(), GenericArgs::AngleBracketed { args, constraints } if args.is_empty() && constraints.is_empty())
});
if !trait_path_is_simple {
return false;
}
if trait_impl_has_associated_items(imp, krate) {
return false;
}
let Type::ResolvedPath(for_path) = &imp.for_ else {
return false;
};
let Some(for_type_item) = krate.index.get(&for_path.id) else {
return false;
};
let Some(for_generics) = get_trait_for_type_generics(for_type_item) else {
return false;
};
let Some(for_args_box) = &for_path.args else {
return imp.generics.params.is_empty() && imp.generics.where_predicates.is_empty();
};
let GenericArgs::AngleBracketed { args: for_args, .. } = for_args_box.as_ref() else {
return false;
};
if for_generics.params.len() != for_args.len()
|| for_generics.params.len() != imp.generics.params.len()
{
return false;
}
if for_generics.where_predicates != imp.generics.where_predicates {
return false;
}
let impl_params_map: HashMap<&String, &GenericParamDef> =
imp.generics.params.iter().map(|p| (&p.name, p)).collect();
for (for_arg, for_param_def) in for_args.iter().zip(for_generics.params.iter()) {
let for_arg_name = match for_arg {
GenericArg::Lifetime(name) => Some(name),
GenericArg::Type(Type::Generic(param_name)) => Some(param_name),
_ => return false, };
let Some(for_arg_name_str) = for_arg_name else {
return false;
};
let Some(impl_param_def) = impl_params_map.get(for_arg_name_str) else {
return false; };
match (&impl_param_def.kind, &for_param_def.kind) {
(
rustdoc_types::GenericParamDefKind::Lifetime {
outlives: impl_outlives,
},
rustdoc_types::GenericParamDefKind::Lifetime {
outlives: for_outlives,
},
) => {
if impl_outlives != for_outlives {
return false;
}
}
(
rustdoc_types::GenericParamDefKind::Type {
bounds: impl_bounds,
..
},
rustdoc_types::GenericParamDefKind::Type {
bounds: for_bounds, ..
},
) => {
if impl_bounds != for_bounds {
return false;
}
}
(
rustdoc_types::GenericParamDefKind::Const { .. },
rustdoc_types::GenericParamDefKind::Const { .. },
) => {
}
_ => return false, }
}
true
}
impl FormattedTraitImpl {
fn from_impl(
imp: &Impl,
impl_id: Option<Id>,
trait_path: &Path,
krate: &Crate,
printer: &Printer, ) -> Self {
let trait_path_str = format_id_path_canonical(&trait_path.id, krate);
let cleaned_trait_path = clean_trait_path(&trait_path_str);
let display_path_with_generics = format!(
"{}{}{}",
if imp.is_negative {
"!"
} else {
Default::default()
},
cleaned_trait_path,
if let Some(args) = &trait_path.args {
let args_str = format_generic_args(args, krate);
if !args_str.is_empty() {
format!("<{}>", args_str)
} else {
String::new()
}
} else {
String::new()
}
);
let is_passthrough_generic_impl = is_passthrough_generic_impl_check(imp, trait_path, krate);
let category = if imp.is_synthetic {
TraitImplCategory::Auto
} else if imp.blanket_impl.is_some() {
TraitImplCategory::Blanket
} else if is_passthrough_generic_impl && !trait_impl_has_associated_items(imp, krate) {
TraitImplCategory::Simple
} else {
TraitImplCategory::GenericOrComplex
};
let mut list_entry = String::new();
match category {
TraitImplCategory::Simple | TraitImplCategory::Auto => {
write!(list_entry, "- `{}`", display_path_with_generics).unwrap();
}
TraitImplCategory::GenericOrComplex => {
if let Some(impl_block_str) = generate_impl_trait_block(imp, printer.krate) {
if !impl_block_str.trim_end_matches("{\n}").trim().is_empty() {
writeln!(list_entry, "- `{}`", display_path_with_generics).unwrap();
writeln!(list_entry).unwrap();
let full_code_block = format!("```rust\n{}\n```", impl_block_str);
let indented_block = indent_string(&full_code_block, 4);
writeln!(list_entry, "{}", indented_block).unwrap(); } else {
write!(list_entry, "- `{}`", display_path_with_generics).unwrap();
}
} else {
write!(list_entry, "- `{}`", display_path_with_generics).unwrap();
}
}
TraitImplCategory::Blanket => {
let where_clause =
format_generics_where_only(&imp.generics.where_predicates, krate);
if !where_clause.is_empty() {
if where_clause.lines().count() == 1 {
write!(
list_entry,
"- `{}` (`{}`)",
display_path_with_generics, where_clause,
)
.unwrap();
} else {
writeln!(list_entry, "- `{}`", display_path_with_generics).unwrap();
let code_block = format!("```rust\n{}\n```", where_clause);
let indented_block = indent_string(&code_block, 4);
write!(list_entry, "\n{}\n", indented_block).unwrap(); }
} else {
write!(list_entry, "- `{}`", display_path_with_generics).unwrap();
}
}
}
FormattedTraitImpl {
trait_id: trait_path.id,
trait_generics: generic_args_to_generics(trait_path.args.clone(), krate),
is_unsafe_impl: imp.is_unsafe,
is_negative: imp.is_negative,
category,
formatted_markdown_list_entry: list_entry.trim_end().to_string(), impl_id,
}
}
fn get_impl_data<'krate_borrow>(
&self,
krate: &'krate_borrow Crate,
) -> Option<(&'krate_borrow Impl, Id)> {
self.impl_id
.and_then(|id| krate.index.get(&id))
.and_then(|item| match &item.inner {
ItemEnum::Impl(imp_data) => Some((imp_data, item.id)),
_ => None,
})
}
#[allow(dead_code)] fn has_associated_items(&self, krate: &Crate) -> bool {
if let Some(trait_item) = krate.index.get(&self.trait_id)
&& let ItemEnum::Trait(trait_data) = &trait_item.inner
{
return trait_data.items.iter().any(|assoc_id| {
if let Some(assoc_item_details) = krate.index.get(assoc_id) {
matches!(
assoc_item_details.inner,
ItemEnum::AssocType { .. } | ItemEnum::AssocConst { .. }
)
} else {
false
}
});
}
false
}
}
fn generate_item_declaration(item: &Item, krate: &Crate, current_module_path: &[String]) -> String {
let name = item.name.as_deref().unwrap_or(match &item.inner {
ItemEnum::StructField(_) => "{unnamed_field}", _ => "{unnamed}",
});
match &item.inner {
ItemEnum::Struct(s) => {
let mut fq_path_parts = current_module_path.to_vec();
if !name.is_empty() {
fq_path_parts.push(name.to_string());
}
let fq_path = fq_path_parts.join("::");
format!(
"struct {}{}",
fq_path,
format_generics_params_only(&s.generics.params, krate)
)
}
ItemEnum::Enum(e) => {
let mut fq_path_parts = current_module_path.to_vec();
if !name.is_empty() {
fq_path_parts.push(name.to_string());
}
let fq_path = fq_path_parts.join("::");
format!(
"enum {}{}",
fq_path,
format_generics_params_only(&e.generics.params, krate)
)
}
ItemEnum::Union(u) => {
let mut fq_path_parts = current_module_path.to_vec();
if !name.is_empty() {
fq_path_parts.push(name.to_string());
}
let fq_path = fq_path_parts.join("::");
format!(
"union {}{}",
fq_path,
format_generics_params_only(&u.generics.params, krate)
)
}
ItemEnum::Trait(t) => {
let unsafe_kw = if t.is_unsafe { "unsafe " } else { "" };
let auto = if t.is_auto { "auto " } else { "" };
let mut fq_path_parts = current_module_path.to_vec();
if !name.is_empty() {
fq_path_parts.push(name.to_string());
}
let fq_path = fq_path_parts.join("::");
format!(
"{}{}{}{}{}",
auto,
unsafe_kw,
"trait ",
fq_path, format_generics_params_only(&t.generics.params, krate)
)
}
ItemEnum::Function(f) => {
let mut code = String::new();
write!(code, "{}", format_attributes_inline(&item.attrs)).unwrap(); write!(code, "fn {}", name).unwrap();
write!(
code,
"{}",
format_generics_params_only(&f.generics.params, krate)
)
.unwrap();
write!(code, "(").unwrap();
let args_str = f
.sig
.inputs
.iter()
.map(|(n, t)| format!("{}: {}", n, format_type(t, krate))) .collect::<Vec<_>>()
.join(", ");
write!(code, "{}", args_str).unwrap();
if f.sig.is_c_variadic {
write!(code, ", ...").unwrap();
}
write!(code, ")").unwrap();
if let Some(output_type) = &f.sig.output {
write!(code, " -> {}", format_type(output_type, krate)).unwrap();
}
code
}
ItemEnum::TypeAlias(ta) => format!(
"type {}{}",
name,
format_generics_params_only(&ta.generics.params, krate)
),
ItemEnum::TraitAlias(ta) => format!(
"trait {}{}",
name,
format_generics_params_only(&ta.generics.params, krate)
),
ItemEnum::Constant { .. } => format!("const {}", name), ItemEnum::Static(s) => format!("static {}{}", if s.is_mutable { "mut " } else { "" }, name),
ItemEnum::Macro(_) => format!("macro {}!", name),
ItemEnum::ProcMacro(pm) => {
let kind_str = match pm.kind {
rustdoc_types::MacroKind::Bang => "!",
rustdoc_types::MacroKind::Attr => "#[]",
rustdoc_types::MacroKind::Derive => "#[derive]",
};
format!("proc_macro {}{}", name, kind_str)
}
ItemEnum::Primitive(_) => format!("primitive {}", name),
ItemEnum::Module(_) => format!("mod {}", name), ItemEnum::ExternCrate {
name: crate_name, ..
} => format!("extern crate {}", crate_name),
ItemEnum::Use(_) => format!("use {}", name), ItemEnum::ExternType => format!("extern type {}", name),
ItemEnum::Variant(v) => format_variant_signature(item, v, krate), ItemEnum::StructField(_) => name.to_string(), ItemEnum::AssocConst { .. } => format!("const {}", name),
ItemEnum::AssocType { .. } => format!("type {}", name),
ItemEnum::Impl(_) => "impl".to_string(), }
}
fn generate_struct_code_block(item: &Item, s: &Struct, krate: &Crate) -> String {
let name = item
.name
.as_deref()
.expect("Struct item should have a name");
let mut code = String::new();
write!(
code,
"{}pub struct {}",
format_attributes(&item.attrs), name
)
.unwrap();
let generics_str = format_generics_full(&s.generics, krate);
let where_is_multiline = generics_str.contains("where\n");
write!(code, "{}", generics_str).unwrap();
match &s.kind {
StructKind::Plain { fields, .. } => {
if where_is_multiline {
write!(code, " {{").unwrap(); } else {
write!(code, " {{").unwrap(); }
if !fields.is_empty() {
writeln!(code).unwrap();
}
for field_id in fields {
if let Some(field_item) = krate.index.get(field_id)
&& let ItemEnum::StructField(field_type) = &field_item.inner
{
let field_name = field_item.name.as_deref().unwrap_or("_");
writeln!(
code,
" {}pub {}: {},",
format_attributes_inline(&field_item.attrs), field_name,
format_type(field_type, krate)
)
.unwrap();
}
}
if !fields.is_empty() && !code.ends_with('\n') {
writeln!(code).unwrap();
}
write!(code, "}}").unwrap();
}
StructKind::Tuple(fields) => {
write!(code, "(").unwrap();
let field_types: Vec<String> = fields
.iter()
.filter_map(|opt_id| {
opt_id
.as_ref()
.and_then(|id| krate.index.get(id))
.and_then(|field_item| {
if let ItemEnum::StructField(field_type) = &field_item.inner {
Some(format!(
"{}pub {}",
format_attributes_inline(&field_item.attrs), format_type(field_type, krate)
))
} else {
None
}
})
})
.collect();
write!(code, "{}", field_types.join(", ")).unwrap();
write!(code, ")").unwrap();
if !where_is_multiline {
write!(code, ";").unwrap();
}
}
StructKind::Unit => {
if !where_is_multiline {
write!(code, ";").unwrap();
}
}
}
code
}
fn generate_enum_code_block(item: &Item, e: &Enum, krate: &Crate) -> String {
let name = item.name.as_deref().expect("Enum item should have a name");
let mut code = String::new();
write!(
code,
"{}pub enum {}",
format_attributes(&item.attrs), name
)
.unwrap();
let generics_str = format_generics_full(&e.generics, krate);
write!(code, "{}", generics_str).unwrap();
write!(code, " {{").unwrap();
if !e.variants.is_empty() {
writeln!(code).unwrap();
}
for variant_id in &e.variants {
if let Some(variant_item) = krate.index.get(variant_id)
&& let ItemEnum::Variant(variant_data) = &variant_item.inner
{
write!(
code,
" {}",
format_variant_definition(variant_item, variant_data, krate) )
.unwrap();
if let Some(discr) = &variant_data.discriminant {
write!(code, " = {}", format_discriminant_expr(discr)).unwrap();
}
writeln!(code, ",").unwrap();
}
}
if !e.variants.is_empty() && !code.ends_with('\n') {
writeln!(code).unwrap();
}
write!(code, "}}").unwrap();
code
}
fn generate_union_code_block(item: &Item, u: &Union, krate: &Crate) -> String {
let name = item.name.as_deref().expect("Union item should have a name");
let mut code = String::new();
write!(
code,
"{}pub union {}",
format_attributes(&item.attrs), name
)
.unwrap();
let generics_str = format_generics_full(&u.generics, krate);
write!(code, "{}", generics_str).unwrap();
write!(code, " {{").unwrap();
if !u.fields.is_empty() {
writeln!(code).unwrap();
}
for field_id in &u.fields {
if let Some(field_item) = krate.index.get(field_id)
&& let ItemEnum::StructField(field_type) = &field_item.inner
{
let field_name = field_item.name.as_deref().unwrap_or("_");
writeln!(
code,
" {}pub {}: {},",
format_attributes_inline(&field_item.attrs), field_name,
format_type(field_type, krate)
)
.unwrap();
}
}
if !u.fields.is_empty() && !code.ends_with('\n') {
writeln!(code).unwrap();
}
write!(code, "}}").unwrap();
code
}
fn generate_trait_code_block(item: &Item, t: &Trait, krate: &Crate) -> String {
let name = item.name.as_deref().expect("Trait item should have a name");
let mut code = String::new();
write!(code, "{}", format_attributes(&item.attrs)).unwrap();
if t.is_auto {
write!(code, "pub auto ").unwrap();
}
if t.is_unsafe {
write!(code, "pub unsafe ").unwrap();
} else if !t.is_auto {
write!(code, "pub ").unwrap();
}
write!(code, "trait {}", name).unwrap();
write!(
code,
"{}",
format_generics_params_only(&t.generics.params, krate)
)
.unwrap();
if !t.bounds.is_empty() {
write!(
code,
": {}",
t.bounds
.iter()
.map(|b| format_generic_bound(b, krate))
.collect::<Vec<_>>()
.join(" + ")
)
.unwrap();
}
let where_clause = format_generics_where_only(&t.generics.where_predicates, krate);
if !where_clause.is_empty() {
if where_clause.contains('\n') {
write!(code, "\n {}", where_clause).unwrap(); } else {
write!(code, " {}", where_clause).unwrap(); }
}
if t.items.is_empty() {
write!(code, " {{}}").unwrap();
} else {
if where_clause.contains('\n') {
write!(code, " {{").unwrap(); } else {
write!(code, " {{").unwrap(); }
writeln!(code).unwrap();
for item_id in &t.items {
if let Some(assoc_item) = krate.index.get(item_id) {
match &assoc_item.inner {
ItemEnum::AssocConst { type_, value, .. } => {
write!(
code,
" {}const {}: {}",
format_attributes_inline(&assoc_item.attrs), assoc_item.name.as_deref().unwrap_or("_"),
format_type(type_, krate)
)
.unwrap();
if let Some(val) = value {
write!(code, " = {};", val).unwrap(); } else {
write!(code, ";").unwrap();
}
writeln!(code).unwrap();
}
ItemEnum::AssocType { bounds, type_, .. } => {
write!(
code,
" {}type {}",
format_attributes_inline(&assoc_item.attrs), assoc_item.name.as_deref().unwrap_or("_")
)
.unwrap();
if !bounds.is_empty() {
write!(
code,
": {}",
bounds
.iter()
.map(|b| format_generic_bound(b, krate))
.collect::<Vec<_>>()
.join(" + ")
)
.unwrap();
}
if let Some(ty) = type_ {
write!(code, " = {};", format_type(ty, krate)).unwrap();
} else {
write!(code, ";").unwrap();
}
writeln!(code).unwrap();
}
ItemEnum::Function(f) => {
writeln!(
code,
" {};",
generate_function_code_block(assoc_item, f, krate)
)
.unwrap();
}
_ => {} }
}
}
if !code.ends_with('\n') {
writeln!(code).unwrap();
}
write!(code, "}}").unwrap();
}
code
}
fn format_impl_decl(imp: &Impl, krate: &Crate) -> String {
let mut decl = String::new();
if imp.is_unsafe {
write!(decl, "unsafe ").unwrap();
}
write!(decl, "impl").unwrap();
let generics_params = format_generics_params_only(&imp.generics.params, krate);
if !generics_params.is_empty() {
write!(decl, "{}", generics_params).unwrap();
}
if let Some(trait_path) = &imp.trait_ {
write!(decl, " {} for", format_path(trait_path, krate)).unwrap();
}
write!(decl, " {}", format_type(&imp.for_, krate)).unwrap();
let where_clause = format_generics_where_only(&imp.generics.where_predicates, krate);
if !where_clause.is_empty() {
if where_clause.contains('\n') {
write!(decl, "\n {}", where_clause).unwrap(); } else {
write!(decl, " {}", where_clause).unwrap(); }
}
decl
}
fn format_impl_decl_header_only(imp: &Impl, krate: &Crate) -> String {
let mut decl = String::new();
if imp.is_unsafe {
write!(decl, "unsafe ").unwrap();
}
write!(decl, "impl").unwrap();
let generics_params = format_generics_params_only(&imp.generics.params, krate);
if !generics_params.is_empty() {
write!(decl, "{}", generics_params).unwrap();
}
if let Some(trait_path) = &imp.trait_ {
write!(decl, " {} for", format_path(trait_path, krate)).unwrap();
}
write!(decl, " {}", format_type(&imp.for_, krate)).unwrap();
decl
}
fn generate_impl_trait_block(imp: &Impl, krate: &Crate) -> Option<String> {
let mut code = String::new();
let impl_header = format_impl_decl(imp, krate);
writeln!(code, "{} {{", impl_header).unwrap();
let mut assoc_items_content = String::new();
let mut has_printable_assoc_items = false;
for assoc_item_id in &imp.items {
if let Some(assoc_item) = krate.index.get(assoc_item_id) {
match &assoc_item.inner {
ItemEnum::AssocConst { type_, value, .. } => {
has_printable_assoc_items = true;
write!(
assoc_items_content,
" {}const {}: {}",
format_attributes_inline(&assoc_item.attrs), assoc_item.name.as_deref().unwrap_or("_"),
format_type(type_, krate)
)
.unwrap();
if let Some(val) = value {
write!(assoc_items_content, " = {};", val).unwrap();
} else {
write!(assoc_items_content, ";").unwrap();
}
writeln!(assoc_items_content).unwrap();
}
ItemEnum::AssocType { bounds, type_, .. } => {
has_printable_assoc_items = true;
write!(
assoc_items_content,
" {}type {}",
format_attributes_inline(&assoc_item.attrs), assoc_item.name.as_deref().unwrap_or("_")
)
.unwrap();
if !bounds.is_empty() {
let bounds_str = bounds
.iter()
.map(|b| format_generic_bound(b, krate))
.collect::<Vec<_>>()
.join(" + ");
write!(assoc_items_content, ": {}", bounds_str).unwrap();
}
if let Some(ty) = type_ {
write!(assoc_items_content, " = {}", format_type(ty, krate)).unwrap();
}
write!(assoc_items_content, ";").unwrap();
writeln!(assoc_items_content).unwrap();
}
_ => {}
}
}
}
if has_printable_assoc_items {
if impl_header.contains('\n') && !assoc_items_content.starts_with('\n') {
writeln!(code).unwrap();
}
write!(code, "{}", assoc_items_content).unwrap();
if !code.ends_with('\n') && !assoc_items_content.is_empty() {
writeln!(code).unwrap();
}
} else if impl_header.contains('\n') {
writeln!(code).unwrap();
}
write!(code, "}}").unwrap();
if !has_printable_assoc_items {
return None;
}
Some(code)
}
fn generate_function_code_block(item: &Item, f: &Function, krate: &Crate) -> String {
let name = item.name.as_deref().expect("Function should have a name");
let mut code = String::new();
write!(code, "{}", format_attributes_inline(&item.attrs)).unwrap(); write!(code, "pub ").unwrap();
if f.header.is_const {
write!(code, "const ").unwrap();
}
if f.header.is_async {
write!(code, "async ").unwrap();
}
if f.header.is_unsafe {
write!(code, "unsafe ").unwrap();
}
if !matches!(f.header.abi, Abi::Rust) {
write!(code, "extern \"{:?}\" ", f.header.abi).unwrap(); }
write!(code, "fn {}", name).unwrap();
let generics_str = format_generics_full(&f.generics, krate);
let where_is_multiline = generics_str.contains("where\n");
write!(code, "{}", generics_str).unwrap();
write!(code, "(").unwrap();
let args_str = f
.sig
.inputs
.iter()
.map(|(n, t)| format!("{}: {}", n, format_type(t, krate))) .collect::<Vec<_>>()
.join(", ");
write!(code, "{}", args_str).unwrap();
if f.sig.is_c_variadic {
write!(code, ", ...").unwrap();
}
write!(code, ")").unwrap();
if let Some(output_type) = &f.sig.output {
write!(code, " -> {}", format_type(output_type, krate)).unwrap();
}
if f.has_body {
if where_is_multiline {
write!(code, " {{ ... }}").unwrap(); } else {
write!(code, " {{ ... }}").unwrap(); }
} else if !where_is_multiline {
write!(code, ";").unwrap();
}
code
}
fn format_variant_definition(item: &Item, v: &Variant, krate: &Crate) -> String {
let name = item.name.as_deref().unwrap_or("{Unnamed}");
let attrs_str = format_attributes_inline(&item.attrs); match &v.kind {
VariantKind::Plain => format!("{}{}", attrs_str, name),
VariantKind::Tuple(fields) => {
let types: Vec<String> = fields
.iter()
.filter_map(|opt_id| {
opt_id
.as_ref()
.and_then(|id| krate.index.get(id))
.and_then(|field_item| {
if let ItemEnum::StructField(ty) = &field_item.inner {
Some(format!(
"{}{}", format_attributes_inline(&field_item.attrs), format_type(ty, krate)
))
} else {
None
}
})
})
.collect();
format!("{}{}({})", attrs_str, name, types.join(", "))
}
VariantKind::Struct { fields, .. } => {
let fields_str: Vec<String> = fields
.iter()
.filter_map(|id| {
krate.index.get(id).and_then(|field_item| {
if let ItemEnum::StructField(ty) = &field_item.inner {
let field_name = field_item.name.as_deref().unwrap_or("_");
Some(format!(
"{}{}: {}", format_attributes_inline(&field_item.attrs), field_name,
format_type(ty, krate)
))
} else {
None
}
})
})
.collect();
format!("{}{}{{ {} }}", attrs_str, name, fields_str.join(", "))
}
}
}
fn format_variant_signature(item: &Item, v: &Variant, krate: &Crate) -> String {
let name = item.name.as_deref().unwrap_or("{Unnamed}");
let mut sig = match &v.kind {
VariantKind::Plain => name.to_string(),
VariantKind::Tuple(fields) => {
let types: Vec<String> = fields
.iter()
.filter_map(|opt_id| {
opt_id
.as_ref()
.and_then(|id| krate.index.get(id))
.and_then(|field_item| {
if let ItemEnum::StructField(ty) = &field_item.inner {
Some(format_type(ty, krate)) } else {
None
}
})
})
.collect();
format!("{}({})", name, types.join(", "))
}
VariantKind::Struct { fields, .. } => {
let fields_str: Vec<String> = fields
.iter()
.filter_map(|id| {
krate.index.get(id).and_then(|field_item| {
if let ItemEnum::StructField(ty) = &field_item.inner {
let field_name = field_item.name.as_deref().unwrap_or("_");
Some(format!("{}: {}", field_name, format_type(ty, krate)))
} else {
None
}
})
})
.collect();
format!("{} {{ {} }}", name, fields_str.join(", "))
}
};
if let Some(discr) = &v.discriminant {
write!(sig, " = {}", format_discriminant_expr(discr)).unwrap();
}
sig
}
#[derive(Debug, Default, Clone)] struct ModuleTree {
children: HashMap<Id, Vec<Id>>,
all_modules: HashSet<Id>,
top_level_modules: Vec<Id>,
}
pub struct Printer<'a> {
krate: &'a Crate,
manifest_data: CrateManifestData,
paths: Vec<String>,
crate_extra: Option<CrateExtra>,
include_other: bool,
template_mode: bool,
no_common_traits: bool,
selected_ids: HashSet<Id>,
resolved_modules: HashMap<Id, ResolvedModule>,
graph: IdGraph,
printed_ids: HashMap<Id, String>, output: String,
module_tree: ModuleTree,
doc_path: Vec<usize>,
current_module_path: Vec<String>,
crate_common_traits: HashSet<FormattedTraitImpl>,
all_type_ids_with_impls: HashSet<Id>,
module_common_traits: HashMap<Id, HashSet<FormattedTraitImpl>>,
}
impl<'a> Printer<'a> {
pub fn new(manifest: &'a CargoManifest, krate: &'a Crate) -> Self {
Printer {
krate,
manifest_data: CrateManifestData::from_cargo_manifest(manifest),
paths: Vec::new(),
crate_extra: None,
include_other: false,
template_mode: false,
no_common_traits: false,
selected_ids: HashSet::new(), resolved_modules: HashMap::new(), graph: IdGraph::default(), printed_ids: HashMap::new(), output: String::new(),
module_tree: Self::build_module_tree(krate), doc_path: Vec::new(),
current_module_path: vec![],
crate_common_traits: HashSet::new(), all_type_ids_with_impls: HashSet::new(), module_common_traits: HashMap::new(), }
}
pub fn paths(mut self, paths: &[String]) -> Self {
self.paths = paths.to_vec();
self
}
pub fn crate_extra(mut self, extra: CrateExtra) -> Self {
self.crate_extra = Some(extra);
self
}
pub fn include_other(mut self) -> Self {
self.include_other = true;
self
}
pub fn template_mode(mut self) -> Self {
self.template_mode = true;
self
}
pub fn no_common_traits(mut self) -> Self {
self.no_common_traits = true;
self
}
pub fn print(mut self) -> Result<String> {
self.resolved_modules = graph::build_resolved_module_index(self.krate);
let (selected_ids, graph) =
graph::select_items(self.krate, &self.paths, &self.resolved_modules)?;
self.selected_ids = selected_ids;
self.graph = graph;
info!(
"Generating documentation for {} selected items.",
self.selected_ids.len()
);
if self.selected_ids.is_empty()
&& self
.crate_extra
.as_ref()
.is_none_or(|ce| ce.examples.is_empty())
{
return Ok("No items selected for documentation and no examples found.".to_string());
}
let (crate_common_traits, all_type_ids_with_impls) = Self::calculate_crate_common_traits(
self.krate,
&self.selected_ids, self.no_common_traits,
&self, );
self.crate_common_traits = crate_common_traits;
self.all_type_ids_with_impls = all_type_ids_with_impls;
Ok(self.finalize())
}
fn calculate_crate_common_traits(
krate: &Crate,
selected_ids: &HashSet<Id>,
no_common_traits: bool,
printer: &Printer,
) -> (HashSet<FormattedTraitImpl>, HashSet<Id>) {
let mut all_type_ids_with_impls = HashSet::new();
if no_common_traits {
for item in krate.index.values() {
if let ItemEnum::Impl(imp) = &item.inner
&& let Some(for_type_id) = get_type_id(&imp.for_)
&& selected_ids.contains(&for_type_id)
{
all_type_ids_with_impls.insert(for_type_id);
}
}
return (HashSet::new(), all_type_ids_with_impls);
}
let mut trait_format_counts: HashMap<Id, HashMap<FormattedTraitImpl, usize>> =
HashMap::new();
for item in krate.index.values() {
if let ItemEnum::Impl(imp) = &item.inner
&& let Some(for_type_id) = get_type_id(&imp.for_)
&& selected_ids.contains(&for_type_id)
{
all_type_ids_with_impls.insert(for_type_id);
if let Some(trait_path) = &imp.trait_ {
let norm_impl =
FormattedTraitImpl::from_impl(imp, None, trait_path, krate, printer);
*trait_format_counts
.entry(trait_path.id)
.or_default()
.entry(norm_impl)
.or_insert(0) += 1;
}
}
}
debug!(
"Found {} types with trait implementations for crate-level common trait calculation.",
all_type_ids_with_impls.len()
);
if all_type_ids_with_impls.len() < 2 {
debug!("Too few trait implementations, skipping crate 'Common Traits'");
return (HashSet::new(), all_type_ids_with_impls);
}
let mut common_traits_set = HashSet::new();
if all_type_ids_with_impls.is_empty() {
return (common_traits_set, all_type_ids_with_impls);
}
let type_count_threshold = (all_type_ids_with_impls.len() as f32 * 0.5).ceil() as usize;
debug!(
"Crate common trait threshold (types implementing): {} (out of {} types)",
type_count_threshold,
all_type_ids_with_impls.len()
);
for (trait_id, format_map) in trait_format_counts {
let total_implementations_for_trait = format_map.values().sum::<usize>();
if total_implementations_for_trait < type_count_threshold {
trace!(
"Trait ID {:?} not common enough ({} implementations, need {})",
trait_id, total_implementations_for_trait, type_count_threshold
);
continue;
}
let mut has_positive = false;
let mut has_negative = false;
for f_impl in format_map.keys() {
if f_impl.is_negative {
has_negative = true;
} else {
has_positive = true;
}
}
if has_positive && has_negative {
warn!(
"Trait ID {:?} has mixed positive and negative implementations, cannot be common.",
trait_id
);
continue;
}
if format_map.len() == 1 {
if let Some(formatted_impl) = format_map.keys().next() {
common_traits_set.insert(formatted_impl.clone());
debug!(
"Identified crate-common trait (single format): {:?} for trait ID {:?}",
formatted_impl.formatted_markdown_list_entry, trait_id
);
}
} else {
let simple_format_count = format_map
.iter()
.find(|(f_impl, _)| f_impl.category == TraitImplCategory::Simple)
.map_or(0, |(_, count)| *count);
if simple_format_count * 2 > total_implementations_for_trait {
if let Some(simple_impl) = format_map
.keys()
.find(|f_impl| f_impl.category == TraitImplCategory::Simple)
{
common_traits_set.insert(simple_impl.clone());
debug!(
"Identified crate-common trait (simple format predominant): {:?} for trait ID {:?}",
simple_impl.formatted_markdown_list_entry, trait_id
);
}
} else {
trace!(
"Trait ID {:?} has multiple formats, but Simple is not predominant ({} of {}).",
trait_id, simple_format_count, total_implementations_for_trait
);
}
}
}
(common_traits_set, all_type_ids_with_impls)
}
fn calculate_module_common_traits(&self, module_id: &Id) -> HashSet<FormattedTraitImpl> {
if self.no_common_traits {
return HashSet::new();
}
let mut module_common_traits = self.crate_common_traits.clone();
let mut module_types_considered = HashSet::new();
if let Some(resolved_mod) = self.resolved_modules.get(module_id) {
for item_id_in_mod in &resolved_mod.items {
if let Some(item) = self.krate.index.get(item_id_in_mod)
&& matches!(
item.inner,
ItemEnum::Struct(_)
| ItemEnum::Enum(_)
| ItemEnum::Union(_)
| ItemEnum::Primitive(_)
)
&& self.selected_ids.contains(item_id_in_mod)
{
let has_impls = self.krate.index.values().any(|idx_item| {
if let ItemEnum::Impl(imp) = &idx_item.inner
&& let Some(for_id) = get_type_id(&imp.for_)
{
return for_id == *item_id_in_mod;
}
false
});
if has_impls {
module_types_considered.insert(*item_id_in_mod);
}
}
}
}
let module_types_with_impls_count = module_types_considered.len();
if module_types_with_impls_count <= 1 {
return module_common_traits;
}
let mut trait_format_counts: HashMap<Id, HashMap<FormattedTraitImpl, usize>> =
HashMap::new();
for item_id_in_mod in &module_types_considered {
for krate_item in self.krate.index.values() {
if let ItemEnum::Impl(imp) = &krate_item.inner
&& let Some(for_id) = get_type_id(&imp.for_)
&& for_id == *item_id_in_mod
&& let Some(trait_path) = &imp.trait_
{
let norm_impl =
FormattedTraitImpl::from_impl(imp, None, trait_path, self.krate, self);
*trait_format_counts
.entry(trait_path.id)
.or_default()
.entry(norm_impl)
.or_insert(0) += 1;
}
}
}
let type_count_threshold = (module_types_with_impls_count as f32 * 0.5).ceil() as usize;
debug!(
"Module {:?} common trait threshold (types implementing): {} (out of {} types in module)",
module_id, type_count_threshold, module_types_with_impls_count
);
for (trait_id, format_map) in trait_format_counts {
let total_implementations_for_trait = format_map.values().sum::<usize>();
if total_implementations_for_trait < type_count_threshold {
trace!(
"Module {:?} trait ID {:?} not common enough ({} implementations, need {})",
module_id, trait_id, total_implementations_for_trait, type_count_threshold
);
continue;
}
let mut has_positive = false;
let mut has_negative = false;
for f_impl in format_map.keys() {
if f_impl.is_negative {
has_negative = true;
} else {
has_positive = true;
}
}
if has_positive && has_negative {
warn!(
"Module {:?} trait ID {:?} has mixed positive and negative implementations, cannot be common.",
module_id, trait_id
);
continue;
}
if format_map.len() == 1 {
if let Some(formatted_impl) = format_map.keys().next()
&& module_common_traits.insert(formatted_impl.clone())
{
debug!(
"Identified module-specific common trait (single format) for {:?}: {:?} for trait ID {:?}",
self.krate
.paths
.get(module_id)
.map(|p| p.path.join("::"))
.unwrap_or_default(),
formatted_impl.formatted_markdown_list_entry,
trait_id
);
}
} else {
let simple_format_count = format_map
.iter()
.find(|(f_impl, _)| f_impl.category == TraitImplCategory::Simple)
.map_or(0, |(_, count)| *count);
if simple_format_count * 2 > total_implementations_for_trait {
if let Some(simple_impl) = format_map
.keys()
.find(|f_impl| f_impl.category == TraitImplCategory::Simple)
&& module_common_traits.insert(simple_impl.clone())
{
debug!(
"Identified module-specific common trait (simple format predominant) for {:?}: {:?} for trait ID {:?}",
self.krate
.paths
.get(module_id)
.map(|p| p.path.join("::"))
.unwrap_or_default(),
simple_impl.formatted_markdown_list_entry,
trait_id
);
}
} else {
trace!(
"Module {:?} trait ID {:?} has multiple formats, but Simple is not predominant ({} of {}).",
module_id, trait_id, simple_format_count, total_implementations_for_trait
);
}
}
}
module_common_traits
}
fn build_module_tree(krate: &'a Crate) -> ModuleTree {
let mut tree = ModuleTree::default();
let mut parent_map: HashMap<Id, Id> = HashMap::new();
for (id, item) in &krate.index {
if let ItemEnum::Module(module_data) = &item.inner {
tree.all_modules.insert(*id);
let mut children = Vec::new();
for child_id in &module_data.items {
if let Some(child_item) = krate.index.get(child_id)
&& let ItemEnum::Module(_) = child_item.inner
{
children.push(*child_id);
parent_map.insert(*child_id, *id);
}
}
if !children.is_empty() {
children.sort_by_key(|child_id| {
krate
.paths
.get(child_id)
.map(|p| p.path.join("::"))
.unwrap_or_default()
});
tree.children.insert(*id, children);
}
}
}
for module_id in &tree.all_modules {
if *module_id != krate.root && !parent_map.contains_key(module_id) {
tree.top_level_modules.push(*module_id);
}
}
tree.top_level_modules.sort_by_key(|id| {
krate
.paths
.get(id)
.map(|p| p.path.join("::"))
.unwrap_or_default()
});
tree
}
fn get_current_header_level(&self) -> usize {
self.doc_path.len() + 1 }
fn get_header_prefix(&self) -> String {
let level = self.get_current_header_level();
if self.doc_path.is_empty() || level < 2 {
return String::new(); }
if level == 2 {
format!("{}:", self.doc_path.last().unwrap_or(&0))
} else {
self.doc_path
.iter()
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join(".")
+ ":"
}
}
fn get_template_marker(&self) -> String {
if self.doc_path.is_empty() {
"{{MISSING_DOCS}}".to_string()
} else {
format!(
"{{{{MISSING_DOCS_{}}}}}", self.doc_path
.iter()
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join("_")
)
}
}
fn post_increment_current_level(&mut self) {
if let Some(last) = self.doc_path.last_mut() {
*last += 1;
} else {
warn!("Attempted to increment document path level when path was empty.");
}
}
fn push_level(&mut self) {
self.doc_path.push(1);
}
fn pop_level(&mut self) {
self.doc_path.pop();
}
fn get_item_kind(&self, id: &Id) -> Option<ItemKind> {
self.krate
.index
.get(id)
.map(Printer::infer_item_kind) .or_else(|| self.krate.paths.get(id).map(|summary| summary.kind))
}
pub(crate) fn infer_item_kind(item: &Item) -> ItemKind {
match item.inner {
ItemEnum::Module(_) => ItemKind::Module,
ItemEnum::ExternCrate { .. } => ItemKind::ExternCrate,
ItemEnum::Use { .. } => ItemKind::Use, ItemEnum::Union(_) => ItemKind::Union,
ItemEnum::Struct(_) => ItemKind::Struct,
ItemEnum::StructField(_) => ItemKind::StructField,
ItemEnum::Enum(_) => ItemKind::Enum,
ItemEnum::Variant(_) => ItemKind::Variant,
ItemEnum::Function(_) => ItemKind::Function,
ItemEnum::Trait(_) => ItemKind::Trait,
ItemEnum::TraitAlias(_) => ItemKind::TraitAlias,
ItemEnum::Impl { .. } => ItemKind::Impl,
ItemEnum::TypeAlias(_) => ItemKind::TypeAlias,
ItemEnum::Constant { .. } => ItemKind::Constant, ItemEnum::Static(_) => ItemKind::Static,
ItemEnum::ExternType => ItemKind::ExternType, ItemEnum::Macro(_) => ItemKind::Macro,
ItemEnum::ProcMacro(ref pm) => match pm.kind {
rustdoc_types::MacroKind::Bang => ItemKind::Macro, rustdoc_types::MacroKind::Attr => ItemKind::ProcAttribute,
rustdoc_types::MacroKind::Derive => ItemKind::ProcDerive,
},
ItemEnum::Primitive(_) => ItemKind::Primitive,
ItemEnum::AssocConst { .. } => ItemKind::AssocConst,
ItemEnum::AssocType { .. } => ItemKind::AssocType,
}
}
fn print_docs(&mut self, item: &Item) {
let header_level = self.get_current_header_level(); match (&item.docs, self.template_mode) {
(Some(_), true) => {
let marker = self.get_template_marker();
writeln!(self.output, "{}\n", marker).unwrap();
}
(Some(docs), false) => {
if !docs.trim().is_empty() {
let adjusted_docs = adjust_markdown_headers(docs.trim(), header_level);
writeln!(self.output, "{}\n", adjusted_docs).unwrap();
}
}
(None, _) => {}
}
}
fn print_item_details(&mut self, id: &Id) -> bool {
if !self.selected_ids.contains(id) {
return false; }
let Some(item) = self.krate.index.get(id) else {
warn!("Item details for ID {id:?} not found in index");
return false;
};
if matches!(item.inner, ItemEnum::Use(_) | ItemEnum::Module(_)) {
return false;
}
let item_header_level = self.get_current_header_level();
let header_prefix = self.get_header_prefix();
let declaration = generate_item_declaration(item, self.krate, &self.current_module_path);
if let Some(existing_prefix) = self.printed_ids.get(id) {
writeln!(
self.output,
"\n{} {} `{}` (See section {} for details)\n",
"#".repeat(item_header_level),
header_prefix,
declaration,
existing_prefix
)
.unwrap();
return false; }
self.printed_ids.insert(*id, header_prefix.clone());
writeln!(
self.output,
"\n{} {} `{}`\n", "#".repeat(item_header_level),
header_prefix,
declaration
)
.unwrap();
self.push_level();
let code_block = match &item.inner {
ItemEnum::Struct(s) => Some(generate_struct_code_block(item, s, self.krate)),
ItemEnum::Enum(e) => Some(generate_enum_code_block(item, e, self.krate)),
ItemEnum::Union(u) => Some(generate_union_code_block(item, u, self.krate)),
ItemEnum::Trait(t) => Some(generate_trait_code_block(item, t, self.krate)),
ItemEnum::Function(f) => {
let has_attrs = f.header.is_const
|| f.header.is_async
|| f.header.is_unsafe
|| !matches!(f.header.abi, Abi::Rust)
|| !item.attrs.is_empty(); let has_where = !f.generics.where_predicates.is_empty();
if has_attrs || has_where {
Some(generate_function_code_block(item, f, self.krate))
} else {
None }
}
_ => None,
};
if let Some(code) = code_block {
writeln!(self.output, "```rust\n{}\n```\n", code).unwrap();
}
let has_stripped = matches!(
&item.inner,
ItemEnum::Struct(Struct {
kind: StructKind::Plain {
has_stripped_fields: true,
..
},
..
})
);
if has_stripped {
writeln!(self.output, "_[Private fields hidden]_\n").unwrap();
}
self.print_docs(item);
match &item.inner {
ItemEnum::Struct(s) => self.print_struct_fields(item, s),
ItemEnum::Enum(e) => self.print_enum_variants(item, e),
ItemEnum::Union(u) => self.print_union_fields(item, u),
ItemEnum::Trait(t) => self.print_trait_associated_items(item, t),
_ => {}
}
let impl_ids = match &item.inner {
ItemEnum::Struct(s) => Some(&s.impls),
ItemEnum::Enum(e) => Some(&e.impls),
ItemEnum::Trait(t) => Some(&t.implementations), ItemEnum::Union(u) => Some(&u.impls),
ItemEnum::Primitive(p) => Some(&p.impls),
_ => None,
};
if let Some(ids) = impl_ids {
match &item.inner {
ItemEnum::Trait(_) => self.print_trait_implementors(ids, item),
_ => self.print_item_implementations(ids, item),
}
}
self.pop_level();
true }
#[allow(unused)]
fn has_documented_fields(&self, s: &Struct) -> bool {
let field_ids = match &s.kind {
StructKind::Plain { fields, .. } => fields.clone(),
StructKind::Tuple(fields) => fields.iter().filter_map(|opt_id| *opt_id).collect(),
StructKind::Unit => vec![],
};
field_ids.iter().any(|field_id| {
self.selected_ids.contains(field_id)
&& self.krate.index.get(field_id).is_some_and(|item| {
(self.template_mode && item.docs.is_some()) || has_docs(item)
})
})
}
fn print_struct_fields(&mut self, _item: &Item, s: &Struct) {
let all_field_ids: Vec<Id> = match &s.kind {
StructKind::Plain { fields, .. } => fields.clone(),
StructKind::Tuple(fields) => fields.iter().filter_map(|opt_id| *opt_id).collect(),
StructKind::Unit => vec![],
};
let mut has_printable_field = false;
for field_id in &all_field_ids {
if !self.selected_ids.contains(field_id) {
continue; }
if let Some(item) = self.krate.index.get(field_id) {
let field_has_printable_docs =
(self.template_mode && item.docs.is_some()) || has_docs(item);
if field_has_printable_docs {
if !self.printed_ids.contains_key(field_id) {
has_printable_field = true;
}
} else {
self.printed_ids.insert(*field_id, self.get_header_prefix());
}
} else {
self.printed_ids.insert(*field_id, self.get_header_prefix());
}
}
if !has_printable_field {
return;
}
let fields_header_level = self.get_current_header_level();
let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Fields\n", "#".repeat(fields_header_level),
header_prefix
)
.unwrap();
self.push_level();
for field_id in &all_field_ids {
if self.print_field_details(field_id) {
self.post_increment_current_level();
}
}
self.pop_level();
self.post_increment_current_level();
}
fn print_union_fields(&mut self, _item: &Item, u: &Union) {
let all_field_ids: Vec<Id> = u.fields.clone();
let mut has_printable_field = false;
for field_id in &all_field_ids {
if !self.selected_ids.contains(field_id) {
continue;
}
if let Some(item) = self.krate.index.get(field_id) {
let field_has_printable_docs =
(self.template_mode && item.docs.is_some()) || has_docs(item);
if field_has_printable_docs {
if !self.printed_ids.contains_key(field_id) {
has_printable_field = true;
}
} else {
self.printed_ids.insert(*field_id, self.get_header_prefix());
}
} else {
self.printed_ids.insert(*field_id, self.get_header_prefix());
}
}
if !has_printable_field && !u.has_stripped_fields {
return;
}
let fields_header_level = self.get_current_header_level();
let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Fields\n",
"#".repeat(fields_header_level),
header_prefix
)
.unwrap();
self.push_level();
for field_id in &all_field_ids {
if self.print_field_details(field_id) {
self.post_increment_current_level();
}
}
if u.has_stripped_fields {
writeln!(self.output, "_[Private fields hidden]_").unwrap();
}
self.pop_level();
self.post_increment_current_level();
}
fn print_field_details(&mut self, field_id: &Id) -> bool {
if !self.selected_ids.contains(field_id) || self.printed_ids.contains_key(field_id) {
return false; }
if let Some(item) = self.krate.index.get(field_id) {
let field_has_printable_docs =
(self.template_mode && item.docs.is_some()) || has_docs(item);
if !field_has_printable_docs {
return false;
}
let header_prefix = self.get_header_prefix();
self.printed_ids.insert(*field_id, header_prefix.clone());
if let ItemEnum::StructField(_field_type) = &item.inner {
let name = item.name.as_deref().unwrap_or("_");
let field_header_level = self.get_current_header_level();
writeln!(
self.output,
"{} {} `{}`\n", "#".repeat(field_header_level),
header_prefix,
name
)
.unwrap();
self.print_docs(item);
return true; }
}
self.printed_ids.insert(*field_id, self.get_header_prefix());
false
}
fn print_variant_field_details(&mut self, field_id: &Id) -> bool {
if !self.selected_ids.contains(field_id) || self.printed_ids.contains_key(field_id) {
return false; }
if let Some(item) = self.krate.index.get(field_id) {
let field_has_printable_docs =
(self.template_mode && item.docs.is_some()) || has_docs(item);
if !field_has_printable_docs {
return false;
}
let header_prefix = self.get_header_prefix();
self.printed_ids.insert(*field_id, header_prefix.clone());
if let ItemEnum::StructField(_field_type) = &item.inner {
let name = item.name.as_deref().unwrap_or("_"); let field_header_level = self.get_current_header_level();
let header_name = if name == "_" || name.chars().all(|c| c.is_ascii_digit()) {
format!("Field {}", name)
} else {
name.to_string()
};
writeln!(
self.output,
"{} {} `{}`\n", "#".repeat(field_header_level),
header_prefix,
header_name
)
.unwrap();
self.print_docs(item);
self.post_increment_current_level();
return true; }
}
self.printed_ids.insert(*field_id, self.get_header_prefix());
false
}
#[allow(unused)]
fn has_printable_variants(&self, e: &Enum) -> bool {
e.variants.iter().any(|variant_id| {
if !self.selected_ids.contains(variant_id) {
return false;
}
if let Some(item) = self.krate.index.get(variant_id) {
if (self.template_mode && item.docs.is_some()) || has_docs(item) {
return true;
}
if let ItemEnum::Variant(v) = &item.inner {
let field_ids: Vec<Id> = match &v.kind {
VariantKind::Plain => vec![],
VariantKind::Tuple(fields) => {
fields.iter().filter_map(|opt_id| *opt_id).collect()
}
VariantKind::Struct { fields, .. } => fields.clone(),
};
for field_id in field_ids {
if self.selected_ids.contains(&field_id)
&& let Some(f_item) = self.krate.index.get(&field_id)
&& ((self.template_mode && f_item.docs.is_some()) || has_docs(f_item))
{
return true;
}
}
}
}
false
})
}
fn print_enum_variants(&mut self, _item: &Item, e: &Enum) {
let mut has_printable_variant_or_field = false;
let mut printed_any_variant = false;
for variant_id in &e.variants {
if !self.selected_ids.contains(variant_id) {
continue; }
if let Some(item) = self.krate.index.get(variant_id) {
let variant_has_printable_docs =
(self.template_mode && item.docs.is_some()) || has_docs(item);
let mut variant_has_printable_field = false;
if let ItemEnum::Variant(v) = &item.inner {
let field_ids: Vec<Id> = match &v.kind {
VariantKind::Plain => vec![],
VariantKind::Tuple(fields) => {
fields.iter().filter_map(|opt_id| *opt_id).collect()
}
VariantKind::Struct { fields, .. } => fields.clone(),
};
for field_id in field_ids {
if self.selected_ids.contains(&field_id) {
let field_has_printable_docs =
self.krate.index.get(&field_id).is_some_and(|f_item| {
(self.template_mode && f_item.docs.is_some())
|| has_docs(f_item)
});
if field_has_printable_docs {
if !self.printed_ids.contains_key(&field_id) {
variant_has_printable_field = true;
}
} else {
self.printed_ids.insert(field_id, self.get_header_prefix());
}
} else {
self.printed_ids.insert(field_id, self.get_header_prefix());
}
}
}
if variant_has_printable_docs || variant_has_printable_field {
if !self.printed_ids.contains_key(variant_id) {
has_printable_variant_or_field = true;
}
} else {
self.printed_ids
.insert(*variant_id, self.get_header_prefix());
}
} else {
self.printed_ids
.insert(*variant_id, self.get_header_prefix());
}
}
if !has_printable_variant_or_field && !e.has_stripped_variants {
return;
}
let variants_header_level = self.get_current_header_level();
let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Variants\n", "#".repeat(variants_header_level),
header_prefix
)
.unwrap();
self.push_level();
for variant_id in &e.variants {
if self.print_variant_details(variant_id) {
printed_any_variant = true;
}
}
if e.has_stripped_variants {
if printed_any_variant {
writeln!(self.output).unwrap();
}
writeln!(self.output, "_[Private variants hidden]_").unwrap();
}
self.pop_level(); self.post_increment_current_level();
}
fn print_variant_details(&mut self, variant_id: &Id) -> bool {
if !self.selected_ids.contains(variant_id) {
if self.printed_ids.contains_key(variant_id) {
return false;
}
} else if self.printed_ids.contains_key(variant_id) {
return false;
}
if let Some(item) = self.krate.index.get(variant_id)
&& let ItemEnum::Variant(variant_data) = &item.inner
{
let variant_has_printable_docs =
(self.template_mode && item.docs.is_some()) || has_docs(item);
let mut printable_fields = Vec::new();
let mut printed_any_field = false;
let (field_ids, stripped) = match &variant_data.kind {
VariantKind::Plain => (vec![], false),
VariantKind::Tuple(fields) => {
(fields.iter().filter_map(|opt_id| *opt_id).collect(), false)
}
VariantKind::Struct {
fields,
has_stripped_fields: s,
} => (fields.clone(), *s),
};
for field_id in &field_ids {
if self.selected_ids.contains(field_id) {
let field_has_printable_docs =
self.krate.index.get(field_id).is_some_and(|f_item| {
(self.template_mode && f_item.docs.is_some()) || has_docs(f_item)
});
if field_has_printable_docs && !self.printed_ids.contains_key(field_id) {
printable_fields.push(*field_id);
} else {
self.printed_ids.insert(*field_id, self.get_header_prefix());
}
} else {
self.printed_ids.insert(*field_id, self.get_header_prefix());
}
}
if !variant_has_printable_docs && printable_fields.is_empty() {
self.printed_ids
.insert(*variant_id, self.get_header_prefix());
return false;
}
let header_prefix = self.get_header_prefix();
self.printed_ids.insert(*variant_id, header_prefix.clone());
let signature = format_variant_signature(item, variant_data, self.krate);
let variant_header_level = self.get_current_header_level();
writeln!(
self.output,
"{} {} `{}`\n", "#".repeat(variant_header_level),
header_prefix,
signature
)
.unwrap();
self.push_level();
self.print_docs(item);
if !printable_fields.is_empty() || stripped {
let field_section_level = self.get_current_header_level();
let fields_header_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Fields\n", "#".repeat(field_section_level),
fields_header_prefix
)
.unwrap();
self.push_level();
for field_id in printable_fields {
if self.print_variant_field_details(&field_id) {
printed_any_field = true;
}
}
if stripped {
if printed_any_field {
writeln!(self.output).unwrap(); }
writeln!(self.output, "_[Private fields hidden]_").unwrap();
}
self.pop_level();
}
self.pop_level();
self.post_increment_current_level();
return true; }
self.printed_ids
.insert(*variant_id, self.get_header_prefix());
false
}
fn print_trait_associated_items(&mut self, _trait_item: &Item, t: &Trait) {
let mut required_types = Vec::new();
let mut required_methods = Vec::new();
let mut provided_methods = Vec::new();
let mut has_printable_assoc_item = false;
for item_id in &t.items {
if !self.selected_ids.contains(item_id) {
continue;
}
if !self.printed_ids.contains_key(item_id) {
self.printed_ids.insert(*item_id, self.get_header_prefix());
}
if let Some(assoc_item) = self.krate.index.get(item_id) {
let item_has_printable_docs =
(self.template_mode && assoc_item.docs.is_some()) || has_docs(assoc_item);
if item_has_printable_docs {
has_printable_assoc_item = true;
}
match &assoc_item.inner {
ItemEnum::AssocType { .. } => {
required_types.push((*item_id, item_has_printable_docs));
}
ItemEnum::Function(f) => {
if !f.has_body {
required_methods.push((*item_id, item_has_printable_docs));
} else {
provided_methods.push((*item_id, item_has_printable_docs));
}
}
ItemEnum::AssocConst { .. } => {
required_types.push((*item_id, item_has_printable_docs));
}
_ => {} }
}
}
if !has_printable_assoc_item {
return;
}
required_types.sort_by_key(|(id, _)| self.krate.index.get(id).and_then(|i| i.name.clone()));
required_methods
.sort_by_key(|(id, _)| self.krate.index.get(id).and_then(|i| i.name.clone()));
provided_methods
.sort_by_key(|(id, _)| self.krate.index.get(id).and_then(|i| i.name.clone()));
if required_types.iter().any(|(_, has_docs)| *has_docs) {
let sub_level = self.get_current_header_level();
let sub_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Required Associated Types\n",
"#".repeat(sub_level),
sub_prefix
)
.unwrap();
self.push_level();
for (id, has_docs) in required_types {
if has_docs {
self.print_associated_item_summary(&id);
}
}
self.pop_level();
self.post_increment_current_level();
}
if required_methods.iter().any(|(_, has_docs)| *has_docs) {
let sub_level = self.get_current_header_level();
let sub_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Required Methods\n",
"#".repeat(sub_level),
sub_prefix
)
.unwrap();
self.push_level();
for (id, has_docs) in required_methods {
if has_docs {
self.print_associated_item_summary(&id);
}
}
self.pop_level();
self.post_increment_current_level();
}
if provided_methods.iter().any(|(_, has_docs)| *has_docs) {
let sub_level = self.get_current_header_level();
let sub_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Provided Methods\n",
"#".repeat(sub_level),
sub_prefix
)
.unwrap();
self.push_level();
for (id, has_docs) in provided_methods {
if has_docs {
self.print_associated_item_summary(&id);
}
}
self.pop_level();
self.post_increment_current_level();
}
}
fn generate_associated_item_summary(&mut self, assoc_item_id: &Id) -> Option<String> {
if !self.selected_ids.contains(assoc_item_id) {
return None;
}
if let Some(item) = self.krate.index.get(assoc_item_id) {
let mut summary = String::new();
if let ItemEnum::Function(f) = &item.inner {
let has_attrs = f.header.is_const
|| f.header.is_async
|| f.header.is_unsafe
|| !matches!(f.header.abi, Abi::Rust)
|| !item.attrs.is_empty(); let has_where = !f.generics.where_predicates.is_empty();
if has_attrs || has_where {
let code = generate_function_code_block(item, f, self.krate);
writeln!(summary, "```rust\n{}\n```\n", code).unwrap();
}
}
let mut temp_printer = self.clone_with_new_output();
temp_printer.doc_path = self.doc_path.clone();
temp_printer.print_docs(item);
write!(summary, "{}", temp_printer.output).unwrap();
match &item.inner {
ItemEnum::AssocConst { type_, value } => {
writeln!(summary, "_Type: `{}`_", format_type(type_, self.krate)).unwrap();
if let Some(val) = value {
writeln!(summary, "_Default: `{}`_\n", val).unwrap(); }
}
ItemEnum::AssocType { bounds, type_, .. } => {
if !bounds.is_empty() {
let bounds_str = bounds
.iter()
.map(|b| format_generic_bound(b, self.krate))
.collect::<Vec<_>>()
.join(" + ");
writeln!(summary, "_Bounds: `{}`_", bounds_str).unwrap();
}
if let Some(ty) = type_ {
writeln!(summary, "_Default: `{}`_\n", format_type(ty, self.krate))
.unwrap(); }
}
_ => {}
}
Some(summary)
} else {
None
}
}
fn print_associated_item_summary(&mut self, assoc_item_id: &Id) {
if let Some(item) = self.krate.index.get(assoc_item_id) {
if let Some(summary) = self.generate_associated_item_summary(assoc_item_id) {
let declaration =
generate_item_declaration(item, self.krate, &self.current_module_path);
let assoc_item_header_level = self.get_current_header_level();
let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} `{}`\n", "#".repeat(assoc_item_header_level),
header_prefix,
declaration
)
.unwrap();
if !summary.trim().is_empty() {
writeln!(self.output, "{}", summary.trim()).unwrap();
}
writeln!(self.output).unwrap();
self.post_increment_current_level();
}
}
}
fn format_trait_list(&mut self, traits_to_format: &[FormattedTraitImpl]) -> String {
if traits_to_format.is_empty() {
return String::new();
}
let mut output = String::new();
let mut simple_impls = Vec::new();
let mut generic_or_complex_impls = Vec::new();
let mut auto_traits = Vec::new();
let mut blanket_impls = Vec::new();
for norm_trait in traits_to_format {
match norm_trait.category {
TraitImplCategory::Simple => simple_impls.push(norm_trait),
TraitImplCategory::GenericOrComplex => generic_or_complex_impls.push(norm_trait),
TraitImplCategory::Auto => auto_traits.push(norm_trait),
TraitImplCategory::Blanket => blanket_impls.push(norm_trait),
}
}
simple_impls.sort_by_key(|t| &t.formatted_markdown_list_entry);
generic_or_complex_impls.sort_by_key(|t| &t.formatted_markdown_list_entry);
auto_traits.sort_by_key(|t| &t.formatted_markdown_list_entry);
blanket_impls.sort_by_key(|t| &t.formatted_markdown_list_entry);
self.push_level();
let mut preceding_section = false;
let mut print_section =
|traits: &[&FormattedTraitImpl], current_output: &mut String, _section_name: &str| {
if !traits.is_empty() {
if preceding_section {
writeln!(current_output).unwrap();
}
for norm_trait in traits {
writeln!(
current_output,
"{}",
norm_trait.formatted_markdown_list_entry
)
.unwrap();
if let Some((trait_impl, impl_id)) = norm_trait.get_impl_data(self.krate) {
self.printed_ids.insert(impl_id, self.get_header_prefix());
for assoc_item_id in &trait_impl.items {
if self.selected_ids.contains(assoc_item_id) {
self.printed_ids
.insert(*assoc_item_id, self.get_header_prefix());
}
}
}
self.post_increment_current_level();
}
preceding_section = true;
}
};
print_section(&simple_impls, &mut output, "Simple");
print_section(&generic_or_complex_impls, &mut output, "Generic or Complex");
print_section(&auto_traits, &mut output, "Auto");
print_section(&blanket_impls, &mut output, "Blanket");
self.pop_level();
output
}
fn print_item_implementations(&mut self, impl_ids: &[Id], target_item: &Item) {
let target_item_id = target_item.id;
let target_name = target_item
.name
.as_deref()
.unwrap_or(match &target_item.inner {
ItemEnum::Primitive(Primitive { name, .. }) => name.as_str(),
_ => "{unknown_item_type}",
});
let mut item_specific_impl_data = Vec::new();
for impl_id in impl_ids {
if let Some(impl_item) = self.krate.index.get(impl_id)
&& self.selected_ids.contains(&impl_item.id)
&& let ItemEnum::Impl(imp) = &impl_item.inner
{
if get_type_id(&imp.for_) == Some(target_item_id) {
item_specific_impl_data.push((impl_item, imp.clone()));
}
}
}
let inherent_impl_items: Vec<_> = item_specific_impl_data
.iter()
.filter(|(_, imp)| imp.trait_.is_none())
.collect();
if !inherent_impl_items.is_empty() {
for (impl_item, imp) in inherent_impl_items {
if self.printed_ids.contains_key(&impl_item.id) {
continue;
}
self.print_impl_block_details(impl_item, imp);
}
}
let trait_impl_data: Vec<FormattedTraitImpl> = item_specific_impl_data
.iter()
.filter_map(|(impl_item, imp)| {
if self.printed_ids.contains_key(&impl_item.id) {
return None; }
imp.trait_.as_ref().map(|tp| {
FormattedTraitImpl::from_impl(imp, Some(impl_item.id), tp, self.krate, self)
})
})
.collect();
if trait_impl_data.is_empty() {
return;
}
let current_module_id = self
.current_module_path
.last()
.and_then(|mod_name| {
self.resolved_modules
.values()
.find(|rm| {
self.krate
.paths
.get(&rm.id)
.is_some_and(|p| p.path.last() == Some(mod_name))
})
.map(|rm| rm.id)
})
.unwrap_or(self.krate.root);
let module_common_traits = self
.module_common_traits
.get(¤t_module_id)
.cloned()
.unwrap_or_default();
let mut non_common_trait_impls = Vec::new();
let mut missing_module_common_trait_paths = HashSet::new();
for common_trait_format in &module_common_traits {
missing_module_common_trait_paths.insert(common_trait_format.trait_id);
}
for norm_trait in &trait_impl_data {
if module_common_traits
.iter()
.any(|ct| ct.trait_id == norm_trait.trait_id)
{
missing_module_common_trait_paths.remove(&norm_trait.trait_id);
if !module_common_traits.contains(norm_trait) {
non_common_trait_impls.push(norm_trait.clone());
} else {
if let Some((trait_impl, impl_id)) = norm_trait.get_impl_data(self.krate) {
self.printed_ids.insert(impl_id, self.get_header_prefix());
for assoc_item_id in &trait_impl.items {
if self.selected_ids.contains(assoc_item_id) {
self.printed_ids
.insert(*assoc_item_id, self.get_header_prefix());
}
}
}
}
} else {
non_common_trait_impls.push(norm_trait.clone());
}
}
if !non_common_trait_impls.is_empty() || !missing_module_common_trait_paths.is_empty() {
let trait_impl_header_level = self.get_current_header_level();
let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Trait Implementations for `{}`\n",
"#".repeat(trait_impl_header_level),
header_prefix,
target_name
)
.unwrap();
if !missing_module_common_trait_paths.is_empty() {
let mut sorted_missing_common_trait_names: Vec<String> =
missing_module_common_trait_paths
.iter()
.filter_map(|trait_id| {
module_common_traits
.iter()
.find(|ct| ct.trait_id == *trait_id)
.map(|ct| {
ct.formatted_markdown_list_entry
.split_once("`")
.and_then(|(_, rest)| rest.split_once("`"))
.map(|(path, _)| path.to_string())
.unwrap_or_else(|| {
format_id_path_canonical(trait_id, self.krate)
}) })
})
.collect();
sorted_missing_common_trait_names.sort_unstable();
if !sorted_missing_common_trait_names.is_empty() {
writeln!(
self.output,
"**(Note: Does not implement common trait(s): `{}`)**\n",
sorted_missing_common_trait_names.join("`, `")
)
.unwrap();
}
}
let formatted_list = self.format_trait_list(&non_common_trait_impls);
if !formatted_list.is_empty() {
write!(self.output, "{}", formatted_list).unwrap();
}
self.post_increment_current_level();
}
}
fn print_trait_implementors(&mut self, impl_ids: &[Id], _trait_item: &Item) {
let implementors: Vec<&Item> = impl_ids
.iter()
.filter_map(|id| self.krate.index.get(id))
.filter(|item| {
self.selected_ids.contains(&item.id) && matches!(item.inner, ItemEnum::Impl(_))
})
.collect();
if !implementors.is_empty() {
let implementors_section_level = self.get_current_header_level();
let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Implementors\n",
"#".repeat(implementors_section_level),
header_prefix
)
.unwrap();
self.push_level();
for impl_item in implementors {
if let ItemEnum::Impl(imp) = &impl_item.inner {
let impl_header_only = format_impl_decl_header_only(imp, self.krate);
let impl_header_level = self.get_current_header_level();
let impl_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} `{}`\n",
"#".repeat(impl_header_level),
impl_prefix,
impl_header_only.trim()
)
.unwrap();
if !imp.generics.where_predicates.is_empty() {
let where_clause =
format_generics_where_only(&imp.generics.where_predicates, self.krate);
writeln!(self.output, "```rust\n{}\n```\n", where_clause).unwrap();
}
let mut temp_printer = self.clone_with_new_output();
temp_printer.doc_path = self.doc_path.clone();
temp_printer.print_docs(impl_item);
write!(self.output, "{}", temp_printer.output).unwrap();
self.printed_ids
.insert(impl_item.id, self.get_header_prefix());
for assoc_item_id in &imp.items {
if self.selected_ids.contains(assoc_item_id) {
self.printed_ids
.insert(*assoc_item_id, self.get_header_prefix());
}
}
self.post_increment_current_level();
}
}
self.pop_level();
self.post_increment_current_level();
}
}
fn print_impl_block_details(&mut self, impl_item: &Item, imp: &Impl) {
let header_prefix = self.get_header_prefix();
if self
.printed_ids
.insert(impl_item.id, header_prefix.clone())
.is_some()
{
return;
}
self.post_increment_current_level();
let impl_header_level = self.get_current_header_level();
let impl_header = format_impl_decl(imp, self.krate);
writeln!(
self.output,
"{} {} `{}`\n", "#".repeat(impl_header_level),
header_prefix, impl_header.trim() )
.unwrap();
let mut temp_printer = self.clone_with_new_output();
temp_printer.doc_path = self.doc_path.clone();
temp_printer.print_docs(impl_item);
write!(self.output, "{}", temp_printer.output).unwrap();
let mut assoc_consts = vec![];
let mut assoc_types = vec![];
let mut assoc_fns = vec![];
for assoc_item_id in &imp.items {
if !self.selected_ids.contains(assoc_item_id) {
continue;
}
if let Some(assoc_item) = self.krate.index.get(assoc_item_id) {
match &assoc_item.inner {
ItemEnum::AssocConst { .. } => assoc_consts.push(assoc_item_id),
ItemEnum::AssocType { .. } => assoc_types.push(assoc_item_id),
ItemEnum::Function(_) => assoc_fns.push(assoc_item_id),
_ => {} }
}
}
self.push_level();
if !assoc_consts.is_empty() {
for id in assoc_consts {
self.print_associated_item_summary(id);
if !self.printed_ids.contains_key(id) {
self.printed_ids.insert(*id, self.get_header_prefix());
}
}
}
if !assoc_types.is_empty() {
for id in assoc_types {
self.print_associated_item_summary(id);
if !self.printed_ids.contains_key(id) {
self.printed_ids.insert(*id, self.get_header_prefix());
}
}
}
if !assoc_fns.is_empty() {
for id in assoc_fns {
self.print_associated_item_summary(id);
if !self.printed_ids.contains_key(id) {
self.printed_ids.insert(*id, self.get_header_prefix());
}
}
}
self.pop_level(); }
fn print_items_of_kind(&mut self, item_ids: &[Id], kind: ItemKind, header_name: &str) -> bool {
let mut items_to_print: Vec<&Id> = item_ids
.iter()
.filter(|id| self.selected_ids.contains(id))
.filter(|id| self.get_item_kind(id) == Some(kind))
.collect();
if items_to_print.is_empty() {
return false; }
items_to_print
.sort_by_key(|id| self.krate.index.get(id).and_then(|item| item.name.clone()));
let section_header_level = self.get_current_header_level();
let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"\n{} {} {}",
"#".repeat(section_header_level),
header_prefix,
header_name
)
.unwrap();
self.push_level();
for id in items_to_print {
if self.print_item_details(id) {
self.post_increment_current_level();
} else {
self.post_increment_current_level();
}
}
self.pop_level();
true
}
fn print_module_contents(&mut self, module_id: &Id) {
if let Some(resolved_module) = self.resolved_modules.get(module_id) {
let mut items_by_kind: HashMap<ItemKind, Vec<Id>> = HashMap::new();
let mut cross_referenced_items: Vec<(Id, String, String)> = Vec::new();
for id in &resolved_module.items {
if !self.selected_ids.contains(id) {
continue;
}
if let Some(existing_prefix) = self.printed_ids.get(id) {
if let Some(item) = self.krate.index.get(id) {
if !matches!(
item.inner,
ItemEnum::Impl(_)
| ItemEnum::Use { .. }
| ItemEnum::StructField(_)
| ItemEnum::Variant(_) | ItemEnum::AssocConst { .. } | ItemEnum::AssocType { .. }
| ItemEnum::Module(_)
) {
let decl = generate_item_declaration(
item,
self.krate,
&self.current_module_path,
);
cross_referenced_items.push((*id, decl, existing_prefix.clone()));
}
}
continue; }
if let Some(kind) = self.get_item_kind(id) {
match kind {
ItemKind::Impl
| ItemKind::Variant | ItemKind::StructField | ItemKind::AssocConst | ItemKind::AssocType | ItemKind::Use | ItemKind::Module => continue, _ => {}
}
items_by_kind.entry(kind).or_default().push(*id);
}
}
for ids in items_by_kind.values_mut() {
ids.sort_by_key(|id| self.krate.index.get(id).and_then(|item| item.name.clone()));
}
cross_referenced_items.sort_by_key(|(_, decl, _)| decl.clone());
let print_order = [
(ItemKind::Macro, "Macros"),
(ItemKind::ProcAttribute, "Attribute Macros"),
(ItemKind::ProcDerive, "Derive Macros"),
(ItemKind::Struct, "Structs"),
(ItemKind::Enum, "Enums"),
(ItemKind::Union, "Unions"),
(ItemKind::Trait, "Traits"),
(ItemKind::Function, "Functions"),
(ItemKind::TypeAlias, "Type Aliases"),
(ItemKind::TraitAlias, "Trait Aliases"),
(ItemKind::Static, "Statics"),
(ItemKind::Constant, "Constants"),
(ItemKind::ExternCrate, "External Crates"),
(ItemKind::ExternType, "External Types"),
(ItemKind::Primitive, "Primitives"),
];
for (kind, header_name) in print_order {
if let Some(ids) = items_by_kind.get(&kind) {
if ids.is_empty() {
continue;
}
if self.print_items_of_kind(ids, kind, header_name) {
self.post_increment_current_level();
}
}
}
if !cross_referenced_items.is_empty() {
let re_exports_header_level = self.get_current_header_level();
let re_exports_prefix = self.get_header_prefix();
writeln!(
self.output,
"\n{} {} Re-exports\n",
"#".repeat(re_exports_header_level),
re_exports_prefix
)
.unwrap();
for (_id, declaration, original_prefix) in cross_referenced_items {
writeln!(
self.output,
"- `{}` (See section {} for details)",
declaration, original_prefix
)
.unwrap();
}
writeln!(self.output).unwrap(); self.post_increment_current_level();
}
} else {
warn!(
"Could not find resolved module data for ID: {:?}",
module_id
);
}
}
fn print_graph_context(&mut self, id: &Id) {
let incoming_edges_data: Vec<Edge> = self
.graph
.find_incoming_edges(id)
.into_iter()
.cloned()
.collect();
if !incoming_edges_data.is_empty() {
writeln!(self.output, "_Referenced by:_").unwrap();
let mut sorted_edges = incoming_edges_data;
sorted_edges.sort_by_key(|edge| {
(
format_id_path_canonical(&edge.source, self.krate),
format!("{:?}", edge.label),
)
});
self.push_level();
for edge in sorted_edges {
self.post_increment_current_level(); let source_path = format_id_path_canonical(&edge.source, self.krate);
let template_marker = if self.template_mode
&& self
.krate
.index
.get(&edge.source)
.is_some_and(|i| i.docs.is_some())
{
format!("\n {}", self.get_template_marker())
} else {
"".to_string()
};
writeln!(
self.output,
"- `{}` ({}){}",
source_path,
edge.label, template_marker
)
.unwrap();
}
self.pop_level(); writeln!(self.output).unwrap(); } else {
writeln!(
self.output,
"_Item has no known incoming references in the graph._\n"
)
.unwrap();
}
}
fn clone_with_new_output(&self) -> Self {
Printer {
krate: self.krate,
manifest_data: self.manifest_data.clone(),
paths: self.paths.clone(),
crate_extra: self.crate_extra.clone(),
include_other: self.include_other,
template_mode: self.template_mode,
no_common_traits: self.no_common_traits,
selected_ids: self.selected_ids.clone(), resolved_modules: self.resolved_modules.clone(),
graph: self.graph.clone(),
printed_ids: self.printed_ids.clone(),
output: String::new(), module_tree: self.module_tree.clone(),
doc_path: self.doc_path.clone(),
current_module_path: self.current_module_path.clone(),
crate_common_traits: self.crate_common_traits.clone(),
all_type_ids_with_impls: self.all_type_ids_with_impls.clone(),
module_common_traits: self.module_common_traits.clone(),
}
}
fn print_module_recursive(&mut self, module_id: Id) {
if module_id != self.krate.root && !self.selected_ids.contains(&module_id) {
return;
}
if let Some(item) = self.krate.index.get(&module_id) {
let module_segment = item.name.as_deref().unwrap_or("").to_string();
if module_id == self.krate.root {
self.current_module_path = vec![
self.krate
.index
.get(&self.krate.root)
.unwrap()
.name
.as_ref()
.unwrap()
.replace('-', "_"),
];
} else {
self.current_module_path.push(module_segment);
}
let module_header_level = self.get_current_header_level(); let header_prefix = self.get_header_prefix();
let module_path_str = self.current_module_path.join("::");
let display_path = if module_path_str.is_empty() {
item.name.as_deref().unwrap_or("::")
} else {
&module_path_str
};
writeln!(
self.output,
"\n{} {} Module: `{}`\n", "#".repeat(module_header_level),
header_prefix,
display_path
)
.unwrap();
self.printed_ids
.entry(module_id)
.or_insert_with(|| header_prefix.clone());
self.push_level();
self.print_docs(item);
if !self.no_common_traits {
let mod_common = self.calculate_module_common_traits(&module_id);
self.module_common_traits
.insert(module_id, mod_common.clone());
let displayable_module_common: Vec<FormattedTraitImpl> = mod_common
.iter()
.filter(|nt| !self.crate_common_traits.contains(nt)) .cloned()
.collect();
if !displayable_module_common.is_empty() {
let common_traits_header_level = self.get_current_header_level(); let common_traits_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Common Traits\n",
"#".repeat(common_traits_header_level),
common_traits_prefix
)
.unwrap();
writeln!(self.output, "In addition to the crate's 'Common Traits', the following traits are commonly implemented by types in this module. Unless otherwise noted, you can assume these traits are implemented:\n").unwrap();
let formatted_list = self.format_trait_list(&displayable_module_common);
if !formatted_list.is_empty() {
write!(self.output, "{}", formatted_list).unwrap();
}
self.post_increment_current_level(); }
}
self.print_module_contents(&module_id);
self.pop_level();
self.post_increment_current_level();
if let Some(children) = self.module_tree.children.get(&module_id).cloned() {
for child_id in children {
self.print_module_recursive(child_id);
}
}
if module_id != self.krate.root {
self.current_module_path.pop();
}
}
}
fn finalize(mut self) -> String {
let root_item = self.krate.index.get(&self.krate.root).unwrap(); let crate_name = root_item.name.as_deref().unwrap_or("Unknown Crate");
let crate_version = self.krate.crate_version.as_deref().unwrap_or("");
let crate_header_level = 1;
self.doc_path.clear();
writeln!(
self.output,
"{} {} API ({})\n", "#".repeat(crate_header_level),
crate_name,
crate_version
)
.unwrap();
self.push_level();
if let Some(desc) = &self.manifest_data.description {
writeln!(self.output, "{}\n", desc).unwrap();
}
let manifest_section_level = self.get_current_header_level(); let manifest_header_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Manifest\n",
"#".repeat(manifest_section_level),
manifest_header_prefix
)
.unwrap();
if let Some(hp) = &self.manifest_data.homepage {
writeln!(self.output, "- Homepage: <{}>", hp).unwrap();
}
if let Some(repo) = &self.manifest_data.repository {
writeln!(self.output, "- Repository: <{}>", repo).unwrap();
}
if !self.manifest_data.categories.is_empty() {
writeln!(
self.output,
"- Categories: {}",
self.manifest_data.categories.join(", ")
)
.unwrap();
}
if let Some(lic) = &self.manifest_data.license {
writeln!(self.output, "- License: {}", lic).unwrap();
}
if let Some(rv) = &self.manifest_data.rust_version {
writeln!(self.output, "- rust-version: `{}`", rv).unwrap();
}
if let Some(ed) = &self.manifest_data.edition {
writeln!(self.output, "- edition: `{}`", ed).unwrap();
}
writeln!(self.output).unwrap();
let features_section_level = self.get_current_header_level() + 1; self.push_level(); let features_header_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} Features\n",
"#".repeat(features_section_level),
features_header_prefix
)
.unwrap();
if self.manifest_data.features.is_empty() {
writeln!(self.output, "- None").unwrap();
} else {
let mut sorted_features: Vec<_> = self.manifest_data.features.keys().collect();
sorted_features.sort_unstable();
for feature_name in sorted_features {
writeln!(self.output, "- `{}`", feature_name).unwrap();
}
}
writeln!(self.output).unwrap(); self.pop_level();
self.post_increment_current_level();
if let Some(extra) = &self.crate_extra
&& let Some(readme) = &extra.readme_content
{
info!("Injecting README content.");
let section_level = self.get_current_header_level(); let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"\n{} {} README\n",
"#".repeat(section_level),
header_prefix
)
.unwrap();
let adjusted_readme = adjust_markdown_headers(readme, section_level);
writeln!(self.output, "{}\n", adjusted_readme).unwrap();
self.post_increment_current_level(); }
if !self.no_common_traits && !self.crate_common_traits.is_empty() {
let common_traits_level = self.get_current_header_level(); let common_traits_prefix = self.get_header_prefix();
writeln!(
self.output,
"\n{} {} Common Traits\n",
"#".repeat(common_traits_level),
common_traits_prefix
)
.unwrap();
writeln!(self.output, "The following traits are commonly implemented by types in this crate. Unless otherwise noted, you can assume these traits are implemented:\n").unwrap();
let sorted_common_traits: Vec<FormattedTraitImpl> = {
let mut traits: Vec<_> = self.crate_common_traits.iter().cloned().collect();
traits.sort_by_key(|t| t.formatted_markdown_list_entry.clone());
traits
};
let formatted_list = self.format_trait_list(&sorted_common_traits);
if !formatted_list.is_empty() {
write!(self.output, "{}", formatted_list).unwrap();
}
writeln!(self.output).unwrap();
self.post_increment_current_level(); }
if let Some(resolved_root_module) = self.resolved_modules.get(&self.krate.root) {
let macro_ids: Vec<Id> = resolved_root_module
.items
.iter()
.filter(|id| self.selected_ids.contains(id))
.filter(|id| {
matches!(
self.get_item_kind(id),
Some(ItemKind::Macro | ItemKind::ProcAttribute | ItemKind::ProcDerive)
)
})
.cloned() .collect();
if !macro_ids.is_empty() {
let section_level = self.get_current_header_level(); let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"\n{} {} Macros",
"#".repeat(section_level),
header_prefix
)
.unwrap();
self.push_level(); let mut sorted_macros = macro_ids;
sorted_macros
.sort_by_key(|id| self.krate.index.get(id).and_then(|item| item.name.clone()));
for id in sorted_macros {
self.print_item_details(&id); }
self.pop_level(); self.post_increment_current_level(); }
}
self.print_module_recursive(self.krate.root);
let top_level_ids = self.module_tree.top_level_modules.clone();
for module_id in top_level_ids {
self.print_module_recursive(module_id); }
let mut unprinted_ids = Vec::new();
for id in &self.selected_ids {
if !self.printed_ids.contains_key(id) {
if let Some(item) = self.krate.index.get(id) {
if !matches!(
item.inner,
ItemEnum::Impl(_)
| ItemEnum::Use { .. }
| ItemEnum::StructField(_)
| ItemEnum::Module(_) ) && item.name.is_some()
{
unprinted_ids.push(*id);
}
else if item.name.is_none()
|| matches!(item.inner, ItemEnum::StructField(_) | ItemEnum::Module(_))
{
self.printed_ids.insert(*id, "SKIPPED_OTHER".to_string());
}
} else {
unprinted_ids.push(*id);
}
}
}
if !unprinted_ids.is_empty() {
if self.include_other {
warn!(
"Found {} selected items that were not printed in the main structure. Including them in the 'Other' section.",
unprinted_ids.len()
);
let other_section_level = self.get_current_header_level(); let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"\n{} {} Other", "#".repeat(other_section_level),
header_prefix
)
.unwrap();
self.push_level();
unprinted_ids.sort_by_key(|id| {
(
self.krate.paths.get(id).map(|p| p.path.clone()),
self.krate.index.get(id).and_then(|i| i.name.clone()),
)
});
for id in &unprinted_ids {
let path_str = format_id_path_canonical(id, self.krate);
warn!("Including unprinted item in 'Other' section: {}", path_str);
if let Some(item) = self.krate.index.get(id) {
self.print_item_details(id);
if let Some(span) = &item.span {
writeln!(
self.output,
"_Source: `{}:{}:{}`_\n", span.filename.display(),
span.begin.0 + 1, span.begin.1 + 1 )
.unwrap();
}
self.print_graph_context(id);
} else {
self.post_increment_current_level(); let other_item_level = self.get_current_header_level();
let item_prefix = self.get_header_prefix();
writeln!(
self.output,
"\n{} {} `{}`\n",
"#".repeat(other_item_level),
item_prefix,
path_str )
.unwrap();
writeln!(self.output, "_Error: Item details not found in index._\n")
.unwrap();
self.print_graph_context(id); }
}
self.pop_level(); } else {
let mut counts_by_kind: HashMap<ItemKind, usize> = HashMap::new(); for id in &unprinted_ids {
if let Some(kind) = self.get_item_kind(id) {
*counts_by_kind.entry(kind).or_insert(0) += 1;
} else {
*counts_by_kind.entry(ItemKind::StructField).or_insert(0) += 1;
}
}
warn!(
"Skipped printing {} items not fitting into standard sections (use --include-other to see them):",
unprinted_ids.len()
);
let mut sorted_counts: Vec<_> = counts_by_kind.into_iter().collect();
sorted_counts.sort_by_key(|(kind, _)| format!("{:?}", kind));
for (kind, count) in sorted_counts {
warn!(" - {:?}: {}", kind, count);
}
}
}
let examples_readme_content_clone = self
.crate_extra
.as_ref()
.and_then(|extra| extra.examples_readme_content.clone());
let examples_clone = self
.crate_extra
.as_ref()
.map_or_else(Vec::new, |extra| extra.examples.clone());
if !examples_clone.is_empty() || examples_readme_content_clone.is_some() {
let examples_section_level = self.get_current_header_level(); let header_prefix = self.get_header_prefix();
writeln!(
self.output,
"\n{} {} Examples Appendix\n",
"#".repeat(examples_section_level),
header_prefix
)
.unwrap();
self.push_level();
if let Some(readme) = examples_readme_content_clone {
let adjusted_readme = adjust_markdown_headers(&readme, examples_section_level);
writeln!(self.output, "{}\n", adjusted_readme).unwrap();
}
for (filename, content) in &examples_clone {
let example_header_level = self.get_current_header_level(); let example_prefix = self.get_header_prefix();
writeln!(
self.output,
"{} {} `{}`\n",
"#".repeat(example_header_level),
example_prefix,
filename
)
.unwrap();
writeln!(self.output, "```rust\n{}\n```\n", content).unwrap();
self.post_increment_current_level(); }
self.pop_level(); self.post_increment_current_level(); }
self.output
}
}