]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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 crate::utils::span_lint; | |
9 | use if_chain::if_chain; | |
10 | use rustc_ast::ast::{self, MetaItem, MetaItemKind}; | |
11 | use rustc_ast::attr; | |
12 | use rustc_hir as hir; | |
13 | use rustc_lint::{LateContext, LateLintPass, LintContext}; | |
14 | use rustc_middle::ty; | |
15 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
16 | use rustc_span::source_map::Span; | |
17 | use rustc_span::sym; | |
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 Default for MissingDoc { | |
41 | #[must_use] | |
42 | fn default() -> Self { | |
43 | Self::new() | |
44 | } | |
45 | } | |
46 | ||
47 | impl MissingDoc { | |
48 | #[must_use] | |
49 | pub fn new() -> Self { | |
50 | Self { | |
51 | doc_hidden_stack: vec![false], | |
52 | } | |
53 | } | |
54 | ||
55 | fn doc_hidden(&self) -> bool { | |
56 | *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") | |
57 | } | |
58 | ||
59 | fn has_include(meta: Option<MetaItem>) -> bool { | |
60 | if_chain! { | |
61 | if let Some(meta) = meta; | |
62 | if let MetaItemKind::List(list) = meta.kind; | |
63 | if let Some(meta) = list.get(0); | |
64 | if let Some(name) = meta.ident(); | |
65 | then { | |
66 | name.name == sym::include | |
67 | } else { | |
68 | false | |
69 | } | |
70 | } | |
71 | } | |
72 | ||
73 | fn check_missing_docs_attrs( | |
74 | &self, | |
75 | cx: &LateContext<'_>, | |
76 | attrs: &[ast::Attribute], | |
77 | sp: Span, | |
78 | article: &'static str, | |
79 | desc: &'static str, | |
80 | ) { | |
81 | // If we're building a test harness, then warning about | |
82 | // documentation is probably not really relevant right now. | |
83 | if cx.sess().opts.test { | |
84 | return; | |
85 | } | |
86 | ||
87 | // `#[doc(hidden)]` disables missing_docs check. | |
88 | if self.doc_hidden() { | |
89 | return; | |
90 | } | |
91 | ||
92 | if sp.from_expansion() { | |
93 | return; | |
94 | } | |
95 | ||
96 | let has_doc = attrs | |
97 | .iter() | |
98 | .any(|a| a.is_doc_comment() || a.doc_str().is_some() || a.is_value_str() || Self::has_include(a.meta())); | |
99 | if !has_doc { | |
100 | span_lint( | |
101 | cx, | |
102 | MISSING_DOCS_IN_PRIVATE_ITEMS, | |
103 | sp, | |
104 | &format!("missing documentation for {} {}", article, desc), | |
105 | ); | |
106 | } | |
107 | } | |
108 | } | |
109 | ||
110 | impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]); | |
111 | ||
112 | impl<'tcx> LateLintPass<'tcx> for MissingDoc { | |
113 | fn enter_lint_attrs(&mut self, _: &LateContext<'tcx>, attrs: &'tcx [ast::Attribute]) { | |
114 | let doc_hidden = self.doc_hidden() | |
115 | || attrs.iter().any(|attr| { | |
116 | attr.has_name(sym::doc) | |
117 | && match attr.meta_item_list() { | |
118 | None => false, | |
119 | Some(l) => attr::list_contains_name(&l[..], sym::hidden), | |
120 | } | |
121 | }); | |
122 | self.doc_hidden_stack.push(doc_hidden); | |
123 | } | |
124 | ||
125 | fn exit_lint_attrs(&mut self, _: &LateContext<'tcx>, _: &'tcx [ast::Attribute]) { | |
126 | self.doc_hidden_stack.pop().expect("empty doc_hidden_stack"); | |
127 | } | |
128 | ||
129 | fn check_crate(&mut self, cx: &LateContext<'tcx>, krate: &'tcx hir::Crate<'_>) { | |
130 | let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID); | |
131 | self.check_missing_docs_attrs(cx, attrs, krate.item.span, "the", "crate"); | |
132 | } | |
133 | ||
134 | fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { | |
135 | match it.kind { | |
136 | hir::ItemKind::Fn(..) => { | |
137 | // ignore main() | |
138 | if it.ident.name == sym::main { | |
139 | let def_key = cx.tcx.hir().def_key(it.def_id); | |
140 | if def_key.parent == Some(hir::def_id::CRATE_DEF_INDEX) { | |
141 | return; | |
142 | } | |
143 | } | |
144 | }, | |
145 | hir::ItemKind::Const(..) | |
146 | | hir::ItemKind::Enum(..) | |
147 | | hir::ItemKind::Mod(..) | |
148 | | hir::ItemKind::Static(..) | |
149 | | hir::ItemKind::Struct(..) | |
150 | | hir::ItemKind::Trait(..) | |
151 | | hir::ItemKind::TraitAlias(..) | |
152 | | hir::ItemKind::TyAlias(..) | |
153 | | hir::ItemKind::Union(..) | |
154 | | hir::ItemKind::OpaqueTy(..) => {}, | |
155 | hir::ItemKind::ExternCrate(..) | |
156 | | hir::ItemKind::ForeignMod { .. } | |
157 | | hir::ItemKind::GlobalAsm(..) | |
158 | | hir::ItemKind::Impl { .. } | |
159 | | hir::ItemKind::Use(..) => return, | |
160 | }; | |
161 | ||
162 | let (article, desc) = cx.tcx.article_and_description(it.def_id.to_def_id()); | |
163 | ||
164 | let attrs = cx.tcx.hir().attrs(it.hir_id()); | |
165 | self.check_missing_docs_attrs(cx, attrs, it.span, article, desc); | |
166 | } | |
167 | ||
168 | fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx hir::TraitItem<'_>) { | |
169 | let (article, desc) = cx.tcx.article_and_description(trait_item.def_id.to_def_id()); | |
170 | ||
171 | let attrs = cx.tcx.hir().attrs(trait_item.hir_id()); | |
172 | self.check_missing_docs_attrs(cx, attrs, trait_item.span, article, desc); | |
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 | match cx.tcx.associated_item(impl_item.def_id).container { | |
178 | ty::TraitContainer(_) => return, | |
179 | ty::ImplContainer(cid) => { | |
180 | if cx.tcx.impl_trait_ref(cid).is_some() { | |
181 | return; | |
182 | } | |
183 | }, | |
184 | } | |
185 | ||
186 | let (article, desc) = cx.tcx.article_and_description(impl_item.def_id.to_def_id()); | |
187 | let attrs = cx.tcx.hir().attrs(impl_item.hir_id()); | |
188 | self.check_missing_docs_attrs(cx, attrs, impl_item.span, article, desc); | |
189 | } | |
190 | ||
191 | fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) { | |
192 | if !sf.is_positional() { | |
193 | let attrs = cx.tcx.hir().attrs(sf.hir_id); | |
194 | self.check_missing_docs_attrs(cx, attrs, sf.span, "a", "struct field"); | |
195 | } | |
196 | } | |
197 | ||
198 | fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) { | |
199 | let attrs = cx.tcx.hir().attrs(v.id); | |
200 | self.check_missing_docs_attrs(cx, attrs, v.span, "a", "variant"); | |
201 | } | |
202 | } |