Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new lint: doc_comment_double_space_linebreak #12876

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5351,6 +5351,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_linebreak`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreak
[`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes
[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,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_LINEBREAK_INFO,
crate::doc::DOC_LAZY_CONTINUATION_INFO,
crate::doc::DOC_LINK_WITH_QUOTES_INFO,
crate::doc::DOC_MARKDOWN_INFO,
Expand Down
41 changes: 41 additions & 0 deletions clippy_lints/src/doc/doc_comment_double_space_linebreak.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use rustc_errors::Applicability;
use rustc_lint::LateContext;
use rustc_span::Span;

use super::DOC_COMMENT_DOUBLE_SPACE_LINEBREAK;

pub fn check(cx: &LateContext<'_>, collected_breaks: &[Span]) {
let replacements: Vec<_> = collect_doc_replacements(cx, collected_breaks);

if let Some((&(lo_span, _), &(hi_span, _))) = replacements.first().zip(replacements.last()) {
span_lint_and_then(
cx,
DOC_COMMENT_DOUBLE_SPACE_LINEBREAK,
lo_span.to(hi_span),
"doc comment uses two spaces for a hard line break",
|diag| {
diag.multipart_suggestion(
"replace this double space with a backslash",
replacements,
Applicability::MachineApplicable,
);
},
);
}
}

fn collect_doc_replacements(cx: &LateContext<'_>, spans: &[Span]) -> Vec<(Span, String)> {
spans
.iter()
.map(|span| {
// we already made sure the snippet exists when collecting spans
let s = snippet_opt(cx, *span).expect("snippet was already validated to exist");
let after_newline = s.trim_start_matches(' ');

let new_comment = format!("\\{after_newline}");
(*span, new_comment)
})
.collect()
}
49 changes: 49 additions & 0 deletions clippy_lints/src/doc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clippy_config::Conf;
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
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};
Expand Down Expand Up @@ -32,6 +33,7 @@ use rustc_span::{sym, Span};
use std::ops::Range;
use url::Url;

mod doc_comment_double_space_linebreak;
mod empty_line_after;
mod link_with_quotes;
mod markdown;
Expand Down Expand Up @@ -532,6 +534,38 @@ declare_clippy_lint! {
"empty line after doc comments"
}

declare_clippy_lint! {
/// 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.80.0"]
pub DOC_COMMENT_DOUBLE_SPACE_LINEBREAK,
pedantic,
"double-space used for doc comment linebreak instead of `\\`"
}

pub struct Documentation {
valid_idents: FxHashSet<String>,
check_private_items: bool,
Expand Down Expand Up @@ -561,6 +595,7 @@ impl_lint_pass!(Documentation => [
EMPTY_LINE_AFTER_OUTER_ATTR,
EMPTY_LINE_AFTER_DOC_COMMENTS,
TOO_LONG_FIRST_DOC_PARAGRAPH,
DOC_COMMENT_DOUBLE_SPACE_LINEBREAK
]);

impl<'tcx> LateLintPass<'tcx> for Documentation {
Expand Down Expand Up @@ -694,6 +729,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
return None;
}

suspicious_doc_comments::check(cx, attrs);

let (fragments, _) = attrs_to_doc_fragments(
attrs.iter().filter_map(|attr| {
if in_external_macro(cx.sess(), attr.span) {
Expand Down Expand Up @@ -776,6 +813,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();
Expand Down Expand Up @@ -894,6 +932,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);
xFrednet marked this conversation as resolved.
Show resolved Hide resolved
}
},
Text(text) => {
paragraph_range.end = range.end;
Expand Down Expand Up @@ -943,6 +989,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
FootnoteReference(_) => {}
}
}

doc_comment_double_space_linebreak::check(cx, &collected_breaks);

headers
}

Expand Down
94 changes: 94 additions & 0 deletions tests/ui/doc/doc_comment_double_space_linebreak.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#![feature(custom_inner_attributes)]
#![rustfmt::skip]

#![warn(clippy::doc_comment_double_space_linebreak)]
#![allow(unused, clippy::empty_docs)]

//! 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
*/
}

/// 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() {}

/// 🌹 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() {}

/// here we mix\
/// double spaces\
/// and also\
/// adding backslash\
/// to some of them\
/// to see how that looks
fn mixed() {}

fn main() {}
94 changes: 94 additions & 0 deletions tests/ui/doc/doc_comment_double_space_linebreak.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#![feature(custom_inner_attributes)]
#![rustfmt::skip]

#![warn(clippy::doc_comment_double_space_linebreak)]
#![allow(unused, clippy::empty_docs)]

//! 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() {
Jacherr marked this conversation as resolved.
Show resolved Hide resolved
/*
Should not warn on block comments
*/

/*
Should not warn on block comments
with double space at the end of a line
*/
}

/// 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() {}

/// 🌹 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() {}

/// here we mix
/// double spaces\
/// and also
/// adding backslash\
/// to some of them
/// to see how that looks
fn mixed() {}

fn main() {}
Loading
Loading