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 : 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,50 @@ 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 (
497+ cx,
498+ item,
499+ attrs,
500+ headers. first_paragraph_len ,
501+ self . check_private_items ,
502+ ) ;
503+ match item. kind {
504+ ItemKind :: Fn ( sig, _, body_id) => {
505+ if !( is_entrypoint_fn ( cx, item. owner_id . to_def_id ( ) )
506+ || in_external_macro ( cx. tcx . sess , item. span ) )
507+ {
508+ let body = cx. tcx . hir ( ) . body ( body_id) ;
509+
510+ let panic_info = FindPanicUnwrap :: find_span ( cx, cx. tcx . typeck ( item. owner_id ) , body. value ) ;
511+ missing_headers:: check (
512+ cx,
513+ item. owner_id ,
514+ sig,
515+ headers,
516+ Some ( body_id) ,
517+ panic_info,
518+ self . check_private_items ,
519+ ) ;
520+ }
521+ } ,
522+ ItemKind :: Trait ( _, unsafety, ..) => match ( headers. safety , unsafety) {
523+ ( false , Safety :: Unsafe ) => span_lint (
467524 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- ) ,
525+ MISSING_SAFETY_DOC ,
526+ cx. tcx . def_span ( item. owner_id ) ,
527+ "docs for unsafe trait missing `# Safety` section" ,
528+ ) ,
529+ ( true , Safety :: Safe ) => span_lint (
530+ cx,
531+ UNNECESSARY_SAFETY_DOC ,
532+ cx. tcx . def_span ( item. owner_id ) ,
533+ "docs for safe trait have unnecessary `# Safety` section" ,
534+ ) ,
535+ _ => ( ) ,
536+ } ,
490537 _ => ( ) ,
491- } ,
492- _ => ( ) ,
538+ }
493539 } ,
494540 Node :: TraitItem ( trait_item) => {
495541 if let TraitItemKind :: Fn ( sig, ..) = trait_item. kind
@@ -547,6 +593,7 @@ struct DocHeaders {
547593 safety : bool ,
548594 errors : bool ,
549595 panics : bool ,
596+ first_paragraph_len : usize ,
550597}
551598
552599/// Does some pre-processing on raw, desugared `#[doc]` attributes such as parsing them and
@@ -653,6 +700,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
653700 let mut paragraph_range = 0 ..0 ;
654701 let mut code_level = 0 ;
655702 let mut blockquote_level = 0 ;
703+ let mut is_first_paragraph = true ;
656704
657705 let mut containers = Vec :: new ( ) ;
658706
@@ -720,6 +768,10 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
720768 }
721769 ticks_unbalanced = false ;
722770 paragraph_range = range;
771+ if is_first_paragraph {
772+ headers. first_paragraph_len = doc[ paragraph_range. clone ( ) ] . chars ( ) . count ( ) ;
773+ is_first_paragraph = false ;
774+ }
723775 } ,
724776 End ( TagEnd :: Heading ( _) | TagEnd :: Paragraph | TagEnd :: Item ) => {
725777 if let End ( TagEnd :: Heading ( _) ) = event {
0 commit comments