1
1
mod lazy_continuation;
2
+ mod too_long_first_doc_paragraph;
3
+
2
4
use clippy_config:: Conf ;
3
5
use clippy_utils:: attrs:: is_doc_hidden;
4
6
use clippy_utils:: diagnostics:: { span_lint, span_lint_and_help} ;
@@ -422,6 +424,38 @@ declare_clippy_lint! {
422
424
"require every line of a paragraph to be indented and marked"
423
425
}
424
426
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 ) ]
425
459
pub struct Documentation {
426
460
valid_idents : & ' static FxHashSet < String > ,
427
461
check_private_items : bool ,
@@ -448,6 +482,7 @@ impl_lint_pass!(Documentation => [
448
482
SUSPICIOUS_DOC_COMMENTS ,
449
483
EMPTY_DOCS ,
450
484
DOC_LAZY_CONTINUATION ,
485
+ TOO_LONG_FIRST_DOC_PARAGRAPH ,
451
486
] ) ;
452
487
453
488
impl < ' tcx > LateLintPass < ' tcx > for Documentation {
@@ -457,39 +492,44 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
457
492
} ;
458
493
459
494
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 (
467
518
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
+ } ,
490
531
_ => ( ) ,
491
- } ,
492
- _ => ( ) ,
532
+ }
493
533
} ,
494
534
Node :: TraitItem ( trait_item) => {
495
535
if let TraitItemKind :: Fn ( sig, ..) = trait_item. kind
@@ -547,6 +587,7 @@ struct DocHeaders {
547
587
safety : bool ,
548
588
errors : bool ,
549
589
panics : bool ,
590
+ first_paragraph_len : usize ,
550
591
}
551
592
552
593
/// 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: &[
586
627
acc
587
628
} ) ;
588
629
doc. pop ( ) ;
630
+ let doc = doc. trim ( ) ;
589
631
590
- if doc. trim ( ) . is_empty ( ) {
632
+ if doc. is_empty ( ) {
591
633
if let Some ( span) = span_of_fragments ( & fragments) {
592
634
span_lint_and_help (
593
635
cx,
@@ -611,7 +653,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
611
653
cx,
612
654
valid_idents,
613
655
parser. into_offset_iter ( ) ,
614
- & doc,
656
+ doc,
615
657
Fragments {
616
658
fragments : & fragments,
617
659
doc : & doc,
@@ -653,6 +695,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
653
695
let mut paragraph_range = 0 ..0 ;
654
696
let mut code_level = 0 ;
655
697
let mut blockquote_level = 0 ;
698
+ let mut is_first_paragraph = true ;
656
699
657
700
let mut containers = Vec :: new ( ) ;
658
701
@@ -720,6 +763,10 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
720
763
}
721
764
ticks_unbalanced = false ;
722
765
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
+ }
723
770
} ,
724
771
End ( TagEnd :: Heading ( _) | TagEnd :: Paragraph | TagEnd :: Item ) => {
725
772
if let End ( TagEnd :: Heading ( _) ) = event {
0 commit comments