use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_then}; use if_chain::if_chain; use rustc_ast::{token::CommentKind, AttrKind, AttrStyle, Attribute, Item}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::Span; declare_clippy_lint! { /// ### What it does /// Detects the use of outer doc comments (`///`, `/**`) followed by a bang (`!`): `///!` /// /// ### Why is this bad? /// Triple-slash comments (known as "outer doc comments") apply to items that follow it. /// An outer doc comment followed by a bang (i.e. `///!`) has no specific meaning. /// /// The user most likely meant to write an inner doc comment (`//!`, `/*!`), which /// applies to the parent item (i.e. the item that the comment is contained in, /// usually a module or crate). /// /// ### Known problems /// Inner doc comments can only appear before items, so there are certain cases where the suggestion /// made by this lint is not valid code. For example: /// ```rs /// fn foo() {} /// ///! /// fn bar() {} /// ``` /// This lint detects the doc comment and suggests changing it to `//!`, but an inner doc comment /// is not valid at that position. /// /// ### Example /// In this example, the doc comment is attached to the *function*, rather than the *module*. /// ```rust /// pub mod util { /// ///! This module contains utility functions. /// /// pub fn dummy() {} /// } /// ``` /// /// Use instead: /// ```rust /// pub mod util { /// //! This module contains utility functions. /// /// pub fn dummy() {} /// } /// ``` #[clippy::version = "1.70.0"] pub SUSPICIOUS_DOC_COMMENTS, suspicious, "suspicious usage of (outer) doc comments" } declare_lint_pass!(SuspiciousDocComments => [SUSPICIOUS_DOC_COMMENTS]); const WARNING: &str = "this is an outer doc comment and does not apply to the parent module or crate"; const HELP: &str = "use an inner doc comment to document the parent module or crate"; impl EarlyLintPass for SuspiciousDocComments { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { let replacements = collect_doc_comment_replacements(&item.attrs); if let Some(((lo_span, _), (hi_span, _))) = replacements.first().zip(replacements.last()) { let span = lo_span.to(*hi_span); span_lint_and_then(cx, SUSPICIOUS_DOC_COMMENTS, span, WARNING, |diag| { multispan_sugg_with_applicability(diag, HELP, Applicability::MaybeIncorrect, replacements); }); } } } fn collect_doc_comment_replacements(attrs: &[Attribute]) -> Vec<(Span, String)> { attrs .iter() .filter_map(|attr| { if_chain! { if let AttrKind::DocComment(com_kind, sym) = attr.kind; if let AttrStyle::Outer = attr.style; if let Some(com) = sym.as_str().strip_prefix('!'); then { let sugg = match com_kind { CommentKind::Line => format!("//!{com}"), CommentKind::Block => format!("/*!{com}*/") }; Some((attr.span, sugg)) } else { None } } }) .collect() }