rustdoc/passes/strip_hidden.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
//! Strip all doc(hidden) items from the output.
use std::mem;
use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
use rustc_middle::ty::TyCtxt;
use rustc_span::symbol::sym;
use tracing::debug;
use crate::clean;
use crate::clean::utils::inherits_doc_hidden;
use crate::clean::{Item, ItemIdSet};
use crate::core::DocContext;
use crate::fold::{DocFolder, strip_item};
use crate::passes::{ImplStripper, Pass};
pub(crate) const STRIP_HIDDEN: Pass = Pass {
name: "strip-hidden",
run: Some(strip_hidden),
description: "strips all `#[doc(hidden)]` items from the output",
};
/// Strip items marked `#[doc(hidden)]`
pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
let mut retained = ItemIdSet::default();
let is_json_output = cx.output_format.is_json() && !cx.show_coverage;
// strip all #[doc(hidden)] items
let krate = {
let mut stripper = Stripper {
retained: &mut retained,
update_retained: true,
tcx: cx.tcx,
is_in_hidden_item: false,
last_reexport: None,
};
stripper.fold_crate(krate)
};
// strip all impls referencing stripped items
let mut stripper = ImplStripper {
tcx: cx.tcx,
retained: &retained,
cache: &cx.cache,
is_json_output,
document_private: cx.render_options.document_private,
document_hidden: cx.render_options.document_hidden,
};
stripper.fold_crate(krate)
}
struct Stripper<'a, 'tcx> {
retained: &'a mut ItemIdSet,
update_retained: bool,
tcx: TyCtxt<'tcx>,
is_in_hidden_item: bool,
last_reexport: Option<LocalDefId>,
}
impl<'a, 'tcx> Stripper<'a, 'tcx> {
fn set_last_reexport_then_fold_item(&mut self, i: Item) -> Item {
let prev_from_reexport = self.last_reexport;
if i.inline_stmt_id.is_some() {
self.last_reexport = i.item_id.as_def_id().and_then(|def_id| def_id.as_local());
}
let ret = self.fold_item_recur(i);
self.last_reexport = prev_from_reexport;
ret
}
fn set_is_in_hidden_item_and_fold(&mut self, is_in_hidden_item: bool, i: Item) -> Item {
let prev = self.is_in_hidden_item;
self.is_in_hidden_item |= is_in_hidden_item;
let ret = self.set_last_reexport_then_fold_item(i);
self.is_in_hidden_item = prev;
ret
}
/// In case `i` is a non-hidden impl block, then we special-case it by changing the value
/// of `is_in_hidden_item` to `true` because the impl children inherit its visibility.
fn recurse_in_impl_or_exported_macro(&mut self, i: Item) -> Item {
let prev = mem::replace(&mut self.is_in_hidden_item, false);
let ret = self.set_last_reexport_then_fold_item(i);
self.is_in_hidden_item = prev;
ret
}
}
impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> {
fn fold_item(&mut self, i: Item) -> Option<Item> {
let has_doc_hidden = i.is_doc_hidden();
let is_impl_or_exported_macro = match i.kind {
clean::ImplItem(..) => true,
// If the macro has the `#[macro_export]` attribute, it means it's accessible at the
// crate level so it should be handled differently.
clean::MacroItem(..) => {
i.attrs.other_attrs.iter().any(|attr| attr.has_name(sym::macro_export))
}
_ => false,
};
let mut is_hidden = has_doc_hidden;
if !is_impl_or_exported_macro {
is_hidden = self.is_in_hidden_item || has_doc_hidden;
if !is_hidden && i.inline_stmt_id.is_none() {
// `i.inline_stmt_id` is `Some` if the item is directly reexported. If it is, we
// don't need to check it, because the reexport itself was already checked.
//
// If this item is the child of a reexported module, `self.last_reexport` will be
// `Some` even though `i.inline_stmt_id` is `None`. Hiddenness inheritance needs to
// account for the possibility that an item's true parent module is hidden, but it's
// inlined into a visible module true. This code shouldn't be reachable if the
// module's reexport is itself hidden, for the same reason it doesn't need to be
// checked if `i.inline_stmt_id` is Some: hidden reexports are never inlined.
is_hidden = i
.item_id
.as_def_id()
.and_then(|def_id| def_id.as_local())
.map(|def_id| inherits_doc_hidden(self.tcx, def_id, self.last_reexport))
.unwrap_or(false);
}
}
if !is_hidden {
if self.update_retained {
self.retained.insert(i.item_id);
}
return Some(if is_impl_or_exported_macro {
self.recurse_in_impl_or_exported_macro(i)
} else {
self.set_is_in_hidden_item_and_fold(false, i)
});
}
debug!("strip_hidden: stripping {:?} {:?}", i.type_(), i.name);
// Use a dedicated hidden item for fields, variants, and modules.
// We need to keep private fields and variants, so that the docs
// can show a placeholder "// some variants omitted". We need to keep
// private modules, because they can contain impl blocks, and impl
// block privacy is inherited from the type and trait, not from the
// module it's defined in. Both of these are marked "stripped," and
// not included in the final docs, but since they still have an effect
// on the final doc, cannot be completely removed from the Clean IR.
match i.kind {
clean::StructFieldItem(..) | clean::ModuleItem(..) | clean::VariantItem(..) => {
// We need to recurse into stripped modules to
// strip things like impl methods but when doing so
// we must not add any items to the `retained` set.
let old = mem::replace(&mut self.update_retained, false);
let ret = self.set_is_in_hidden_item_and_fold(true, i);
self.update_retained = old;
if ret.item_id == clean::ItemId::DefId(CRATE_DEF_ID.into()) {
// We don't strip the current crate, even if it has `#[doc(hidden)]`.
debug!("strip_hidden: Not strippping local crate");
Some(ret)
} else {
Some(strip_item(ret))
}
}
_ => {
let ret = self.set_is_in_hidden_item_and_fold(true, i);
if has_doc_hidden {
// If the item itself has `#[doc(hidden)]`, then we simply remove it.
None
} else {
// However if it's a "descendant" of a `#[doc(hidden)]` item, then we strip it.
Some(strip_item(ret))
}
}
}
}
}