@@ -10,7 +10,10 @@ use rustc_interface::interface;
1010use rustc_middle:: hir:: map:: Map ;
1111use rustc_middle:: hir:: nested_filter;
1212use rustc_middle:: ty:: TyCtxt ;
13+ use rustc_parse:: maybe_new_parser_from_source_str;
14+ use rustc_parse:: parser:: attr:: InnerAttrPolicy ;
1315use rustc_session:: config:: { self , CrateType , ErrorOutputType } ;
16+ use rustc_session:: parse:: ParseSess ;
1417use rustc_session:: { lint, DiagnosticOutput , Session } ;
1518use rustc_span:: edition:: Edition ;
1619use rustc_span:: source_map:: SourceMap ;
@@ -493,7 +496,7 @@ crate fn make_test(
493496 edition : Edition ,
494497 test_id : Option < & str > ,
495498) -> ( String , usize , bool ) {
496- let ( crate_attrs, everything_else, crates) = partition_source ( s) ;
499+ let ( crate_attrs, everything_else, crates) = partition_source ( s, edition ) ;
497500 let everything_else = everything_else. trim ( ) ;
498501 let mut line_offset = 0 ;
499502 let mut prog = String :: new ( ) ;
@@ -525,9 +528,7 @@ crate fn make_test(
525528 rustc_span:: create_session_if_not_set_then ( edition, |_| {
526529 use rustc_errors:: emitter:: { Emitter , EmitterWriter } ;
527530 use rustc_errors:: Handler ;
528- use rustc_parse:: maybe_new_parser_from_source_str;
529531 use rustc_parse:: parser:: ForceCollect ;
530- use rustc_session:: parse:: ParseSess ;
531532 use rustc_span:: source_map:: FilePathMapping ;
532533
533534 let filename = FileName :: anon_source_code ( s) ;
@@ -697,8 +698,39 @@ crate fn make_test(
697698 ( prog, line_offset, supports_color)
698699}
699700
700- // FIXME(aburka): use a real parser to deal with multiline attributes
701- fn partition_source ( s : & str ) -> ( String , String , String ) {
701+ fn check_if_attr_is_complete ( source : & str , edition : Edition ) -> bool {
702+ if source. is_empty ( ) {
703+ // Empty content so nothing to check in here...
704+ return true ;
705+ }
706+ rustc_span:: create_session_if_not_set_then ( edition, |_| {
707+ let filename = FileName :: anon_source_code ( source) ;
708+ let sess = ParseSess :: with_silent_emitter ( None ) ;
709+ let mut parser = match maybe_new_parser_from_source_str ( & sess, filename, source. to_owned ( ) )
710+ {
711+ Ok ( p) => p,
712+ Err ( _) => {
713+ debug ! ( "Cannot build a parser to check mod attr so skipping..." ) ;
714+ return true ;
715+ }
716+ } ;
717+ // If a parsing error happened, it's very likely that the attribute is incomplete.
718+ if !parser. parse_attribute ( InnerAttrPolicy :: Permitted ) . is_ok ( ) {
719+ return false ;
720+ }
721+ // We now check if there is an unclosed delimiter for the attribute. To do so, we look at
722+ // the `unclosed_delims` and see if the opening square bracket was closed.
723+ parser
724+ . unclosed_delims ( )
725+ . get ( 0 )
726+ . map ( |unclosed| {
727+ unclosed. unclosed_span . map ( |s| s. lo ( ) ) . unwrap_or ( BytePos ( 0 ) ) != BytePos ( 2 )
728+ } )
729+ . unwrap_or ( true )
730+ } )
731+ }
732+
733+ fn partition_source ( s : & str , edition : Edition ) -> ( String , String , String ) {
702734 #[ derive( Copy , Clone , PartialEq ) ]
703735 enum PartitionState {
704736 Attrs ,
@@ -710,15 +742,23 @@ fn partition_source(s: &str) -> (String, String, String) {
710742 let mut crates = String :: new ( ) ;
711743 let mut after = String :: new ( ) ;
712744
745+ let mut mod_attr_pending = String :: new ( ) ;
746+
713747 for line in s. lines ( ) {
714748 let trimline = line. trim ( ) ;
715749
716750 // FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be
717751 // shunted into "everything else"
718752 match state {
719753 PartitionState :: Attrs => {
720- state = if trimline. starts_with ( "#![" )
721- || trimline. chars ( ) . all ( |c| c. is_whitespace ( ) )
754+ state = if trimline. starts_with ( "#![" ) {
755+ if !check_if_attr_is_complete ( line, edition) {
756+ mod_attr_pending = line. to_owned ( ) ;
757+ } else {
758+ mod_attr_pending. clear ( ) ;
759+ }
760+ PartitionState :: Attrs
761+ } else if trimline. chars ( ) . all ( |c| c. is_whitespace ( ) )
722762 || ( trimline. starts_with ( "//" ) && !trimline. starts_with ( "///" ) )
723763 {
724764 PartitionState :: Attrs
@@ -727,7 +767,21 @@ fn partition_source(s: &str) -> (String, String, String) {
727767 {
728768 PartitionState :: Crates
729769 } else {
730- PartitionState :: Other
770+ // First we check if the previous attribute was "complete"...
771+ if !mod_attr_pending. is_empty ( ) {
772+ // If not, then we append the new line into the pending attribute to check
773+ // if this time it's complete...
774+ mod_attr_pending. push_str ( line) ;
775+ if !trimline. is_empty ( ) && check_if_attr_is_complete ( line, edition) {
776+ // If it's complete, then we can clear the pending content.
777+ mod_attr_pending. clear ( ) ;
778+ }
779+ // In any case, this is considered as `PartitionState::Attrs` so it's
780+ // prepended before rustdoc's inserts.
781+ PartitionState :: Attrs
782+ } else {
783+ PartitionState :: Other
784+ }
731785 } ;
732786 }
733787 PartitionState :: Crates => {
0 commit comments