1
Fork 0

new lint: doc_comment_double_space_linebreaks (#12876)

Fixes https://github.com/rust-lang/rust-clippy/issues/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`
This commit is contained in:
Fridtjof Stoldt 2025-03-16 14:44:02 +00:00 committed by GitHub
commit 3c7dfacf44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 331 additions and 3 deletions

View file

@ -5570,6 +5570,7 @@ Released 2018-09-13
[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
[`doc_comment_double_space_linebreaks`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreaks
[`doc_include_without_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_include_without_cfg
[`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
[`doc_link_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_code

View file

@ -137,6 +137,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::disallowed_names::DISALLOWED_NAMES_INFO,
crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO,
crate::disallowed_types::DISALLOWED_TYPES_INFO,
crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS_INFO,
crate::doc::DOC_INCLUDE_WITHOUT_CFG_INFO,
crate::doc::DOC_LAZY_CONTINUATION_INFO,
crate::doc::DOC_LINK_CODE_INFO,

View file

@ -0,0 +1,33 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::Applicability;
use rustc_lint::LateContext;
use rustc_span::{BytePos, Span};
use super::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS;
pub fn check(cx: &LateContext<'_>, collected_breaks: &[Span]) {
if collected_breaks.is_empty() {
return;
}
let breaks: Vec<_> = collected_breaks
.iter()
.map(|span| span.with_hi(span.lo() + BytePos(2)))
.collect();
span_lint_and_then(
cx,
DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS,
breaks.clone(),
"doc comment uses two spaces for a hard line break",
|diag| {
let suggs: Vec<_> = breaks.iter().map(|span| (*span, "\\".to_string())).collect();
diag.tool_only_multipart_suggestion(
"replace this double space with a backslash:",
suggs,
Applicability::MachineApplicable,
);
diag.help("replace this double space with a backslash: `\\`");
},
);
}

View file

@ -1,12 +1,10 @@
#![allow(clippy::lint_without_lint_pass)]
mod lazy_continuation;
mod too_long_first_doc_paragraph;
use clippy_config::Conf;
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::Visitable;
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
@ -33,12 +31,15 @@ use rustc_span::{Span, sym};
use std::ops::Range;
use url::Url;
mod doc_comment_double_space_linebreaks;
mod include_in_doc_without_cfg;
mod lazy_continuation;
mod link_with_quotes;
mod markdown;
mod missing_headers;
mod needless_doctest_main;
mod suspicious_doc_comments;
mod too_long_first_doc_paragraph;
declare_clippy_lint! {
/// ### What it does
@ -567,6 +568,39 @@ declare_clippy_lint! {
"link reference defined in list item or quote"
}
declare_clippy_lint! {
/// ### What it does
/// Detects doc comment linebreaks that use double spaces to separate lines, instead of back-slash (`\`).
///
/// ### Why is this bad?
/// Double spaces, when used as doc comment linebreaks, can be difficult to see, and may
/// accidentally be removed during automatic formatting or manual refactoring. The use of a back-slash (`\`)
/// is clearer in this regard.
///
/// ### Example
/// The two replacement dots in this example represent a double space.
/// ```no_run
/// /// This command takes two numbers as inputs and··
/// /// adds them together, and then returns the result.
/// fn add(l: i32, r: i32) -> i32 {
/// l + r
/// }
/// ```
///
/// Use instead:
/// ```no_run
/// /// This command takes two numbers as inputs and\
/// /// adds them together, and then returns the result.
/// fn add(l: i32, r: i32) -> i32 {
/// l + r
/// }
/// ```
#[clippy::version = "1.87.0"]
pub DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS,
pedantic,
"double-space used for doc comment linebreak instead of `\\`"
}
pub struct Documentation {
valid_idents: FxHashSet<String>,
check_private_items: bool,
@ -598,6 +632,7 @@ impl_lint_pass!(Documentation => [
DOC_OVERINDENTED_LIST_ITEMS,
TOO_LONG_FIRST_DOC_PARAGRAPH,
DOC_INCLUDE_WITHOUT_CFG,
DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS
]);
impl EarlyLintPass for Documentation {
@ -894,6 +929,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
let mut paragraph_range = 0..0;
let mut code_level = 0;
let mut blockquote_level = 0;
let mut collected_breaks: Vec<Span> = Vec::new();
let mut is_first_paragraph = true;
let mut containers = Vec::new();
@ -1069,6 +1105,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
&containers[..],
);
}
if let Some(span) = fragments.span(cx, range.clone())
&& !span.from_expansion()
&& let Some(snippet) = snippet_opt(cx, span)
&& !snippet.trim().starts_with('\\')
&& event == HardBreak {
collected_breaks.push(span);
}
},
Text(text) => {
paragraph_range.end = range.end;
@ -1119,6 +1163,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
FootnoteReference(_) => {}
}
}
doc_comment_double_space_linebreaks::check(cx, &collected_breaks);
headers
}

View file

@ -0,0 +1,98 @@
#![feature(custom_inner_attributes)]
#![rustfmt::skip]
#![warn(clippy::doc_comment_double_space_linebreaks)]
#![allow(unused, clippy::empty_docs)]
//~v doc_comment_double_space_linebreaks
//! Should warn on double space linebreaks\
//! in file/module doc comment
/// Should not warn on single-line doc comments
fn single_line() {}
/// Should not warn on single-line doc comments
/// split across multiple lines
fn single_line_split() {}
// Should not warn on normal comments
// note: cargo fmt can remove double spaces from normal and block comments
// Should not warn on normal comments
// with double spaces at the end of a line
#[doc = "This is a doc attribute, which should not be linted"]
fn normal_comment() {
/*
Should not warn on block comments
*/
/*
Should not warn on block comments
with double space at the end of a line
*/
}
//~v doc_comment_double_space_linebreaks
/// Should warn when doc comment uses double space\
/// as a line-break, even when there are multiple\
/// in a row
fn double_space_doc_comment() {}
/// Should not warn when back-slash is used \
/// as a line-break
fn back_slash_doc_comment() {}
//~v doc_comment_double_space_linebreaks
/// 🌹 are 🟥\
/// 🌷 are 🟦\
/// 📎 is 😎\
/// and so are 🫵\
/// (hopefully no formatting weirdness linting this)
fn multi_byte_chars_tada() {}
macro_rules! macro_that_makes_function {
() => {
/// Shouldn't lint on this!
/// (hopefully)
fn my_macro_created_function() {}
}
}
macro_that_makes_function!();
// dont lint when its alone on a line
///
fn alone() {}
/// | First column | Second column |
/// | ------------ | ------------- |
/// | Not a line | break when |
/// | after a line | in a table |
fn table() {}
/// ```text
/// It's also not a hard line break if
/// there's two spaces at the end of a
/// line in a block code.
/// ```
fn codeblock() {}
/// It's also not a hard line break `if
/// there's` two spaces in the middle of inline code.
fn inline() {}
/// It's also not a hard line break [when](
/// https://example.com) in a URL.
fn url() {}
//~v doc_comment_double_space_linebreaks
/// here we mix\
/// double spaces\
/// and also\
/// adding backslash\
/// to some of them\
/// to see how that looks
fn mixed() {}
fn main() {}

View file

@ -0,0 +1,98 @@
#![feature(custom_inner_attributes)]
#![rustfmt::skip]
#![warn(clippy::doc_comment_double_space_linebreaks)]
#![allow(unused, clippy::empty_docs)]
//~v doc_comment_double_space_linebreaks
//! Should warn on double space linebreaks
//! in file/module doc comment
/// Should not warn on single-line doc comments
fn single_line() {}
/// Should not warn on single-line doc comments
/// split across multiple lines
fn single_line_split() {}
// Should not warn on normal comments
// note: cargo fmt can remove double spaces from normal and block comments
// Should not warn on normal comments
// with double spaces at the end of a line
#[doc = "This is a doc attribute, which should not be linted"]
fn normal_comment() {
/*
Should not warn on block comments
*/
/*
Should not warn on block comments
with double space at the end of a line
*/
}
//~v doc_comment_double_space_linebreaks
/// Should warn when doc comment uses double space
/// as a line-break, even when there are multiple
/// in a row
fn double_space_doc_comment() {}
/// Should not warn when back-slash is used \
/// as a line-break
fn back_slash_doc_comment() {}
//~v doc_comment_double_space_linebreaks
/// 🌹 are 🟥
/// 🌷 are 🟦
/// 📎 is 😎
/// and so are 🫵
/// (hopefully no formatting weirdness linting this)
fn multi_byte_chars_tada() {}
macro_rules! macro_that_makes_function {
() => {
/// Shouldn't lint on this!
/// (hopefully)
fn my_macro_created_function() {}
}
}
macro_that_makes_function!();
// dont lint when its alone on a line
///
fn alone() {}
/// | First column | Second column |
/// | ------------ | ------------- |
/// | Not a line | break when |
/// | after a line | in a table |
fn table() {}
/// ```text
/// It's also not a hard line break if
/// there's two spaces at the end of a
/// line in a block code.
/// ```
fn codeblock() {}
/// It's also not a hard line break `if
/// there's` two spaces in the middle of inline code.
fn inline() {}
/// It's also not a hard line break [when](
/// https://example.com) in a URL.
fn url() {}
//~v doc_comment_double_space_linebreaks
/// here we mix
/// double spaces\
/// and also
/// adding backslash\
/// to some of them
/// to see how that looks
fn mixed() {}
fn main() {}

View file

@ -0,0 +1,50 @@
error: doc comment uses two spaces for a hard line break
--> tests/ui/doc/doc_comment_double_space_linebreaks.rs:8:43
|
LL | //! Should warn on double space linebreaks
| ^^
|
= help: replace this double space with a backslash: `\`
= note: `-D clippy::doc-comment-double-space-linebreaks` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::doc_comment_double_space_linebreaks)]`
error: doc comment uses two spaces for a hard line break
--> tests/ui/doc/doc_comment_double_space_linebreaks.rs:37:51
|
LL | /// Should warn when doc comment uses double space
| ^^
LL | /// as a line-break, even when there are multiple
| ^^
|
= help: replace this double space with a backslash: `\`
error: doc comment uses two spaces for a hard line break
--> tests/ui/doc/doc_comment_double_space_linebreaks.rs:47:12
|
LL | /// 🌹 are 🟥
| ^^
LL | /// 🌷 are 🟦
| ^^
LL | /// 📎 is 😎
| ^^
LL | /// and so are 🫵
| ^^
|
= help: replace this double space with a backslash: `\`
error: doc comment uses two spaces for a hard line break
--> tests/ui/doc/doc_comment_double_space_linebreaks.rs:90:16
|
LL | /// here we mix
| ^^
LL | /// double spaces\
LL | /// and also
| ^^
LL | /// adding backslash\
LL | /// to some of them
| ^^
|
= help: replace this double space with a backslash: `\`
error: aborting due to 4 previous errors