]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/missing_doc.rs
d0c99b352452faa413e18c131d8e4f325add219b
[rust.git] / clippy_lints / src / missing_doc.rs
1 // Note: More specifically this lint is largely inspired (aka copied) from
2 // *rustc*'s
3 // [`missing_doc`].
4 //
5 // [`missing_doc`]: https://github.com/rust-lang/rust/blob/cf9cf7c923eb01146971429044f216a3ca905e06/compiler/rustc_lint/src/builtin.rs#L415
6 //
7
8 use clippy_utils::attrs::is_doc_hidden;
9 use clippy_utils::diagnostics::span_lint;
10 use clippy_utils::is_from_proc_macro;
11 use rustc_ast::ast::{self, MetaItem, MetaItemKind};
12 use rustc_hir as hir;
13 use rustc_lint::{LateContext, LateLintPass, LintContext};
14 use rustc_middle::ty::DefIdTree;
15 use rustc_session::{declare_tool_lint, impl_lint_pass};
16 use rustc_span::def_id::CRATE_DEF_ID;
17 use rustc_span::source_map::Span;
18 use rustc_span::sym;
19
20 declare_clippy_lint! {
21     /// ### What it does
22     /// Warns if there is missing doc for any documentable item
23     /// (public or private).
24     ///
25     /// ### Why is this bad?
26     /// Doc is good. *rustc* has a `MISSING_DOCS`
27     /// allowed-by-default lint for
28     /// public members, but has no way to enforce documentation of private items.
29     /// This lint fixes that.
30     #[clippy::version = "pre 1.29.0"]
31     pub MISSING_DOCS_IN_PRIVATE_ITEMS,
32     restriction,
33     "detects missing documentation for public and private members"
34 }
35
36 pub struct MissingDoc {
37     /// Stack of whether #[doc(hidden)] is set
38     /// at each level which has lint attributes.
39     doc_hidden_stack: Vec<bool>,
40 }
41
42 impl Default for MissingDoc {
43     #[must_use]
44     fn default() -> Self {
45         Self::new()
46     }
47 }
48
49 impl MissingDoc {
50     #[must_use]
51     pub fn new() -> Self {
52         Self {
53             doc_hidden_stack: vec![false],
54         }
55     }
56
57     fn doc_hidden(&self) -> bool {
58         *self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
59     }
60
61     fn has_include(meta: Option<MetaItem>) -> bool {
62         if_chain! {
63             if let Some(meta) = meta;
64             if let MetaItemKind::List(list) = meta.kind;
65             if let Some(meta) = list.get(0);
66             if let Some(name) = meta.ident();
67             then {
68                 name.name == sym::include
69             } else {
70                 false
71             }
72         }
73     }
74
75     fn check_missing_docs_attrs(
76         &self,
77         cx: &LateContext<'_>,
78         attrs: &[ast::Attribute],
79         sp: Span,
80         article: &'static str,
81         desc: &'static str,
82     ) {
83         // If we're building a test harness, then warning about
84         // documentation is probably not really relevant right now.
85         if cx.sess().opts.test {
86             return;
87         }
88
89         // `#[doc(hidden)]` disables missing_docs check.
90         if self.doc_hidden() {
91             return;
92         }
93
94         if sp.from_expansion() {
95             return;
96         }
97
98         let has_doc = attrs
99             .iter()
100             .any(|a| a.doc_str().is_some() || Self::has_include(a.meta()));
101         if !has_doc {
102             span_lint(
103                 cx,
104                 MISSING_DOCS_IN_PRIVATE_ITEMS,
105                 sp,
106                 &format!("missing documentation for {article} {desc}"),
107             );
108         }
109     }
110 }
111
112 impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]);
113
114 impl<'tcx> LateLintPass<'tcx> for MissingDoc {
115     fn enter_lint_attrs(&mut self, _: &LateContext<'tcx>, attrs: &'tcx [ast::Attribute]) {
116         let doc_hidden = self.doc_hidden() || is_doc_hidden(attrs);
117         self.doc_hidden_stack.push(doc_hidden);
118     }
119
120     fn exit_lint_attrs(&mut self, _: &LateContext<'tcx>, _: &'tcx [ast::Attribute]) {
121         self.doc_hidden_stack.pop().expect("empty doc_hidden_stack");
122     }
123
124     fn check_crate(&mut self, cx: &LateContext<'tcx>) {
125         let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID);
126         self.check_missing_docs_attrs(cx, attrs, cx.tcx.def_span(CRATE_DEF_ID), "the", "crate");
127     }
128
129     fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
130         match it.kind {
131             hir::ItemKind::Fn(..) => {
132                 // ignore main()
133                 if it.ident.name == sym::main {
134                     let at_root = cx.tcx.local_parent(it.owner_id.def_id) == CRATE_DEF_ID;
135                     if at_root {
136                         return;
137                     }
138                 }
139             },
140             hir::ItemKind::Const(..)
141             | hir::ItemKind::Enum(..)
142             | hir::ItemKind::Macro(..)
143             | hir::ItemKind::Mod(..)
144             | hir::ItemKind::Static(..)
145             | hir::ItemKind::Struct(..)
146             | hir::ItemKind::Trait(..)
147             | hir::ItemKind::TraitAlias(..)
148             | hir::ItemKind::TyAlias(..)
149             | hir::ItemKind::Union(..)
150             | hir::ItemKind::OpaqueTy(..) => {},
151             hir::ItemKind::ExternCrate(..)
152             | hir::ItemKind::ForeignMod { .. }
153             | hir::ItemKind::GlobalAsm(..)
154             | hir::ItemKind::Impl { .. }
155             | hir::ItemKind::Use(..) => return,
156         };
157
158         let (article, desc) = cx.tcx.article_and_description(it.owner_id.to_def_id());
159
160         let attrs = cx.tcx.hir().attrs(it.hir_id());
161         if !is_from_proc_macro(cx, it) {
162             self.check_missing_docs_attrs(cx, attrs, it.span, article, desc);
163         }
164     }
165
166     fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx hir::TraitItem<'_>) {
167         let (article, desc) = cx.tcx.article_and_description(trait_item.owner_id.to_def_id());
168
169         let attrs = cx.tcx.hir().attrs(trait_item.hir_id());
170         if !is_from_proc_macro(cx, trait_item) {
171             self.check_missing_docs_attrs(cx, attrs, trait_item.span, article, desc);
172         }
173     }
174
175     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
176         // If the method is an impl for a trait, don't doc.
177         if let Some(cid) = cx.tcx.associated_item(impl_item.owner_id).impl_container(cx.tcx) {
178             if cx.tcx.bound_impl_trait_ref(cid).is_some() {
179                 return;
180             }
181         } else {
182             return;
183         }
184
185         let (article, desc) = cx.tcx.article_and_description(impl_item.owner_id.to_def_id());
186         let attrs = cx.tcx.hir().attrs(impl_item.hir_id());
187         if !is_from_proc_macro(cx, impl_item) {
188             self.check_missing_docs_attrs(cx, attrs, impl_item.span, article, desc);
189         }
190     }
191
192     fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) {
193         if !sf.is_positional() {
194             let attrs = cx.tcx.hir().attrs(sf.hir_id);
195             if !is_from_proc_macro(cx, sf) {
196                 self.check_missing_docs_attrs(cx, attrs, sf.span, "a", "struct field");
197             }
198         }
199     }
200
201     fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) {
202         let attrs = cx.tcx.hir().attrs(v.hir_id);
203         if !is_from_proc_macro(cx, v) {
204             self.check_missing_docs_attrs(cx, attrs, v.span, "a", "variant");
205         }
206     }
207 }