88use clippy_utils:: attrs:: is_doc_hidden;
99use clippy_utils:: diagnostics:: span_lint;
1010use clippy_utils:: is_from_proc_macro;
11+ use clippy_utils:: source:: snippet_opt;
1112use rustc_ast:: ast:: { self , MetaItem , MetaItemKind } ;
1213use rustc_hir as hir;
1314use rustc_hir:: def_id:: LocalDefId ;
@@ -32,13 +33,22 @@ declare_clippy_lint! {
3233 "detects missing documentation for private members"
3334}
3435
36+ macro_rules! note_prev_span_then_ret {
37+ ( $prev_span: expr, $span: expr) => { {
38+ $prev_span = Some ( $span) ;
39+ return ;
40+ } } ;
41+ }
42+
3543pub struct MissingDoc {
3644 /// Whether to **only** check for missing documentation in items visible within the current
3745 /// crate. For example, `pub(crate)` items.
3846 crate_items_only : bool ,
3947 /// Stack of whether #[doc(hidden)] is set
4048 /// at each level which has lint attributes.
4149 doc_hidden_stack : Vec < bool > ,
50+ /// Used to keep tracking of the previous item, field or variants etc, to get the search span.
51+ prev_span : Option < Span > ,
4252}
4353
4454impl Default for MissingDoc {
@@ -54,6 +64,7 @@ impl MissingDoc {
5464 Self {
5565 crate_items_only,
5666 doc_hidden_stack : vec ! [ false ] ,
67+ prev_span : None ,
5768 }
5869 }
5970
@@ -108,7 +119,8 @@ impl MissingDoc {
108119
109120 let has_doc = attrs
110121 . iter ( )
111- . any ( |a| a. doc_str ( ) . is_some ( ) || Self :: has_include ( a. meta ( ) ) ) ;
122+ . any ( |a| a. doc_str ( ) . is_some ( ) || Self :: has_include ( a. meta ( ) ) )
123+ || matches ! ( self . search_span( sp) , Some ( span) if span_to_snippet_contains_docs( cx, span) ) ;
112124
113125 if !has_doc {
114126 span_lint (
@@ -119,6 +131,32 @@ impl MissingDoc {
119131 ) ;
120132 }
121133 }
134+
135+ /// Return a span to search for doc comments manually.
136+ ///
137+ /// # Example
138+ /// ```ignore
139+ /// fn foo() { ... }
140+ /// ^^^^^^^^^^^^^^^^ prev_span
141+ /// ↑
142+ /// | search_span |
143+ /// ↓
144+ /// fn bar() { ... }
145+ /// ^^^^^^^^^^^^^^^^ cur_span
146+ /// ```
147+ fn search_span ( & self , cur_span : Span ) -> Option < Span > {
148+ let prev_span = self . prev_span ?;
149+ let start_pos = if prev_span. contains ( cur_span) {
150+ // In case when the prev_span is an entire struct, or enum,
151+ // and the current span is a field, or variant, we need to search from
152+ // the starting pos of the previous span.
153+ prev_span. lo ( )
154+ } else {
155+ prev_span. hi ( )
156+ } ;
157+ let search_span = cur_span. with_lo ( start_pos) . with_hi ( cur_span. lo ( ) ) ;
158+ Some ( search_span)
159+ }
122160}
123161
124162impl_lint_pass ! ( MissingDoc => [ MISSING_DOCS_IN_PRIVATE_ITEMS ] ) ;
@@ -138,14 +176,18 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
138176 self . check_missing_docs_attrs ( cx, CRATE_DEF_ID , attrs, cx. tcx . def_span ( CRATE_DEF_ID ) , "the" , "crate" ) ;
139177 }
140178
179+ fn check_crate_post ( & mut self , _: & LateContext < ' tcx > ) {
180+ self . prev_span = None ;
181+ }
182+
141183 fn check_item ( & mut self , cx : & LateContext < ' tcx > , it : & ' tcx hir:: Item < ' _ > ) {
142184 match it. kind {
143185 hir:: ItemKind :: Fn ( ..) => {
144186 // ignore main()
145187 if it. ident . name == sym:: main {
146188 let at_root = cx. tcx . local_parent ( it. owner_id . def_id ) == CRATE_DEF_ID ;
147189 if at_root {
148- return ;
190+ note_prev_span_then_ret ! ( self . prev_span , it . span ) ;
149191 }
150192 }
151193 } ,
@@ -164,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
164206 | hir:: ItemKind :: ForeignMod { .. }
165207 | hir:: ItemKind :: GlobalAsm ( ..)
166208 | hir:: ItemKind :: Impl { .. }
167- | hir:: ItemKind :: Use ( ..) => return ,
209+ | hir:: ItemKind :: Use ( ..) => note_prev_span_then_ret ! ( self . prev_span , it . span ) ,
168210 } ;
169211
170212 let ( article, desc) = cx. tcx . article_and_description ( it. owner_id . to_def_id ( ) ) ;
@@ -173,6 +215,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
173215 if !is_from_proc_macro ( cx, it) {
174216 self . check_missing_docs_attrs ( cx, it. owner_id . def_id , attrs, it. span , article, desc) ;
175217 }
218+ self . prev_span = Some ( it. span ) ;
176219 }
177220
178221 fn check_trait_item ( & mut self , cx : & LateContext < ' tcx > , trait_item : & ' tcx hir:: TraitItem < ' _ > ) {
@@ -182,23 +225,25 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
182225 if !is_from_proc_macro ( cx, trait_item) {
183226 self . check_missing_docs_attrs ( cx, trait_item. owner_id . def_id , attrs, trait_item. span , article, desc) ;
184227 }
228+ self . prev_span = Some ( trait_item. span ) ;
185229 }
186230
187231 fn check_impl_item ( & mut self , cx : & LateContext < ' tcx > , impl_item : & ' tcx hir:: ImplItem < ' _ > ) {
188232 // If the method is an impl for a trait, don't doc.
189233 if let Some ( cid) = cx. tcx . associated_item ( impl_item. owner_id ) . impl_container ( cx. tcx ) {
190234 if cx. tcx . impl_trait_ref ( cid) . is_some ( ) {
191- return ;
235+ note_prev_span_then_ret ! ( self . prev_span , impl_item . span ) ;
192236 }
193237 } else {
194- return ;
238+ note_prev_span_then_ret ! ( self . prev_span , impl_item . span ) ;
195239 }
196240
197241 let ( article, desc) = cx. tcx . article_and_description ( impl_item. owner_id . to_def_id ( ) ) ;
198242 let attrs = cx. tcx . hir ( ) . attrs ( impl_item. hir_id ( ) ) ;
199243 if !is_from_proc_macro ( cx, impl_item) {
200244 self . check_missing_docs_attrs ( cx, impl_item. owner_id . def_id , attrs, impl_item. span , article, desc) ;
201245 }
246+ self . prev_span = Some ( impl_item. span ) ;
202247 }
203248
204249 fn check_field_def ( & mut self , cx : & LateContext < ' tcx > , sf : & ' tcx hir:: FieldDef < ' _ > ) {
@@ -208,12 +253,21 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
208253 self . check_missing_docs_attrs ( cx, sf. def_id , attrs, sf. span , "a" , "struct field" ) ;
209254 }
210255 }
256+ self . prev_span = Some ( sf. span ) ;
211257 }
212258
213259 fn check_variant ( & mut self , cx : & LateContext < ' tcx > , v : & ' tcx hir:: Variant < ' _ > ) {
214260 let attrs = cx. tcx . hir ( ) . attrs ( v. hir_id ) ;
215261 if !is_from_proc_macro ( cx, v) {
216262 self . check_missing_docs_attrs ( cx, v. def_id , attrs, v. span , "a" , "variant" ) ;
217263 }
264+ self . prev_span = Some ( v. span ) ;
218265 }
219266}
267+
268+ fn span_to_snippet_contains_docs ( cx : & LateContext < ' _ > , search_span : Span ) -> bool {
269+ let Some ( snippet) = snippet_opt ( cx, search_span) else {
270+ return false ;
271+ } ;
272+ snippet. lines ( ) . rev ( ) . any ( |line| line. trim ( ) . starts_with ( "///" ) )
273+ }
0 commit comments