| use clippy_config::Conf; | |
| use clippy_utils::diagnostics::span_lint; | |
| use clippy_utils::{is_doc_hidden, is_from_proc_macro}; | |
| use rustc_attr_ir::{AttrArgs, Attribute, AttributeKind}; | |
| use rustc_hir::def_id::LocalDefId; | |
| use rustc_hir::{Body, BodyId, FieldDef, HirId, ImplItem, Item, ItemKind, Node, TraitItem, Variant}; | |
| use rustc_lint::{LateContext, LateLintPass, impl_lint_pass}; | |
| use rustc_middle::middle::privacy::Level; | |
| use rustc_middle::ty::Visibility; | |
| use rustc_span::def_id::CRATE_DEF_ID; | |
| use rustc_span::sym; | |
| use rustc_span::symbol::kw; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Warns if there is missing documentation for any private documentable item. | |
| /// | |
| /// ### Why restrict this? | |
| /// Doc is good. *rustc* has a `MISSING_DOCS` | |
| /// allowed-by-default lint for | |
| /// public members, but has no way to enforce documentation of private items. | |
| /// This lint fixes that. | |
| #[clippy::version = "pre 1.29.0"] | |
| pub MISSING_DOCS_IN_PRIVATE_ITEMS, | |
| restriction, | |
| "detects missing documentation for private members" | |
| } | |
| impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]); | |
| pub struct MissingDoc { | |
| /// Whether to **only** check for missing documentation in items visible within the current | |
| /// crate. For example, `pub(crate)` items. | |
| crate_items_only: bool, | |
| /// Whether to allow fields starting with an underscore to skip documentation requirements | |
| allow_unused: bool, | |
| /// The current number of modules since the crate root. | |
| module_depth: u32, | |
| macro_module_depth: u32, | |
| /// The current level of the attribute stack. | |
| attr_depth: u32, | |
| /// What `attr_depth` level the first `doc(hidden)` attribute was seen. This is zero if the | |
| /// attribute hasn't been seen. | |
| doc_hidden_depth: u32, | |
| /// What `attr_depth` level the first `automatically_derived` attribute was seen. This is zero | |
| /// if the attribute hasn't been seen. | |
| automatically_derived_depth: u32, | |
| /// The id of the first body we've seen. | |
| in_body: Option<BodyId>, | |
| /// The module/crate id an item must be visible at to be linted. | |
| require_visibility_at: Option<LocalDefId>, | |
| } | |
| impl MissingDoc { | |
| pub fn new(conf: &'static Conf) -> Self { | |
| Self { | |
| crate_items_only: conf.missing_docs_in_crate_items, | |
| allow_unused: conf.missing_docs_allow_unused, | |
| module_depth: 0, | |
| macro_module_depth: 0, | |
| attr_depth: 0, | |
| doc_hidden_depth: 0, | |
| automatically_derived_depth: 0, | |
| in_body: None, | |
| require_visibility_at: None, | |
| } | |
| } | |
| fn is_missing_docs(&self, cx: &LateContext<'_>, def_id: LocalDefId, hir_id: HirId) -> bool { | |
| if cx.tcx.sess.opts.test { | |
| return false; | |
| } | |
| match cx.effective_visibilities.effective_vis(def_id) { | |
| None if self.require_visibility_at.is_some() => return false, | |
| None if self.crate_items_only && self.module_depth != 0 => return false, | |
| // `missing_docs` lint uses `Reexported` because rustdoc doesn't render documentation | |
| // for items without a reachable path. | |
| Some(vis) if vis.is_public_at_level(Level::Reexported) => return false, | |
| Some(vis) => { | |
| if self.crate_items_only { | |
| // Use the `Reachable` level since rustdoc will be able to render the documentation | |
| // when building private docs. | |
| let vis = vis.at_level(Level::Reachable); | |
| if !(vis.is_public() || matches!(vis, Visibility::Restricted(id) if id.is_top_level_module())) { | |
| return false; | |
| } | |
| } else if let Some(id) = self.require_visibility_at | |
| && !vis.at_level(Level::Reexported).is_accessible_from(id, cx.tcx) | |
| { | |
| return false; | |
| } | |
| }, | |
| None => {}, | |
| } | |
| !cx.tcx.hir_attrs(hir_id).iter().any(is_doc_attr) | |
| } | |
| } | |
| impl<'tcx> LateLintPass<'tcx> for MissingDoc { | |
| fn check_attributes(&mut self, _: &LateContext<'tcx>, attrs: &'tcx [Attribute]) { | |
| self.attr_depth += 1; | |
| if self.doc_hidden_depth == 0 && is_doc_hidden(attrs) { | |
| self.doc_hidden_depth = self.attr_depth; | |
| } | |
| } | |
| fn check_attributes_post(&mut self, _: &LateContext<'tcx>, _: &'tcx [Attribute]) { | |
| self.attr_depth -= 1; | |
| if self.attr_depth < self.doc_hidden_depth { | |
| self.doc_hidden_depth = 0; | |
| } | |
| if self.attr_depth < self.automatically_derived_depth { | |
| self.automatically_derived_depth = 0; | |
| } | |
| } | |
| fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { | |
| if self.doc_hidden_depth != 0 || self.automatically_derived_depth != 0 || self.in_body.is_some() { | |
| return; | |
| } | |
| let span = match item.kind { | |
| // ignore main() | |
| ItemKind::Fn { ident, .. } | |
| if ident.name == sym::main && cx.tcx.local_parent(item.owner_id.def_id) == CRATE_DEF_ID => | |
| { | |
| return; | |
| }, | |
| ItemKind::Const(ident, ..) if ident.name == kw::Underscore => return, | |
| ItemKind::Impl { .. } => { | |
| if cx.tcx.is_automatically_derived(item.owner_id.def_id.to_def_id()) { | |
| self.automatically_derived_depth = self.attr_depth; | |
| } | |
| return; | |
| }, | |
| ItemKind::ExternCrate(..) | |
| | ItemKind::ForeignMod { .. } | |
| | ItemKind::GlobalAsm { .. } | |
| | ItemKind::Use(..) | |
| | ItemKind::TestBinderConstraints { .. } => return, | |
| ItemKind::Mod(ident, ..) => { | |
| if item.span.from_expansion() && item.span.eq_ctxt(ident.span) { | |
| self.module_depth += 1; | |
| self.require_visibility_at = cx.tcx.opt_local_parent(item.owner_id.def_id); | |
| self.macro_module_depth = self.module_depth; | |
| return; | |
| } | |
| ident.span | |
| }, | |
| ItemKind::Const(ident, ..) | |
| | ItemKind::Enum(ident, ..) | |
| | ItemKind::Fn { ident, .. } | |
| | ItemKind::Macro(ident, ..) | |
| | ItemKind::Static(_, ident, ..) | |
| | ItemKind::Struct(ident, ..) | |
| | ItemKind::Trait { ident, .. } | |
| | ItemKind::TraitAlias(_, ident, ..) | |
| | ItemKind::TyAlias(ident, ..) | |
| | ItemKind::Union(ident, ..) => ident.span, | |
| }; | |
| if !item.span.from_expansion() | |
| && self.is_missing_docs(cx, item.owner_id.def_id, item.hir_id()) | |
| && !is_from_proc_macro(cx, item) | |
| { | |
| let (article, desc) = cx.tcx.article_and_description(item.owner_id.to_def_id()); | |
| span_lint( | |
| cx, | |
| MISSING_DOCS_IN_PRIVATE_ITEMS, | |
| span, | |
| format!("missing documentation for {article} {desc}"), | |
| ); | |
| } | |
| if matches!(item.kind, ItemKind::Mod(..)) { | |
| self.module_depth += 1; | |
| } | |
| } | |
| fn check_item_post(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { | |
| if matches!(item.kind, ItemKind::Mod(..)) | |
| && self.doc_hidden_depth == 0 | |
| && self.automatically_derived_depth == 0 | |
| && self.in_body.is_none() | |
| { | |
| self.module_depth -= 1; | |
| if self.module_depth < self.macro_module_depth { | |
| self.require_visibility_at = None; | |
| } | |
| } | |
| } | |
| fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { | |
| if self.doc_hidden_depth == 0 | |
| && self.automatically_derived_depth == 0 | |
| && self.in_body.is_none() | |
| && !item.span.from_expansion() | |
| && self.is_missing_docs(cx, item.owner_id.def_id, item.hir_id()) | |
| && !is_from_proc_macro(cx, item) | |
| { | |
| let (article, desc) = cx.tcx.article_and_description(item.owner_id.to_def_id()); | |
| span_lint( | |
| cx, | |
| MISSING_DOCS_IN_PRIVATE_ITEMS, | |
| item.ident.span, | |
| format!("missing documentation for {article} {desc}"), | |
| ); | |
| } | |
| } | |
| fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { | |
| if self.doc_hidden_depth == 0 | |
| && self.automatically_derived_depth == 0 | |
| && self.in_body.is_none() | |
| && let Node::Item(parent) = cx.tcx.parent_hir_node(item.hir_id()) | |
| && let ItemKind::Impl(impl_) = parent.kind | |
| && impl_.of_trait.is_none() | |
| && !item.span.from_expansion() | |
| && self.is_missing_docs(cx, item.owner_id.def_id, item.hir_id()) | |
| && !is_from_proc_macro(cx, item) | |
| { | |
| let (article, desc) = cx.tcx.article_and_description(item.owner_id.to_def_id()); | |
| span_lint( | |
| cx, | |
| MISSING_DOCS_IN_PRIVATE_ITEMS, | |
| item.ident.span, | |
| format!("missing documentation for {article} {desc}"), | |
| ); | |
| } | |
| } | |
| fn check_body(&mut self, _: &LateContext<'tcx>, body: &Body<'tcx>) { | |
| if self.doc_hidden_depth == 0 && self.automatically_derived_depth == 0 && self.in_body.is_none() { | |
| self.in_body = Some(body.id()); | |
| } | |
| } | |
| fn check_body_post(&mut self, _: &LateContext<'tcx>, body: &Body<'tcx>) { | |
| if self.in_body == Some(body.id()) { | |
| self.in_body = None; | |
| } | |
| } | |
| fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx FieldDef<'_>) { | |
| if self.doc_hidden_depth == 0 | |
| && self.automatically_derived_depth == 0 | |
| && self.in_body.is_none() | |
| && !field.is_positional() | |
| && !field.span.from_expansion() | |
| && !(self.allow_unused && field.ident.name.as_str().starts_with('_')) | |
| && self.is_missing_docs(cx, field.def_id, field.hir_id) | |
| && !is_from_proc_macro(cx, field) | |
| { | |
| span_lint( | |
| cx, | |
| MISSING_DOCS_IN_PRIVATE_ITEMS, | |
| field.ident.span, | |
| "missing documentation for a field", | |
| ); | |
| } | |
| } | |
| fn check_variant(&mut self, cx: &LateContext<'tcx>, variant: &'tcx Variant<'_>) { | |
| if self.doc_hidden_depth == 0 | |
| && self.automatically_derived_depth == 0 | |
| && self.in_body.is_none() | |
| && !variant.span.from_expansion() | |
| && self.is_missing_docs(cx, variant.def_id, variant.hir_id) | |
| && !is_from_proc_macro(cx, variant) | |
| { | |
| span_lint( | |
| cx, | |
| MISSING_DOCS_IN_PRIVATE_ITEMS, | |
| variant.ident.span, | |
| "missing documentation for a variant", | |
| ); | |
| } | |
| } | |
| } | |
| fn is_doc_attr(attr: &Attribute) -> bool { | |
| match attr { | |
| Attribute::Parsed(AttributeKind::DocComment { .. }) => true, | |
| Attribute::Unparsed(attr) | |
| if let [name] = &*attr.path.segments | |
| && *name == sym::doc => | |
| { | |
| matches!(attr.args, AttrArgs::Eq { .. }) | |
| }, | |
| _ => false, | |
| } | |
| } |