@@ -23,7 +23,7 @@ use rustc_resolve::rustdoc::{
2323} ;
2424use rustc_session:: impl_lint_pass;
2525use rustc_span:: edition:: Edition ;
26- use rustc_span:: { sym, Span } ;
26+ use rustc_span:: { sym, Span , DUMMY_SP } ;
2727use std:: ops:: Range ;
2828use url:: Url ;
2929
@@ -338,6 +338,29 @@ declare_clippy_lint! {
338338 "suspicious usage of (outer) doc comments"
339339}
340340
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+
341364#[ derive( Clone ) ]
342365pub struct Documentation {
343366 valid_idents : FxHashSet < String > ,
@@ -364,7 +387,8 @@ impl_lint_pass!(Documentation => [
364387 NEEDLESS_DOCTEST_MAIN ,
365388 TEST_ATTR_IN_DOCTEST ,
366389 UNNECESSARY_SAFETY_DOC ,
367- SUSPICIOUS_DOC_COMMENTS
390+ SUSPICIOUS_DOC_COMMENTS ,
391+ EMPTY_DOCS ,
368392] ) ;
369393
370394impl < ' tcx > LateLintPass < ' tcx > for Documentation {
@@ -378,6 +402,20 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
378402 let Some ( headers) = check_attrs ( cx, & self . valid_idents , attrs) else {
379403 return ;
380404 } ;
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+
381419 match item. kind {
382420 hir:: ItemKind :: Fn ( ref sig, _, body_id) => {
383421 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 {
477515 safety : bool ,
478516 errors : bool ,
479517 panics : bool ,
518+ empty : bool ,
480519}
481520
482521/// 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: &[
509548 doc. pop ( ) ;
510549
511550 if doc. is_empty ( ) {
512- return Some ( DocHeaders :: default ( ) ) ;
551+ return Some ( DocHeaders {
552+ empty : true ,
553+ ..DocHeaders :: default ( )
554+ } ) ;
513555 }
514556
515557 let mut cb = fake_broken_link_callback;
@@ -717,3 +759,20 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
717759 self . cx . tcx . hir ( )
718760 }
719761}
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