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