@@ -402,30 +402,63 @@ pub fn make_test(s: &str,
402402 // are intended to be crate attributes.
403403 prog. push_str ( & crate_attrs) ;
404404
405+ // Uses libsyntax to parse the doctest and find if there's a main fn and the extern
406+ // crate already is included.
407+ let ( already_has_main, already_has_extern_crate) = crate :: syntax:: with_globals ( || {
408+ use crate :: syntax:: { ast, parse:: { self , ParseSess } , source_map:: FilePathMapping } ;
409+ use crate :: syntax_pos:: FileName ;
410+
411+ let filename = FileName :: Anon ;
412+ let source = s. to_owned ( ) ;
413+ let sess = ParseSess :: new ( FilePathMapping :: empty ( ) ) ;
414+
415+ let mut parser = parse:: new_parser_from_source_str ( & sess, filename, source) ;
416+
417+ let mut found_main = false ;
418+ let mut found_extern_crate = cratename. is_none ( ) ;
419+
420+ while let Ok ( Some ( item) ) = parser. parse_item ( ) {
421+ if !found_main {
422+ if let ast:: ItemKind :: Fn ( ..) = item. node {
423+ if item. ident . as_str ( ) == "main" {
424+ found_main = true ;
425+ }
426+ }
427+ }
428+
429+ if !found_extern_crate {
430+ if let ast:: ItemKind :: ExternCrate ( original) = item. node {
431+ // This code will never be reached if `cratename` is none ecause
432+ // `found_extern_crate` is initialized to `true` if it is none.
433+ let cratename = cratename. unwrap ( ) ;
434+
435+ match original {
436+ Some ( name) => found_extern_crate = name. as_str ( ) == cratename,
437+ None => found_extern_crate = item. ident . as_str ( ) == cratename,
438+ }
439+ }
440+ }
441+
442+ if found_main && found_extern_crate {
443+ break ;
444+ }
445+ }
446+
447+ ( found_main, found_extern_crate)
448+ } ) ;
449+
405450 // Don't inject `extern crate std` because it's already injected by the
406451 // compiler.
407- if !s . contains ( "extern crate" ) && !opts. no_crate_inject && cratename != Some ( "std" ) {
452+ if !already_has_extern_crate && !opts. no_crate_inject && cratename != Some ( "std" ) {
408453 if let Some ( cratename) = cratename {
454+ // Make sure its actually used if not included.
409455 if s. contains ( cratename) {
410456 prog. push_str ( & format ! ( "extern crate {};\n " , cratename) ) ;
411457 line_offset += 1 ;
412458 }
413459 }
414460 }
415461
416- // FIXME (#21299): prefer libsyntax or some other actual parser over this
417- // best-effort ad hoc approach
418- let already_has_main = s. lines ( )
419- . map ( |line| {
420- let comment = line. find ( "//" ) ;
421- if let Some ( comment_begins) = comment {
422- & line[ 0 ..comment_begins]
423- } else {
424- line
425- }
426- } )
427- . any ( |code| code. contains ( "fn main" ) ) ;
428-
429462 if dont_insert_main || already_has_main {
430463 prog. push_str ( everything_else) ;
431464 } else {
@@ -1014,4 +1047,38 @@ assert_eq!(2+2, 4);
10141047 let output = make_test ( input, None , false , & opts) ;
10151048 assert_eq ! ( output, ( expected, 1 ) ) ;
10161049 }
1050+
1051+ #[ test]
1052+ fn make_test_issues_21299_33731 ( ) {
1053+ let opts = TestOptions :: default ( ) ;
1054+
1055+ let input =
1056+ "// fn main
1057+ assert_eq!(2+2, 4);" ;
1058+
1059+ let expected =
1060+ "#![allow(unused)]
1061+ fn main() {
1062+ // fn main
1063+ assert_eq!(2+2, 4);
1064+ }" . to_string ( ) ;
1065+
1066+ let output = make_test ( input, None , false , & opts) ;
1067+ assert_eq ! ( output, ( expected, 2 ) ) ;
1068+
1069+ let input =
1070+ "extern crate hella_qwop;
1071+ assert_eq!(asdf::foo, 4);" ;
1072+
1073+ let expected =
1074+ "#![allow(unused)]
1075+ extern crate hella_qwop;
1076+ extern crate asdf;
1077+ fn main() {
1078+ assert_eq!(asdf::foo, 4);
1079+ }" . to_string ( ) ;
1080+
1081+ let output = make_test ( input, Some ( "asdf" ) , false , & opts) ;
1082+ assert_eq ! ( output, ( expected, 3 ) ) ;
1083+ }
10171084}
0 commit comments