-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathfour_forward_slashes.rs
99 lines (94 loc) · 3.58 KB
/
four_forward_slashes.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::Applicability;
use rustc_hir::Item;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
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
/// ```no_run
/// //// My amazing data structure
/// pub struct Foo {
/// // ...
/// }
/// ```
///
/// Use instead:
/// ```no_run
/// /// My amazing data structure
/// pub struct Foo {
/// // ...
/// }
/// ```
#[clippy::version = "1.73.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 sm = cx.sess().source_map();
let mut span = cx
.tcx
.hir_attrs(item.hir_id())
.iter()
.filter(|i| i.is_doc_comment())
.fold(item.span.shrink_to_lo(), |span, attr| span.to(attr.span()));
let (Some(file), _, _, end_line, _) = sm.span_to_location_info(span) else {
return;
};
let mut bad_comments = vec![];
for line in (0..end_line.saturating_sub(1)).rev() {
let Some(contents) = file.get_line(line).map(|c| c.trim().to_owned()) else {
return;
};
// Keep searching until we find the next item
if !contents.is_empty() && !contents.starts_with("//") && !contents.starts_with("#[") {
break;
}
if contents.starts_with("////") && !matches!(contents.chars().nth(4), Some('/' | '!')) {
let bounds = file.line_bounds(line);
let line_span = Span::with_root_ctxt(bounds.start, bounds.end);
span = line_span.to(span);
bad_comments.push((line_span, contents));
}
}
if !bad_comments.is_empty() {
span_lint_and_then(
cx,
FOUR_FORWARD_SLASHES,
span,
"this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't",
|diag| {
let msg = if bad_comments.len() == 1 {
"make this a doc comment by removing one `/`"
} else {
"turn these into doc comments by removing one `/`"
};
diag.multipart_suggestion(
msg,
bad_comments
.into_iter()
// 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
.map(|(span, c)| (span, c.replacen("////", "///", 1) + "\n"))
.collect(),
Applicability::MachineApplicable,
);
},
);
}
}
}