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