Skip to content

Commit 7bef775

Browse files
committed
new restriction lint: doc_comment_double_space_linebreak
1 parent a81f1c8 commit 7bef775

7 files changed

+266
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5351,6 +5351,7 @@ Released 2018-09-13
53515351
[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
53525352
[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
53535353
[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
5354+
[`doc_comment_double_space_linebreak`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreak
53545355
[`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
53555356
[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes
53565357
[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
132132
crate::disallowed_names::DISALLOWED_NAMES_INFO,
133133
crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO,
134134
crate::disallowed_types::DISALLOWED_TYPES_INFO,
135+
crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAK_INFO,
135136
crate::doc::DOC_LAZY_CONTINUATION_INFO,
136137
crate::doc::DOC_LINK_WITH_QUOTES_INFO,
137138
crate::doc::DOC_MARKDOWN_INFO,
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use std::borrow::Cow;
2+
3+
use clippy_utils::diagnostics::span_lint_and_then;
4+
use clippy_utils::source::snippet;
5+
use pulldown_cmark::CowStr;
6+
use rustc_errors::Applicability;
7+
use rustc_lint::LateContext;
8+
use rustc_span::Span;
9+
10+
use super::DOC_COMMENT_DOUBLE_SPACE_LINEBREAK;
11+
12+
pub fn check(cx: &LateContext<'_>, collected_breaks: Vec<(Span, (Span, CowStr<'_>), Cow<'_, str>)>) {
13+
let replacements: Vec<_> = collect_doc_replacements(cx, &collected_breaks);
14+
15+
if let Some((&(lo_span, _), &(hi_span, _))) = replacements.first().zip(replacements.last()) {
16+
span_lint_and_then(
17+
cx,
18+
DOC_COMMENT_DOUBLE_SPACE_LINEBREAK,
19+
lo_span.to(hi_span),
20+
"doc comments should use a back-slash (\\) instead of a double space to indicate a linebreak",
21+
|diag| {
22+
diag.multipart_suggestion(
23+
"replace this double space with a back-slash",
24+
replacements,
25+
Applicability::MachineApplicable,
26+
);
27+
},
28+
);
29+
}
30+
}
31+
32+
fn collect_doc_replacements(
33+
cx: &LateContext<'_>,
34+
attrs: &[(Span, (Span, CowStr<'_>), Cow<'_, str>)],
35+
) -> Vec<(Span, String)> {
36+
attrs
37+
.iter()
38+
.map(|attr| {
39+
let full = attr.0;
40+
let new_comment = format!("\\\n/// ");
41+
(full, new_comment)
42+
})
43+
.collect()
44+
}
45+
46+
/*
47+
use clippy_utils::diagnostics::span_lint_and_then;
48+
use rustc_ast::token::CommentKind;
49+
use rustc_ast::{AttrKind, AttrStyle, Attribute};
50+
use rustc_errors::Applicability;
51+
use rustc_lint::LateContext;
52+
use rustc_span::Span;
53+
54+
use super::DOC_COMMENT_DOUBLE_SPACE_LINEBREAK;
55+
56+
pub fn check(cx: &LateContext<'_>, attrs: &[Attribute]) {
57+
let replacements: Vec<_> = collect_doc_replacements(attrs);
58+
59+
if let Some((&(lo_span, _), &(hi_span, _))) = replacements.first().zip(replacements.last()) {
60+
span_lint_and_then(
61+
cx,
62+
DOC_COMMENT_DOUBLE_SPACE_LINEBREAK,
63+
lo_span.to(hi_span),
64+
"doc comments should use a back-slash (\\) instead of a double space to indicate a linebreak",
65+
|diag| {
66+
diag.multipart_suggestion(
67+
"replace this double space with a back-slash",
68+
replacements,
69+
Applicability::MachineApplicable,
70+
);
71+
},
72+
);
73+
}
74+
}
75+
76+
fn collect_doc_replacements(attrs: &[Attribute]) -> Vec<(Span, String)> {
77+
attrs
78+
.iter()
79+
.filter_map(|attr| {
80+
if let AttrKind::DocComment(com_kind, sym) = attr.kind
81+
&& !attr.span.from_expansion()
82+
&& com_kind == CommentKind::Line
83+
&& let comment = sym.as_str()
84+
&& comment.ends_with(" ")
85+
{
86+
let pre = match attr.style {
87+
AttrStyle::Outer => "///",
88+
AttrStyle::Inner => "//!",
89+
};
90+
91+
let len = comment.len();
92+
let new_comment = format!("{pre}{}\\", &comment[..len - 2]);
93+
Some((attr.span, new_comment))
94+
} else {
95+
None
96+
}
97+
})
98+
.collect()
99+
}
100+
*/

clippy_lints/src/doc/mod.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use clippy_config::Conf;
55
use clippy_utils::attrs::is_doc_hidden;
66
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
77
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
8+
use clippy_utils::source::snippet;
89
use clippy_utils::ty::is_type_diagnostic_item;
910
use clippy_utils::visitors::Visitable;
1011
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
@@ -29,10 +30,12 @@ use rustc_resolve::rustdoc::{
2930
use rustc_session::impl_lint_pass;
3031
use rustc_span::edition::Edition;
3132
use rustc_span::{sym, Span};
33+
use std::borrow::Cow;
3234
use std::ops::Range;
3335
use url::Url;
3436

3537
mod empty_line_after;
38+
mod doc_comment_double_space_linebreak;
3639
mod link_with_quotes;
3740
mod markdown;
3841
mod missing_headers;
@@ -532,6 +535,38 @@ declare_clippy_lint! {
532535
"empty line after doc comments"
533536
}
534537

538+
declare_clippy_lint! {
539+
/// Detects doc comment linebreaks that use double spaces to separate lines, instead of back-slash (\).
540+
///
541+
/// ### Why is this bad?
542+
/// Double spaces, when used as doc comment linebreaks, can be difficult to see, and may
543+
/// accidentally be removed during automatic fofmatting or manual refactoring. The use of a back-slash (\)
544+
/// is clearer in this regard.
545+
///
546+
/// ### Example
547+
/// The two dots in this example represent a double space.
548+
/// ```no_run
549+
/// /// This command takes two numbers as inputs and..
550+
/// /// adds them together, and then returns the result.
551+
/// fn add(l: i32, r: i32) -> i32 {
552+
/// l + r
553+
/// }
554+
/// ``````
555+
///
556+
/// Use instead:
557+
/// ```no_run
558+
/// /// This command takes two numbers as inputs and \
559+
/// /// adds them together, and then returns the result.
560+
/// fn add(l: i32, r: i32) -> i32 {
561+
/// l + r
562+
/// }
563+
/// ```
564+
#[clippy::version = "1.80.0"]
565+
pub DOC_COMMENT_DOUBLE_SPACE_LINEBREAK,
566+
restriction,
567+
"double-space used for doc comment linebreak instead of `\\`"
568+
}
569+
535570
pub struct Documentation {
536571
valid_idents: FxHashSet<String>,
537572
check_private_items: bool,
@@ -561,6 +596,7 @@ impl_lint_pass!(Documentation => [
561596
EMPTY_LINE_AFTER_OUTER_ATTR,
562597
EMPTY_LINE_AFTER_DOC_COMMENTS,
563598
TOO_LONG_FIRST_DOC_PARAGRAPH,
599+
DOC_COMMENT_DOUBLE_SPACE_LINEBREAK
564600
]);
565601

566602
impl<'tcx> LateLintPass<'tcx> for Documentation {
@@ -694,6 +730,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
694730
return None;
695731
}
696732

733+
suspicious_doc_comments::check(cx, attrs);
734+
697735
let (fragments, _) = attrs_to_doc_fragments(
698736
attrs.iter().filter_map(|attr| {
699737
if in_external_macro(cx.sess(), attr.span) {
@@ -776,8 +814,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
776814
let mut paragraph_range = 0..0;
777815
let mut code_level = 0;
778816
let mut blockquote_level = 0;
779-
let mut is_first_paragraph = true;
780-
817+
let mut collected_breaks: Vec<(Span, (Span, CowStr<'_>), Cow<'_, str>)> = Vec::new();
781818
let mut containers = Vec::new();
782819

783820
let mut events = events.peekable();
@@ -894,8 +931,20 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
894931
&containers[..],
895932
);
896933
}
934+
935+
if let Some(span) = fragments.span(cx, range.clone())
936+
&& let Some(ref p) = prev_text
937+
&& let snippet = snippet(cx, span, "..")
938+
&& !snippet.trim().starts_with("\\")
939+
&& event == HardBreak {
940+
collected_breaks.push((span, p.clone(), snippet));
941+
prev_text = None;
942+
}
897943
},
898944
Text(text) => {
945+
if let Some(span) = fragments.span(cx, range.clone()) {
946+
prev_text = Some((span, text.clone()));
947+
}
899948
paragraph_range.end = range.end;
900949
let range_ = range.clone();
901950
ticks_unbalanced |= text.contains('`')
@@ -943,6 +992,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
943992
FootnoteReference(_) => {}
944993
}
945994
}
995+
996+
doc_comment_double_space_linebreak::check(cx, collected_breaks);
997+
946998
headers
947999
}
9481000

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#![feature(custom_inner_attributes)]
2+
#![rustfmt::skip]
3+
4+
#![warn(clippy::doc_comment_double_space_linebreak)]
5+
#![allow(unused)]
6+
7+
//! Should warn on double space linebreaks \
8+
//! in file/module doc comment
9+
10+
/// Should not warn on single-line doc comments
11+
fn single_line() {}
12+
13+
/// Should not warn on single-line doc comments
14+
/// split across multiple lines
15+
fn single_line_split() {}
16+
17+
// Should not warn on normal comments
18+
19+
// note: cargo fmt can remove double spaces from normal and block comments
20+
// Should not warn on normal comments
21+
// with double spaces at the end of a line
22+
23+
fn normal_comment() {
24+
/*
25+
Should not warn on block comments
26+
*/
27+
28+
/*
29+
Should not warn on block comments
30+
with double space at the end of a line
31+
*/
32+
}
33+
34+
/// Should warn when doc comment uses double space \
35+
/// as a line-break, even when there are multiple \
36+
/// in a row
37+
fn double_space_doc_comment() {}
38+
39+
/// Should not warn when back-slash is used \
40+
/// as a line-break
41+
fn back_slash_doc_comment() {}
42+
43+
fn main() {}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#![feature(custom_inner_attributes)]
2+
#![rustfmt::skip]
3+
4+
#![warn(clippy::doc_comment_double_space_linebreak)]
5+
#![allow(unused)]
6+
7+
//! Should warn on double space linebreaks
8+
//! in file/module doc comment
9+
10+
/// Should not warn on single-line doc comments
11+
fn single_line() {}
12+
13+
/// Should not warn on single-line doc comments
14+
/// split across multiple lines
15+
fn single_line_split() {}
16+
17+
// Should not warn on normal comments
18+
19+
// note: cargo fmt can remove double spaces from normal and block comments
20+
// Should not warn on normal comments
21+
// with double spaces at the end of a line
22+
23+
fn normal_comment() {
24+
/*
25+
Should not warn on block comments
26+
*/
27+
28+
/*
29+
Should not warn on block comments
30+
with double space at the end of a line
31+
*/
32+
}
33+
34+
/// Should warn when doc comment uses double space
35+
/// as a line-break, even when there are multiple
36+
/// in a row
37+
fn double_space_doc_comment() {}
38+
39+
/// Should not warn when back-slash is used \
40+
/// as a line-break
41+
fn back_slash_doc_comment() {}
42+
43+
fn main() {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error: doc comments should use a back-slash (\) instead of a double space to indicate a linebreak
2+
--> tests/ui/doc/doc_comment_double_space_linebreak.rs:7:1
3+
|
4+
LL | //! Should warn on double space linebreaks
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace this double space with a back-slash: `//! Should warn on double space linebreaks \`
6+
|
7+
= note: `-D clippy::doc-comment-double-space-linebreak` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::doc_comment_double_space_linebreak)]`
9+
10+
error: doc comments should use a back-slash (\) instead of a double space to indicate a linebreak
11+
--> tests/ui/doc/doc_comment_double_space_linebreak.rs:34:1
12+
|
13+
LL | / /// Should warn when doc comment uses double space
14+
LL | | /// as a line-break, even when there are multiple
15+
| |___________________________________________________^
16+
|
17+
help: replace this double space with a back-slash
18+
|
19+
LL + /// Should warn when doc comment uses double space \
20+
LL + /// as a line-break, even when there are multiple \
21+
|
22+
23+
error: aborting due to 2 previous errors
24+

0 commit comments

Comments
 (0)