@@ -19,7 +19,8 @@ use rustc_middle::hir::nested_filter;
19
19
use rustc_middle:: lint:: in_external_macro;
20
20
use rustc_middle:: ty;
21
21
use rustc_resolve:: rustdoc:: {
22
- add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range, DocFragment ,
22
+ add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range, span_of_fragments,
23
+ DocFragment ,
23
24
} ;
24
25
use rustc_session:: impl_lint_pass;
25
26
use rustc_span:: edition:: Edition ;
@@ -338,6 +339,30 @@ declare_clippy_lint! {
338
339
"suspicious usage of (outer) doc comments"
339
340
}
340
341
342
+ declare_clippy_lint ! {
343
+ /// ### What it does
344
+ /// Detects documentation that is empty.
345
+ /// ### Why is this bad?
346
+ /// Empty docs clutter code without adding value, reducing readability and maintainability.
347
+ /// ### Example
348
+ /// ```no_run
349
+ /// ///
350
+ /// fn returns_true() -> bool {
351
+ /// true
352
+ /// }
353
+ /// ```
354
+ /// Use instead:
355
+ /// ```no_run
356
+ /// fn returns_true() -> bool {
357
+ /// true
358
+ /// }
359
+ /// ```
360
+ #[ clippy:: version = "1.78.0" ]
361
+ pub EMPTY_DOCS ,
362
+ suspicious,
363
+ "docstrings exist but documentation is empty"
364
+ }
365
+
341
366
#[ derive( Clone ) ]
342
367
pub struct Documentation {
343
368
valid_idents : FxHashSet < String > ,
@@ -364,7 +389,8 @@ impl_lint_pass!(Documentation => [
364
389
NEEDLESS_DOCTEST_MAIN ,
365
390
TEST_ATTR_IN_DOCTEST ,
366
391
UNNECESSARY_SAFETY_DOC ,
367
- SUSPICIOUS_DOC_COMMENTS
392
+ SUSPICIOUS_DOC_COMMENTS ,
393
+ EMPTY_DOCS ,
368
394
] ) ;
369
395
370
396
impl < ' tcx > LateLintPass < ' tcx > for Documentation {
@@ -373,11 +399,22 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
373
399
check_attrs ( cx, & self . valid_idents , attrs) ;
374
400
}
375
401
402
+ fn check_variant ( & mut self , cx : & LateContext < ' tcx > , variant : & ' tcx hir:: Variant < ' tcx > ) {
403
+ let attrs = cx. tcx . hir ( ) . attrs ( variant. hir_id ) ;
404
+ check_attrs ( cx, & self . valid_idents , attrs) ;
405
+ }
406
+
407
+ fn check_field_def ( & mut self , cx : & LateContext < ' tcx > , variant : & ' tcx hir:: FieldDef < ' tcx > ) {
408
+ let attrs = cx. tcx . hir ( ) . attrs ( variant. hir_id ) ;
409
+ check_attrs ( cx, & self . valid_idents , attrs) ;
410
+ }
411
+
376
412
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
377
413
let attrs = cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) ;
378
414
let Some ( headers) = check_attrs ( cx, & self . valid_idents , attrs) else {
379
415
return ;
380
416
} ;
417
+
381
418
match item. kind {
382
419
hir:: ItemKind :: Fn ( ref sig, _, body_id) => {
383
420
if !( is_entrypoint_fn ( cx, item. owner_id . to_def_id ( ) ) || in_external_macro ( cx. tcx . sess , item. span ) ) {
@@ -502,13 +539,23 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
502
539
suspicious_doc_comments:: check ( cx, attrs) ;
503
540
504
541
let ( fragments, _) = attrs_to_doc_fragments ( attrs. iter ( ) . map ( |attr| ( attr, None ) ) , true ) ;
505
- let mut doc = String :: new ( ) ;
506
- for fragment in & fragments {
507
- add_doc_fragment ( & mut doc , fragment ) ;
508
- }
542
+ let mut doc = fragments . iter ( ) . fold ( String :: new ( ) , | mut acc , fragment| {
543
+ add_doc_fragment ( & mut acc , fragment ) ;
544
+ acc
545
+ } ) ;
509
546
doc. pop ( ) ;
510
547
511
- if doc. is_empty ( ) {
548
+ if doc. trim ( ) . is_empty ( ) {
549
+ if let Some ( span) = span_of_fragments ( & fragments) {
550
+ span_lint_and_help (
551
+ cx,
552
+ EMPTY_DOCS ,
553
+ span,
554
+ "empty doc comment" ,
555
+ None ,
556
+ "consider removing or filling it" ,
557
+ ) ;
558
+ }
512
559
return Some ( DocHeaders :: default ( ) ) ;
513
560
}
514
561
0 commit comments