| use std::cell::RefCell; | |
| use std::collections::BTreeMap; | |
| use std::fmt::{self, Write as _}; | |
| use std::io; | |
| use std::path::{Path, PathBuf}; | |
| use std::sync::mpsc::{Receiver, channel}; | |
| use askama::Template; | |
| use rustc_ast::join_path_syms; | |
| use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; | |
| use rustc_hir::Attribute; | |
| use rustc_hir::attrs::AttributeKind; | |
| use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE}; | |
| use rustc_middle::ty::TyCtxt; | |
| use rustc_session::Session; | |
| use rustc_span::edition::Edition; | |
| use rustc_span::{BytePos, FileName, RemapPathScopeComponents, Symbol}; | |
| use serde::ser::SerializeSeq; | |
| use tracing::info; | |
| use super::print_item::{full_path, print_item, print_item_path, print_ty_path}; | |
| use super::sidebar::{ModuleLike, Sidebar, print_sidebar, sidebar_module_like}; | |
| use super::{AllTypes, StylePath}; | |
| use crate::clean::types::ExternalLocation; | |
| use crate::clean::utils::has_doc_flag; | |
| use crate::clean::{self, ExternalCrate}; | |
| use crate::config::{EmitType, ModuleSorting, RenderOptions}; | |
| use crate::docfs::{DocFS, PathError}; | |
| use crate::error::Error; | |
| use crate::formats::FormatRenderer; | |
| use crate::formats::cache::Cache; | |
| use crate::formats::item_type::ItemType; | |
| use crate::html::escape::Escape; | |
| use crate::html::macro_expansion::ExpandedCode; | |
| use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary}; | |
| use crate::html::render::write_shared::write_shared; | |
| use crate::html::span_map::{LinkFromSrc, Span, collect_spans_and_sources}; | |
| use crate::html::url_parts_builder::UrlPartsBuilder; | |
| use crate::html::{layout, sources}; | |
| use crate::scrape_examples::AllCallLocations; | |
| use crate::try_err; | |
| /// Major driving force in all rustdoc rendering. This contains information | |
| /// about where in the tree-like hierarchy rendering is occurring and controls | |
| /// how the current page is being rendered. | |
| /// | |
| /// It is intended that this context is a lightweight object which can be fairly | |
| /// easily cloned because it is cloned per work-job (about once per item in the | |
| /// rustdoc tree). | |
| pub(crate) struct Context<'tcx> { | |
| /// Current hierarchy of components leading down to what's currently being | |
| /// rendered | |
| pub(crate) current: Vec<Symbol>, | |
| /// The current destination folder of where HTML artifacts should be placed. | |
| /// This changes as the context descends into the module hierarchy. | |
| pub(crate) dst: PathBuf, | |
| /// Tracks section IDs for `Deref` targets so they match in both the main | |
| /// body and the sidebar. | |
| pub(super) deref_id_map: RefCell<DefIdMap<String>>, | |
| /// The map used to ensure all generated 'id=' attributes are unique. | |
| pub(super) id_map: RefCell<IdMap>, | |
| /// Shared mutable state. | |
| /// | |
| /// Issue for improving the situation: [#82381][] | |
| /// | |
| /// [#82381]: https://github.com/rust-lang/rust/issues/82381 | |
| pub(crate) shared: SharedContext<'tcx>, | |
| /// Collection of all types with notable traits referenced in the current module. | |
| pub(crate) types_with_notable_traits: RefCell<FxIndexSet<clean::Type>>, | |
| /// Contains information that needs to be saved and reset after rendering an item which is | |
| /// not a module. | |
| pub(crate) info: ContextInfo, | |
| } | |
| /// This struct contains the information that needs to be reset between each | |
| /// [`FormatRenderer::item`] call. | |
| /// | |
| /// When we enter a new module, we set these values for the whole module but they might be updated | |
| /// in each child item (especially if it's a module). So to prevent these changes to impact other | |
| /// items rendering in the same module, we need to reset them to the module's set values. | |
| #[derive(Clone, Copy)] | |
| pub(crate) struct ContextInfo { | |
| /// A flag, which when `true`, will render pages which redirect to the | |
| /// real location of an item. This is used to allow external links to | |
| /// publicly reused items to redirect to the right location. | |
| pub(super) render_redirect_pages: bool, | |
| /// This flag indicates whether source links should be generated or not. If | |
| /// the source files are present in the html rendering, then this will be | |
| /// `true`. | |
| pub(crate) include_sources: bool, | |
| /// Field used during rendering, to know if we're inside an inlined item. | |
| pub(crate) is_inside_inlined_module: bool, | |
| } | |
| impl ContextInfo { | |
| fn new(include_sources: bool) -> Self { | |
| Self { render_redirect_pages: false, include_sources, is_inside_inlined_module: false } | |
| } | |
| } | |
| /// Shared mutable state used in [`Context`] and elsewhere. | |
| pub(crate) struct SharedContext<'tcx> { | |
| pub(crate) tcx: TyCtxt<'tcx>, | |
| /// The path to the crate root source minus the file name. | |
| /// Used for simplifying paths to the highlighted source code files. | |
| pub(crate) src_root: PathBuf, | |
| /// This describes the layout of each page, and is not modified after | |
| /// creation of the context (contains info like the favicon and added html). | |
| pub(crate) layout: layout::Layout, | |
| /// The local file sources we've emitted and their respective url-paths. | |
| pub(crate) local_sources: FxIndexMap<PathBuf, String>, | |
| /// Show the memory layout of types in the docs. | |
| pub(super) show_type_layout: bool, | |
| /// The base-URL of the issue tracker for when an item has been tagged with | |
| /// an issue number. | |
| pub(super) issue_tracker_base_url: Option<String>, | |
| /// The directories that have already been created in this doc run. Used to reduce the number | |
| /// of spurious `create_dir_all` calls. | |
| created_dirs: RefCell<FxHashSet<PathBuf>>, | |
| /// This flag indicates whether listings of modules (in the side bar and documentation itself) | |
| /// should be ordered alphabetically or in order of appearance (in the source code). | |
| pub(super) module_sorting: ModuleSorting, | |
| /// Additional CSS files to be added to the generated docs. | |
| pub(crate) style_files: Vec<StylePath>, | |
| /// Suffix to add on resource files (if suffix is "-v2" then "search-index.js" becomes | |
| /// "search-index-v2.js"). | |
| pub(crate) resource_suffix: String, | |
| /// Optional path string to be used to load static files on output pages. If not set, uses | |
| /// combinations of `../` to reach the documentation root. | |
| pub(crate) static_root_path: Option<String>, | |
| /// The fs handle we are working with. | |
| pub(crate) fs: DocFS, | |
| pub(super) codes: ErrorCodes, | |
| pub(super) playground: Option<markdown::Playground>, | |
| all: RefCell<AllTypes>, | |
| /// Storage for the errors produced while generating documentation so they | |
| /// can be printed together at the end. | |
| errors: Receiver<String>, | |
| /// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set | |
| /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of | |
| /// the crate. | |
| redirections: Option<RefCell<FxHashMap<String, String>>>, | |
| /// Correspondence map used to link types used in the source code pages to allow to click on | |
| /// links to jump to the type's definition. | |
| pub(crate) span_correspondence_map: FxHashMap<Span, LinkFromSrc>, | |
| pub(crate) expanded_codes: FxHashMap<BytePos, Vec<ExpandedCode>>, | |
| /// The [`Cache`] used during rendering. | |
| pub(crate) cache: Cache, | |
| pub(crate) call_locations: AllCallLocations, | |
| } | |
| impl SharedContext<'_> { | |
| pub(crate) fn ensure_dir(&self, dst: &Path) -> Result<(), Error> { | |
| let mut dirs = self.created_dirs.borrow_mut(); | |
| if !dirs.contains(dst) { | |
| try_err!(self.fs.create_dir_all(dst), dst); | |
| dirs.insert(dst.to_path_buf()); | |
| } | |
| Ok(()) | |
| } | |
| pub(crate) fn edition(&self) -> Edition { | |
| self.tcx.sess.edition() | |
| } | |
| } | |
| struct SidebarItem { | |
| name: String, | |
| /// Bang macros can now be used as attribute/derive macros, making it tricky to correctly | |
| /// handle all their cases at once, which means that even if they are categorized as | |
| /// derive/attribute macros, they should still link to a "macro_rules" URL. | |
| is_macro_rules: bool, | |
| } | |
| impl serde::Serialize for SidebarItem { | |
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | |
| where | |
| S: serde::Serializer, | |
| { | |
| if self.is_macro_rules { | |
| let mut seq = serializer.serialize_seq(Some(2))?; | |
| seq.serialize_element(&self.name)?; | |
| seq.serialize_element(&1)?; | |
| seq.end() | |
| } else { | |
| serializer.serialize_some(&Some(&self.name)) | |
| } | |
| } | |
| } | |
| impl<'tcx> Context<'tcx> { | |
| pub(crate) fn tcx(&self) -> TyCtxt<'tcx> { | |
| self.shared.tcx | |
| } | |
| pub(crate) fn cache(&self) -> &Cache { | |
| &self.shared.cache | |
| } | |
| pub(super) fn sess(&self) -> &'tcx Session { | |
| self.shared.tcx.sess | |
| } | |
| pub(super) fn derive_id<S: AsRef<str> + ToString>(&self, id: S) -> String { | |
| self.id_map.borrow_mut().derive(id) | |
| } | |
| /// String representation of how to get back to the root path of the 'doc/' | |
| /// folder in terms of a relative URL. | |
| pub(super) fn root_path(&self) -> String { | |
| "../".repeat(self.current.len()) | |
| } | |
| fn render_item(&mut self, it: &clean::Item, is_module: bool) -> String { | |
| let mut render_redirect_pages = self.info.render_redirect_pages; | |
| // If the item is stripped but inlined, links won't point to the item so no need to generate | |
| // a file for it. | |
| if it.is_stripped() | |
| && let Some(def_id) = it.def_id() | |
| && def_id.is_local() | |
| && (self.info.is_inside_inlined_module | |
| || self.shared.cache.inlined_items.contains(&def_id)) | |
| { | |
| // For now we're forced to generate a redirect page for stripped items until | |
| // `record_extern_fqn` correctly points to external items. | |
| render_redirect_pages = true; | |
| } | |
| if !render_redirect_pages { | |
| let mut title = String::new(); | |
| if !is_module { | |
| title.push_str(it.name.unwrap().as_str()); | |
| } | |
| let short_title; | |
| let short_title = if is_module { | |
| let module_name = self.current.last().unwrap(); | |
| short_title = if it.is_crate() { | |
| format!("Crate {module_name}") | |
| } else { | |
| format!("Module {module_name}") | |
| }; | |
| &short_title[..] | |
| } else { | |
| it.name.as_ref().unwrap().as_str() | |
| }; | |
| if !it.is_fake_item() { | |
| if !is_module { | |
| title.push_str(" in "); | |
| } | |
| // No need to include the namespace for primitive types and keywords | |
| title.push_str(&join_path_syms(&self.current)); | |
| }; | |
| title.push_str(" - Rust"); | |
| let tyname = it.type_(); | |
| let desc = plain_text_summary(&it.doc_value(), &it.link_names(self.cache())); | |
| let desc = if !desc.is_empty() { | |
| desc | |
| } else if it.is_crate() { | |
| format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate) | |
| } else { | |
| format!( | |
| "API documentation for the Rust `{name}` {tyname} in crate `{krate}`.", | |
| name = it.name.as_ref().unwrap(), | |
| krate = self.shared.layout.krate, | |
| ) | |
| }; | |
| let name; | |
| let tyname_s = if it.is_crate() { | |
| name = format!("{tyname} crate"); | |
| name.as_str() | |
| } else { | |
| tyname.as_str() | |
| }; | |
| let content = print_item(self, it); | |
| let page = layout::Page { | |
| css_class: tyname_s, | |
| root_path: &self.root_path(), | |
| static_root_path: self.shared.static_root_path.as_deref(), | |
| title: &title, | |
| short_title, | |
| description: &desc, | |
| resource_suffix: &self.shared.resource_suffix, | |
| rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), |d| { | |
| d.rust_logo.is_some() | |
| }), | |
| }; | |
| layout::render( | |
| &self.shared.layout, | |
| &page, | |
| fmt::from_fn(|f| print_sidebar(self, it, f)), | |
| content, | |
| &self.shared.style_files, | |
| ) | |
| } else { | |
| if let Some(info) = self.cache().paths.get(&it.item_id.expect_def_id()) | |
| && (self.current.len() + 1 != info.parts.len() | |
| || self.current.iter().zip(info.parts.iter()).any(|(a, b)| a != b)) | |
| { | |
| // We checked that the redirection isn't pointing to the current file, | |
| // preventing an infinite redirection loop in the generated | |
| // documentation. | |
| let path = fmt::from_fn(|f| { | |
| for name in &info.parts[..info.parts.len() - 1] { | |
| write!(f, "{name}/")?; | |
| } | |
| write!(f, "{}", print_ty_path(info.ty, info.parts.last().unwrap().as_str())) | |
| }); | |
| match self.shared.redirections { | |
| Some(ref redirections) => { | |
| let mut current_path = String::new(); | |
| for name in &self.current { | |
| current_path.push_str(name.as_str()); | |
| current_path.push('/'); | |
| } | |
| let _ = write!( | |
| current_path, | |
| "{}", | |
| print_ty_path(info.ty, info.parts.last().unwrap().as_str()) | |
| ); | |
| redirections.borrow_mut().insert(current_path, path.to_string()); | |
| } | |
| None => { | |
| return layout::redirect(&format!("{root}{path}", root = self.root_path())); | |
| } | |
| } | |
| } | |
| String::new() | |
| } | |
| } | |
| /// Construct a map of items shown in the sidebar to a plain-text summary of their docs. | |
| fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<SidebarItem>> { | |
| // BTreeMap instead of HashMap to get a sorted output | |
| let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); | |
| let mut inserted: FxHashMap<ItemType, FxHashSet<Symbol>> = FxHashMap::default(); | |
| for item in &m.items { | |
| if item.is_stripped() { | |
| continue; | |
| } | |
| let name = match item.name { | |
| None => continue, | |
| Some(s) => s, | |
| }; | |
| let is_macro_rules = item.is_decl_macro(); | |
| for type_ in item.types() { | |
| if inserted.entry(type_).or_default().insert(name) { | |
| let type_ = type_.to_string(); | |
| let name = name.to_string(); | |
| map.entry(type_).or_default().push(SidebarItem { name, is_macro_rules }); | |
| } | |
| } | |
| } | |
| match self.shared.module_sorting { | |
| ModuleSorting::Alphabetical => { | |
| for items in map.values_mut() { | |
| items.sort_by(|a, b| a.name.cmp(&b.name)); | |
| } | |
| } | |
| ModuleSorting::DeclarationOrder => {} | |
| } | |
| map | |
| } | |
| /// Generates a url appropriate for an `href` attribute back to the source of | |
| /// this item. | |
| /// | |
| /// The url generated, when clicked, will redirect the browser back to the | |
| /// original source code. | |
| /// | |
| /// If `None` is returned, then a source link couldn't be generated. This | |
| /// may happen, for example, with externally inlined items where the source | |
| /// of their crate documentation isn't known. | |
| pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> { | |
| self.href_from_span(item.span(self.tcx())?, true) | |
| } | |
| pub(crate) fn href_from_span(&self, span: clean::Span, with_lines: bool) -> Option<String> { | |
| let mut root = self.root_path(); | |
| let mut path: String; | |
| let cnum = span.cnum(self.sess()); | |
| // We can safely ignore synthetic `SourceFile`s. | |
| let file = match span.filename(self.sess()) { | |
| FileName::Real(ref path) => path | |
| .local_path() | |
| .unwrap_or(path.path(RemapPathScopeComponents::DOCUMENTATION)) | |
| .to_path_buf(), | |
| _ => return None, | |
| }; | |
| let file = &file; | |
| let krate_sym; | |
| let (krate, path) = if cnum == LOCAL_CRATE { | |
| if let Some(path) = self.shared.local_sources.get(file) { | |
| (self.shared.layout.krate.as_str(), path) | |
| } else { | |
| return None; | |
| } | |
| } else { | |
| let (krate, src_root) = match *self.cache().extern_locations.get(&cnum)? { | |
| ExternalLocation::Local => { | |
| let e = ExternalCrate { crate_num: cnum }; | |
| (e.name(self.tcx()), e.src_root(self.tcx())) | |
| } | |
| ExternalLocation::Remote { ref url, .. } => { | |
| // FIXME: relative extern URLs are not depth-adjusted for source pages | |
| root = url.to_string(); | |
| let e = ExternalCrate { crate_num: cnum }; | |
| (e.name(self.tcx()), e.src_root(self.tcx())) | |
| } | |
| ExternalLocation::Unknown => return None, | |
| }; | |
| let href = RefCell::new(PathBuf::new()); | |
| sources::clean_path( | |
| &src_root, | |
| file, | |
| |component| { | |
| href.borrow_mut().push(component); | |
| }, | |
| || { | |
| href.borrow_mut().pop(); | |
| }, | |
| ); | |
| path = href.into_inner().to_string_lossy().into_owned(); | |
| if let Some(c) = path.as_bytes().last() | |
| && *c != b'/' | |
| { | |
| path.push('/'); | |
| } | |
| let mut fname = file.file_name().expect("source has no filename").to_os_string(); | |
| fname.push(".html"); | |
| path.push_str(&fname.to_string_lossy()); | |
| krate_sym = krate; | |
| (krate_sym.as_str(), &path) | |
| }; | |
| let anchor = if with_lines { | |
| let loline = span.lo(self.sess()).line; | |
| let hiline = span.hi(self.sess()).line; | |
| format!( | |
| "#{}", | |
| if loline == hiline { loline.to_string() } else { format!("{loline}-{hiline}") } | |
| ) | |
| } else { | |
| "".to_string() | |
| }; | |
| Some(format!( | |
| "{root}src/{krate}/{path}{anchor}", | |
| root = Escape(&root), | |
| krate = krate, | |
| path = path, | |
| anchor = anchor | |
| )) | |
| } | |
| pub(crate) fn href_from_span_relative( | |
| &self, | |
| span: clean::Span, | |
| relative_to: &str, | |
| ) -> Option<String> { | |
| self.href_from_span(span, false).map(|s| { | |
| let mut url = UrlPartsBuilder::new(); | |
| let mut dest_href_parts = s.split('/'); | |
| let mut cur_href_parts = relative_to.split('/'); | |
| for (cur_href_part, dest_href_part) in (&mut cur_href_parts).zip(&mut dest_href_parts) { | |
| if cur_href_part != dest_href_part { | |
| url.push(dest_href_part); | |
| break; | |
| } | |
| } | |
| for dest_href_part in dest_href_parts { | |
| url.push(dest_href_part); | |
| } | |
| let loline = span.lo(self.sess()).line; | |
| let hiline = span.hi(self.sess()).line; | |
| format!( | |
| "{}{}#{}", | |
| "../".repeat(cur_href_parts.count()), | |
| url.finish(), | |
| if loline == hiline { loline.to_string() } else { format!("{loline}-{hiline}") } | |
| ) | |
| }) | |
| } | |
| } | |
| impl<'tcx> Context<'tcx> { | |
| pub(crate) fn init( | |
| krate: clean::Crate, | |
| options: RenderOptions, | |
| cache: Cache, | |
| tcx: TyCtxt<'tcx>, | |
| expanded_codes: FxHashMap<BytePos, Vec<ExpandedCode>>, | |
| ) -> Result<(Self, clean::Crate), Error> { | |
| // need to save a copy of the options for rendering the index page | |
| let md_opts = options.clone(); | |
| let RenderOptions { | |
| output, | |
| external_html, | |
| id_map, | |
| playground_url, | |
| module_sorting, | |
| themes: style_files, | |
| default_settings, | |
| extension_css, | |
| resource_suffix, | |
| static_root_path, | |
| generate_redirect_map, | |
| show_type_layout, | |
| emit, | |
| generate_link_to_definition, | |
| call_locations, | |
| no_emit_shared, | |
| html_no_source, | |
| .. | |
| } = options; | |
| let src_root = match krate.src(tcx) { | |
| FileName::Real(ref p) => { | |
| match p | |
| .local_path() | |
| .unwrap_or(p.path(RemapPathScopeComponents::DOCUMENTATION)) | |
| .parent() | |
| { | |
| Some(p) => p.to_path_buf(), | |
| None => PathBuf::new(), | |
| } | |
| } | |
| _ => PathBuf::new(), | |
| }; | |
| // If user passed in `--playground-url` arg, we fill in crate name here | |
| let mut playground = None; | |
| if let Some(url) = playground_url { | |
| playground = Some(markdown::Playground { crate_name: Some(krate.name(tcx)), url }); | |
| } | |
| let krate_version = cache.crate_version.as_deref().unwrap_or_default(); | |
| let mut layout = layout::Layout { | |
| logo: String::new(), | |
| favicon: String::new(), | |
| external_html, | |
| default_settings, | |
| krate: krate.name(tcx).to_string(), | |
| krate_version: krate_version.to_string(), | |
| css_file_extension: extension_css, | |
| scrape_examples_extension: !call_locations.is_empty(), | |
| }; | |
| let mut issue_tracker_base_url = None; | |
| let mut include_sources = !html_no_source; | |
| // Crawl the crate attributes looking for attributes which control how we're | |
| // going to emit HTML | |
| for attr in &krate.module.attrs.other_attrs { | |
| let Attribute::Parsed(AttributeKind::Doc(d)) = attr else { continue }; | |
| if let Some((html_favicon_url, _)) = d.html_favicon_url { | |
| layout.favicon = html_favicon_url.to_string(); | |
| } | |
| if let Some((html_logo_url, _)) = d.html_logo_url { | |
| layout.logo = html_logo_url.to_string(); | |
| } | |
| if let Some((html_playground_url, _)) = d.html_playground_url { | |
| playground = Some(markdown::Playground { | |
| crate_name: Some(krate.name(tcx)), | |
| url: html_playground_url.to_string(), | |
| }); | |
| } | |
| if let Some((s, _)) = d.issue_tracker_base_url { | |
| issue_tracker_base_url = Some(s.to_string()); | |
| } | |
| if d.html_no_source.is_some() { | |
| include_sources = false; | |
| } | |
| } | |
| let (local_sources, matches) = collect_spans_and_sources( | |
| tcx, | |
| &krate, | |
| &src_root, | |
| include_sources, | |
| generate_link_to_definition, | |
| ); | |
| let (sender, receiver) = channel(); | |
| let scx = SharedContext { | |
| tcx, | |
| src_root, | |
| local_sources, | |
| issue_tracker_base_url, | |
| layout, | |
| created_dirs: Default::default(), | |
| module_sorting, | |
| style_files, | |
| resource_suffix, | |
| static_root_path, | |
| fs: DocFS::new(sender), | |
| codes: ErrorCodes::from(options.unstable_features.is_nightly_build()), | |
| playground, | |
| all: RefCell::new(AllTypes::new()), | |
| errors: receiver, | |
| redirections: if generate_redirect_map { Some(Default::default()) } else { None }, | |
| show_type_layout, | |
| span_correspondence_map: matches, | |
| cache, | |
| call_locations, | |
| expanded_codes, | |
| }; | |
| let dst = output; | |
| scx.ensure_dir(&dst)?; | |
| let mut cx = Context { | |
| current: Vec::new(), | |
| dst, | |
| id_map: RefCell::new(id_map), | |
| deref_id_map: Default::default(), | |
| shared: scx, | |
| types_with_notable_traits: RefCell::new(FxIndexSet::default()), | |
| info: ContextInfo::new(include_sources), | |
| }; | |
| if emit.contains(&EmitType::HtmlNonStaticFiles) { | |
| sources::render(&mut cx, &krate)?; | |
| } | |
| if !no_emit_shared { | |
| write_shared(&mut cx, &krate, &md_opts, tcx)?; | |
| } | |
| Ok((cx, krate)) | |
| } | |
| } | |
| /// Generates the documentation for `crate` into the directory `dst` | |
| impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { | |
| const DESCR: &'static str = "html"; | |
| const RUN_ON_MODULE: bool = true; | |
| const NON_STATIC_FILE_EMIT_TYPE: EmitType = EmitType::HtmlNonStaticFiles; | |
| type ModuleData = ContextInfo; | |
| fn save_module_data(&mut self) -> Self::ModuleData { | |
| self.deref_id_map.borrow_mut().clear(); | |
| self.id_map.borrow_mut().clear(); | |
| self.types_with_notable_traits.borrow_mut().clear(); | |
| self.info | |
| } | |
| fn restore_module_data(&mut self, info: Self::ModuleData) { | |
| self.info = info; | |
| } | |
| fn after_krate(mut self) -> Result<(), Error> { | |
| let crate_name = self.tcx().crate_name(LOCAL_CRATE); | |
| let final_file = self.dst.join(crate_name.as_str()).join("all.html"); | |
| let shared = &self.shared; | |
| let page = layout::Page { | |
| title: "List of all items in this crate", | |
| short_title: "All", | |
| css_class: "mod sys", | |
| root_path: "../", | |
| static_root_path: shared.static_root_path.as_deref(), | |
| description: "List of all items in this crate", | |
| resource_suffix: &shared.resource_suffix, | |
| rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), |d| d.rust_logo.is_some()), | |
| }; | |
| let all = shared.all.replace(AllTypes::new()); | |
| let mut sidebar = String::new(); | |
| // all.html is not customizable, so a blank id map is fine | |
| let blocks = sidebar_module_like(all.item_sections(), &mut IdMap::new(), ModuleLike::Crate); | |
| let bar = Sidebar { | |
| title_prefix: "", | |
| title: "", | |
| is_crate: false, | |
| is_mod: false, | |
| parent_is_crate: false, | |
| blocks: vec![blocks], | |
| path: String::new(), | |
| }; | |
| bar.render_into(&mut sidebar).unwrap(); | |
| let v = layout::render(&shared.layout, &page, sidebar, all.print(), &shared.style_files); | |
| shared.fs.write(final_file, v)?; | |
| if let Some(ref redirections) = shared.redirections | |
| && !redirections.borrow().is_empty() | |
| { | |
| let redirect_map_path = self.dst.join(crate_name.as_str()).join("redirect-map.json"); | |
| let paths = serde_json::to_string(&*redirections.borrow()).unwrap(); | |
| shared.ensure_dir(&self.dst.join(crate_name.as_str()))?; | |
| shared.fs.write(redirect_map_path, paths)?; | |
| } | |
| // Flush pending errors. | |
| self.shared.fs.close(); | |
| let nb_errors = self.shared.errors.iter().map(|err| self.tcx().dcx().err(err)).count(); | |
| if nb_errors > 0 { Err(Error::new(io::Error::other("I/O error"), "")) } else { Ok(()) } | |
| } | |
| fn mod_item_in(&mut self, item: &clean::Item) -> Result<(), Error> { | |
| // Stripped modules survive the rustdoc passes (i.e., `strip-private`) | |
| // if they contain impls for public types. These modules can also | |
| // contain items such as publicly re-exported structures. | |
| // | |
| // External crates will provide links to these structures, so | |
| // these modules are recursed into, but not rendered normally | |
| // (a flag on the context). | |
| if !self.info.render_redirect_pages { | |
| self.info.render_redirect_pages = item.is_stripped(); | |
| } | |
| let item_name = item.name.unwrap(); | |
| self.dst.push(item_name.as_str()); | |
| self.current.push(item_name); | |
| info!("Recursing into {}", self.dst.display()); | |
| if !item.is_stripped() { | |
| let buf = self.render_item(item, true); | |
| // buf will be empty if the module is stripped and there is no redirect for it | |
| if !buf.is_empty() { | |
| self.shared.ensure_dir(&self.dst)?; | |
| let joint_dst = self.dst.join("index.html"); | |
| self.shared.fs.write(joint_dst, buf)?; | |
| } | |
| } | |
| if !self.info.is_inside_inlined_module { | |
| if let Some(def_id) = item.def_id() | |
| && self.cache().inlined_items.contains(&def_id) | |
| { | |
| self.info.is_inside_inlined_module = true; | |
| } | |
| } else if !self.cache().document_hidden && item.is_doc_hidden() { | |
| // We're not inside an inlined module anymore since this one cannot be re-exported. | |
| self.info.is_inside_inlined_module = false; | |
| } | |
| // Render sidebar-items.js used throughout this module. | |
| if !self.info.render_redirect_pages { | |
| let (clean::StrippedItem(clean::ModuleItem(ref module)) | |
| | clean::ModuleItem(ref module)) = item.kind | |
| else { | |
| unreachable!() | |
| }; | |
| let items = self.build_sidebar_items(module); | |
| let js_dst = self.dst.join(format!("sidebar-items{}.js", self.shared.resource_suffix)); | |
| let v = format!("window.SIDEBAR_ITEMS = {};", serde_json::to_string(&items).unwrap()); | |
| self.shared.fs.write(js_dst, v)?; | |
| } | |
| Ok(()) | |
| } | |
| fn mod_item_out(&mut self) -> Result<(), Error> { | |
| info!("Recursed; leaving {}", self.dst.display()); | |
| // Go back to where we were at | |
| self.dst.pop(); | |
| self.current.pop(); | |
| Ok(()) | |
| } | |
| fn item(&mut self, item: &clean::Item) -> Result<(), Error> { | |
| // Stripped modules survive the rustdoc passes (i.e., `strip-private`) | |
| // if they contain impls for public types. These modules can also | |
| // contain items such as publicly re-exported structures. | |
| // | |
| // External crates will provide links to these structures, so | |
| // these modules are recursed into, but not rendered normally | |
| // (a flag on the context). | |
| if !self.info.render_redirect_pages { | |
| self.info.render_redirect_pages = item.is_stripped(); | |
| } | |
| let buf = self.render_item(item, false); | |
| // buf will be empty if the item is stripped and there is no redirect for it | |
| if !buf.is_empty() { | |
| if !self.info.render_redirect_pages { | |
| self.shared.all.borrow_mut().append(full_path(self, item), &item); | |
| } | |
| let file_name = print_item_path(item).to_string(); | |
| self.shared.ensure_dir(&self.dst)?; | |
| let joint_dst = self.dst.join(&file_name); | |
| self.shared.fs.write(joint_dst, buf)?; | |
| // If the item is a macro, redirect from the old macro URL (with !) | |
| // to the new one (without). | |
| let item_type = item.type_(); | |
| if item_type == ItemType::Macro { | |
| let name = item.name.as_ref().unwrap(); | |
| let redir_name = format!("{item_type}.{name}!.html"); | |
| if let Some(ref redirections) = self.shared.redirections { | |
| let crate_name = &self.shared.layout.krate; | |
| redirections.borrow_mut().insert( | |
| format!("{crate_name}/{redir_name}"), | |
| format!("{crate_name}/{file_name}"), | |
| ); | |
| } else { | |
| let v = layout::redirect(&file_name); | |
| let redir_dst = self.dst.join(redir_name); | |
| self.shared.fs.write(redir_dst, v)?; | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| } |