rustdoc: render doc(hidden) as a code attribute

Move `#[doc(hidden)]` into the shared code-attribute renderer so it
matches the styling and placement of other attributes in rustdoc HTML.
This commit is contained in:
neo 2026-01-12 20:54:50 +09:00
parent 88ad3d44ca
commit b4c4d95950
4 changed files with 62 additions and 31 deletions

View file

@ -1390,10 +1390,6 @@ impl clean::FnDecl {
pub(crate) fn visibility_print_with_space(item: &clean::Item, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| {
if item.is_doc_hidden() {
f.write_str("#[doc(hidden)] ")?;
}
let Some(vis) = item.visibility(cx.tcx()) else {
return Ok(());
};

View file

@ -1075,6 +1075,7 @@ fn assoc_type(
cx: &Context<'_>,
) -> impl fmt::Display {
fmt::from_fn(move |w| {
render_attributes_in_code(w, it, &" ".repeat(indent), cx)?;
write!(
w,
"{indent}{vis}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
@ -2931,6 +2932,21 @@ fn render_attributes_in_code(
prefix: &str,
cx: &Context<'_>,
) -> fmt::Result {
render_attributes_in_code_with_options(w, item, prefix, cx, true, "")
}
pub(super) fn render_attributes_in_code_with_options(
w: &mut impl fmt::Write,
item: &clean::Item,
prefix: &str,
cx: &Context<'_>,
render_doc_hidden: bool,
open_tag: &str,
) -> fmt::Result {
w.write_str(open_tag)?;
if render_doc_hidden && item.is_doc_hidden() {
render_code_attribute(prefix, "#[doc(hidden)]", w)?;
}
for attr in &item.attrs.other_attrs {
let hir::Attribute::Parsed(kind) = attr else { continue };
let attr = match kind {

View file

@ -344,15 +344,38 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
}
for (_, myitem) in &not_stripped_items[&type_] {
let visibility_and_hidden = |item: &clean::Item| match item.visibility(tcx) {
Some(ty::Visibility::Restricted(_)) => {
if item.is_doc_hidden() {
// Don't separate with a space when there are two of them
"<span title=\"Restricted Visibility\">&nbsp;🔒</span><span title=\"Hidden item\">👻</span> "
} else {
"<span title=\"Restricted Visibility\">&nbsp;🔒</span> "
}
}
_ if item.is_doc_hidden() => "<span title=\"Hidden item\">&nbsp;👻</span> ",
_ => "",
};
match myitem.kind {
clean::ExternCrateItem { ref src } => {
use crate::html::format::print_anchor;
let visibility_and_hidden = visibility_and_hidden(myitem);
// Module listings use the hidden marker, so skip doc(hidden) here.
super::render_attributes_in_code_with_options(
w,
myitem,
"",
cx,
false,
"<dt><code>",
)?;
match *src {
Some(src) => {
write!(
w,
"<dt><code>{}extern crate {} as {};",
"{}extern crate {} as {};",
visibility_print_with_space(myitem, cx),
print_anchor(myitem.item_id.expect_def_id(), src, cx),
EscapeBodyTextWithWbr(myitem.name.unwrap().as_str())
@ -361,7 +384,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
None => {
write!(
w,
"<dt><code>{}extern crate {};",
"{}extern crate {};",
visibility_print_with_space(myitem, cx),
print_anchor(
myitem.item_id.expect_def_id(),
@ -371,7 +394,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
)?;
}
}
write!(w, "</code></dt>")?
write!(w, "</code>{visibility_and_hidden}</dt>")?
}
clean::ImportItem(ref import) => {
let (stab_tags, deprecation) = match import.source.did {
@ -386,6 +409,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
}
None => (String::new(), item.is_deprecated(tcx)),
};
let visibility_and_hidden = visibility_and_hidden(myitem);
let id = match import.kind {
clean::ImportKind::Simple(s) => {
format!(" id=\"{}\"", cx.derive_id(format!("reexport.{s}")))
@ -397,13 +421,13 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
"<dt{id}{deprecation_attr}><code>",
deprecation_attr = deprecation_class_attr(deprecation)
)?;
render_attributes_in_code(w, myitem, "", cx)?;
write!(
w,
"{vis}{imp}</code>{stab_tags}\
"{vis}{imp}</code>{visibility_and_hidden}{stab_tags}\
</dt>",
vis = visibility_print_with_space(myitem, cx),
imp = print_import(import, cx),
visibility_and_hidden = visibility_and_hidden,
)?;
}
_ => {
@ -421,20 +445,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i
}
_ => "",
};
let visibility_and_hidden = match myitem.visibility(tcx) {
Some(ty::Visibility::Restricted(_)) => {
if myitem.is_doc_hidden() {
// Don't separate with a space when there are two of them
"<span title=\"Restricted Visibility\">&nbsp;🔒</span><span title=\"Hidden item\">👻</span> "
} else {
"<span title=\"Restricted Visibility\">&nbsp;🔒</span> "
}
}
_ if myitem.is_doc_hidden() => {
"<span title=\"Hidden item\">&nbsp;👻</span> "
}
_ => "",
};
let visibility_and_hidden = visibility_and_hidden(myitem);
let docs = MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx))
.into_string();
@ -1868,7 +1879,6 @@ fn item_variants(
fn item_macro(cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) -> impl fmt::Display {
fmt::from_fn(|w| {
wrap_item(w, |w| {
// FIXME: Also print `#[doc(hidden)]` for `macro_rules!` if it `is_doc_hidden`.
render_attributes_in_code(w, it, "", cx)?;
if !t.macro_rules {
write!(w, "{}", visibility_print_with_space(it, cx))?;

View file

@ -5,25 +5,32 @@
#![crate_name = "foo"]
//@ has 'foo/index.html'
//@ has - '//dt/span[@title="Hidden item"]' '👻'
//@ has - '//*[@id="reexport.hidden_reexport"]/code' '#[doc(hidden)] pub use hidden::inside_hidden as hidden_reexport;'
//@ matches - '//dt[code]' 'pub extern crate .*hidden_core;.*👻'
//@ has - '//dt/code' 'pub extern crate core as hidden_core;'
#[doc(hidden)]
pub extern crate core as hidden_core;
//@ has - '//*[@id="reexport.hidden_reexport"]/span[@title="Hidden item"]' '👻'
//@ has - '//*[@id="reexport.hidden_reexport"]/code' 'pub use hidden::inside_hidden as hidden_reexport;'
#[doc(hidden)]
pub use hidden::inside_hidden as hidden_reexport;
//@ has - '//dt/a[@class="trait"]' 'TraitHidden'
//@ has 'foo/trait.TraitHidden.html'
//@ has - '//code' '#[doc(hidden)] pub trait TraitHidden'
//@ has 'foo/trait.TraitHidden.html' '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[doc(hidden)]'
//@ has 'foo/trait.TraitHidden.html' '//*[@class="rust item-decl"]/code' 'pub trait TraitHidden'
#[doc(hidden)]
pub trait TraitHidden {}
//@ has 'foo/index.html' '//dt/a[@class="trait"]' 'Trait'
pub trait Trait {
//@ has 'foo/trait.Trait.html'
//@ has - '//*[@id="associatedconstant.BAR"]/*[@class="code-header"]' '#[doc(hidden)] const BAR: u32 = 0'
//@ has - '//*[@id="associatedconstant.BAR"]/*[@class="code-header"]/*[@class="code-attribute"]' '#[doc(hidden)]'
#[doc(hidden)]
const BAR: u32 = 0;
//@ has - '//*[@id="method.foo"]/*[@class="code-header"]/*[@class="code-attribute"]' '#[doc(hidden)]'
//@ has - '//*[@id="method.foo"]/*[@class="code-header"]' 'fn foo()'
#[doc(hidden)]
fn foo() {}
@ -44,15 +51,16 @@ impl Struct {
}
impl Trait for Struct {
//@ has - '//*[@id="associatedconstant.BAR"]/*[@class="code-header"]' '#[doc(hidden)] const BAR: u32 = 0'
//@ has - '//*[@id="method.foo"]/*[@class="code-header"]' '#[doc(hidden)] fn foo()'
//@ has - '//*[@id="associatedconstant.BAR"]/*[@class="code-header"]/*[@class="code-attribute"]' '#[doc(hidden)]'
//@ has - '//*[@id="method.foo"]/*[@class="code-header"]/*[@class="code-attribute"]' '#[doc(hidden)]'
}
//@ has - '//*[@id="impl-TraitHidden-for-Struct"]/*[@class="code-header"]' 'impl TraitHidden for Struct'
impl TraitHidden for Struct {}
//@ has 'foo/index.html' '//dt/a[@class="enum"]' 'HiddenEnum'
//@ has 'foo/enum.HiddenEnum.html'
//@ has - '//code' '#[doc(hidden)] pub enum HiddenEnum'
//@ has 'foo/enum.HiddenEnum.html' '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[doc(hidden)]'
//@ has 'foo/enum.HiddenEnum.html' '//*[@class="rust item-decl"]/code' 'pub enum HiddenEnum'
#[doc(hidden)]
pub enum HiddenEnum {
A,
@ -60,6 +68,7 @@ pub enum HiddenEnum {
//@ has 'foo/index.html' '//dt/a[@class="enum"]' 'Enum'
pub enum Enum {
//@ has 'foo/enum.Enum.html' '//*[@id="variant.A"]/*[@class="code-header"]/*[@class="code-attribute"]' '#[doc(hidden)]'
//@ has 'foo/enum.Enum.html' '//*[@id="variant.A"]/*[@class="code-header"]' 'A'
#[doc(hidden)]
A,