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 : 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,50 @@ 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 (
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 (
467
524
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
+ } ,
490
537
_ => ( ) ,
491
- } ,
492
- _ => ( ) ,
538
+ }
493
539
} ,
494
540
Node :: TraitItem ( trait_item) => {
495
541
if let TraitItemKind :: Fn ( sig, ..) = trait_item. kind
@@ -547,6 +593,7 @@ struct DocHeaders {
547
593
safety : bool ,
548
594
errors : bool ,
549
595
panics : bool ,
596
+ first_paragraph_len : usize ,
550
597
}
551
598
552
599
/// 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
653
700
let mut paragraph_range = 0 ..0 ;
654
701
let mut code_level = 0 ;
655
702
let mut blockquote_level = 0 ;
703
+ let mut is_first_paragraph = true ;
656
704
657
705
let mut containers = Vec :: new ( ) ;
658
706
@@ -720,6 +768,10 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
720
768
}
721
769
ticks_unbalanced = false ;
722
770
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
+ }
723
775
} ,
724
776
End ( TagEnd :: Heading ( _) | TagEnd :: Paragraph | TagEnd :: Item ) => {
725
777
if let End ( TagEnd :: Heading ( _) ) = event {
0 commit comments