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,38 @@ 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 not
427
+ /// too long.
428
+ ///
429
+ /// ### Why is this bad?
430
+ /// Documentation will show the first paragraph of the doscstring in the summary page of a
431
+ /// module, so having a nice, short summary in the first paragraph is part of writing good docs.
432
+ ///
433
+ /// ### Example
434
+ /// ```no_run
435
+ /// /// A very short summary.
436
+ /// /// A much longer explanation that goes into a lot more detail about
437
+ /// /// how the thing works, possibly with doclinks and so one,
438
+ /// /// and probably spanning a many rows.
439
+ /// struct Foo {}
440
+ /// ```
441
+ /// Use instead:
442
+ /// ```no_run
443
+ /// /// A very short summary.
444
+ /// ///
445
+ /// /// A much longer explanation that goes into a lot more detail about
446
+ /// /// how the thing works, possibly with doclinks and so one,
447
+ /// /// and probably spanning a many rows.
448
+ /// struct Foo {}
449
+ /// ```
450
+ #[ clippy:: version = "1.81.0" ]
451
+ pub TOO_LONG_FIRST_DOC_PARAGRAPH ,
452
+ style,
453
+ "ensure that the first line of a documentation paragraph isn't too long"
454
+ }
455
+
423
456
#[ derive( Clone ) ]
424
457
pub struct Documentation {
425
458
valid_idents : FxHashSet < String > ,
@@ -447,6 +480,7 @@ impl_lint_pass!(Documentation => [
447
480
SUSPICIOUS_DOC_COMMENTS ,
448
481
EMPTY_DOCS ,
449
482
DOC_LAZY_CONTINUATION ,
483
+ TOO_LONG_FIRST_DOC_PARAGRAPH ,
450
484
] ) ;
451
485
452
486
impl < ' tcx > LateLintPass < ' tcx > for Documentation {
@@ -456,39 +490,44 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
456
490
} ;
457
491
458
492
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 (
493
+ Node :: Item ( item) => {
494
+ too_long_first_doc_paragraph:: check ( cx, attrs, item. kind , headers. first_paragraph_len ) ;
495
+ match item. kind {
496
+ ItemKind :: Fn ( sig, _, body_id) => {
497
+ if !( is_entrypoint_fn ( cx, item. owner_id . to_def_id ( ) )
498
+ || in_external_macro ( cx. tcx . sess , item. span ) )
499
+ {
500
+ let body = cx. tcx . hir ( ) . body ( body_id) ;
501
+
502
+ let panic_info = FindPanicUnwrap :: find_span ( cx, cx. tcx . typeck ( item. owner_id ) , body. value ) ;
503
+ missing_headers:: check (
504
+ cx,
505
+ item. owner_id ,
506
+ sig,
507
+ headers,
508
+ Some ( body_id) ,
509
+ panic_info,
510
+ self . check_private_items ,
511
+ ) ;
512
+ }
513
+ } ,
514
+ ItemKind :: Trait ( _, unsafety, ..) => match ( headers. safety , unsafety) {
515
+ ( false , Safety :: Unsafe ) => span_lint (
466
516
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
- ) ,
517
+ MISSING_SAFETY_DOC ,
518
+ cx. tcx . def_span ( item. owner_id ) ,
519
+ "docs for unsafe trait missing `# Safety` section" ,
520
+ ) ,
521
+ ( true , Safety :: Safe ) => span_lint (
522
+ cx,
523
+ UNNECESSARY_SAFETY_DOC ,
524
+ cx. tcx . def_span ( item. owner_id ) ,
525
+ "docs for safe trait have unnecessary `# Safety` section" ,
526
+ ) ,
527
+ _ => ( ) ,
528
+ } ,
489
529
_ => ( ) ,
490
- } ,
491
- _ => ( ) ,
530
+ }
492
531
} ,
493
532
Node :: TraitItem ( trait_item) => {
494
533
if let TraitItemKind :: Fn ( sig, ..) = trait_item. kind
@@ -546,6 +585,7 @@ struct DocHeaders {
546
585
safety : bool ,
547
586
errors : bool ,
548
587
panics : bool ,
588
+ first_paragraph_len : usize ,
549
589
}
550
590
551
591
/// Does some pre-processing on raw, desugared `#[doc]` attributes such as parsing them and
@@ -585,8 +625,9 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
585
625
acc
586
626
} ) ;
587
627
doc. pop ( ) ;
628
+ let doc = doc. trim ( ) ;
588
629
589
- if doc. trim ( ) . is_empty ( ) {
630
+ if doc. is_empty ( ) {
590
631
if let Some ( span) = span_of_fragments ( & fragments) {
591
632
span_lint_and_help (
592
633
cx,
@@ -610,7 +651,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
610
651
cx,
611
652
valid_idents,
612
653
parser. into_offset_iter ( ) ,
613
- & doc,
654
+ doc,
614
655
Fragments {
615
656
fragments : & fragments,
616
657
doc : & doc,
@@ -652,6 +693,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
652
693
let mut paragraph_range = 0 ..0 ;
653
694
let mut code_level = 0 ;
654
695
let mut blockquote_level = 0 ;
696
+ let mut is_first_paragraph = true ;
655
697
656
698
let mut containers = Vec :: new ( ) ;
657
699
@@ -719,6 +761,10 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
719
761
}
720
762
ticks_unbalanced = false ;
721
763
paragraph_range = range;
764
+ if is_first_paragraph {
765
+ headers. first_paragraph_len = doc[ paragraph_range. clone ( ) ] . chars ( ) . count ( ) ;
766
+ is_first_paragraph = false ;
767
+ }
722
768
} ,
723
769
End ( Heading ( _, _, _) | Paragraph | Item ) => {
724
770
if let End ( Heading ( _, _, _) ) = event {
0 commit comments