@@ -19,7 +19,8 @@ use rustc_middle::hir::nested_filter;
1919use rustc_middle:: lint:: in_external_macro;
2020use rustc_middle:: ty;
2121use 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 ,
2324} ;
2425use rustc_session:: impl_lint_pass;
2526use rustc_span:: edition:: Edition ;
@@ -338,6 +339,30 @@ declare_clippy_lint! {
338339 "suspicious usage of (outer) doc comments"
339340}
340341
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+
341366#[ derive( Clone ) ]
342367pub struct Documentation {
343368 valid_idents : FxHashSet < String > ,
@@ -364,7 +389,8 @@ impl_lint_pass!(Documentation => [
364389 NEEDLESS_DOCTEST_MAIN ,
365390 TEST_ATTR_IN_DOCTEST ,
366391 UNNECESSARY_SAFETY_DOC ,
367- SUSPICIOUS_DOC_COMMENTS
392+ SUSPICIOUS_DOC_COMMENTS ,
393+ EMPTY_DOCS ,
368394] ) ;
369395
370396impl < ' tcx > LateLintPass < ' tcx > for Documentation {
@@ -373,11 +399,22 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
373399 check_attrs ( cx, & self . valid_idents , attrs) ;
374400 }
375401
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+
376412 fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
377413 let attrs = cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) ;
378414 let Some ( headers) = check_attrs ( cx, & self . valid_idents , attrs) else {
379415 return ;
380416 } ;
417+
381418 match item. kind {
382419 hir:: ItemKind :: Fn ( ref sig, _, body_id) => {
383420 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: &[
502539 suspicious_doc_comments:: check ( cx, attrs) ;
503540
504541 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+ } ) ;
509546 doc. pop ( ) ;
510547
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+ }
512559 return Some ( DocHeaders :: default ( ) ) ;
513560 }
514561
0 commit comments