11mod  lazy_continuation; 
2+ mod  too_long_first_doc_paragraph; 
23use  clippy_utils:: attrs:: is_doc_hidden; 
34use  clippy_utils:: diagnostics:: { span_lint,  span_lint_and_help} ; 
45use  clippy_utils:: macros:: { is_panic,  root_macro_call_first_node} ; 
@@ -420,6 +421,37 @@ declare_clippy_lint! {
420421    "require every line of a paragraph to be indented and marked" 
421422} 
422423
424+ declare_clippy_lint !  { 
425+     /// ### What it does 
426+      /// Checks if the first line in the documentation of items listed in module page is too long. 
427+      /// 
428+      /// ### Why is this bad? 
429+      /// Documentation will show the first paragraph of the doscstring in the summary page of a 
430+      /// module, so having a nice, short summary in the first paragraph is part of writing good docs. 
431+      /// 
432+      /// ### Example 
433+      /// ```no_run 
434+      /// /// A very short summary. 
435+      /// /// A much longer explanation that goes into a lot more detail about 
436+      /// /// how the thing works, possibly with doclinks and so one, 
437+      /// /// and probably spanning a many rows. 
438+      /// struct Foo {} 
439+      /// ``` 
440+      /// Use instead: 
441+      /// ```no_run 
442+      /// /// A very short summary. 
443+      /// /// 
444+      /// /// A much longer explanation that goes into a lot more detail about 
445+      /// /// how the thing works, possibly with doclinks and so one, 
446+      /// /// and probably spanning a many rows. 
447+      /// struct Foo {} 
448+      /// ``` 
449+      #[ clippy:: version = "1.81.0" ] 
450+     pub  TOO_LONG_FIRST_DOC_PARAGRAPH , 
451+     style, 
452+     "ensure that the first line of a documentation paragraph isn't too long" 
453+ } 
454+ 
423455#[ derive( Clone ) ]  
424456pub  struct  Documentation  { 
425457    valid_idents :  FxHashSet < String > , 
@@ -447,6 +479,7 @@ impl_lint_pass!(Documentation => [
447479    SUSPICIOUS_DOC_COMMENTS , 
448480    EMPTY_DOCS , 
449481    DOC_LAZY_CONTINUATION , 
482+     TOO_LONG_FIRST_DOC_PARAGRAPH , 
450483] ) ; 
451484
452485impl < ' tcx >  LateLintPass < ' tcx >  for  Documentation  { 
@@ -456,39 +489,44 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
456489        } ; 
457490
458491        match  cx. tcx . hir_node ( cx. last_node_with_lint_attrs )  { 
459-             Node :: Item ( item)  => match  item. kind  { 
460-                 ItemKind :: Fn ( sig,  _,  body_id)  => { 
461-                     if  !( is_entrypoint_fn ( cx,  item. owner_id . to_def_id ( ) )  || in_external_macro ( cx. tcx . sess ,  item. span ) )  { 
462-                         let  body = cx. tcx . hir ( ) . body ( body_id) ; 
463- 
464-                         let  panic_info = FindPanicUnwrap :: find_span ( cx,  cx. tcx . typeck ( item. owner_id ) ,  body. value ) ; 
465-                         missing_headers:: check ( 
492+             Node :: Item ( item)  => { 
493+                 too_long_first_doc_paragraph:: check ( cx,  attrs,  item. kind ,  headers. first_paragraph_len ) ; 
494+                 match  item. kind  { 
495+                     ItemKind :: Fn ( sig,  _,  body_id)  => { 
496+                         if  !( is_entrypoint_fn ( cx,  item. owner_id . to_def_id ( ) ) 
497+                             || in_external_macro ( cx. tcx . sess ,  item. span ) ) 
498+                         { 
499+                             let  body = cx. tcx . hir ( ) . body ( body_id) ; 
500+ 
501+                             let  panic_info = FindPanicUnwrap :: find_span ( cx,  cx. tcx . typeck ( item. owner_id ) ,  body. value ) ; 
502+                             missing_headers:: check ( 
503+                                 cx, 
504+                                 item. owner_id , 
505+                                 sig, 
506+                                 headers, 
507+                                 Some ( body_id) , 
508+                                 panic_info, 
509+                                 self . check_private_items , 
510+                             ) ; 
511+                         } 
512+                     } , 
513+                     ItemKind :: Trait ( _,  unsafety,  ..)  => match  ( headers. safety ,  unsafety)  { 
514+                         ( false ,  Safety :: Unsafe )  => span_lint ( 
466515                            cx, 
467-                             item. owner_id , 
468-                             sig, 
469-                             headers, 
470-                             Some ( body_id) , 
471-                             panic_info, 
472-                             self . check_private_items , 
473-                         ) ; 
474-                     } 
475-                 } , 
476-                 ItemKind :: Trait ( _,  unsafety,  ..)  => match  ( headers. safety ,  unsafety)  { 
477-                     ( false ,  Safety :: Unsafe )  => span_lint ( 
478-                         cx, 
479-                         MISSING_SAFETY_DOC , 
480-                         cx. tcx . def_span ( item. owner_id ) , 
481-                         "docs for unsafe trait missing `# Safety` section" , 
482-                     ) , 
483-                     ( true ,  Safety :: Safe )  => span_lint ( 
484-                         cx, 
485-                         UNNECESSARY_SAFETY_DOC , 
486-                         cx. tcx . def_span ( item. owner_id ) , 
487-                         "docs for safe trait have unnecessary `# Safety` section" , 
488-                     ) , 
516+                             MISSING_SAFETY_DOC , 
517+                             cx. tcx . def_span ( item. owner_id ) , 
518+                             "docs for unsafe trait missing `# Safety` section" , 
519+                         ) , 
520+                         ( true ,  Safety :: Safe )  => span_lint ( 
521+                             cx, 
522+                             UNNECESSARY_SAFETY_DOC , 
523+                             cx. tcx . def_span ( item. owner_id ) , 
524+                             "docs for safe trait have unnecessary `# Safety` section" , 
525+                         ) , 
526+                         _ => ( ) , 
527+                     } , 
489528                    _ => ( ) , 
490-                 } , 
491-                 _ => ( ) , 
529+                 } 
492530            } , 
493531            Node :: TraitItem ( trait_item)  => { 
494532                if  let  TraitItemKind :: Fn ( sig,  ..)  = trait_item. kind 
@@ -546,6 +584,7 @@ struct DocHeaders {
546584    safety :  bool , 
547585    errors :  bool , 
548586    panics :  bool , 
587+     first_paragraph_len :  usize , 
549588} 
550589
551590/// Does some pre-processing on raw, desugared `#[doc]` attributes such as parsing them and 
@@ -585,8 +624,9 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
585624        acc
586625    } ) ; 
587626    doc. pop ( ) ; 
627+     let  doc = doc. trim ( ) ; 
588628
589-     if  doc. trim ( ) . is_empty ( )  { 
629+     if  doc. is_empty ( )  { 
590630        if  let  Some ( span)  = span_of_fragments ( & fragments)  { 
591631            span_lint_and_help ( 
592632                cx, 
@@ -610,7 +650,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
610650        cx, 
611651        valid_idents, 
612652        parser. into_offset_iter ( ) , 
613-         & doc, 
653+         doc, 
614654        Fragments  { 
615655            fragments :  & fragments, 
616656            doc :  & doc, 
@@ -652,6 +692,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
652692    let  mut  paragraph_range = 0 ..0 ; 
653693    let  mut  code_level = 0 ; 
654694    let  mut  blockquote_level = 0 ; 
695+     let  mut  is_first_paragraph = true ; 
655696
656697    let  mut  containers = Vec :: new ( ) ; 
657698
@@ -719,6 +760,10 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
719760                } 
720761                ticks_unbalanced = false ; 
721762                paragraph_range = range; 
763+                 if  is_first_paragraph { 
764+                     headers. first_paragraph_len  = doc[ paragraph_range. clone ( ) ] . chars ( ) . count ( ) ; 
765+                     is_first_paragraph = false ; 
766+                 } 
722767            } , 
723768            End ( Heading ( _,  _,  _)  | Paragraph  | Item )  => { 
724769                if  let  End ( Heading ( _,  _,  _) )  = event { 
0 commit comments