11mod lazy_continuation;
2+ mod too_long_first_doc_paragraph;
3+
24use clippy_config:: Conf ;
35use clippy_utils:: attrs:: is_doc_hidden;
46use clippy_utils:: diagnostics:: { span_lint, span_lint_and_help} ;
@@ -422,6 +424,38 @@ declare_clippy_lint! {
422424 "require every line of a paragraph to be indented and marked"
423425}
424426
427+ declare_clippy_lint ! {
428+ /// ### What it does
429+ /// Checks if the first line in the documentation of items listed in module page is too long.
430+ ///
431+ /// ### Why is this bad?
432+ /// Documentation will show the first paragraph of the doscstring in the summary page of a
433+ /// module, so having a nice, short summary in the first paragraph is part of writing good docs.
434+ ///
435+ /// ### Example
436+ /// ```no_run
437+ /// /// A very short summary.
438+ /// /// A much longer explanation that goes into a lot more detail about
439+ /// /// how the thing works, possibly with doclinks and so one,
440+ /// /// and probably spanning a many rows.
441+ /// struct Foo {}
442+ /// ```
443+ /// Use instead:
444+ /// ```no_run
445+ /// /// A very short summary.
446+ /// ///
447+ /// /// A much longer explanation that goes into a lot more detail about
448+ /// /// how the thing works, possibly with doclinks and so one,
449+ /// /// and probably spanning a many rows.
450+ /// struct Foo {}
451+ /// ```
452+ #[ clippy:: version = "1.81.0" ]
453+ pub TOO_LONG_FIRST_DOC_PARAGRAPH ,
454+ style,
455+ "ensure that the first line of a documentation paragraph isn't too long"
456+ }
457+
458+ #[ derive( Clone ) ]
425459pub struct Documentation {
426460 valid_idents : & ' static FxHashSet < String > ,
427461 check_private_items : bool ,
@@ -448,6 +482,7 @@ impl_lint_pass!(Documentation => [
448482 SUSPICIOUS_DOC_COMMENTS ,
449483 EMPTY_DOCS ,
450484 DOC_LAZY_CONTINUATION ,
485+ TOO_LONG_FIRST_DOC_PARAGRAPH ,
451486] ) ;
452487
453488impl < ' tcx > LateLintPass < ' tcx > for Documentation {
@@ -457,39 +492,44 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
457492 } ;
458493
459494 match cx. tcx . hir_node ( cx. last_node_with_lint_attrs ) {
460- Node :: Item ( item) => match item. kind {
461- ItemKind :: Fn ( sig, _, body_id) => {
462- if !( is_entrypoint_fn ( cx, item. owner_id . to_def_id ( ) ) || in_external_macro ( cx. tcx . sess , item. span ) ) {
463- let body = cx. tcx . hir ( ) . body ( body_id) ;
464-
465- let panic_info = FindPanicUnwrap :: find_span ( cx, cx. tcx . typeck ( item. owner_id ) , body. value ) ;
466- missing_headers:: check (
495+ Node :: Item ( item) => {
496+ too_long_first_doc_paragraph:: check ( cx, attrs, item. kind , headers. first_paragraph_len ) ;
497+ match item. kind {
498+ ItemKind :: Fn ( sig, _, body_id) => {
499+ if !( is_entrypoint_fn ( cx, item. owner_id . to_def_id ( ) )
500+ || in_external_macro ( cx. tcx . sess , item. span ) )
501+ {
502+ let body = cx. tcx . hir ( ) . body ( body_id) ;
503+
504+ let panic_info = FindPanicUnwrap :: find_span ( cx, cx. tcx . typeck ( item. owner_id ) , body. value ) ;
505+ missing_headers:: check (
506+ cx,
507+ item. owner_id ,
508+ sig,
509+ headers,
510+ Some ( body_id) ,
511+ panic_info,
512+ self . check_private_items ,
513+ ) ;
514+ }
515+ } ,
516+ ItemKind :: Trait ( _, unsafety, ..) => match ( headers. safety , unsafety) {
517+ ( false , Safety :: Unsafe ) => span_lint (
467518 cx,
468- item. owner_id ,
469- sig,
470- headers,
471- Some ( body_id) ,
472- panic_info,
473- self . check_private_items ,
474- ) ;
475- }
476- } ,
477- ItemKind :: Trait ( _, unsafety, ..) => match ( headers. safety , unsafety) {
478- ( false , Safety :: Unsafe ) => span_lint (
479- cx,
480- MISSING_SAFETY_DOC ,
481- cx. tcx . def_span ( item. owner_id ) ,
482- "docs for unsafe trait missing `# Safety` section" ,
483- ) ,
484- ( true , Safety :: Safe ) => span_lint (
485- cx,
486- UNNECESSARY_SAFETY_DOC ,
487- cx. tcx . def_span ( item. owner_id ) ,
488- "docs for safe trait have unnecessary `# Safety` section" ,
489- ) ,
519+ MISSING_SAFETY_DOC ,
520+ cx. tcx . def_span ( item. owner_id ) ,
521+ "docs for unsafe trait missing `# Safety` section" ,
522+ ) ,
523+ ( true , Safety :: Safe ) => span_lint (
524+ cx,
525+ UNNECESSARY_SAFETY_DOC ,
526+ cx. tcx . def_span ( item. owner_id ) ,
527+ "docs for safe trait have unnecessary `# Safety` section" ,
528+ ) ,
529+ _ => ( ) ,
530+ } ,
490531 _ => ( ) ,
491- } ,
492- _ => ( ) ,
532+ }
493533 } ,
494534 Node :: TraitItem ( trait_item) => {
495535 if let TraitItemKind :: Fn ( sig, ..) = trait_item. kind
@@ -547,6 +587,7 @@ struct DocHeaders {
547587 safety : bool ,
548588 errors : bool ,
549589 panics : bool ,
590+ first_paragraph_len : usize ,
550591}
551592
552593/// Does some pre-processing on raw, desugared `#[doc]` attributes such as parsing them and
@@ -586,8 +627,9 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
586627 acc
587628 } ) ;
588629 doc. pop ( ) ;
630+ let doc = doc. trim ( ) ;
589631
590- if doc. trim ( ) . is_empty ( ) {
632+ if doc. is_empty ( ) {
591633 if let Some ( span) = span_of_fragments ( & fragments) {
592634 span_lint_and_help (
593635 cx,
@@ -611,7 +653,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
611653 cx,
612654 valid_idents,
613655 parser. into_offset_iter ( ) ,
614- & doc,
656+ doc,
615657 Fragments {
616658 fragments : & fragments,
617659 doc : & doc,
@@ -653,6 +695,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
653695 let mut paragraph_range = 0 ..0 ;
654696 let mut code_level = 0 ;
655697 let mut blockquote_level = 0 ;
698+ let mut is_first_paragraph = true ;
656699
657700 let mut containers = Vec :: new ( ) ;
658701
@@ -720,6 +763,10 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
720763 }
721764 ticks_unbalanced = false ;
722765 paragraph_range = range;
766+ if is_first_paragraph {
767+ headers. first_paragraph_len = doc[ paragraph_range. clone ( ) ] . chars ( ) . count ( ) ;
768+ is_first_paragraph = false ;
769+ }
723770 } ,
724771 End ( TagEnd :: Heading ( _) | TagEnd :: Paragraph | TagEnd :: Item ) => {
725772 if let End ( TagEnd :: Heading ( _) ) = event {
0 commit comments