1
1
use std:: ops:: Range ;
2
2
use std:: { io, thread} ;
3
3
4
- use crate :: doc:: NEEDLESS_DOCTEST_MAIN ;
4
+ use crate :: doc:: { NEEDLESS_DOCTEST_MAIN , TEST_ATTR_IN_DOCTEST } ;
5
5
use clippy_utils:: diagnostics:: span_lint;
6
- use rustc_ast:: { Async , Fn , FnRetTy , ItemKind } ;
6
+ use rustc_ast:: { Async , Fn , FnRetTy , Item , ItemKind } ;
7
7
use rustc_data_structures:: sync:: Lrc ;
8
8
use rustc_errors:: emitter:: EmitterWriter ;
9
9
use rustc_errors:: Handler ;
@@ -13,14 +13,24 @@ use rustc_parse::parser::ForceCollect;
13
13
use rustc_session:: parse:: ParseSess ;
14
14
use rustc_span:: edition:: Edition ;
15
15
use rustc_span:: source_map:: { FilePathMapping , SourceMap } ;
16
- use rustc_span:: { sym, FileName } ;
16
+ use rustc_span:: { sym, FileName , Pos } ;
17
17
18
18
use super :: Fragments ;
19
19
20
+ fn get_test_spans ( item : & Item , test_attr_spans : & mut Vec < Range < usize > > ) {
21
+ test_attr_spans. extend (
22
+ item. attrs
23
+ . iter ( )
24
+ . filter ( |attr| attr. has_name ( sym:: test) )
25
+ . map ( |attr| attr. span . lo ( ) . to_usize ( ) ..attr. span . hi ( ) . to_usize ( ) ) ,
26
+ ) ;
27
+ }
28
+
20
29
pub fn check ( cx : & LateContext < ' _ > , text : & str , edition : Edition , range : Range < usize > , fragments : Fragments < ' _ > ) {
21
- fn has_needless_main ( code : String , edition : Edition ) -> bool {
30
+ fn check_code_sample ( code : String , edition : Edition ) -> ( bool , Vec < Range < usize > > ) {
22
31
rustc_driver:: catch_fatal_errors ( || {
23
32
rustc_span:: create_session_globals_then ( edition, || {
33
+ let mut test_attr_spans = vec ! [ ] ;
24
34
let filename = FileName :: anon_source_code ( & code) ;
25
35
26
36
let fallback_bundle =
@@ -35,17 +45,19 @@ pub fn check(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<us
35
45
Ok ( p) => p,
36
46
Err ( errs) => {
37
47
drop ( errs) ;
38
- return false ;
48
+ return ( false , test_attr_spans ) ;
39
49
} ,
40
50
} ;
41
51
42
52
let mut relevant_main_found = false ;
53
+ let mut eligible = true ;
43
54
loop {
44
55
match parser. parse_item ( ForceCollect :: No ) {
45
56
Ok ( Some ( item) ) => match & item. kind {
46
57
ItemKind :: Fn ( box Fn {
47
58
sig, body : Some ( block) , ..
48
59
} ) if item. ident . name == sym:: main => {
60
+ get_test_spans ( & item, & mut test_attr_spans) ;
49
61
let is_async = matches ! ( sig. header. asyncness, Async :: Yes { .. } ) ;
50
62
let returns_nothing = match & sig. decl . output {
51
63
FnRetTy :: Default ( ..) => true ,
@@ -58,27 +70,32 @@ pub fn check(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<us
58
70
relevant_main_found = true ;
59
71
} else {
60
72
// This main function should not be linted, we're done
61
- return false ;
73
+ eligible = false ;
62
74
}
63
75
} ,
76
+ // Another function was found; this case is ignored for needless_doctest_main
77
+ ItemKind :: Fn ( box Fn { .. } ) => {
78
+ eligible = false ;
79
+ get_test_spans ( & item, & mut test_attr_spans) ;
80
+ } ,
64
81
// Tests with one of these items are ignored
65
82
ItemKind :: Static ( ..)
66
83
| ItemKind :: Const ( ..)
67
84
| ItemKind :: ExternCrate ( ..)
68
- | ItemKind :: ForeignMod ( ..)
69
- // Another function was found; this case is ignored
70
- | ItemKind :: Fn ( .. ) => return false ,
85
+ | ItemKind :: ForeignMod ( ..) => {
86
+ eligible = false ;
87
+ } ,
71
88
_ => { } ,
72
89
} ,
73
90
Ok ( None ) => break ,
74
91
Err ( e) => {
75
92
e. cancel ( ) ;
76
- return false ;
93
+ return ( false , test_attr_spans ) ;
77
94
} ,
78
95
}
79
96
}
80
97
81
- relevant_main_found
98
+ ( relevant_main_found & eligible , test_attr_spans )
82
99
} )
83
100
} )
84
101
. ok ( )
@@ -90,11 +107,16 @@ pub fn check(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<us
90
107
// Because of the global session, we need to create a new session in a different thread with
91
108
// the edition we need.
92
109
let text = text. to_owned ( ) ;
93
- if thread:: spawn ( move || has_needless_main ( text, edition) )
110
+ let ( has_main , test_attr_spans ) = thread:: spawn ( move || check_code_sample ( text, edition) )
94
111
. join ( )
95
- . expect ( "thread::spawn failed" )
96
- && let Some ( span) = fragments. span ( cx, range. start ..range. end - trailing_whitespace)
97
- {
112
+ . expect ( "thread::spawn failed" ) ;
113
+ if has_main && let Some ( span) = fragments. span ( cx, range. start ..range. end - trailing_whitespace) {
98
114
span_lint ( cx, NEEDLESS_DOCTEST_MAIN , span, "needless `fn main` in doctest" ) ;
99
115
}
116
+ for span in test_attr_spans {
117
+ let span = ( range. start + span. start ) ..( range. start + span. end ) ;
118
+ if let Some ( span) = fragments. span ( cx, span) {
119
+ span_lint ( cx, TEST_ATTR_IN_DOCTEST , span, "unit tests in doctest are not executed" ) ;
120
+ }
121
+ }
100
122
}
0 commit comments