diff options
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/suspicious_doc_comments.rs')
-rw-r--r-- | src/tools/clippy/clippy_lints/src/suspicious_doc_comments.rs | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/suspicious_doc_comments.rs b/src/tools/clippy/clippy_lints/src/suspicious_doc_comments.rs new file mode 100644 index 000000000..e5746ca99 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/suspicious_doc_comments.rs @@ -0,0 +1,94 @@ +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() +} |