]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/missing_doc.rs
Merge remote-tracking branch 'origin/beta_backport' into HEAD
[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/d6d05904697d89099b55da3331155392f1db9c00/src/librustc_lint/builtin.rs#L246
6 //
7
8 use crate::utils::sym;
9 use crate::utils::{in_macro_or_desugar, span_lint};
10 use if_chain::if_chain;
11 use rustc::hir;
12 use rustc::lint::{LateContext, LateLintPass, LintArray, LintContext, LintPass};
13 use rustc::ty;
14 use rustc::{declare_tool_lint, impl_lint_pass};
15 use syntax::ast::{self, MetaItem, MetaItemKind};
16 use syntax::attr;
17 use syntax::source_map::Span;
18
19 declare_clippy_lint! {
20     /// **What it does:** Warns if there is missing doc for any documentable item
21     /// (public or private).
22     ///
23     /// **Why is this bad?** Doc is good. *rustc* has a `MISSING_DOCS`
24     /// allowed-by-default lint for
25     /// public members, but has no way to enforce documentation of private items.
26     /// This lint fixes that.
27     ///
28     /// **Known problems:** None.
29     pub MISSING_DOCS_IN_PRIVATE_ITEMS,
30     restriction,
31     "detects missing documentation for public and private members"
32 }
33
34 pub struct MissingDoc {
35     /// Stack of whether #[doc(hidden)] is set
36     /// at each level which has lint attributes.
37     doc_hidden_stack: Vec<bool>,
38 }
39
40 impl ::std::default::Default for MissingDoc {
41     fn default() -> Self {
42         Self::new()
43     }
44 }
45
46 impl MissingDoc {
47     pub fn new() -> Self {
48         Self {
49             doc_hidden_stack: vec![false],
50         }
51     }
52
53     fn doc_hidden(&self) -> bool {
54         *self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
55     }
56
57     fn has_include(meta: Option<MetaItem>) -> bool {
58         if_chain! {
59             if let Some(meta) = meta;
60             if let MetaItemKind::List(list) = meta.node;
61             if let Some(meta) = list.get(0);
62             if let Some(name) = meta.ident();
63             then {
64                 name.as_str() == "include"
65             } else {
66                 false
67             }
68         }
69     }
70
71     fn check_missing_docs_attrs(
72         &self,
73         cx: &LateContext<'_, '_>,
74         attrs: &[ast::Attribute],
75         sp: Span,
76         desc: &'static str,
77     ) {
78         // If we're building a test harness, then warning about
79         // documentation is probably not really relevant right now.
80         if cx.sess().opts.test {
81             return;
82         }
83
84         // `#[doc(hidden)]` disables missing_docs check.
85         if self.doc_hidden() {
86             return;
87         }
88
89         if in_macro_or_desugar(sp) {
90             return;
91         }
92
93         let has_doc = attrs
94             .iter()
95             .any(|a| a.check_name(*sym::doc) && (a.is_value_str() || Self::has_include(a.meta())));
96         if !has_doc {
97             span_lint(
98                 cx,
99                 MISSING_DOCS_IN_PRIVATE_ITEMS,
100                 sp,
101                 &format!("missing documentation for {}", desc),
102             );
103         }
104     }
105 }
106
107 impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]);
108
109 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingDoc {
110     fn enter_lint_attrs(&mut self, _: &LateContext<'a, 'tcx>, attrs: &'tcx [ast::Attribute]) {
111         let doc_hidden = self.doc_hidden()
112             || attrs.iter().any(|attr| {
113                 attr.check_name(*sym::doc)
114                     && match attr.meta_item_list() {
115                         None => false,
116                         Some(l) => attr::list_contains_name(&l[..], *sym::hidden),
117                     }
118             });
119         self.doc_hidden_stack.push(doc_hidden);
120     }
121
122     fn exit_lint_attrs(&mut self, _: &LateContext<'a, 'tcx>, _: &'tcx [ast::Attribute]) {
123         self.doc_hidden_stack.pop().expect("empty doc_hidden_stack");
124     }
125
126     fn check_crate(&mut self, cx: &LateContext<'a, 'tcx>, krate: &'tcx hir::Crate) {
127         self.check_missing_docs_attrs(cx, &krate.attrs, krate.span, "crate");
128     }
129
130     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, it: &'tcx hir::Item) {
131         let desc = match it.node {
132             hir::ItemKind::Const(..) => "a constant",
133             hir::ItemKind::Enum(..) => "an enum",
134             hir::ItemKind::Fn(..) => {
135                 // ignore main()
136                 if it.ident.name == *sym::main {
137                     let def_id = cx.tcx.hir().local_def_id_from_hir_id(it.hir_id);
138                     let def_key = cx.tcx.hir().def_key(def_id);
139                     if def_key.parent == Some(hir::def_id::CRATE_DEF_INDEX) {
140                         return;
141                     }
142                 }
143                 "a function"
144             },
145             hir::ItemKind::Mod(..) => "a module",
146             hir::ItemKind::Static(..) => "a static",
147             hir::ItemKind::Struct(..) => "a struct",
148             hir::ItemKind::Trait(..) => "a trait",
149             hir::ItemKind::TraitAlias(..) => "a trait alias",
150             hir::ItemKind::Ty(..) => "a type alias",
151             hir::ItemKind::Union(..) => "a union",
152             hir::ItemKind::Existential(..) => "an existential type",
153             hir::ItemKind::ExternCrate(..)
154             | hir::ItemKind::ForeignMod(..)
155             | hir::ItemKind::GlobalAsm(..)
156             | hir::ItemKind::Impl(..)
157             | hir::ItemKind::Use(..) => return,
158         };
159
160         self.check_missing_docs_attrs(cx, &it.attrs, it.span, desc);
161     }
162
163     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, trait_item: &'tcx hir::TraitItem) {
164         let desc = match trait_item.node {
165             hir::TraitItemKind::Const(..) => "an associated constant",
166             hir::TraitItemKind::Method(..) => "a trait method",
167             hir::TraitItemKind::Type(..) => "an associated type",
168         };
169
170         self.check_missing_docs_attrs(cx, &trait_item.attrs, trait_item.span, desc);
171     }
172
173     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx hir::ImplItem) {
174         // If the method is an impl for a trait, don't doc.
175         let def_id = cx.tcx.hir().local_def_id_from_hir_id(impl_item.hir_id);
176         match cx.tcx.associated_item(def_id).container {
177             ty::TraitContainer(_) => return,
178             ty::ImplContainer(cid) => {
179                 if cx.tcx.impl_trait_ref(cid).is_some() {
180                     return;
181                 }
182             },
183         }
184
185         let desc = match impl_item.node {
186             hir::ImplItemKind::Const(..) => "an associated constant",
187             hir::ImplItemKind::Method(..) => "a method",
188             hir::ImplItemKind::Type(_) => "an associated type",
189             hir::ImplItemKind::Existential(_) => "an existential type",
190         };
191         self.check_missing_docs_attrs(cx, &impl_item.attrs, impl_item.span, desc);
192     }
193
194     fn check_struct_field(&mut self, cx: &LateContext<'a, 'tcx>, sf: &'tcx hir::StructField) {
195         if !sf.is_positional() {
196             self.check_missing_docs_attrs(cx, &sf.attrs, sf.span, "a struct field");
197         }
198     }
199
200     fn check_variant(&mut self, cx: &LateContext<'a, 'tcx>, v: &'tcx hir::Variant, _: &hir::Generics) {
201         self.check_missing_docs_attrs(cx, &v.node.attrs, v.span, "a variant");
202     }
203 }