| use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC}; | |
| use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; | |
| use clippy_utils::macros::{is_panic, root_macro_call_first_node}; | |
| use clippy_utils::res::MaybeDef; | |
| use clippy_utils::ty::implements_trait_with_env; | |
| use clippy_utils::visitors::for_each_expr; | |
| use clippy_utils::{fulfill_or_allowed, is_doc_hidden, is_inside_always_const_context, method_chain_args, return_ty}; | |
| use rustc_hir::{BodyId, FnSig, OwnerId, Safety}; | |
| use rustc_lint::LateContext; | |
| use rustc_middle::ty; | |
| use rustc_span::{Span, sym}; | |
| use std::ops::ControlFlow; | |
| pub fn check( | |
| cx: &LateContext<'_>, | |
| owner_id: OwnerId, | |
| sig: FnSig<'_>, | |
| headers: DocHeaders, | |
| body_id: Option<BodyId>, | |
| check_private_items: bool, | |
| ) { | |
| if !check_private_items && !cx.effective_visibilities.is_exported(owner_id.def_id) { | |
| return; // Private functions do not require doc comments | |
| } | |
| // do not lint if any parent has `#[doc(hidden)]` attribute (#7347) | |
| if !check_private_items | |
| && cx | |
| .tcx | |
| .hir_parent_iter(owner_id.into()) | |
| .any(|(id, _node)| is_doc_hidden(cx.tcx.hir_attrs(id))) | |
| { | |
| return; | |
| } | |
| let span = cx.tcx.def_span(owner_id); | |
| match (headers.safety, sig.header.safety()) { | |
| (false, Safety::Unsafe) => span_lint( | |
| cx, | |
| MISSING_SAFETY_DOC, | |
| span, | |
| "unsafe function's docs are missing a `# Safety` section", | |
| ), | |
| (true, Safety::Safe) => span_lint( | |
| cx, | |
| UNNECESSARY_SAFETY_DOC, | |
| span, | |
| "safe function's docs have unnecessary `# Safety` section", | |
| ), | |
| _ => (), | |
| } | |
| if !headers.panics | |
| && let Some(body_id) = body_id | |
| && let Some(panic_span) = find_panic(cx, body_id) | |
| { | |
| span_lint_and_note( | |
| cx, | |
| MISSING_PANICS_DOC, | |
| span, | |
| "docs for function which may panic missing `# Panics` section", | |
| Some(panic_span), | |
| "first possible panic found here", | |
| ); | |
| } | |
| if !headers.errors { | |
| if return_ty(cx, owner_id).is_diag_item(cx, sym::Result) { | |
| span_lint( | |
| cx, | |
| MISSING_ERRORS_DOC, | |
| span, | |
| "docs for function returning `Result` missing `# Errors` section", | |
| ); | |
| } else if let Some(body_id) = body_id | |
| && let Some(future) = cx.tcx.lang_items().future_trait() | |
| && let typeck = cx.tcx.typeck_body(body_id) | |
| && let body = cx.tcx.hir_body(body_id) | |
| && let ret_ty = typeck.expr_ty(body.value) | |
| && implements_trait_with_env( | |
| cx.tcx, | |
| ty::TypingEnv::non_body_analysis(cx.tcx, owner_id.def_id), | |
| ret_ty, | |
| future, | |
| Some(owner_id.def_id.to_def_id()), | |
| &[], | |
| ) | |
| && let ty::Coroutine(_, subs) = ret_ty.kind() | |
| && subs.as_coroutine().return_ty().is_diag_item(cx, sym::Result) | |
| { | |
| span_lint( | |
| cx, | |
| MISSING_ERRORS_DOC, | |
| span, | |
| "docs for function returning `Result` missing `# Errors` section", | |
| ); | |
| } | |
| } | |
| } | |
| fn find_panic(cx: &LateContext<'_>, body_id: BodyId) -> Option<Span> { | |
| let mut panic_span = None; | |
| let typeck = cx.tcx.typeck_body(body_id); | |
| for_each_expr(cx, cx.tcx.hir_body(body_id), |expr| { | |
| if is_inside_always_const_context(cx.tcx, expr.hir_id) { | |
| return ControlFlow::<!>::Continue(()); | |
| } | |
| if let Some(macro_call) = root_macro_call_first_node(cx, expr) | |
| && (is_panic(cx, macro_call.def_id) | |
| || matches!( | |
| cx.tcx.get_diagnostic_name(macro_call.def_id), | |
| Some(sym::assert_macro | sym::assert_eq_macro | sym::assert_ne_macro) | |
| )) | |
| && !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id]) | |
| && panic_span.is_none() | |
| { | |
| panic_span = Some(macro_call.span); | |
| } | |
| // check for `unwrap` and `expect` for both `Option` and `Result` | |
| if let Some(arglists) = | |
| method_chain_args(expr, &[sym::unwrap]).or_else(|| method_chain_args(expr, &[sym::expect])) | |
| && let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs() | |
| && matches!(receiver_ty.opt_diag_name(cx), Some(sym::Option | sym::Result)) | |
| && !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id]) | |
| && panic_span.is_none() | |
| { | |
| panic_span = Some(expr.span); | |
| } | |
| // Visit all nodes to fulfill any `#[expect]`s after the first linted panic | |
| ControlFlow::<!>::Continue(()) | |
| }); | |
| panic_span | |
| } |