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