11use std:: ops:: Range ;
22use std:: { io, thread} ;
33
4- use crate :: doc:: NEEDLESS_DOCTEST_MAIN ;
4+ use crate :: doc:: { NEEDLESS_DOCTEST_MAIN , TEST_ATTR_IN_DOCTEST } ;
55use clippy_utils:: diagnostics:: span_lint;
6- use rustc_ast:: { Async , Fn , FnRetTy , ItemKind } ;
6+ use rustc_ast:: { Async , Fn , FnRetTy , Item , ItemKind } ;
77use rustc_data_structures:: sync:: Lrc ;
88use rustc_errors:: emitter:: EmitterWriter ;
99use rustc_errors:: Handler ;
@@ -13,14 +13,33 @@ use rustc_parse::parser::ForceCollect;
1313use rustc_session:: parse:: ParseSess ;
1414use rustc_span:: edition:: Edition ;
1515use rustc_span:: source_map:: { FilePathMapping , SourceMap } ;
16- use rustc_span:: { sym, FileName } ;
16+ use rustc_span:: { sym, FileName , Pos } ;
1717
1818use super :: Fragments ;
1919
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 > > ) {
2240 rustc_driver:: catch_fatal_errors ( || {
2341 rustc_span:: create_session_globals_then ( edition, || {
42+ let mut test_attr_spans = vec ! [ ] ;
2443 let filename = FileName :: anon_source_code ( & code) ;
2544
2645 let fallback_bundle =
@@ -35,17 +54,21 @@ pub fn check(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<us
3554 Ok ( p) => p,
3655 Err ( errs) => {
3756 drop ( errs) ;
38- return false ;
57+ return ( false , test_attr_spans ) ;
3958 } ,
4059 } ;
4160
4261 let mut relevant_main_found = false ;
62+ let mut eligible = true ;
4363 loop {
4464 match parser. parse_item ( ForceCollect :: No ) {
4565 Ok ( Some ( item) ) => match & item. kind {
4666 ItemKind :: Fn ( box Fn {
4767 sig, body : Some ( block) , ..
4868 } ) if item. ident . name == sym:: main => {
69+ if !ignore {
70+ get_test_spans ( & item, & mut test_attr_spans) ;
71+ }
4972 let is_async = matches ! ( sig. header. asyncness, Async :: Yes { .. } ) ;
5073 let returns_nothing = match & sig. decl . output {
5174 FnRetTy :: Default ( ..) => true ,
@@ -58,27 +81,34 @@ pub fn check(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<us
5881 relevant_main_found = true ;
5982 } else {
6083 // 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) ;
6292 }
6393 } ,
6494 // Tests with one of these items are ignored
6595 ItemKind :: Static ( ..)
6696 | ItemKind :: Const ( ..)
6797 | 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+ } ,
71101 _ => { } ,
72102 } ,
73103 Ok ( None ) => break ,
74104 Err ( e) => {
75105 e. cancel ( ) ;
76- return false ;
106+ return ( false , test_attr_spans ) ;
77107 } ,
78108 }
79109 }
80110
81- relevant_main_found
111+ ( relevant_main_found & eligible , test_attr_spans )
82112 } )
83113 } )
84114 . ok ( )
@@ -90,11 +120,16 @@ pub fn check(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<us
90120 // Because of the global session, we need to create a new session in a different thread with
91121 // the edition we need.
92122 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 ) )
94124 . 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) {
98127 span_lint ( cx, NEEDLESS_DOCTEST_MAIN , span, "needless `fn main` in doctest" ) ;
99128 }
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+ }
100135}
0 commit comments