95 lines
3.4 KiB
Rust
95 lines
3.4 KiB
Rust
|
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()
|
||
|
}
|