@@ -23,7 +23,7 @@ use rustc_resolve::rustdoc::{
23
23
} ;
24
24
use rustc_session:: impl_lint_pass;
25
25
use rustc_span:: edition:: Edition ;
26
- use rustc_span:: { sym, Span } ;
26
+ use rustc_span:: { sym, Span , DUMMY_SP } ;
27
27
use std:: ops:: Range ;
28
28
use url:: Url ;
29
29
@@ -338,6 +338,29 @@ declare_clippy_lint! {
338
338
"suspicious usage of (outer) doc comments"
339
339
}
340
340
341
+ declare_clippy_lint ! {
342
+ /// ### What it does
343
+ /// Detects documentation that is empty.
344
+ /// ### Why is this bad?
345
+ /// It is unlikely that there is any reason to have empty documentation for an item
346
+ /// ### Example
347
+ /// ```rust
348
+ /// ///
349
+ /// fn returns_true() -> bool {
350
+ /// true
351
+ /// }
352
+ /// Use instead:
353
+ /// ```rust
354
+ /// fn returns_true() -> bool {
355
+ /// true
356
+ /// }
357
+ /// ```
358
+ #[ clippy:: version = "1.78.0" ]
359
+ pub EMPTY_DOCS ,
360
+ suspicious,
361
+ "docstrings exist but documentation is empty"
362
+ }
363
+
341
364
#[ derive( Clone ) ]
342
365
pub struct Documentation {
343
366
valid_idents : FxHashSet < String > ,
@@ -364,7 +387,8 @@ impl_lint_pass!(Documentation => [
364
387
NEEDLESS_DOCTEST_MAIN ,
365
388
TEST_ATTR_IN_DOCTEST ,
366
389
UNNECESSARY_SAFETY_DOC ,
367
- SUSPICIOUS_DOC_COMMENTS
390
+ SUSPICIOUS_DOC_COMMENTS ,
391
+ EMPTY_DOCS ,
368
392
] ) ;
369
393
370
394
impl < ' tcx > LateLintPass < ' tcx > for Documentation {
@@ -378,6 +402,20 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
378
402
let Some ( headers) = check_attrs ( cx, & self . valid_idents , attrs) else {
379
403
return ;
380
404
} ;
405
+
406
+ if let Some ( span) = get_empty_doc_combined_span ( attrs, item. span )
407
+ && headers. empty
408
+ {
409
+ span_lint_and_help (
410
+ cx,
411
+ EMPTY_DOCS ,
412
+ span,
413
+ "empty doc comment" ,
414
+ None ,
415
+ "consider removing or filling it" ,
416
+ ) ;
417
+ }
418
+
381
419
match item. kind {
382
420
hir:: ItemKind :: Fn ( ref sig, _, body_id) => {
383
421
if !( is_entrypoint_fn ( cx, item. owner_id . to_def_id ( ) ) || in_external_macro ( cx. tcx . sess , item. span ) ) {
@@ -477,6 +515,7 @@ struct DocHeaders {
477
515
safety : bool ,
478
516
errors : bool ,
479
517
panics : bool ,
518
+ empty : bool ,
480
519
}
481
520
482
521
/// Does some pre-processing on raw, desugared `#[doc]` attributes such as parsing them and
@@ -509,7 +548,10 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
509
548
doc. pop ( ) ;
510
549
511
550
if doc. is_empty ( ) {
512
- return Some ( DocHeaders :: default ( ) ) ;
551
+ return Some ( DocHeaders {
552
+ empty : true ,
553
+ ..DocHeaders :: default ( )
554
+ } ) ;
513
555
}
514
556
515
557
let mut cb = fake_broken_link_callback;
@@ -717,3 +759,20 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
717
759
self . cx . tcx . hir ( )
718
760
}
719
761
}
762
+
763
+ fn get_empty_doc_combined_span ( attrs : & [ Attribute ] , item_span : Span ) -> Option < Span > {
764
+ let mut attrs_span = DUMMY_SP ;
765
+ if attrs. len ( ) > 0 {
766
+ attrs_span = attrs
767
+ . iter ( )
768
+ . map ( |attr| attr. span )
769
+ . fold ( attrs[ 0 ] . span , |acc, next| acc. to ( next) ) ;
770
+ }
771
+
772
+ match ( !item_span. is_dummy ( ) , !attrs_span. is_dummy ( ) ) {
773
+ ( true , true ) => Some ( item_span. shrink_to_lo ( ) . to ( attrs_span) ) ,
774
+ ( true , false ) => Some ( item_span) ,
775
+ ( false , true ) => Some ( attrs_span) ,
776
+ ( false , false ) => None ,
777
+ }
778
+ }
0 commit comments