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,38 @@ 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 not 
427+      /// too long. 
428+      /// 
429+      /// ### Why is this bad? 
430+      /// Documentation will show the first paragraph of the doscstring in the summary page of a 
431+      /// module, so having a nice, short summary in the first paragraph is part of writing good docs. 
432+      /// 
433+      /// ### Example 
434+      /// ```no_run 
435+      /// /// A very short summary. 
436+      /// /// A much longer explanation that goes into a lot more detail about 
437+      /// /// how the thing works, possibly with doclinks and so one, 
438+      /// /// and probably spanning a many rows. 
439+      /// struct Foo {} 
440+      /// ``` 
441+      /// Use instead: 
442+      /// ```no_run 
443+      /// /// A very short summary. 
444+      /// /// 
445+      /// /// A much longer explanation that goes into a lot more detail about 
446+      /// /// how the thing works, possibly with doclinks and so one, 
447+      /// /// and probably spanning a many rows. 
448+      /// struct Foo {} 
449+      /// ``` 
450+      #[ clippy:: version = "1.81.0" ] 
451+     pub  TOO_LONG_FIRST_DOC_PARAGRAPH , 
452+     style, 
453+     "ensure that the first line of a documentation paragraph isn't too long" 
454+ } 
455+ 
423456#[ derive( Clone ) ]  
424457pub  struct  Documentation  { 
425458    valid_idents :  FxHashSet < String > , 
@@ -447,6 +480,7 @@ impl_lint_pass!(Documentation => [
447480    SUSPICIOUS_DOC_COMMENTS , 
448481    EMPTY_DOCS , 
449482    DOC_LAZY_CONTINUATION , 
483+     TOO_LONG_FIRST_DOC_PARAGRAPH , 
450484] ) ; 
451485
452486impl < ' tcx >  LateLintPass < ' tcx >  for  Documentation  { 
@@ -456,39 +490,44 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
456490        } ; 
457491
458492        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 ( 
493+             Node :: Item ( item)  => { 
494+                 too_long_first_doc_paragraph:: check ( cx,  attrs,  item. kind ,  headers. first_paragraph_len ) ; 
495+                 match  item. kind  { 
496+                     ItemKind :: Fn ( sig,  _,  body_id)  => { 
497+                         if  !( is_entrypoint_fn ( cx,  item. owner_id . to_def_id ( ) ) 
498+                             || in_external_macro ( cx. tcx . sess ,  item. span ) ) 
499+                         { 
500+                             let  body = cx. tcx . hir ( ) . body ( body_id) ; 
501+ 
502+                             let  panic_info = FindPanicUnwrap :: find_span ( cx,  cx. tcx . typeck ( item. owner_id ) ,  body. value ) ; 
503+                             missing_headers:: check ( 
504+                                 cx, 
505+                                 item. owner_id , 
506+                                 sig, 
507+                                 headers, 
508+                                 Some ( body_id) , 
509+                                 panic_info, 
510+                                 self . check_private_items , 
511+                             ) ; 
512+                         } 
513+                     } , 
514+                     ItemKind :: Trait ( _,  unsafety,  ..)  => match  ( headers. safety ,  unsafety)  { 
515+                         ( false ,  Safety :: Unsafe )  => span_lint ( 
466516                            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-                     ) , 
517+                             MISSING_SAFETY_DOC , 
518+                             cx. tcx . def_span ( item. owner_id ) , 
519+                             "docs for unsafe trait missing `# Safety` section" , 
520+                         ) , 
521+                         ( true ,  Safety :: Safe )  => span_lint ( 
522+                             cx, 
523+                             UNNECESSARY_SAFETY_DOC , 
524+                             cx. tcx . def_span ( item. owner_id ) , 
525+                             "docs for safe trait have unnecessary `# Safety` section" , 
526+                         ) , 
527+                         _ => ( ) , 
528+                     } , 
489529                    _ => ( ) , 
490-                 } , 
491-                 _ => ( ) , 
530+                 } 
492531            } , 
493532            Node :: TraitItem ( trait_item)  => { 
494533                if  let  TraitItemKind :: Fn ( sig,  ..)  = trait_item. kind 
@@ -546,6 +585,7 @@ struct DocHeaders {
546585    safety :  bool , 
547586    errors :  bool , 
548587    panics :  bool , 
588+     first_paragraph_len :  usize , 
549589} 
550590
551591/// Does some pre-processing on raw, desugared `#[doc]` attributes such as parsing them and 
@@ -585,8 +625,9 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
585625        acc
586626    } ) ; 
587627    doc. pop ( ) ; 
628+     let  doc = doc. trim ( ) ; 
588629
589-     if  doc. trim ( ) . is_empty ( )  { 
630+     if  doc. is_empty ( )  { 
590631        if  let  Some ( span)  = span_of_fragments ( & fragments)  { 
591632            span_lint_and_help ( 
592633                cx, 
@@ -610,7 +651,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
610651        cx, 
611652        valid_idents, 
612653        parser. into_offset_iter ( ) , 
613-         & doc, 
654+         doc, 
614655        Fragments  { 
615656            fragments :  & fragments, 
616657            doc :  & doc, 
@@ -652,6 +693,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
652693    let  mut  paragraph_range = 0 ..0 ; 
653694    let  mut  code_level = 0 ; 
654695    let  mut  blockquote_level = 0 ; 
696+     let  mut  is_first_paragraph = true ; 
655697
656698    let  mut  containers = Vec :: new ( ) ; 
657699
@@ -719,6 +761,10 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
719761                } 
720762                ticks_unbalanced = false ; 
721763                paragraph_range = range; 
764+                 if  is_first_paragraph { 
765+                     headers. first_paragraph_len  = doc[ paragraph_range. clone ( ) ] . chars ( ) . count ( ) ; 
766+                     is_first_paragraph = false ; 
767+                 } 
722768            } , 
723769            End ( Heading ( _,  _,  _)  | Paragraph  | Item )  => { 
724770                if  let  End ( Heading ( _,  _,  _) )  = event { 
0 commit comments