1
1
mod lazy_continuation;
2
+ mod too_long_first_doc_paragraph;
2
3
use clippy_utils:: attrs:: is_doc_hidden;
3
4
use clippy_utils:: diagnostics:: { span_lint, span_lint_and_help} ;
4
5
use clippy_utils:: macros:: { is_panic, root_macro_call_first_node} ;
@@ -420,6 +421,37 @@ declare_clippy_lint! {
420
421
"require every line of a paragraph to be indented and marked"
421
422
}
422
423
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
+
423
455
#[ derive( Clone ) ]
424
456
pub struct Documentation {
425
457
valid_idents : FxHashSet < String > ,
@@ -447,6 +479,7 @@ impl_lint_pass!(Documentation => [
447
479
SUSPICIOUS_DOC_COMMENTS ,
448
480
EMPTY_DOCS ,
449
481
DOC_LAZY_CONTINUATION ,
482
+ TOO_LONG_FIRST_DOC_PARAGRAPH ,
450
483
] ) ;
451
484
452
485
impl < ' tcx > LateLintPass < ' tcx > for Documentation {
@@ -456,39 +489,44 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
456
489
} ;
457
490
458
491
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 (
466
515
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
+ } ,
489
528
_ => ( ) ,
490
- } ,
491
- _ => ( ) ,
529
+ }
492
530
} ,
493
531
Node :: TraitItem ( trait_item) => {
494
532
if let TraitItemKind :: Fn ( sig, ..) = trait_item. kind
@@ -546,6 +584,7 @@ struct DocHeaders {
546
584
safety : bool ,
547
585
errors : bool ,
548
586
panics : bool ,
587
+ first_paragraph_len : usize ,
549
588
}
550
589
551
590
/// 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: &[
585
624
acc
586
625
} ) ;
587
626
doc. pop ( ) ;
627
+ let doc = doc. trim ( ) ;
588
628
589
- if doc. trim ( ) . is_empty ( ) {
629
+ if doc. is_empty ( ) {
590
630
if let Some ( span) = span_of_fragments ( & fragments) {
591
631
span_lint_and_help (
592
632
cx,
@@ -610,7 +650,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
610
650
cx,
611
651
valid_idents,
612
652
parser. into_offset_iter ( ) ,
613
- & doc,
653
+ doc,
614
654
Fragments {
615
655
fragments : & fragments,
616
656
doc : & doc,
@@ -652,6 +692,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
652
692
let mut paragraph_range = 0 ..0 ;
653
693
let mut code_level = 0 ;
654
694
let mut blockquote_level = 0 ;
695
+ let mut is_first_paragraph = true ;
655
696
656
697
let mut containers = Vec :: new ( ) ;
657
698
@@ -719,6 +760,10 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
719
760
}
720
761
ticks_unbalanced = false ;
721
762
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
+ }
722
767
} ,
723
768
End ( Heading ( _, _, _) | Paragraph | Item ) => {
724
769
if let End ( Heading ( _, _, _) ) = event {
0 commit comments