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: 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))
                }
            }
        }
    }
}