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