83 lines
3.0 KiB
Rust
83 lines
3.0 KiB
Rust
|
use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet_opt};
|
||
|
use rustc_errors::Applicability;
|
||
|
use rustc_hir::Item;
|
||
|
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||
|
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||
|
use rustc_span::{Span, SyntaxContext};
|
||
|
|
||
|
declare_clippy_lint! {
|
||
|
/// ### What it does
|
||
|
/// Checks for outer doc comments written with 4 forward slashes (`////`).
|
||
|
///
|
||
|
/// ### Why is this bad?
|
||
|
/// This is (probably) a typo, and results in it not being a doc comment; just a regular
|
||
|
/// comment.
|
||
|
///
|
||
|
/// ### Example
|
||
|
/// ```rust
|
||
|
/// //// My amazing data structure
|
||
|
/// pub struct Foo {
|
||
|
/// // ...
|
||
|
/// }
|
||
|
/// ```
|
||
|
///
|
||
|
/// Use instead:
|
||
|
/// ```rust
|
||
|
/// /// My amazing data structure
|
||
|
/// pub struct Foo {
|
||
|
/// // ...
|
||
|
/// }
|
||
|
/// ```
|
||
|
#[clippy::version = "1.72.0"]
|
||
|
pub FOUR_FORWARD_SLASHES,
|
||
|
suspicious,
|
||
|
"comments with 4 forward slashes (`////`) likely intended to be doc comments (`///`)"
|
||
|
}
|
||
|
declare_lint_pass!(FourForwardSlashes => [FOUR_FORWARD_SLASHES]);
|
||
|
|
||
|
impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes {
|
||
|
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||
|
if item.span.from_expansion() {
|
||
|
return;
|
||
|
}
|
||
|
let src = cx.sess().source_map();
|
||
|
let item_and_attrs_span = cx
|
||
|
.tcx
|
||
|
.hir()
|
||
|
.attrs(item.hir_id())
|
||
|
.iter()
|
||
|
.fold(item.span.shrink_to_lo(), |span, attr| span.to(attr.span));
|
||
|
let (Some(file), _, _, end_line, _) = src.span_to_location_info(item_and_attrs_span) else {
|
||
|
return;
|
||
|
};
|
||
|
for line in (0..end_line.saturating_sub(1)).rev() {
|
||
|
let Some(contents) = file.get_line(line) else {
|
||
|
continue;
|
||
|
};
|
||
|
let contents = contents.trim();
|
||
|
if contents.is_empty() {
|
||
|
break;
|
||
|
}
|
||
|
if contents.starts_with("////") {
|
||
|
let bounds = file.line_bounds(line);
|
||
|
let span = Span::new(bounds.start, bounds.end, SyntaxContext::root(), None);
|
||
|
|
||
|
if snippet_opt(cx, span).is_some_and(|s| s.trim().starts_with("////")) {
|
||
|
span_lint_and_sugg(
|
||
|
cx,
|
||
|
FOUR_FORWARD_SLASHES,
|
||
|
span,
|
||
|
"comment with 4 forward slashes (`////`). This looks like a doc comment, but it isn't",
|
||
|
"make this a doc comment by removing one `/`",
|
||
|
// It's a little unfortunate but the span includes the `\n` yet the contents
|
||
|
// do not, so we must add it back. If some codebase uses `\r\n` instead they
|
||
|
// will need normalization but it should be fine
|
||
|
contents.replacen("////", "///", 1) + "\n",
|
||
|
Applicability::MachineApplicable,
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|