diff --git a/CHANGELOG.md b/CHANGELOG.md index 51441ab9fc0d..91608fb8d1e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5571,6 +5571,7 @@ Released 2018-09-13 [`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_include_without_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_include_without_cfg +[`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_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_code [`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 0834618499c7..586ee22d9ef7 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -138,6 +138,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO, crate::disallowed_types::DISALLOWED_TYPES_INFO, crate::doc::DOC_INCLUDE_WITHOUT_CFG_INFO, + crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAK_INFO, crate::doc::DOC_LAZY_CONTINUATION_INFO, crate::doc::DOC_LINK_CODE_INFO, crate::doc::DOC_LINK_WITH_QUOTES_INFO, diff --git a/clippy_lints/src/doc/doc_comment_double_space_linebreak.rs b/clippy_lints/src/doc/doc_comment_double_space_linebreak.rs new file mode 100644 index 000000000000..4c2475ad8d85 --- /dev/null +++ b/clippy_lints/src/doc/doc_comment_double_space_linebreak.rs @@ -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() +} diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index c742d6abb5c2..d36ef10f4b94 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -7,6 +7,7 @@ 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,6 +34,7 @@ use rustc_span::{Span, sym}; use std::ops::Range; use url::Url; +mod doc_comment_double_space_linebreak; mod include_in_doc_without_cfg; mod link_with_quotes; mod markdown; @@ -567,6 +569,38 @@ declare_clippy_lint! { "link reference defined in list item or quote" } +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, 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_LINEBREAK ]); impl EarlyLintPass for Documentation { @@ -737,6 +772,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ return None; } + suspicious_doc_comments::check(cx, attrs); + let (fragments, _) = attrs_to_doc_fragments( attrs.iter().filter_map(|attr| { 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, Range = Vec::new(); let mut is_first_paragraph = true; let mut containers = Vec::new(); @@ -1069,6 +1107,14 @@ fn check_doc<'a, Events: Iterator, Range { paragraph_range.end = range.end; @@ -1119,6 +1165,9 @@ fn check_doc<'a, Events: Iterator, Range {} } } + + doc_comment_double_space_linebreak::check(cx, &collected_breaks); + headers } diff --git a/tests/ui/doc/doc_comment_double_space_linebreak.fixed b/tests/ui/doc/doc_comment_double_space_linebreak.fixed new file mode 100644 index 000000000000..efcd56809b7e --- /dev/null +++ b/tests/ui/doc/doc_comment_double_space_linebreak.fixed @@ -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() {} diff --git a/tests/ui/doc/doc_comment_double_space_linebreak.rs b/tests/ui/doc/doc_comment_double_space_linebreak.rs new file mode 100644 index 000000000000..c64eedc24289 --- /dev/null +++ b/tests/ui/doc/doc_comment_double_space_linebreak.rs @@ -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() {} diff --git a/tests/ui/doc/doc_comment_double_space_linebreak.stderr b/tests/ui/doc/doc_comment_double_space_linebreak.stderr new file mode 100644 index 000000000000..4c77f8e15034 --- /dev/null +++ b/tests/ui/doc/doc_comment_double_space_linebreak.stderr @@ -0,0 +1,76 @@ +error: doc comment uses two spaces for a hard line break + --> tests/ui/doc/doc_comment_double_space_linebreak.rs:7:43 + | +LL | //! Should warn on double space linebreaks + | ___________________________________________^ +LL | | //! in file/module doc comment + | |____^ + | + = note: `-D clippy::doc-comment-double-space-linebreak` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_comment_double_space_linebreak)]` +help: replace this double space with a backslash + | +LL ~ //! Should warn on double space linebreaks\ +LL ~ //! in file/module doc comment + | + +error: doc comment uses two spaces for a hard line break + --> tests/ui/doc/doc_comment_double_space_linebreak.rs:35:51 + | +LL | /// Should warn when doc comment uses double space + | ___________________________________________________^ +LL | | /// as a line-break, even when there are multiple +LL | | /// in a row + | |____^ + | +help: replace this double space with a backslash + | +LL ~ /// Should warn when doc comment uses double space\ +LL ~ /// as a line-break, even when there are multiple\ +LL ~ /// in a row + | + +error: doc comment uses two spaces for a hard line break + --> tests/ui/doc/doc_comment_double_space_linebreak.rs:44:12 + | +LL | /// 🌹 are πŸŸ₯ + | ______________^ +LL | | /// 🌷 are 🟦 +LL | | /// πŸ“Ž is 😎 +LL | | /// and so are 🫡 +LL | | /// (hopefully no formatting weirdness linting this) + | |____^ + | +help: replace this double space with a backslash + | +LL ~ /// 🌹 are πŸŸ₯\ +LL ~ /// 🌷 are 🟦\ +LL ~ /// πŸ“Ž is 😎\ +LL ~ /// and so are 🫡\ +LL ~ /// (hopefully no formatting weirdness linting this) + | + +error: doc comment uses two spaces for a hard line break + --> tests/ui/doc/doc_comment_double_space_linebreak.rs:86:16 + | +LL | /// here we mix + | ________________^ +LL | | /// double spaces\ +LL | | /// and also +LL | | /// adding backslash\ +LL | | /// to some of them +LL | | /// to see how that looks + | |____^ + | +help: replace this double space with a backslash + | +LL ~ /// here we mix\ +LL ~ /// double spaces\ +LL ~ /// and also\ +LL ~ /// adding backslash\ +LL ~ /// to some of them\ +LL ~ /// to see how that looks + | + +error: aborting due to 4 previous errors +