@@ -7,6 +7,7 @@ use clippy_config::Conf;
77use clippy_utils:: attrs:: is_doc_hidden;
88use clippy_utils:: diagnostics:: { span_lint, span_lint_and_help, span_lint_and_then} ;
99use clippy_utils:: macros:: { is_panic, root_macro_call_first_node} ;
10+ use clippy_utils:: source:: snippet;
1011use clippy_utils:: ty:: is_type_diagnostic_item;
1112use clippy_utils:: visitors:: Visitable ;
1213use clippy_utils:: { is_entrypoint_fn, is_trait_impl_item, method_chain_args} ;
@@ -29,12 +30,14 @@ use rustc_resolve::rustdoc::{
2930} ;
3031use rustc_session:: impl_lint_pass;
3132use rustc_span:: edition:: Edition ;
32- use rustc_span:: { Span , sym} ;
33+ use rustc_span:: { sym, Span } ;
34+ use std:: borrow:: Cow ;
3335use std:: ops:: Range ;
3436use url:: Url ;
3537
3638mod empty_line_after;
3739mod include_in_doc_without_cfg;
40+ mod doc_comment_double_space_linebreak;
3841mod link_with_quotes;
3942mod markdown;
4043mod missing_headers;
@@ -622,6 +625,38 @@ declare_clippy_lint! {
622625 "link reference defined in list item or quote"
623626}
624627
628+ declare_clippy_lint ! {
629+ /// Detects doc comment linebreaks that use double spaces to separate lines, instead of back-slash (\).
630+ ///
631+ /// ### Why is this bad?
632+ /// Double spaces, when used as doc comment linebreaks, can be difficult to see, and may
633+ /// accidentally be removed during automatic fofmatting or manual refactoring. The use of a back-slash (\)
634+ /// is clearer in this regard.
635+ ///
636+ /// ### Example
637+ /// The two dots in this example represent a double space.
638+ /// ```no_run
639+ /// /// This command takes two numbers as inputs and..
640+ /// /// adds them together, and then returns the result.
641+ /// fn add(l: i32, r: i32) -> i32 {
642+ /// l + r
643+ /// }
644+ /// ``````
645+ ///
646+ /// Use instead:
647+ /// ```no_run
648+ /// /// This command takes two numbers as inputs and \
649+ /// /// adds them together, and then returns the result.
650+ /// fn add(l: i32, r: i32) -> i32 {
651+ /// l + r
652+ /// }
653+ /// ```
654+ #[ clippy:: version = "1.80.0" ]
655+ pub DOC_COMMENT_DOUBLE_SPACE_LINEBREAK ,
656+ restriction,
657+ "double-space used for doc comment linebreak instead of `\\ `"
658+ }
659+
625660pub struct Documentation {
626661 valid_idents : FxHashSet < String > ,
627662 check_private_items : bool ,
@@ -654,6 +689,7 @@ impl_lint_pass!(Documentation => [
654689 EMPTY_LINE_AFTER_DOC_COMMENTS ,
655690 TOO_LONG_FIRST_DOC_PARAGRAPH ,
656691 DOC_INCLUDE_WITHOUT_CFG ,
692+ DOC_COMMENT_DOUBLE_SPACE_LINEBREAK
657693] ) ;
658694
659695impl < ' tcx > LateLintPass < ' tcx > for Documentation {
@@ -788,6 +824,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
788824 return None ;
789825 }
790826
827+ suspicious_doc_comments:: check ( cx, attrs) ;
828+
791829 let ( fragments, _) = attrs_to_doc_fragments (
792830 attrs. iter ( ) . filter_map ( |attr| {
793831 if attr. span . in_external_macro ( cx. sess ( ) . source_map ( ) ) {
@@ -870,8 +908,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
870908 let mut paragraph_range = 0 ..0 ;
871909 let mut code_level = 0 ;
872910 let mut blockquote_level = 0 ;
873- let mut is_first_paragraph = true ;
874-
911+ let mut collected_breaks: Vec < ( Span , ( Span , CowStr < ' _ > ) , Cow < ' _ , str > ) > = Vec :: new ( ) ;
875912 let mut containers = Vec :: new ( ) ;
876913
877914 let mut events = events. peekable ( ) ;
@@ -1044,8 +1081,20 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
10441081 & containers[ ..] ,
10451082 ) ;
10461083 }
1084+
1085+ if let Some ( span) = fragments. span ( cx, range. clone ( ) )
1086+ && let Some ( ref p) = prev_text
1087+ && let snippet = snippet ( cx, span, ".." )
1088+ && !snippet. trim ( ) . starts_with ( "\\ " )
1089+ && event == HardBreak {
1090+ collected_breaks. push ( ( span, p. clone ( ) , snippet) ) ;
1091+ prev_text = None ;
1092+ }
10471093 } ,
10481094 Text ( text) => {
1095+ if let Some ( span) = fragments. span ( cx, range. clone ( ) ) {
1096+ prev_text = Some ( ( span, text. clone ( ) ) ) ;
1097+ }
10491098 paragraph_range. end = range. end ;
10501099 let range_ = range. clone ( ) ;
10511100 ticks_unbalanced |= text. contains ( '`' )
@@ -1094,6 +1143,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
10941143 FootnoteReference ( _) => { }
10951144 }
10961145 }
1146+
1147+ doc_comment_double_space_linebreak:: check ( cx, collected_breaks) ;
1148+
10971149 headers
10981150}
10991151
0 commit comments