1 // Note: More specifically this lint is largely inspired (aka copied) from
5 // [`missing_doc`]: https://github.com/rust-lang/rust/blob/cf9cf7c923eb01146971429044f216a3ca905e06/compiler/rustc_lint/src/builtin.rs#L415
8 use clippy_utils::attrs::is_doc_hidden;
9 use clippy_utils::diagnostics::span_lint;
10 use if_chain::if_chain;
11 use rustc_ast::ast::{self, MetaItem, MetaItemKind};
13 use rustc_lint::{LateContext, LateLintPass, LintContext};
15 use rustc_session::{declare_tool_lint, impl_lint_pass};
16 use rustc_span::source_map::Span;
19 declare_clippy_lint! {
20 /// **What it does:** Warns if there is missing doc for any documentable item
21 /// (public or private).
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.
28 /// **Known problems:** None.
29 pub MISSING_DOCS_IN_PRIVATE_ITEMS,
31 "detects missing documentation for public and private members"
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>,
40 impl Default for MissingDoc {
42 fn default() -> Self {
49 pub fn new() -> Self {
51 doc_hidden_stack: vec![false],
55 fn doc_hidden(&self) -> bool {
56 *self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
59 fn has_include(meta: Option<MetaItem>) -> bool {
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();
66 name.name == sym::include
73 fn check_missing_docs_attrs(
76 attrs: &[ast::Attribute],
78 article: &'static str,
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 {
87 // `#[doc(hidden)]` disables missing_docs check.
88 if self.doc_hidden() {
92 if sp.from_expansion() {
98 .any(|a| a.is_doc_comment() || a.doc_str().is_some() || a.is_value_str() || Self::has_include(a.meta()));
102 MISSING_DOCS_IN_PRIVATE_ITEMS,
104 &format!("missing documentation for {} {}", article, desc),
110 impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]);
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() || is_doc_hidden(attrs);
115 self.doc_hidden_stack.push(doc_hidden);
118 fn exit_lint_attrs(&mut self, _: &LateContext<'tcx>, _: &'tcx [ast::Attribute]) {
119 self.doc_hidden_stack.pop().expect("empty doc_hidden_stack");
122 fn check_crate(&mut self, cx: &LateContext<'tcx>, krate: &'tcx hir::Crate<'_>) {
123 let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID);
124 self.check_missing_docs_attrs(cx, attrs, krate.item.span, "the", "crate");
127 fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
129 hir::ItemKind::Fn(..) => {
131 if it.ident.name == sym::main {
132 let def_key = cx.tcx.hir().def_key(it.def_id);
133 if def_key.parent == Some(hir::def_id::CRATE_DEF_INDEX) {
138 hir::ItemKind::Const(..)
139 | hir::ItemKind::Enum(..)
140 | hir::ItemKind::Mod(..)
141 | hir::ItemKind::Static(..)
142 | hir::ItemKind::Struct(..)
143 | hir::ItemKind::Trait(..)
144 | hir::ItemKind::TraitAlias(..)
145 | hir::ItemKind::TyAlias(..)
146 | hir::ItemKind::Union(..)
147 | hir::ItemKind::OpaqueTy(..) => {},
148 hir::ItemKind::ExternCrate(..)
149 | hir::ItemKind::ForeignMod { .. }
150 | hir::ItemKind::GlobalAsm(..)
151 | hir::ItemKind::Impl { .. }
152 | hir::ItemKind::Use(..) => return,
155 let (article, desc) = cx.tcx.article_and_description(it.def_id.to_def_id());
157 let attrs = cx.tcx.hir().attrs(it.hir_id());
158 self.check_missing_docs_attrs(cx, attrs, it.span, article, desc);
161 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx hir::TraitItem<'_>) {
162 let (article, desc) = cx.tcx.article_and_description(trait_item.def_id.to_def_id());
164 let attrs = cx.tcx.hir().attrs(trait_item.hir_id());
165 self.check_missing_docs_attrs(cx, attrs, trait_item.span, article, desc);
168 fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
169 // If the method is an impl for a trait, don't doc.
170 match cx.tcx.associated_item(impl_item.def_id).container {
171 ty::TraitContainer(_) => return,
172 ty::ImplContainer(cid) => {
173 if cx.tcx.impl_trait_ref(cid).is_some() {
179 let (article, desc) = cx.tcx.article_and_description(impl_item.def_id.to_def_id());
180 let attrs = cx.tcx.hir().attrs(impl_item.hir_id());
181 self.check_missing_docs_attrs(cx, attrs, impl_item.span, article, desc);
184 fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) {
185 if !sf.is_positional() {
186 let attrs = cx.tcx.hir().attrs(sf.hir_id);
187 self.check_missing_docs_attrs(cx, attrs, sf.span, "a", "struct field");
191 fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) {
192 let attrs = cx.tcx.hir().attrs(v.id);
193 self.check_missing_docs_attrs(cx, attrs, v.span, "a", "variant");