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,33 @@ 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
- pub fn check ( cx : & LateContext < ' _ > , text : & str , edition : Edition , range : Range < usize > , fragments : Fragments < ' _ > ) {
21
- fn has_needless_main ( code : String , edition : Edition ) -> bool {
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
+ . find ( |attr| attr. has_name ( sym:: test) )
25
+ . map ( |attr| attr. span . lo ( ) . to_usize ( ) ..item. ident . span . hi ( ) . to_usize ( ) ) ,
26
+ ) ;
27
+ }
28
+
29
+ pub fn check (
30
+ cx : & LateContext < ' _ > ,
31
+ text : & str ,
32
+ edition : Edition ,
33
+ range : Range < usize > ,
34
+ fragments : Fragments < ' _ > ,
35
+ ignore : bool ,
36
+ ) {
37
+ // return whether the code contains a needless `fn main` plus a vector of byte position ranges
38
+ // of all `#[test]` attributes in not ignored code examples
39
+ fn check_code_sample ( code : String , edition : Edition , ignore : bool ) -> ( bool , Vec < Range < usize > > ) {
22
40
rustc_driver:: catch_fatal_errors ( || {
23
41
rustc_span:: create_session_globals_then ( edition, || {
42
+ let mut test_attr_spans = vec ! [ ] ;
24
43
let filename = FileName :: anon_source_code ( & code) ;
25
44
26
45
let fallback_bundle =
@@ -35,17 +54,21 @@ pub fn check(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<us
35
54
Ok ( p) => p,
36
55
Err ( errs) => {
37
56
drop ( errs) ;
38
- return false ;
57
+ return ( false , test_attr_spans ) ;
39
58
} ,
40
59
} ;
41
60
42
61
let mut relevant_main_found = false ;
62
+ let mut eligible = true ;
43
63
loop {
44
64
match parser. parse_item ( ForceCollect :: No ) {
45
65
Ok ( Some ( item) ) => match & item. kind {
46
66
ItemKind :: Fn ( box Fn {
47
67
sig, body : Some ( block) , ..
48
68
} ) if item. ident . name == sym:: main => {
69
+ if !ignore {
70
+ get_test_spans ( & item, & mut test_attr_spans) ;
71
+ }
49
72
let is_async = matches ! ( sig. header. asyncness, Async :: Yes { .. } ) ;
50
73
let returns_nothing = match & sig. decl . output {
51
74
FnRetTy :: Default ( ..) => true ,
@@ -58,27 +81,34 @@ pub fn check(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<us
58
81
relevant_main_found = true ;
59
82
} else {
60
83
// This main function should not be linted, we're done
61
- return false ;
84
+ eligible = false ;
85
+ }
86
+ } ,
87
+ // Another function was found; this case is ignored for needless_doctest_main
88
+ ItemKind :: Fn ( box Fn { .. } ) => {
89
+ eligible = false ;
90
+ if !ignore {
91
+ get_test_spans ( & item, & mut test_attr_spans) ;
62
92
}
63
93
} ,
64
94
// Tests with one of these items are ignored
65
95
ItemKind :: Static ( ..)
66
96
| ItemKind :: Const ( ..)
67
97
| ItemKind :: ExternCrate ( ..)
68
- | ItemKind :: ForeignMod ( ..)
69
- // Another function was found; this case is ignored
70
- | ItemKind :: Fn ( .. ) => return false ,
98
+ | ItemKind :: ForeignMod ( ..) => {
99
+ eligible = false ;
100
+ } ,
71
101
_ => { } ,
72
102
} ,
73
103
Ok ( None ) => break ,
74
104
Err ( e) => {
75
105
e. cancel ( ) ;
76
- return false ;
106
+ return ( false , test_attr_spans ) ;
77
107
} ,
78
108
}
79
109
}
80
110
81
- relevant_main_found
111
+ ( relevant_main_found & eligible , test_attr_spans )
82
112
} )
83
113
} )
84
114
. ok ( )
@@ -90,11 +120,16 @@ pub fn check(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<us
90
120
// Because of the global session, we need to create a new session in a different thread with
91
121
// the edition we need.
92
122
let text = text. to_owned ( ) ;
93
- if thread:: spawn ( move || has_needless_main ( text, edition) )
123
+ let ( has_main , test_attr_spans ) = thread:: spawn ( move || check_code_sample ( text, edition, ignore ) )
94
124
. join ( )
95
- . expect ( "thread::spawn failed" )
96
- && let Some ( span) = fragments. span ( cx, range. start ..range. end - trailing_whitespace)
97
- {
125
+ . expect ( "thread::spawn failed" ) ;
126
+ if has_main && let Some ( span) = fragments. span ( cx, range. start ..range. end - trailing_whitespace) {
98
127
span_lint ( cx, NEEDLESS_DOCTEST_MAIN , span, "needless `fn main` in doctest" ) ;
99
128
}
129
+ for span in test_attr_spans {
130
+ let span = ( range. start + span. start ) ..( range. start + span. end ) ;
131
+ if let Some ( span) = fragments. span ( cx, span) {
132
+ span_lint ( cx, TEST_ATTR_IN_DOCTEST , span, "unit tests in doctest are not executed" ) ;
133
+ }
134
+ }
100
135
}
0 commit comments