8
8
use clippy_utils:: attrs:: is_doc_hidden;
9
9
use clippy_utils:: diagnostics:: span_lint;
10
10
use clippy_utils:: is_from_proc_macro;
11
+ use clippy_utils:: source:: snippet_opt;
11
12
use rustc_ast:: ast:: { self , MetaItem , MetaItemKind } ;
12
13
use rustc_hir as hir;
13
14
use rustc_hir:: def_id:: LocalDefId ;
@@ -32,13 +33,22 @@ declare_clippy_lint! {
32
33
"detects missing documentation for private members"
33
34
}
34
35
36
+ macro_rules! note_prev_span_then_ret {
37
+ ( $prev_span: expr, $span: expr) => { {
38
+ $prev_span = Some ( $span) ;
39
+ return ;
40
+ } } ;
41
+ }
42
+
35
43
pub struct MissingDoc {
36
44
/// Whether to **only** check for missing documentation in items visible within the current
37
45
/// crate. For example, `pub(crate)` items.
38
46
crate_items_only : bool ,
39
47
/// Stack of whether #[doc(hidden)] is set
40
48
/// at each level which has lint attributes.
41
49
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 > ,
42
52
}
43
53
44
54
impl Default for MissingDoc {
@@ -54,6 +64,7 @@ impl MissingDoc {
54
64
Self {
55
65
crate_items_only,
56
66
doc_hidden_stack : vec ! [ false ] ,
67
+ prev_span : None ,
57
68
}
58
69
}
59
70
@@ -108,7 +119,8 @@ impl MissingDoc {
108
119
109
120
let has_doc = attrs
110
121
. 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) ) ;
112
124
113
125
if !has_doc {
114
126
span_lint (
@@ -119,6 +131,32 @@ impl MissingDoc {
119
131
) ;
120
132
}
121
133
}
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
+ }
122
160
}
123
161
124
162
impl_lint_pass ! ( MissingDoc => [ MISSING_DOCS_IN_PRIVATE_ITEMS ] ) ;
@@ -138,14 +176,18 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
138
176
self . check_missing_docs_attrs ( cx, CRATE_DEF_ID , attrs, cx. tcx . def_span ( CRATE_DEF_ID ) , "the" , "crate" ) ;
139
177
}
140
178
179
+ fn check_crate_post ( & mut self , _: & LateContext < ' tcx > ) {
180
+ self . prev_span = None ;
181
+ }
182
+
141
183
fn check_item ( & mut self , cx : & LateContext < ' tcx > , it : & ' tcx hir:: Item < ' _ > ) {
142
184
match it. kind {
143
185
hir:: ItemKind :: Fn ( ..) => {
144
186
// ignore main()
145
187
if it. ident . name == sym:: main {
146
188
let at_root = cx. tcx . local_parent ( it. owner_id . def_id ) == CRATE_DEF_ID ;
147
189
if at_root {
148
- return ;
190
+ note_prev_span_then_ret ! ( self . prev_span , it . span ) ;
149
191
}
150
192
}
151
193
} ,
@@ -164,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
164
206
| hir:: ItemKind :: ForeignMod { .. }
165
207
| hir:: ItemKind :: GlobalAsm ( ..)
166
208
| hir:: ItemKind :: Impl { .. }
167
- | hir:: ItemKind :: Use ( ..) => return ,
209
+ | hir:: ItemKind :: Use ( ..) => note_prev_span_then_ret ! ( self . prev_span , it . span ) ,
168
210
} ;
169
211
170
212
let ( article, desc) = cx. tcx . article_and_description ( it. owner_id . to_def_id ( ) ) ;
@@ -173,6 +215,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
173
215
if !is_from_proc_macro ( cx, it) {
174
216
self . check_missing_docs_attrs ( cx, it. owner_id . def_id , attrs, it. span , article, desc) ;
175
217
}
218
+ self . prev_span = Some ( it. span ) ;
176
219
}
177
220
178
221
fn check_trait_item ( & mut self , cx : & LateContext < ' tcx > , trait_item : & ' tcx hir:: TraitItem < ' _ > ) {
@@ -182,23 +225,25 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
182
225
if !is_from_proc_macro ( cx, trait_item) {
183
226
self . check_missing_docs_attrs ( cx, trait_item. owner_id . def_id , attrs, trait_item. span , article, desc) ;
184
227
}
228
+ self . prev_span = Some ( trait_item. span ) ;
185
229
}
186
230
187
231
fn check_impl_item ( & mut self , cx : & LateContext < ' tcx > , impl_item : & ' tcx hir:: ImplItem < ' _ > ) {
188
232
// If the method is an impl for a trait, don't doc.
189
233
if let Some ( cid) = cx. tcx . associated_item ( impl_item. owner_id ) . impl_container ( cx. tcx ) {
190
234
if cx. tcx . impl_trait_ref ( cid) . is_some ( ) {
191
- return ;
235
+ note_prev_span_then_ret ! ( self . prev_span , impl_item . span ) ;
192
236
}
193
237
} else {
194
- return ;
238
+ note_prev_span_then_ret ! ( self . prev_span , impl_item . span ) ;
195
239
}
196
240
197
241
let ( article, desc) = cx. tcx . article_and_description ( impl_item. owner_id . to_def_id ( ) ) ;
198
242
let attrs = cx. tcx . hir ( ) . attrs ( impl_item. hir_id ( ) ) ;
199
243
if !is_from_proc_macro ( cx, impl_item) {
200
244
self . check_missing_docs_attrs ( cx, impl_item. owner_id . def_id , attrs, impl_item. span , article, desc) ;
201
245
}
246
+ self . prev_span = Some ( impl_item. span ) ;
202
247
}
203
248
204
249
fn check_field_def ( & mut self , cx : & LateContext < ' tcx > , sf : & ' tcx hir:: FieldDef < ' _ > ) {
@@ -208,12 +253,21 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
208
253
self . check_missing_docs_attrs ( cx, sf. def_id , attrs, sf. span , "a" , "struct field" ) ;
209
254
}
210
255
}
256
+ self . prev_span = Some ( sf. span ) ;
211
257
}
212
258
213
259
fn check_variant ( & mut self , cx : & LateContext < ' tcx > , v : & ' tcx hir:: Variant < ' _ > ) {
214
260
let attrs = cx. tcx . hir ( ) . attrs ( v. hir_id ) ;
215
261
if !is_from_proc_macro ( cx, v) {
216
262
self . check_missing_docs_attrs ( cx, v. def_id , attrs, v. span , "a" , "variant" ) ;
217
263
}
264
+ self . prev_span = Some ( v. span ) ;
218
265
}
219
266
}
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