Skip to content

Commit f94f64f

Browse files
JacherrxFrednet
authored andcommitted
new lint: doc_comment_double_space_linebreak
fix typo change replacement character in example, remove extraneous space from suggested change add additional testcases; check doc comment not from expansion do not lint on macros, add more testcases fix wording, remove commented out code, add additonal testcase uibless fix doc comments, use optional snippets Remove unneeded additional space
1 parent 1419ac2 commit f94f64f

7 files changed

+356
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5571,6 +5571,7 @@ Released 2018-09-13
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
55735573
[`doc_include_without_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_include_without_cfg
5574+
[`doc_comment_double_space_linebreak`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreak
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
55765577
[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
138138
crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO,
139139
crate::disallowed_types::DISALLOWED_TYPES_INFO,
140140
crate::doc::DOC_INCLUDE_WITHOUT_CFG_INFO,
141+
crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAK_INFO,
141142
crate::doc::DOC_LAZY_CONTINUATION_INFO,
142143
crate::doc::DOC_LINK_CODE_INFO,
143144
crate::doc::DOC_LINK_WITH_QUOTES_INFO,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::source::snippet_opt;
3+
use rustc_errors::Applicability;
4+
use rustc_lint::LateContext;
5+
use rustc_span::Span;
6+
7+
use super::DOC_COMMENT_DOUBLE_SPACE_LINEBREAK;
8+
9+
pub fn check(cx: &LateContext<'_>, collected_breaks: &[Span]) {
10+
let replacements: Vec<_> = collect_doc_replacements(cx, collected_breaks);
11+
12+
if let Some((&(lo_span, _), &(hi_span, _))) = replacements.first().zip(replacements.last()) {
13+
span_lint_and_then(
14+
cx,
15+
DOC_COMMENT_DOUBLE_SPACE_LINEBREAK,
16+
lo_span.to(hi_span),
17+
"doc comment uses two spaces for a hard line break",
18+
|diag| {
19+
diag.multipart_suggestion(
20+
"replace this double space with a backslash",
21+
replacements,
22+
Applicability::MachineApplicable,
23+
);
24+
},
25+
);
26+
}
27+
}
28+
29+
fn collect_doc_replacements(cx: &LateContext<'_>, spans: &[Span]) -> Vec<(Span, String)> {
30+
spans
31+
.iter()
32+
.map(|span| {
33+
// we already made sure the snippet exists when collecting spans
34+
let s = snippet_opt(cx, *span).expect("snippet was already validated to exist");
35+
let after_newline = s.trim_start_matches(' ');
36+
37+
let new_comment = format!("\\{after_newline}");
38+
(*span, new_comment)
39+
})
40+
.collect()
41+
}

clippy_lints/src/doc/mod.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use clippy_config::Conf;
77
use clippy_utils::attrs::is_doc_hidden;
88
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
99
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
10+
use clippy_utils::source::snippet_opt;
1011
use clippy_utils::ty::is_type_diagnostic_item;
1112
use clippy_utils::visitors::Visitable;
1213
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
@@ -33,6 +34,7 @@ use rustc_span::{Span, sym};
3334
use std::ops::Range;
3435
use url::Url;
3536

37+
mod doc_comment_double_space_linebreak;
3638
mod include_in_doc_without_cfg;
3739
mod link_with_quotes;
3840
mod markdown;
@@ -567,6 +569,38 @@ declare_clippy_lint! {
567569
"link reference defined in list item or quote"
568570
}
569571

572+
declare_clippy_lint! {
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.80.0"]
599+
pub DOC_COMMENT_DOUBLE_SPACE_LINEBREAK,
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_LINEBREAK
601636
]);
602637

603638
impl EarlyLintPass for Documentation {
@@ -737,6 +772,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
737772
return None;
738773
}
739774

775+
suspicious_doc_comments::check(cx, attrs);
776+
740777
let (fragments, _) = attrs_to_doc_fragments(
741778
attrs.iter().filter_map(|attr| {
742779
if attr.doc_str_and_comment_kind().is_none() || attr.span().in_external_macro(cx.sess().source_map()) {
@@ -894,6 +931,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
894931
let mut paragraph_range = 0..0;
895932
let mut code_level = 0;
896933
let mut blockquote_level = 0;
934+
let mut collected_breaks: Vec<Span> = Vec::new();
897935
let mut is_first_paragraph = true;
898936

899937
let mut containers = Vec::new();
@@ -1069,6 +1107,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
10691107
&containers[..],
10701108
);
10711109
}
1110+
1111+
if let Some(span) = fragments.span(cx, range.clone())
1112+
&& !span.from_expansion()
1113+
&& let Some(snippet) = snippet_opt(cx, span)
1114+
&& !snippet.trim().starts_with('\\')
1115+
&& event == HardBreak {
1116+
collected_breaks.push(span);
1117+
}
10721118
},
10731119
Text(text) => {
10741120
paragraph_range.end = range.end;
@@ -1119,6 +1165,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
11191165
FootnoteReference(_) => {}
11201166
}
11211167
}
1168+
1169+
doc_comment_double_space_linebreak::check(cx, &collected_breaks);
1170+
11221171
headers
11231172
}
11241173

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

0 commit comments

Comments
 (0)