summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/html/render
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/html/render')
-rw-r--r--src/librustdoc/html/render/context.rs33
-rw-r--r--src/librustdoc/html/render/mod.rs265
-rw-r--r--src/librustdoc/html/render/print_item.rs174
-rw-r--r--src/librustdoc/html/render/span_map.rs10
-rw-r--r--src/librustdoc/html/render/write_shared.rs290
5 files changed, 321 insertions, 451 deletions
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 5733d1f9c..73690c86f 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -32,7 +32,7 @@ use crate::html::escape::Escape;
use crate::html::format::{join_with_double_colon, Buffer};
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
use crate::html::url_parts_builder::UrlPartsBuilder;
-use crate::html::{layout, sources};
+use crate::html::{layout, sources, static_files};
use crate::scrape_examples::AllCallLocations;
use crate::try_err;
@@ -69,11 +69,13 @@ pub(crate) struct Context<'tcx> {
/// the source files are present in the html rendering, then this will be
/// `true`.
pub(crate) include_sources: bool,
+ /// Collection of all types with notable traits referenced in the current module.
+ pub(crate) types_with_notable_traits: FxHashSet<clean::Type>,
}
// `Context` is cloned a lot, so we don't want the size to grow unexpectedly.
#[cfg(all(not(windows), target_arch = "x86_64", target_pointer_width = "64"))]
-rustc_data_structures::static_assert_size!(Context<'_>, 128);
+rustc_data_structures::static_assert_size!(Context<'_>, 160);
/// Shared mutable state used in [`Context`] and elsewhere.
pub(crate) struct SharedContext<'tcx> {
@@ -498,7 +500,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
);
let (sender, receiver) = channel();
- let mut scx = SharedContext {
+ let scx = SharedContext {
tcx,
src_root,
local_sources,
@@ -521,19 +523,6 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
call_locations,
};
- // Add the default themes to the `Vec` of stylepaths
- //
- // Note that these must be added before `sources::render` is called
- // so that the resulting source pages are styled
- //
- // `light.css` is not disabled because it is the stylesheet that stays loaded
- // by the browser as the theme stylesheet. The theme system (hackily) works by
- // changing the href to this stylesheet. All other themes are disabled to
- // prevent rule conflicts
- scx.style_files.push(StylePath { path: PathBuf::from("light.css") });
- scx.style_files.push(StylePath { path: PathBuf::from("dark.css") });
- scx.style_files.push(StylePath { path: PathBuf::from("ayu.css") });
-
let dst = output;
scx.ensure_dir(&dst)?;
@@ -545,6 +534,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
deref_id_map: FxHashMap::default(),
shared: Rc::new(scx),
include_sources,
+ types_with_notable_traits: FxHashSet::default(),
};
if emit_crate {
@@ -573,6 +563,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
id_map: IdMap::new(),
shared: Rc::clone(&self.shared),
include_sources: self.include_sources,
+ types_with_notable_traits: FxHashSet::default(),
}
}
@@ -647,10 +638,11 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
</section>\
</noscript>\
<link rel=\"stylesheet\" type=\"text/css\" \
- href=\"{root_path}settings{suffix}.css\">\
- <script defer src=\"{root_path}settings{suffix}.js\"></script>",
- root_path = page.static_root_path.unwrap_or(""),
- suffix = page.resource_suffix,
+ href=\"{static_root_path}{settings_css}\">\
+ <script defer src=\"{static_root_path}{settings_js}\"></script>",
+ static_root_path = page.get_static_root_path(),
+ settings_css = static_files::STATIC_FILES.settings_css,
+ settings_js = static_files::STATIC_FILES.settings_js,
)
},
&shared.style_files,
@@ -815,6 +807,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
}
}
}
+
Ok(())
}
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 96c57c8c8..36d15ec3b 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -59,7 +59,7 @@ use rustc_span::{
symbol::{sym, Symbol},
BytePos, FileName, RealFileName,
};
-use serde::ser::SerializeSeq;
+use serde::ser::{SerializeMap, SerializeSeq};
use serde::{Serialize, Serializer};
use crate::clean::{self, ItemId, RenderedLink, SelfTy};
@@ -70,8 +70,8 @@ use crate::formats::{AssocItemRender, Impl, RenderMode};
use crate::html::escape::Escape;
use crate::html::format::{
href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
- print_default_space, print_generic_bounds, print_where_clause, Buffer, Ending, HrefError,
- PrintWithSpace,
+ print_default_space, print_generic_bounds, print_where_clause, visibility_print_with_space,
+ Buffer, Ending, HrefError, PrintWithSpace,
};
use crate::html::highlight;
use crate::html::markdown::{
@@ -747,11 +747,12 @@ fn assoc_const(
extra: &str,
cx: &Context<'_>,
) {
+ let tcx = cx.tcx();
write!(
w,
"{extra}{vis}const <a{href} class=\"constant\">{name}</a>: {ty}",
extra = extra,
- vis = it.visibility.print_with_space(it.item_id, cx),
+ vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
href = assoc_href_attr(it, link, cx),
name = it.name.as_ref().unwrap(),
ty = ty.print(cx),
@@ -764,7 +765,7 @@ fn assoc_const(
// This hurts readability in this context especially when more complex expressions
// are involved and it doesn't add much of value.
// Find a way to print constants here without all that jazz.
- write!(w, "{}", Escape(&default.value(cx.tcx()).unwrap_or_else(|| default.expr(cx.tcx()))));
+ write!(w, "{}", Escape(&default.value(tcx).unwrap_or_else(|| default.expr(tcx))));
}
}
@@ -802,17 +803,18 @@ fn assoc_method(
d: &clean::FnDecl,
link: AssocItemLink<'_>,
parent: ItemType,
- cx: &Context<'_>,
+ cx: &mut Context<'_>,
render_mode: RenderMode,
) {
- let header = meth.fn_header(cx.tcx()).expect("Trying to get header from a non-function item");
+ let tcx = cx.tcx();
+ let header = meth.fn_header(tcx).expect("Trying to get header from a non-function item");
let name = meth.name.as_ref().unwrap();
- let vis = meth.visibility.print_with_space(meth.item_id, cx).to_string();
+ let vis = visibility_print_with_space(meth.visibility(tcx), meth.item_id, cx).to_string();
// FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
// this condition.
let constness = match render_mode {
RenderMode::Normal => {
- print_constness_with_space(&header.constness, meth.const_stability(cx.tcx()))
+ print_constness_with_space(&header.constness, meth.const_stability(tcx))
}
RenderMode::ForDeref { .. } => "",
};
@@ -834,6 +836,8 @@ fn assoc_method(
+ name.as_str().len()
+ generics_len;
+ let notable_traits = d.output.as_return().and_then(|output| notable_traits_button(output, cx));
+
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
header_len += 4;
let indent_str = " ";
@@ -843,10 +847,10 @@ fn assoc_method(
render_attributes_in_code(w, meth);
(0, "", Ending::Newline)
};
- w.reserve(header_len + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len());
+ w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len());
write!(
w,
- "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a{href} class=\"fnname\">{name}</a>\
+ "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a{href} class=\"fn\">{name}</a>\
{generics}{decl}{notable_traits}{where_clause}",
indent = indent_str,
vis = vis,
@@ -859,9 +863,9 @@ fn assoc_method(
name = name,
generics = g.print(cx),
decl = d.full_print(header_len, indent, cx),
- notable_traits = notable_traits_decl(d, cx),
+ notable_traits = notable_traits.unwrap_or_default(),
where_clause = print_where_clause(g, cx, indent, end_newline),
- )
+ );
}
/// Writes a span containing the versions at which an item became stable and/or const-stable. For
@@ -961,7 +965,7 @@ fn render_assoc_item(
item: &clean::Item,
link: AssocItemLink<'_>,
parent: ItemType,
- cx: &Context<'_>,
+ cx: &mut Context<'_>,
render_mode: RenderMode,
) {
match &*item.kind {
@@ -1067,7 +1071,7 @@ fn write_impl_section_heading(w: &mut Buffer, title: &str, id: &str) {
w,
"<h2 id=\"{id}\" class=\"small-section-header\">\
{title}\
- <a href=\"#{id}\" class=\"anchor\"></a>\
+ <a href=\"#{id}\" class=\"anchor\">§</a>\
</h2>"
);
}
@@ -1271,88 +1275,133 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) ->
}
}
-fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String {
- let mut out = Buffer::html();
+pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> Option<String> {
+ let mut has_notable_trait = false;
+
+ let did = ty.def_id(cx.cache())?;
- if let Some((did, ty)) = decl.output.as_return().and_then(|t| Some((t.def_id(cx.cache())?, t)))
+ // Box has pass-through impls for Read, Write, Iterator, and Future when the
+ // boxed type implements one of those. We don't want to treat every Box return
+ // as being notably an Iterator (etc), though, so we exempt it. Pin has the same
+ // issue, with a pass-through impl for Future.
+ if Some(did) == cx.tcx().lang_items().owned_box()
+ || Some(did) == cx.tcx().lang_items().pin_type()
{
- // Box has pass-through impls for Read, Write, Iterator, and Future when the
- // boxed type implements one of those. We don't want to treat every Box return
- // as being notably an Iterator (etc), though, so we exempt it. Pin has the same
- // issue, with a pass-through impl for Future.
- if Some(did) == cx.tcx().lang_items().owned_box()
- || Some(did) == cx.tcx().lang_items().pin_type()
- {
- return "".to_string();
- }
- if let Some(impls) = cx.cache().impls.get(&did) {
- for i in impls {
- let impl_ = i.inner_impl();
- if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache())
+ return None;
+ }
+
+ if let Some(impls) = cx.cache().impls.get(&did) {
+ for i in impls {
+ let impl_ = i.inner_impl();
+ if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache()) {
+ // Two different types might have the same did,
+ // without actually being the same.
+ continue;
+ }
+ if let Some(trait_) = &impl_.trait_ {
+ let trait_did = trait_.def_id();
+
+ if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx()))
{
- // Two different types might have the same did,
- // without actually being the same.
- continue;
+ has_notable_trait = true;
}
- if let Some(trait_) = &impl_.trait_ {
- let trait_did = trait_.def_id();
-
- if cx
- .cache()
- .traits
- .get(&trait_did)
- .map_or(false, |t| t.is_notable_trait(cx.tcx()))
- {
- if out.is_empty() {
- write!(
- &mut out,
- "<span class=\"notable\">Notable traits for {}</span>\
- <code class=\"content\">",
- impl_.for_.print(cx)
- );
- }
+ }
+ }
+ }
- //use the "where" class here to make it small
- write!(
+ if has_notable_trait {
+ cx.types_with_notable_traits.insert(ty.clone());
+ Some(format!(
+ " <a href=\"#\" class=\"notable-traits\" data-ty=\"{ty}\">ⓘ</a>",
+ ty = Escape(&format!("{:#}", ty.print(cx))),
+ ))
+ } else {
+ None
+ }
+}
+
+fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
+ let mut out = Buffer::html();
+
+ let did = ty.def_id(cx.cache()).expect("notable_traits_button already checked this");
+
+ let impls = cx.cache().impls.get(&did).expect("notable_traits_button already checked this");
+
+ for i in impls {
+ let impl_ = i.inner_impl();
+ if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache()) {
+ // Two different types might have the same did,
+ // without actually being the same.
+ continue;
+ }
+ if let Some(trait_) = &impl_.trait_ {
+ let trait_did = trait_.def_id();
+
+ if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx())) {
+ if out.is_empty() {
+ write!(
+ &mut out,
+ "<h3>Notable traits for <code>{}</code></h3>\
+ <pre class=\"content\"><code>",
+ impl_.for_.print(cx)
+ );
+ }
+
+ //use the "where" class here to make it small
+ write!(
+ &mut out,
+ "<span class=\"where fmt-newline\">{}</span>",
+ impl_.print(false, cx)
+ );
+ for it in &impl_.items {
+ if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
+ out.push_str("<span class=\"where fmt-newline\"> ");
+ let empty_set = FxHashSet::default();
+ let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set);
+ assoc_type(
&mut out,
- "<span class=\"where fmt-newline\">{}</span>",
- impl_.print(false, cx)
+ it,
+ &tydef.generics,
+ &[], // intentionally leaving out bounds
+ Some(&tydef.type_),
+ src_link,
+ 0,
+ cx,
);
- for it in &impl_.items {
- if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
- out.push_str("<span class=\"where fmt-newline\"> ");
- let empty_set = FxHashSet::default();
- let src_link =
- AssocItemLink::GotoSource(trait_did.into(), &empty_set);
- assoc_type(
- &mut out,
- it,
- &tydef.generics,
- &[], // intentionally leaving out bounds
- Some(&tydef.type_),
- src_link,
- 0,
- cx,
- );
- out.push_str(";</span>");
- }
- }
+ out.push_str(";</span>");
}
}
}
}
}
-
- if !out.is_empty() {
- out.insert_str(
- 0,
- "<span class=\"notable-traits\"><span class=\"notable-traits-tooltip\">ⓘ\
- <span class=\"notable-traits-tooltiptext\"><span class=\"docblock\">",
- );
- out.push_str("</code></span></span></span></span>");
+ if out.is_empty() {
+ write!(&mut out, "</code></pre>",);
}
- out.into_inner()
+ (format!("{:#}", ty.print(cx)), out.into_inner())
+}
+
+pub(crate) fn notable_traits_json<'a>(
+ tys: impl Iterator<Item = &'a clean::Type>,
+ cx: &Context<'_>,
+) -> String {
+ let mut mp: Vec<(String, String)> = tys.map(|ty| notable_traits_decl(ty, cx)).collect();
+ mp.sort_by(|(name1, _html1), (name2, _html2)| name1.cmp(name2));
+ struct NotableTraitsMap(Vec<(String, String)>);
+ impl Serialize for NotableTraitsMap {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut map = serializer.serialize_map(Some(self.0.len()))?;
+ for item in &self.0 {
+ map.serialize_entry(&item.0, &item.1)?;
+ }
+ map.end()
+ }
+ }
+ serde_json::to_string(&NotableTraitsMap(mp))
+ .expect("serialize (string, string) -> json object cannot fail")
}
#[derive(Clone, Copy, Debug)]
@@ -1487,7 +1536,7 @@ fn render_impl(
render_rightside(w, cx, item, containing_item, render_mode);
if trait_.is_some() {
// Anchors are only used on trait impls.
- write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
+ write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id);
}
w.write_str("<h4 class=\"code-header\">");
render_assoc_item(
@@ -1513,7 +1562,7 @@ fn render_impl(
render_rightside(w, cx, item, containing_item, render_mode);
if trait_.is_some() {
// Anchors are only used on trait impls.
- write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
+ write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id);
}
w.write_str("<h4 class=\"code-header\">");
assoc_const(
@@ -1538,7 +1587,7 @@ fn render_impl(
write!(w, "<section id=\"{}\" class=\"{}{}\">", id, item_type, in_trait_class);
if trait_.is_some() {
// Anchors are only used on trait impls.
- write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
+ write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id);
}
w.write_str("<h4 class=\"code-header\">");
assoc_type(
@@ -1564,7 +1613,7 @@ fn render_impl(
);
if trait_.is_some() {
// Anchors are only used on trait impls.
- write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
+ write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id);
}
w.write_str("<h4 class=\"code-header\">");
assoc_type(
@@ -1797,7 +1846,7 @@ pub(crate) fn render_impl_summary(
};
write!(w, "<section id=\"{}\" class=\"impl has-srclink\"{}>", id, aliases);
render_rightside(w, cx, &i.impl_item, containing_item, RenderMode::Normal);
- write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id);
+ write!(w, "<a href=\"#{}\" class=\"anchor\">§</a>", id);
write!(w, "<h3 class=\"code-header\">");
if let Some(use_absolute) = use_absolute {
@@ -2231,12 +2280,12 @@ fn sidebar_struct(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, s: &clea
let fields = get_struct_fields_name(&s.fields);
if !fields.is_empty() {
- match s.struct_type {
- CtorKind::Fictive => {
+ match s.ctor_kind {
+ None => {
print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter());
}
- CtorKind::Fn => print_sidebar_title(&mut sidebar, "fields", "Tuple Fields"),
- CtorKind::Const => {}
+ Some(CtorKind::Fn) => print_sidebar_title(&mut sidebar, "fields", "Tuple Fields"),
+ Some(CtorKind::Const) => {}
}
}
@@ -2866,11 +2915,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
);
if line_ranges.len() > 1 {
- write!(w, r#"<span class="prev">&pr;</span> <span class="next">&sc;</span>"#);
- }
-
- if needs_expansion {
- write!(w, r#"<span class="expand">&varr;</span>"#);
+ write!(w, r#"<button class="prev">&pr;</button> <button class="next">&sc;</button>"#);
}
// Look for the example file in the source map if it exists, otherwise return a dummy span
@@ -2892,9 +2937,6 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
})()
.unwrap_or(rustc_span::DUMMY_SP);
- // The root path is the inverse of Context::current
- let root_path = vec!["../"; cx.current.len() - 1].join("");
-
let mut decoration_info = FxHashMap::default();
decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]);
decoration_info.insert("highlight", byte_ranges);
@@ -2904,9 +2946,9 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
contents_subset,
file_span,
cx,
- &root_path,
+ &cx.root_path(),
highlight::DecorationInfo(decoration_info),
- sources::SourceContext::Embedded { offset: line_min },
+ sources::SourceContext::Embedded { offset: line_min, needs_expansion },
);
write!(w, "</div></div>");
@@ -2915,14 +2957,23 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
// The call locations are output in sequence, so that sequence needs to be determined.
// Ideally the most "relevant" examples would be shown first, but there's no general algorithm
- // for determining relevance. Instead, we prefer the smallest examples being likely the easiest to
- // understand at a glance.
+ // for determining relevance. We instead proxy relevance with the following heuristics:
+ // 1. Code written to be an example is better than code not written to be an example, e.g.
+ // a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo
+ // directory structure in Rustdoc, so we proxy this by prioritizing code that comes from
+ // a --crate-type bin.
+ // 2. Smaller examples are better than large examples. So we prioritize snippets that have
+ // the smallest number of lines in their enclosing item.
+ // 3. Finally we sort by the displayed file name, which is arbitrary but prevents the
+ // ordering of examples from randomly changing between Rustdoc invocations.
let ordered_locations = {
- let sort_criterion = |(_, call_data): &(_, &CallData)| {
+ fn sort_criterion<'a>(
+ (_, call_data): &(&PathBuf, &'a CallData),
+ ) -> (bool, u32, &'a String) {
// Use the first location because that's what the user will see initially
let (lo, hi) = call_data.locations[0].enclosing_item.byte_span;
- hi - lo
- };
+ (!call_data.is_bin, hi - lo, &call_data.display_name)
+ }
let mut locs = call_locations.iter().collect::<Vec<_>>();
locs.sort_by_key(sort_criterion);
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 632781736..acbe3f228 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -7,19 +7,20 @@ use rustc_hir::def_id::DefId;
use rustc_middle::middle::stability;
use rustc_middle::span_bug;
use rustc_middle::ty::layout::LayoutError;
-use rustc_middle::ty::{Adt, TyCtxt};
+use rustc_middle::ty::{self, Adt, TyCtxt};
use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::{kw, sym, Symbol};
-use rustc_target::abi::{Layout, Primitive, TagEncoding, Variants};
+use rustc_target::abi::{LayoutS, Primitive, TagEncoding, VariantIdx, Variants};
use std::cmp::Ordering;
use std::fmt;
use std::rc::Rc;
use super::{
collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference,
- item_ty_to_section, notable_traits_decl, render_all_impls, render_assoc_item,
- render_assoc_items, render_attributes_in_code, render_attributes_in_pre, render_impl,
- render_rightside, render_stability_since_raw, AssocItemLink, Context, ImplRenderingParameters,
+ item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
+ render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
+ render_impl, render_rightside, render_stability_since_raw,
+ render_stability_since_raw_with_extra, AssocItemLink, Context, ImplRenderingParameters,
};
use crate::clean;
use crate::config::ModuleSorting;
@@ -28,12 +29,12 @@ use crate::formats::{AssocItemRender, Impl, RenderMode};
use crate::html::escape::Escape;
use crate::html::format::{
join_with_double_colon, print_abi_with_space, print_constness_with_space, print_where_clause,
- Buffer, Ending, PrintWithSpace,
+ visibility_print_with_space, Buffer, Ending, PrintWithSpace,
};
-use crate::html::highlight;
use crate::html::layout::Page;
use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
use crate::html::url_parts_builder::UrlPartsBuilder;
+use crate::html::{highlight, static_files};
use askama::Template;
use itertools::Itertools;
@@ -52,8 +53,8 @@ struct PathComponent {
#[derive(Template)]
#[template(path = "print_item.html")]
struct ItemVars<'a> {
- page: &'a Page<'a>,
static_root_path: &'a str,
+ clipboard_svg: &'static static_files::StaticFile,
typ: &'a str,
name: &'a str,
item_type: &'a str,
@@ -147,8 +148,8 @@ pub(super) fn print_item(
};
let item_vars = ItemVars {
- page,
- static_root_path: page.get_static_root_path(),
+ static_root_path: &page.get_static_root_path(),
+ clipboard_svg: &static_files::STATIC_FILES.clipboard_svg,
typ,
name: item.name.as_ref().unwrap().as_str(),
item_type: &item.type_().to_string(),
@@ -183,6 +184,16 @@ pub(super) fn print_item(
unreachable!();
}
}
+
+ // Render notable-traits.js used for all methods in this module.
+ if !cx.types_with_notable_traits.is_empty() {
+ write!(
+ buf,
+ r#"<script type="text/json" id="notable-traits-data">{}</script>"#,
+ notable_traits_json(cx.types_with_notable_traits.iter(), cx)
+ );
+ cx.types_with_notable_traits.clear();
+ }
}
/// For large structs, enums, unions, etc, determine whether to hide their fields
@@ -318,6 +329,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
);
}
+ let tcx = cx.tcx();
match *myitem.kind {
clean::ExternCrateItem { ref src } => {
use crate::html::format::anchor;
@@ -327,14 +339,14 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
Some(src) => write!(
w,
"<div class=\"item-left\"><code>{}extern crate {} as {};",
- myitem.visibility.print_with_space(myitem.item_id, cx),
+ visibility_print_with_space(myitem.visibility(tcx), myitem.item_id, cx),
anchor(myitem.item_id.expect_def_id(), src, cx),
myitem.name.unwrap(),
),
None => write!(
w,
"<div class=\"item-left\"><code>{}extern crate {};",
- myitem.visibility.print_with_space(myitem.item_id, cx),
+ visibility_print_with_space(myitem.visibility(tcx), myitem.item_id, cx),
anchor(myitem.item_id.expect_def_id(), myitem.name.unwrap(), cx),
),
}
@@ -384,7 +396,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
</div>\
{stab_tags_before}{stab_tags}{stab_tags_after}",
stab = stab.unwrap_or_default(),
- vis = myitem.visibility.print_with_space(myitem.item_id, cx),
+ vis = visibility_print_with_space(myitem.visibility(tcx), myitem.item_id, cx),
imp = import.print(cx),
);
w.write_str(ITEM_TABLE_ROW_CLOSE);
@@ -408,8 +420,8 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
let stab = myitem.stability_class(cx.tcx());
let add = if stab.is_some() { " " } else { "" };
- let visibility_emoji = match myitem.visibility {
- clean::Visibility::Restricted(_) => {
+ let visibility_emoji = match myitem.visibility(tcx) {
+ Some(ty::Visibility::Restricted(_)) => {
"<span title=\"Restricted Visibility\">&nbsp;🔒</span> "
}
_ => "",
@@ -496,12 +508,13 @@ fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) ->
}
fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &clean::Function) {
- let header = it.fn_header(cx.tcx()).expect("printing a function which isn't a function");
- let constness = print_constness_with_space(&header.constness, it.const_stability(cx.tcx()));
+ let tcx = cx.tcx();
+ let header = it.fn_header(tcx).expect("printing a function which isn't a function");
+ let constness = print_constness_with_space(&header.constness, it.const_stability(tcx));
let unsafety = header.unsafety.print_with_space();
let abi = print_abi_with_space(header.abi).to_string();
let asyncness = header.asyncness.print_with_space();
- let visibility = it.visibility.print_with_space(it.item_id, cx).to_string();
+ let visibility = visibility_print_with_space(it.visibility(tcx), it.item_id, cx).to_string();
let name = it.name.unwrap();
let generics_len = format!("{:#}", f.generics.print(cx)).len();
@@ -514,6 +527,9 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
+ name.as_str().len()
+ generics_len;
+ let notable_traits =
+ f.decl.output.as_return().and_then(|output| notable_traits_button(output, cx));
+
wrap_into_item_decl(w, |w| {
wrap_item(w, "fn", |w| {
render_attributes_in_pre(w, it, "");
@@ -531,14 +547,15 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
generics = f.generics.print(cx),
where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline),
decl = f.decl.full_print(header_len, 0, cx),
- notable_traits = notable_traits_decl(&f.decl, cx),
+ notable_traits = notable_traits.unwrap_or_default(),
);
});
});
- document(w, cx, it, None, HeadingOffset::H2)
+ document(w, cx, it, None, HeadingOffset::H2);
}
fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Trait) {
+ let tcx = cx.tcx();
let bounds = bounds(&t.bounds, false, cx);
let required_types = t.items.iter().filter(|m| m.is_ty_associated_type()).collect::<Vec<_>>();
let provided_types = t.items.iter().filter(|m| m.is_associated_type()).collect::<Vec<_>>();
@@ -549,8 +566,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
let count_types = required_types.len() + provided_types.len();
let count_consts = required_consts.len() + provided_consts.len();
let count_methods = required_methods.len() + provided_methods.len();
- let must_implement_one_of_functions =
- cx.tcx().trait_def(t.def_id).must_implement_one_of.clone();
+ let must_implement_one_of_functions = tcx.trait_def(t.def_id).must_implement_one_of.clone();
// Output the trait definition
wrap_into_item_decl(w, |w| {
@@ -559,9 +575,9 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
write!(
w,
"{}{}{}trait {}{}{}",
- it.visibility.print_with_space(it.item_id, cx),
- t.unsafety(cx.tcx()).print_with_space(),
- if t.is_auto(cx.tcx()) { "auto " } else { "" },
+ visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
+ t.unsafety(tcx).print_with_space(),
+ if t.is_auto(tcx) { "auto " } else { "" },
it.name.unwrap(),
t.generics.print(cx),
bounds
@@ -701,7 +717,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
write!(
w,
"<h2 id=\"{0}\" class=\"small-section-header\">\
- {1}<a href=\"#{0}\" class=\"anchor\"></a>\
+ {1}<a href=\"#{0}\" class=\"anchor\">§</a>\
</h2>{2}",
id, title, extra_content
)
@@ -1020,7 +1036,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
}
let extern_crates = extern_crates
.into_iter()
- .map(|cnum| cx.shared.tcx.crate_name(cnum).to_string())
+ .map(|cnum| tcx.crate_name(cnum).to_string())
.collect::<Vec<_>>()
.join(",");
let (extern_before, extern_after) =
@@ -1084,7 +1100,7 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea
fn write_content(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) {
wrap_item(w, "typedef", |w| {
render_attributes_in_pre(w, it, "");
- write!(w, "{}", it.visibility.print_with_space(it.item_id, cx));
+ write!(w, "{}", visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx));
write!(
w,
"type {}{}{where_clause} = {type_};",
@@ -1131,7 +1147,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
write!(
w,
"<h2 id=\"fields\" class=\"fields small-section-header\">\
- Fields<a href=\"#fields\" class=\"anchor\"></a>\
+ Fields<a href=\"#fields\" class=\"anchor\">§</a>\
</h2>"
);
for (field, ty) in fields {
@@ -1140,7 +1156,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
write!(
w,
"<span id=\"{id}\" class=\"{shortty} small-section-header\">\
- <a href=\"#{id}\" class=\"anchor field\"></a>\
+ <a href=\"#{id}\" class=\"anchor field\">§</a>\
<code>{name}: {ty}</code>\
</span>",
id = id,
@@ -1173,6 +1189,7 @@ fn print_tuple_struct_fields(w: &mut Buffer, cx: &Context<'_>, s: &[clean::Item]
}
fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::Enum) {
+ let tcx = cx.tcx();
let count_variants = e.variants().count();
wrap_into_item_decl(w, |w| {
wrap_item(w, "enum", |w| {
@@ -1180,7 +1197,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
write!(
w,
"{}enum {}{}",
- it.visibility.print_with_space(it.item_id, cx),
+ visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
it.name.unwrap(),
e.generics.print(cx),
);
@@ -1215,7 +1232,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
w,
v,
None,
- s.struct_type,
+ s.ctor_kind,
&s.fields,
" ",
false,
@@ -1245,35 +1262,35 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
write!(
w,
"<h2 id=\"variants\" class=\"variants small-section-header\">\
- Variants{}<a href=\"#variants\" class=\"anchor\"></a>\
+ Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\
</h2>",
document_non_exhaustive_header(it)
);
document_non_exhaustive(w, it);
+ write!(w, "<div class=\"variants\">");
for variant in e.variants() {
let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap()));
write!(
w,
- "<h3 id=\"{id}\" class=\"variant small-section-header\">\
- <a href=\"#{id}\" class=\"anchor field\"></a>\
- <code>{name}",
+ "<section id=\"{id}\" class=\"variant\">\
+ <a href=\"#{id}\" class=\"anchor\">§</a>",
id = id,
- name = variant.name.unwrap()
);
+ render_stability_since_raw_with_extra(
+ w,
+ variant.stable_since(tcx),
+ variant.const_stability(tcx),
+ it.stable_since(tcx),
+ it.const_stable_since(tcx),
+ " rightside",
+ );
+ write!(w, "<h3 class=\"code-header\">{name}", name = variant.name.unwrap());
if let clean::VariantItem(clean::Variant::Tuple(ref s)) = *variant.kind {
w.write_str("(");
print_tuple_struct_fields(w, cx, s);
w.write_str(")");
}
- w.write_str("</code>");
- render_stability_since_raw(
- w,
- variant.stable_since(cx.tcx()),
- variant.const_stability(cx.tcx()),
- it.stable_since(cx.tcx()),
- it.const_stable_since(cx.tcx()),
- );
- w.write_str("</h3>");
+ w.write_str("</h3></section>");
use crate::clean::Variant;
@@ -1307,8 +1324,8 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
write!(
w,
"<div class=\"sub-variant-field\">\
- <span id=\"{id}\" class=\"variant small-section-header\">\
- <a href=\"#{id}\" class=\"anchor field\"></a>\
+ <span id=\"{id}\" class=\"small-section-header\">\
+ <a href=\"#{id}\" class=\"anchor field\">§</a>\
<code>{f}:&nbsp;{t}</code>\
</span>",
id = id,
@@ -1326,6 +1343,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
document(w, cx, variant, Some(it), HeadingOffset::H4);
}
+ write!(w, "</div>");
}
let def_id = it.item_id.expect_def_id();
render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
@@ -1389,12 +1407,13 @@ fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &clean::Constant) {
wrap_into_item_decl(w, |w| {
wrap_item(w, "const", |w| {
+ let tcx = cx.tcx();
render_attributes_in_code(w, it);
write!(
w,
"{vis}const {name}: {typ}",
- vis = it.visibility.print_with_space(it.item_id, cx),
+ vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
name = it.name.unwrap(),
typ = c.type_.print(cx),
);
@@ -1408,9 +1427,9 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle
// ` = 100i32;`
// instead?
- let value = c.value(cx.tcx());
- let is_literal = c.is_literal(cx.tcx());
- let expr = c.expr(cx.tcx());
+ let value = c.value(tcx);
+ let is_literal = c.is_literal(tcx);
+ let expr = c.expr(tcx);
if value.is_some() || is_literal {
write!(w, " = {expr};", expr = Escape(&expr));
} else {
@@ -1439,7 +1458,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
wrap_into_item_decl(w, |w| {
wrap_item(w, "struct", |w| {
render_attributes_in_code(w, it);
- render_struct(w, it, Some(&s.generics), s.struct_type, &s.fields, "", true, cx);
+ render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx);
});
});
@@ -1453,14 +1472,14 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
_ => None,
})
.peekable();
- if let CtorKind::Fictive | CtorKind::Fn = s.struct_type {
+ if let None | Some(CtorKind::Fn) = s.ctor_kind {
if fields.peek().is_some() {
write!(
w,
"<h2 id=\"fields\" class=\"fields small-section-header\">\
- {}{}<a href=\"#fields\" class=\"anchor\"></a>\
+ {}{}<a href=\"#fields\" class=\"anchor\">§</a>\
</h2>",
- if let CtorKind::Fictive = s.struct_type { "Fields" } else { "Tuple Fields" },
+ if s.ctor_kind.is_none() { "Fields" } else { "Tuple Fields" },
document_non_exhaustive_header(it)
);
document_non_exhaustive(w, it);
@@ -1471,7 +1490,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
write!(
w,
"<span id=\"{id}\" class=\"{item_type} small-section-header\">\
- <a href=\"#{id}\" class=\"anchor field\"></a>\
+ <a href=\"#{id}\" class=\"anchor field\">§</a>\
<code>{name}: {ty}</code>\
</span>",
item_type = ItemType::StructField,
@@ -1495,7 +1514,7 @@ fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
write!(
w,
"{vis}static {mutability}{name}: {typ}",
- vis = it.visibility.print_with_space(it.item_id, cx),
+ vis = visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx),
mutability = s.mutability.print_with_space(),
name = it.name.unwrap(),
typ = s.type_.print(cx)
@@ -1513,7 +1532,7 @@ fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
write!(
w,
" {}type {};\n}}",
- it.visibility.print_with_space(it.item_id, cx),
+ visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx),
it.name.unwrap(),
);
});
@@ -1666,7 +1685,13 @@ fn render_union(
tab: &str,
cx: &Context<'_>,
) {
- write!(w, "{}union {}", it.visibility.print_with_space(it.item_id, cx), it.name.unwrap(),);
+ let tcx = cx.tcx();
+ write!(
+ w,
+ "{}union {}",
+ visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
+ it.name.unwrap(),
+ );
let where_displayed = g
.map(|g| {
@@ -1693,7 +1718,7 @@ fn render_union(
write!(
w,
" {}{}: {},\n{}",
- field.visibility.print_with_space(field.item_id, cx),
+ visibility_print_with_space(field.visibility(tcx), field.item_id, cx),
field.name.unwrap(),
ty.print(cx),
tab
@@ -1714,16 +1739,17 @@ fn render_struct(
w: &mut Buffer,
it: &clean::Item,
g: Option<&clean::Generics>,
- ty: CtorKind,
+ ty: Option<CtorKind>,
fields: &[clean::Item],
tab: &str,
structhead: bool,
cx: &Context<'_>,
) {
+ let tcx = cx.tcx();
write!(
w,
"{}{}{}",
- it.visibility.print_with_space(it.item_id, cx),
+ visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
if structhead { "struct " } else { "" },
it.name.unwrap()
);
@@ -1731,7 +1757,7 @@ fn render_struct(
write!(w, "{}", g.print(cx))
}
match ty {
- CtorKind::Fictive => {
+ None => {
let where_diplayed = g.map(|g| print_where_clause_and_check(w, g, cx)).unwrap_or(false);
// If there wasn't a `where` clause, we add a whitespace.
@@ -1753,7 +1779,7 @@ fn render_struct(
w,
"\n{} {}{}: {},",
tab,
- field.visibility.print_with_space(field.item_id, cx),
+ visibility_print_with_space(field.visibility(tcx), field.item_id, cx),
field.name.unwrap(),
ty.print(cx),
);
@@ -1773,7 +1799,7 @@ fn render_struct(
}
w.write_str("}");
}
- CtorKind::Fn => {
+ Some(CtorKind::Fn) => {
w.write_str("(");
for (i, field) in fields.iter().enumerate() {
if i > 0 {
@@ -1785,7 +1811,7 @@ fn render_struct(
write!(
w,
"{}{}",
- field.visibility.print_with_space(field.item_id, cx),
+ visibility_print_with_space(field.visibility(tcx), field.item_id, cx),
ty.print(cx),
)
}
@@ -1801,7 +1827,7 @@ fn render_struct(
w.write_str(";");
}
}
- CtorKind::Const => {
+ Some(CtorKind::Const) => {
// Needed for PhantomData.
if let Some(g) = g {
write!(w, "{}", print_where_clause(g, cx, 0, Ending::NoNewline));
@@ -1866,11 +1892,11 @@ fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) {
}
fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) {
- fn write_size_of_layout(w: &mut Buffer, layout: Layout<'_>, tag_size: u64) {
- if layout.abi().is_unsized() {
+ fn write_size_of_layout(w: &mut Buffer, layout: &LayoutS<VariantIdx>, tag_size: u64) {
+ if layout.abi.is_unsized() {
write!(w, "(unsized)");
} else {
- let bytes = layout.size().bytes() - tag_size;
+ let bytes = layout.size.bytes() - tag_size;
write!(w, "{size} byte{pl}", size = bytes, pl = if bytes == 1 { "" } else { "s" },);
}
}
@@ -1882,7 +1908,7 @@ fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) {
writeln!(
w,
"<h2 id=\"layout\" class=\"small-section-header\"> \
- Layout<a href=\"#layout\" class=\"anchor\"></a></h2>"
+ Layout<a href=\"#layout\" class=\"anchor\">§</a></h2>"
);
writeln!(w, "<div class=\"docblock\">");
@@ -1901,7 +1927,7 @@ fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) {
chapter for details on type layout guarantees.</p></div>"
);
w.write_str("<p><strong>Size:</strong> ");
- write_size_of_layout(w, ty_layout.layout, 0);
+ write_size_of_layout(w, &ty_layout.layout.0, 0);
writeln!(w, "</p>");
if let Variants::Multiple { variants, tag, tag_encoding, .. } =
&ty_layout.layout.variants()
@@ -1927,7 +1953,7 @@ fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) {
for (index, layout) in variants.iter_enumerated() {
let name = adt.variant(index).name;
write!(w, "<li><code>{name}</code>: ", name = name);
- write_size_of_layout(w, *layout, tag_size);
+ write_size_of_layout(w, layout, tag_size);
writeln!(w, "</li>");
}
w.write_str("</ul>");
diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs
index 151ec2b28..4514894ca 100644
--- a/src/librustdoc/html/render/span_map.rs
+++ b/src/librustdoc/html/render/span_map.rs
@@ -140,7 +140,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
self.tcx.hir()
}
- fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) {
+ fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) {
if self.handle_macro(path.span) {
return;
}
@@ -190,12 +190,4 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
}
intravisit::walk_expr(self, expr);
}
-
- fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) {
- if self.handle_macro(path.span) {
- return;
- }
- self.handle_path(path);
- intravisit::walk_use(self, path, id);
- }
}
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index 85f63c985..94d8a9fec 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -1,10 +1,8 @@
-use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::{self, BufReader};
-use std::path::{Component, Path, PathBuf};
+use std::path::{Component, Path};
use std::rc::Rc;
-use std::sync::LazyLock as Lazy;
use itertools::Itertools;
use rustc_data_structures::flock;
@@ -20,123 +18,20 @@ use crate::error::Error;
use crate::html::{layout, static_files};
use crate::{try_err, try_none};
-static FILES_UNVERSIONED: Lazy<FxHashMap<&str, &[u8]>> = Lazy::new(|| {
- map! {
- "FiraSans-Regular.woff2" => static_files::fira_sans::REGULAR,
- "FiraSans-Medium.woff2" => static_files::fira_sans::MEDIUM,
- "FiraSans-LICENSE.txt" => static_files::fira_sans::LICENSE,
- "SourceSerif4-Regular.ttf.woff2" => static_files::source_serif_4::REGULAR,
- "SourceSerif4-Bold.ttf.woff2" => static_files::source_serif_4::BOLD,
- "SourceSerif4-It.ttf.woff2" => static_files::source_serif_4::ITALIC,
- "SourceSerif4-LICENSE.md" => static_files::source_serif_4::LICENSE,
- "SourceCodePro-Regular.ttf.woff2" => static_files::source_code_pro::REGULAR,
- "SourceCodePro-Semibold.ttf.woff2" => static_files::source_code_pro::SEMIBOLD,
- "SourceCodePro-It.ttf.woff2" => static_files::source_code_pro::ITALIC,
- "SourceCodePro-LICENSE.txt" => static_files::source_code_pro::LICENSE,
- "NanumBarunGothic.ttf.woff2" => static_files::nanum_barun_gothic::REGULAR,
- "NanumBarunGothic-LICENSE.txt" => static_files::nanum_barun_gothic::LICENSE,
- "LICENSE-MIT.txt" => static_files::LICENSE_MIT,
- "LICENSE-APACHE.txt" => static_files::LICENSE_APACHE,
- "COPYRIGHT.txt" => static_files::COPYRIGHT,
- }
-});
-
-enum SharedResource<'a> {
- /// This file will never change, no matter what toolchain is used to build it.
- ///
- /// It does not have a resource suffix.
- Unversioned { name: &'static str },
- /// This file may change depending on the toolchain.
- ///
- /// It has a resource suffix.
- ToolchainSpecific { basename: &'static str },
- /// This file may change for any crate within a build, or based on the CLI arguments.
- ///
- /// This differs from normal invocation-specific files because it has a resource suffix.
- InvocationSpecific { basename: &'a str },
-}
-
-impl SharedResource<'_> {
- fn extension(&self) -> Option<&OsStr> {
- use SharedResource::*;
- match self {
- Unversioned { name }
- | ToolchainSpecific { basename: name }
- | InvocationSpecific { basename: name } => Path::new(name).extension(),
- }
- }
-
- fn path(&self, cx: &Context<'_>) -> PathBuf {
- match self {
- SharedResource::Unversioned { name } => cx.dst.join(name),
- SharedResource::ToolchainSpecific { basename } => cx.suffix_path(basename),
- SharedResource::InvocationSpecific { basename } => cx.suffix_path(basename),
- }
- }
-
- fn should_emit(&self, emit: &[EmitType]) -> bool {
- if emit.is_empty() {
- return true;
- }
- let kind = match self {
- SharedResource::Unversioned { .. } => EmitType::Unversioned,
- SharedResource::ToolchainSpecific { .. } => EmitType::Toolchain,
- SharedResource::InvocationSpecific { .. } => EmitType::InvocationSpecific,
- };
- emit.contains(&kind)
- }
-}
-
-impl Context<'_> {
- fn suffix_path(&self, filename: &str) -> PathBuf {
- // We use splitn vs Path::extension here because we might get a filename
- // like `style.min.css` and we want to process that into
- // `style-suffix.min.css`. Path::extension would just return `css`
- // which would result in `style.min-suffix.css` which isn't what we
- // want.
- let (base, ext) = filename.split_once('.').unwrap();
- let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext);
- self.dst.join(&filename)
- }
-
- fn write_shared(
- &self,
- resource: SharedResource<'_>,
- contents: impl 'static + Send + AsRef<[u8]>,
- emit: &[EmitType],
- ) -> Result<(), Error> {
- if resource.should_emit(emit) {
- self.shared.fs.write(resource.path(self), contents)
- } else {
- Ok(())
- }
- }
-
- fn write_minify(
- &self,
- resource: SharedResource<'_>,
- contents: impl 'static + Send + AsRef<str> + AsRef<[u8]>,
- minify: bool,
- emit: &[EmitType],
- ) -> Result<(), Error> {
- if minify {
- let contents = contents.as_ref();
- let contents = if resource.extension() == Some(OsStr::new("css")) {
- minifier::css::minify(contents)
- .map_err(|e| {
- Error::new(format!("failed to minify CSS file: {}", e), resource.path(self))
- })?
- .to_string()
- } else {
- minifier::js::minify(contents).to_string()
- };
- self.write_shared(resource, contents, emit)
- } else {
- self.write_shared(resource, contents, emit)
- }
- }
-}
-
+/// Rustdoc writes out two kinds of shared files:
+/// - Static files, which are embedded in the rustdoc binary and are written with a
+/// filename that includes a hash of their contents. These will always have a new
+/// URL if the contents change, so they are safe to cache with the
+/// `Cache-Control: immutable` directive. They are written under the static.files/
+/// directory and are written when --emit-type is empty (default) or contains
+/// "toolchain-specific". If using the --static-root-path flag, it should point
+/// to a URL path prefix where each of these filenames can be fetched.
+/// - Invocation specific files. These are generated based on the crate(s) being
+/// documented. Their filenames need to be predictable without knowing their
+/// contents, so they do not include a hash in their filename and are not safe to
+/// cache with `Cache-Control: immutable`. They include the contents of the
+/// --resource-suffix flag and are emitted when --emit-type is empty (default)
+/// or contains "invocation-specific".
pub(super) fn write_shared(
cx: &mut Context<'_>,
krate: &Crate,
@@ -149,139 +44,52 @@ pub(super) fn write_shared(
let lock_file = cx.dst.join(".lock");
let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
- // Minified resources are usually toolchain resources. If they're not, they should use `cx.write_minify` directly.
- fn write_minify(
- basename: &'static str,
- contents: impl 'static + Send + AsRef<str> + AsRef<[u8]>,
- cx: &Context<'_>,
- options: &RenderOptions,
- ) -> Result<(), Error> {
- cx.write_minify(
- SharedResource::ToolchainSpecific { basename },
- contents,
- options.enable_minification,
- &options.emit,
- )
- }
-
- // Toolchain resources should never be dynamic.
- let write_toolchain = |p: &'static _, c: &'static _| {
- cx.write_shared(SharedResource::ToolchainSpecific { basename: p }, c, &options.emit)
- };
-
- // Crate resources should always be dynamic.
- let write_crate = |p: &_, make_content: &dyn Fn() -> Result<Vec<u8>, Error>| {
+ // InvocationSpecific resources should always be dynamic.
+ let write_invocation_specific = |p: &str, make_content: &dyn Fn() -> Result<Vec<u8>, Error>| {
let content = make_content()?;
- cx.write_shared(SharedResource::InvocationSpecific { basename: p }, content, &options.emit)
+ if options.emit.is_empty() || options.emit.contains(&EmitType::InvocationSpecific) {
+ let output_filename = static_files::suffix_path(p, &cx.shared.resource_suffix);
+ cx.shared.fs.write(cx.dst.join(output_filename), content)
+ } else {
+ Ok(())
+ }
};
- // Given "foo.svg", return e.g. "url(\"foo1.58.0.svg\")"
- fn ver_url(cx: &Context<'_>, basename: &'static str) -> String {
- format!(
- "url(\"{}\")",
- SharedResource::ToolchainSpecific { basename }
- .path(cx)
- .file_name()
- .unwrap()
- .to_str()
- .unwrap()
- )
- }
-
- // We use the AUTOREPLACE mechanism to inject into our static JS and CSS certain
- // values that are only known at doc build time. Since this mechanism is somewhat
- // surprising when reading the code, please limit it to rustdoc.css.
- write_minify(
- "rustdoc.css",
- static_files::RUSTDOC_CSS
- .replace(
- "/* AUTOREPLACE: */url(\"toggle-minus.svg\")",
- &ver_url(cx, "toggle-minus.svg"),
- )
- .replace("/* AUTOREPLACE: */url(\"toggle-plus.svg\")", &ver_url(cx, "toggle-plus.svg"))
- .replace("/* AUTOREPLACE: */url(\"down-arrow.svg\")", &ver_url(cx, "down-arrow.svg")),
- cx,
- options,
- )?;
-
- // Add all the static files. These may already exist, but we just
- // overwrite them anyway to make sure that they're fresh and up-to-date.
- write_minify("settings.css", static_files::SETTINGS_CSS, cx, options)?;
- write_minify("noscript.css", static_files::NOSCRIPT_CSS, cx, options)?;
-
- // To avoid "light.css" to be overwritten, we'll first run over the received themes and only
- // then we'll run over the "official" styles.
- let mut themes: FxHashSet<String> = FxHashSet::default();
+ cx.shared
+ .fs
+ .create_dir_all(cx.dst.join("static.files"))
+ .map_err(|e| PathError::new(e, "static.files"))?;
+ // Handle added third-party themes
for entry in &cx.shared.style_files {
let theme = entry.basename()?;
let extension =
try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
- // Handle the official themes
- match theme.as_str() {
- "light" => write_minify("light.css", static_files::themes::LIGHT, cx, options)?,
- "dark" => write_minify("dark.css", static_files::themes::DARK, cx, options)?,
- "ayu" => write_minify("ayu.css", static_files::themes::AYU, cx, options)?,
- _ => {
- // Handle added third-party themes
- let filename = format!("{}.{}", theme, extension);
- write_crate(&filename, &|| Ok(try_err!(fs::read(&entry.path), &entry.path)))?;
- }
- };
-
- themes.insert(theme.to_owned());
- }
-
- if (*cx.shared).layout.logo.is_empty() {
- write_toolchain("rust-logo.svg", static_files::RUST_LOGO_SVG)?;
- }
- if (*cx.shared).layout.favicon.is_empty() {
- write_toolchain("favicon.svg", static_files::RUST_FAVICON_SVG)?;
- write_toolchain("favicon-16x16.png", static_files::RUST_FAVICON_PNG_16)?;
- write_toolchain("favicon-32x32.png", static_files::RUST_FAVICON_PNG_32)?;
- }
- write_toolchain("wheel.svg", static_files::WHEEL_SVG)?;
- write_toolchain("clipboard.svg", static_files::CLIPBOARD_SVG)?;
- write_toolchain("down-arrow.svg", static_files::DOWN_ARROW_SVG)?;
- write_toolchain("toggle-minus.svg", static_files::TOGGLE_MINUS_PNG)?;
- write_toolchain("toggle-plus.svg", static_files::TOGGLE_PLUS_PNG)?;
-
- let mut themes: Vec<&String> = themes.iter().collect();
- themes.sort();
-
- write_minify("main.js", static_files::MAIN_JS, cx, options)?;
- write_minify("search.js", static_files::SEARCH_JS, cx, options)?;
- write_minify("settings.js", static_files::SETTINGS_JS, cx, options)?;
-
- if cx.include_sources {
- write_minify("source-script.js", static_files::sidebar::SOURCE_SCRIPT, cx, options)?;
- }
-
- write_minify("storage.js", static_files::STORAGE_JS, cx, options)?;
+ // Skip the official themes. They are written below as part of STATIC_FILES_LIST.
+ if matches!(theme.as_str(), "light" | "dark" | "ayu") {
+ continue;
+ }
- if cx.shared.layout.scrape_examples_extension {
- cx.write_minify(
- SharedResource::InvocationSpecific { basename: "scrape-examples.js" },
- static_files::SCRAPE_EXAMPLES_JS,
- options.enable_minification,
- &options.emit,
- )?;
+ let bytes = try_err!(fs::read(&entry.path), &entry.path);
+ let filename = format!("{}{}.{}", theme, cx.shared.resource_suffix, extension);
+ cx.shared.fs.write(cx.dst.join(filename), bytes)?;
}
+ // When the user adds their own CSS files with --extend-css, we write that as an
+ // invocation-specific file (that is, with a resource suffix).
if let Some(ref css) = cx.shared.layout.css_file_extension {
let buffer = try_err!(fs::read_to_string(css), css);
- // This varies based on the invocation, so it can't go through the write_minify wrapper.
- cx.write_minify(
- SharedResource::InvocationSpecific { basename: "theme.css" },
- buffer,
- options.enable_minification,
- &options.emit,
- )?;
+ let path = static_files::suffix_path("theme.css", &cx.shared.resource_suffix);
+ cx.shared.fs.write(cx.dst.join(path), buffer)?;
}
- write_minify("normalize.css", static_files::NORMALIZE_CSS, cx, options)?;
- for (name, contents) in &*FILES_UNVERSIONED {
- cx.write_shared(SharedResource::Unversioned { name }, contents, &options.emit)?;
+
+ if options.emit.is_empty() || options.emit.contains(&EmitType::Toolchain) {
+ let static_dir = cx.dst.join(Path::new("static.files"));
+ static_files::for_each(|f: &static_files::StaticFile| {
+ let filename = static_dir.join(f.output_filename());
+ cx.shared.fs.write(filename, f.minified())
+ })?;
}
/// Read a file and return all lines that match the `"{crate}":{data},` format,
@@ -463,7 +271,7 @@ pub(super) fn write_shared(
v.push_str("\\\n}');\ncreateSourceSidebar();\n");
Ok(v.into_bytes())
};
- write_crate("source-files.js", &make_sources)?;
+ write_invocation_specific("source-files.js", &make_sources)?;
}
// Update the search index and crate list.
@@ -477,7 +285,7 @@ pub(super) fn write_shared(
// Sort the indexes by crate so the file will be generated identically even
// with rustdoc running in parallel.
all_indexes.sort();
- write_crate("search-index.js", &|| {
+ write_invocation_specific("search-index.js", &|| {
let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
v.push_str(&all_indexes.join(",\\\n"));
v.push_str(
@@ -490,7 +298,7 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
Ok(v.into_bytes())
})?;
- write_crate("crates.js", &|| {
+ write_invocation_specific("crates.js", &|| {
let krates = krates.iter().map(|k| format!("\"{}\"", k)).join(",");
Ok(format!("window.ALL_CRATES = [{}];", krates).into_bytes())
})?;