Skip to main content

rustdoc/passes/
strip_hidden.rs

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