Skip to content

Commit 3c7dfac

Browse files
authored
new lint: doc_comment_double_space_linebreaks (#12876)
Fixes #12163 I decided to initially make this a restriction lint because it felt a bit niche and opinionated to be a warn-by-default style lint. It may be appropriate as a style lint if the standard or convention *is* to use `\` as doc comment linebreaks - not sure if they are! The wording on the help message could be improved, as well as the name of the lint itself since it's a bit wordy - suggestions welcome. This lint works on both `///` and `//!` doc comments. changelog: new lint: `doc_comment_double_space_linebreaks`
2 parents 5c031d1 + 45e4487 commit 3c7dfac

7 files changed

+331
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5570,6 +5570,7 @@ Released 2018-09-13
55705570
[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
55715571
[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
55725572
[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
5573+
[`doc_comment_double_space_linebreaks`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreaks
55735574
[`doc_include_without_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_include_without_cfg
55745575
[`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
55755576
[`doc_link_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_code

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
137137
crate::disallowed_names::DISALLOWED_NAMES_INFO,
138138
crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO,
139139
crate::disallowed_types::DISALLOWED_TYPES_INFO,
140+
crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS_INFO,
140141
crate::doc::DOC_INCLUDE_WITHOUT_CFG_INFO,
141142
crate::doc::DOC_LAZY_CONTINUATION_INFO,
142143
crate::doc::DOC_LINK_CODE_INFO,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use rustc_errors::Applicability;
3+
use rustc_lint::LateContext;
4+
use rustc_span::{BytePos, Span};
5+
6+
use super::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS;
7+
8+
pub fn check(cx: &LateContext<'_>, collected_breaks: &[Span]) {
9+
if collected_breaks.is_empty() {
10+
return;
11+
}
12+
13+
let breaks: Vec<_> = collected_breaks
14+
.iter()
15+
.map(|span| span.with_hi(span.lo() + BytePos(2)))
16+
.collect();
17+
18+
span_lint_and_then(
19+
cx,
20+
DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS,
21+
breaks.clone(),
22+
"doc comment uses two spaces for a hard line break",
23+
|diag| {
24+
let suggs: Vec<_> = breaks.iter().map(|span| (*span, "\\".to_string())).collect();
25+
diag.tool_only_multipart_suggestion(
26+
"replace this double space with a backslash:",
27+
suggs,
28+
Applicability::MachineApplicable,
29+
);
30+
diag.help("replace this double space with a backslash: `\\`");
31+
},
32+
);
33+
}

clippy_lints/src/doc/mod.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
#![allow(clippy::lint_without_lint_pass)]
22

3-
mod lazy_continuation;
4-
mod too_long_first_doc_paragraph;
5-
63
use clippy_config::Conf;
74
use clippy_utils::attrs::is_doc_hidden;
85
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
96
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
7+
use clippy_utils::source::snippet_opt;
108
use clippy_utils::ty::is_type_diagnostic_item;
119
use clippy_utils::visitors::Visitable;
1210
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
@@ -33,12 +31,15 @@ use rustc_span::{Span, sym};
3331
use std::ops::Range;
3432
use url::Url;
3533

34+
mod doc_comment_double_space_linebreaks;
3635
mod include_in_doc_without_cfg;
36+
mod lazy_continuation;
3737
mod link_with_quotes;
3838
mod markdown;
3939
mod missing_headers;
4040
mod needless_doctest_main;
4141
mod suspicious_doc_comments;
42+
mod too_long_first_doc_paragraph;
4243

4344
declare_clippy_lint! {
4445
/// ### What it does
@@ -567,6 +568,39 @@ declare_clippy_lint! {
567568
"link reference defined in list item or quote"
568569
}
569570

571+
declare_clippy_lint! {
572+
/// ### What it does
573+
/// Detects doc comment linebreaks that use double spaces to separate lines, instead of back-slash (`\`).
574+
///
575+
/// ### Why is this bad?
576+
/// Double spaces, when used as doc comment linebreaks, can be difficult to see, and may
577+
/// accidentally be removed during automatic formatting or manual refactoring. The use of a back-slash (`\`)
578+
/// is clearer in this regard.
579+
///
580+
/// ### Example
581+
/// The two replacement dots in this example represent a double space.
582+
/// ```no_run
583+
/// /// This command takes two numbers as inputs and··
584+
/// /// adds them together, and then returns the result.
585+
/// fn add(l: i32, r: i32) -> i32 {
586+
/// l + r
587+
/// }
588+
/// ```
589+
///
590+
/// Use instead:
591+
/// ```no_run
592+
/// /// This command takes two numbers as inputs and\
593+
/// /// adds them together, and then returns the result.
594+
/// fn add(l: i32, r: i32) -> i32 {
595+
/// l + r
596+
/// }
597+
/// ```
598+
#[clippy::version = "1.87.0"]
599+
pub DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS,
600+
pedantic,
601+
"double-space used for doc comment linebreak instead of `\\`"
602+
}
603+
570604
pub struct Documentation {
571605
valid_idents: FxHashSet<String>,
572606
check_private_items: bool,
@@ -598,6 +632,7 @@ impl_lint_pass!(Documentation => [
598632
DOC_OVERINDENTED_LIST_ITEMS,
599633
TOO_LONG_FIRST_DOC_PARAGRAPH,
600634
DOC_INCLUDE_WITHOUT_CFG,
635+
DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS
601636
]);
602637

603638
impl EarlyLintPass for Documentation {
@@ -894,6 +929,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
894929
let mut paragraph_range = 0..0;
895930
let mut code_level = 0;
896931
let mut blockquote_level = 0;
932+
let mut collected_breaks: Vec<Span> = Vec::new();
897933
let mut is_first_paragraph = true;
898934

899935
let mut containers = Vec::new();
@@ -1069,6 +1105,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
10691105
&containers[..],
10701106
);
10711107
}
1108+
1109+
if let Some(span) = fragments.span(cx, range.clone())
1110+
&& !span.from_expansion()
1111+
&& let Some(snippet) = snippet_opt(cx, span)
1112+
&& !snippet.trim().starts_with('\\')
1113+
&& event == HardBreak {
1114+
collected_breaks.push(span);
1115+
}
10721116
},
10731117
Text(text) => {
10741118
paragraph_range.end = range.end;
@@ -1119,6 +1163,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
11191163
FootnoteReference(_) => {}
11201164
}
11211165
}
1166+
1167+
doc_comment_double_space_linebreaks::check(cx, &collected_breaks);
1168+
11221169
headers
11231170
}
11241171

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#![feature(custom_inner_attributes)]
2+
#![rustfmt::skip]
3+
4+
#![warn(clippy::doc_comment_double_space_linebreaks)]
5+
#![allow(unused, clippy::empty_docs)]
6+
7+
//~v doc_comment_double_space_linebreaks
8+
//! Should warn on double space linebreaks\
9+
//! in file/module doc comment
10+
11+
/// Should not warn on single-line doc comments
12+
fn single_line() {}
13+
14+
/// Should not warn on single-line doc comments
15+
/// split across multiple lines
16+
fn single_line_split() {}
17+
18+
// Should not warn on normal comments
19+
20+
// note: cargo fmt can remove double spaces from normal and block comments
21+
// Should not warn on normal comments
22+
// with double spaces at the end of a line
23+
24+
#[doc = "This is a doc attribute, which should not be linted"]
25+
fn normal_comment() {
26+
/*
27+
Should not warn on block comments
28+
*/
29+
30+
/*
31+
Should not warn on block comments
32+
with double space at the end of a line
33+
*/
34+
}
35+
36+
//~v doc_comment_double_space_linebreaks
37+
/// Should warn when doc comment uses double space\
38+
/// as a line-break, even when there are multiple\
39+
/// in a row
40+
fn double_space_doc_comment() {}
41+
42+
/// Should not warn when back-slash is used \
43+
/// as a line-break
44+
fn back_slash_doc_comment() {}
45+
46+
//~v doc_comment_double_space_linebreaks
47+
/// 🌹 are 🟥\
48+
/// 🌷 are 🟦\
49+
/// 📎 is 😎\
50+
/// and so are 🫵\
51+
/// (hopefully no formatting weirdness linting this)
52+
fn multi_byte_chars_tada() {}
53+
54+
macro_rules! macro_that_makes_function {
55+
() => {
56+
/// Shouldn't lint on this!
57+
/// (hopefully)
58+
fn my_macro_created_function() {}
59+
}
60+
}
61+
62+
macro_that_makes_function!();
63+
64+
// dont lint when its alone on a line
65+
///
66+
fn alone() {}
67+
68+
/// | First column | Second column |
69+
/// | ------------ | ------------- |
70+
/// | Not a line | break when |
71+
/// | after a line | in a table |
72+
fn table() {}
73+
74+
/// ```text
75+
/// It's also not a hard line break if
76+
/// there's two spaces at the end of a
77+
/// line in a block code.
78+
/// ```
79+
fn codeblock() {}
80+
81+
/// It's also not a hard line break `if
82+
/// there's` two spaces in the middle of inline code.
83+
fn inline() {}
84+
85+
/// It's also not a hard line break [when](
86+
/// https://example.com) in a URL.
87+
fn url() {}
88+
89+
//~v doc_comment_double_space_linebreaks
90+
/// here we mix\
91+
/// double spaces\
92+
/// and also\
93+
/// adding backslash\
94+
/// to some of them\
95+
/// to see how that looks
96+
fn mixed() {}
97+
98+
fn main() {}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#![feature(custom_inner_attributes)]
2+
#![rustfmt::skip]
3+
4+
#![warn(clippy::doc_comment_double_space_linebreaks)]
5+
#![allow(unused, clippy::empty_docs)]
6+
7+
//~v doc_comment_double_space_linebreaks
8+
//! Should warn on double space linebreaks
9+
//! in file/module doc comment
10+
11+
/// Should not warn on single-line doc comments
12+
fn single_line() {}
13+
14+
/// Should not warn on single-line doc comments
15+
/// split across multiple lines
16+
fn single_line_split() {}
17+
18+
// Should not warn on normal comments
19+
20+
// note: cargo fmt can remove double spaces from normal and block comments
21+
// Should not warn on normal comments
22+
// with double spaces at the end of a line
23+
24+
#[doc = "This is a doc attribute, which should not be linted"]
25+
fn normal_comment() {
26+
/*
27+
Should not warn on block comments
28+
*/
29+
30+
/*
31+
Should not warn on block comments
32+
with double space at the end of a line
33+
*/
34+
}
35+
36+
//~v doc_comment_double_space_linebreaks
37+
/// Should warn when doc comment uses double space
38+
/// as a line-break, even when there are multiple
39+
/// in a row
40+
fn double_space_doc_comment() {}
41+
42+
/// Should not warn when back-slash is used \
43+
/// as a line-break
44+
fn back_slash_doc_comment() {}
45+
46+
//~v doc_comment_double_space_linebreaks
47+
/// 🌹 are 🟥
48+
/// 🌷 are 🟦
49+
/// 📎 is 😎
50+
/// and so are 🫵
51+
/// (hopefully no formatting weirdness linting this)
52+
fn multi_byte_chars_tada() {}
53+
54+
macro_rules! macro_that_makes_function {
55+
() => {
56+
/// Shouldn't lint on this!
57+
/// (hopefully)
58+
fn my_macro_created_function() {}
59+
}
60+
}
61+
62+
macro_that_makes_function!();
63+
64+
// dont lint when its alone on a line
65+
///
66+
fn alone() {}
67+
68+
/// | First column | Second column |
69+
/// | ------------ | ------------- |
70+
/// | Not a line | break when |
71+
/// | after a line | in a table |
72+
fn table() {}
73+
74+
/// ```text
75+
/// It's also not a hard line break if
76+
/// there's two spaces at the end of a
77+
/// line in a block code.
78+
/// ```
79+
fn codeblock() {}
80+
81+
/// It's also not a hard line break `if
82+
/// there's` two spaces in the middle of inline code.
83+
fn inline() {}
84+
85+
/// It's also not a hard line break [when](
86+
/// https://example.com) in a URL.
87+
fn url() {}
88+
89+
//~v doc_comment_double_space_linebreaks
90+
/// here we mix
91+
/// double spaces\
92+
/// and also
93+
/// adding backslash\
94+
/// to some of them
95+
/// to see how that looks
96+
fn mixed() {}
97+
98+
fn main() {}

0 commit comments

Comments
 (0)