@@ -5,6 +5,7 @@ use clippy_config::Conf;
55use clippy_utils:: attrs:: is_doc_hidden;
66use clippy_utils:: diagnostics:: { span_lint, span_lint_and_help} ;
77use clippy_utils:: macros:: { is_panic, root_macro_call_first_node} ;
8+ use clippy_utils:: source:: snippet;
89use clippy_utils:: ty:: is_type_diagnostic_item;
910use clippy_utils:: visitors:: Visitable ;
1011use clippy_utils:: { is_entrypoint_fn, is_trait_impl_item, method_chain_args} ;
@@ -28,11 +29,13 @@ use rustc_resolve::rustdoc::{
2829} ;
2930use rustc_session:: impl_lint_pass;
3031use rustc_span:: edition:: Edition ;
31- use rustc_span:: { Span , sym} ;
32+ use rustc_span:: { sym, Span } ;
33+ use std:: borrow:: Cow ;
3234use std:: ops:: Range ;
3335use url:: Url ;
3436
3537mod empty_line_after;
38+ mod doc_comment_double_space_linebreak;
3639mod link_with_quotes;
3740mod markdown;
3841mod missing_headers;
@@ -532,6 +535,38 @@ declare_clippy_lint! {
532535 "empty line after doc comments"
533536}
534537
538+ declare_clippy_lint ! {
539+ /// Detects doc comment linebreaks that use double spaces to separate lines, instead of back-slash (\).
540+ ///
541+ /// ### Why is this bad?
542+ /// Double spaces, when used as doc comment linebreaks, can be difficult to see, and may
543+ /// accidentally be removed during automatic fofmatting or manual refactoring. The use of a back-slash (\)
544+ /// is clearer in this regard.
545+ ///
546+ /// ### Example
547+ /// The two dots in this example represent a double space.
548+ /// ```no_run
549+ /// /// This command takes two numbers as inputs and..
550+ /// /// adds them together, and then returns the result.
551+ /// fn add(l: i32, r: i32) -> i32 {
552+ /// l + r
553+ /// }
554+ /// ``````
555+ ///
556+ /// Use instead:
557+ /// ```no_run
558+ /// /// This command takes two numbers as inputs and \
559+ /// /// adds them together, and then returns the result.
560+ /// fn add(l: i32, r: i32) -> i32 {
561+ /// l + r
562+ /// }
563+ /// ```
564+ #[ clippy:: version = "1.80.0" ]
565+ pub DOC_COMMENT_DOUBLE_SPACE_LINEBREAK ,
566+ restriction,
567+ "double-space used for doc comment linebreak instead of `\\ `"
568+ }
569+
535570pub struct Documentation {
536571 valid_idents : FxHashSet < String > ,
537572 check_private_items : bool ,
@@ -561,6 +596,7 @@ impl_lint_pass!(Documentation => [
561596 EMPTY_LINE_AFTER_OUTER_ATTR ,
562597 EMPTY_LINE_AFTER_DOC_COMMENTS ,
563598 TOO_LONG_FIRST_DOC_PARAGRAPH ,
599+ DOC_COMMENT_DOUBLE_SPACE_LINEBREAK
564600] ) ;
565601
566602impl < ' tcx > LateLintPass < ' tcx > for Documentation {
@@ -694,6 +730,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
694730 return None ;
695731 }
696732
733+ suspicious_doc_comments:: check ( cx, attrs) ;
734+
697735 let ( fragments, _) = attrs_to_doc_fragments (
698736 attrs. iter ( ) . filter_map ( |attr| {
699737 if in_external_macro ( cx. sess ( ) , attr. span ) {
@@ -776,8 +814,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
776814 let mut paragraph_range = 0 ..0 ;
777815 let mut code_level = 0 ;
778816 let mut blockquote_level = 0 ;
779- let mut is_first_paragraph = true ;
780-
817+ let mut collected_breaks: Vec < ( Span , ( Span , CowStr < ' _ > ) , Cow < ' _ , str > ) > = Vec :: new ( ) ;
781818 let mut containers = Vec :: new ( ) ;
782819
783820 let mut events = events. peekable ( ) ;
@@ -894,8 +931,20 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
894931 & containers[ ..] ,
895932 ) ;
896933 }
934+
935+ if let Some ( span) = fragments. span ( cx, range. clone ( ) )
936+ && let Some ( ref p) = prev_text
937+ && let snippet = snippet ( cx, span, ".." )
938+ && !snippet. trim ( ) . starts_with ( "\\ " )
939+ && event == HardBreak {
940+ collected_breaks. push ( ( span, p. clone ( ) , snippet) ) ;
941+ prev_text = None ;
942+ }
897943 } ,
898944 Text ( text) => {
945+ if let Some ( span) = fragments. span ( cx, range. clone ( ) ) {
946+ prev_text = Some ( ( span, text. clone ( ) ) ) ;
947+ }
899948 paragraph_range. end = range. end ;
900949 let range_ = range. clone ( ) ;
901950 ticks_unbalanced |= text. contains ( '`' )
@@ -943,6 +992,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
943992 FootnoteReference ( _) => { }
944993 }
945994 }
995+
996+ doc_comment_double_space_linebreak:: check ( cx, collected_breaks) ;
997+
946998 headers
947999}
9481000
0 commit comments