@@ -44,6 +44,7 @@ use rustc::hir;
4444
4545use rustc_const_math:: ConstInt ;
4646use std:: { mem, slice, vec} ;
47+ use std:: iter:: FromIterator ;
4748use std:: path:: PathBuf ;
4849use std:: rc:: Rc ;
4950use std:: sync:: Arc ;
@@ -300,6 +301,11 @@ impl Item {
300301 pub fn doc_value < ' a > ( & ' a self ) -> Option < & ' a str > {
301302 self . attrs . doc_value ( )
302303 }
304+ /// Finds all `doc` attributes as NameValues and returns their corresponding values, joined
305+ /// with newlines.
306+ pub fn collapsed_doc_value ( & self ) -> Option < String > {
307+ self . attrs . collapsed_doc_value ( )
308+ }
303309 pub fn is_crate ( & self ) -> bool {
304310 match self . inner {
305311 StrippedItem ( box ModuleItem ( Module { is_crate : true , ..} ) ) |
@@ -564,9 +570,69 @@ impl<I: IntoIterator<Item=ast::NestedMetaItem>> NestedAttributesExt for I {
564570 }
565571}
566572
573+ /// A portion of documentation, extracted from a `#[doc]` attribute.
574+ ///
575+ /// Each variant contains the line number within the complete doc-comment where the fragment
576+ /// starts, as well as the Span where the corresponding doc comment or attribute is located.
577+ ///
578+ /// Included files are kept separate from inline doc comments so that proper line-number
579+ /// information can be given when a doctest fails. Sugared doc comments and "raw" doc comments are
580+ /// kept separate because of issue #42760.
581+ #[ derive( Clone , RustcEncodable , RustcDecodable , PartialEq , Debug ) ]
582+ pub enum DocFragment {
583+ // FIXME #44229 (misdreavus): sugared and raw doc comments can be brought back together once
584+ // hoedown is completely removed from rustdoc.
585+ /// A doc fragment created from a `///` or `//!` doc comment.
586+ SugaredDoc ( usize , syntax_pos:: Span , String ) ,
587+ /// A doc fragment created from a "raw" `#[doc=""]` attribute.
588+ RawDoc ( usize , syntax_pos:: Span , String ) ,
589+ /// A doc fragment created from a `#[doc(include="filename")]` attribute. Contains both the
590+ /// given filename and the file contents.
591+ Include ( usize , syntax_pos:: Span , String , String ) ,
592+ }
593+
594+ impl DocFragment {
595+ pub fn as_str ( & self ) -> & str {
596+ match * self {
597+ DocFragment :: SugaredDoc ( _, _, ref s) => & s[ ..] ,
598+ DocFragment :: RawDoc ( _, _, ref s) => & s[ ..] ,
599+ DocFragment :: Include ( _, _, _, ref s) => & s[ ..] ,
600+ }
601+ }
602+
603+ pub fn span ( & self ) -> syntax_pos:: Span {
604+ match * self {
605+ DocFragment :: SugaredDoc ( _, span, _) |
606+ DocFragment :: RawDoc ( _, span, _) |
607+ DocFragment :: Include ( _, span, _, _) => span,
608+ }
609+ }
610+ }
611+
612+ impl < ' a > FromIterator < & ' a DocFragment > for String {
613+ fn from_iter < T > ( iter : T ) -> Self
614+ where
615+ T : IntoIterator < Item = & ' a DocFragment >
616+ {
617+ iter. into_iter ( ) . fold ( String :: new ( ) , |mut acc, frag| {
618+ if !acc. is_empty ( ) {
619+ acc. push ( '\n' ) ;
620+ }
621+ match * frag {
622+ DocFragment :: SugaredDoc ( _, _, ref docs)
623+ | DocFragment :: RawDoc ( _, _, ref docs)
624+ | DocFragment :: Include ( _, _, _, ref docs) =>
625+ acc. push_str ( docs) ,
626+ }
627+
628+ acc
629+ } )
630+ }
631+ }
632+
567633#[ derive( Clone , RustcEncodable , RustcDecodable , PartialEq , Debug , Default ) ]
568634pub struct Attributes {
569- pub doc_strings : Vec < String > ,
635+ pub doc_strings : Vec < DocFragment > ,
570636 pub other_attrs : Vec < ast:: Attribute > ,
571637 pub cfg : Option < Rc < Cfg > > ,
572638 pub span : Option < syntax_pos:: Span > ,
@@ -596,6 +662,47 @@ impl Attributes {
596662 None
597663 }
598664
665+ /// Reads a `MetaItem` from within an attribute, looks for whether it is a
666+ /// `#[doc(include="file")]`, and returns the filename and contents of the file as loaded from
667+ /// its expansion.
668+ fn extract_include ( mi : & ast:: MetaItem )
669+ -> Option < ( String , String ) >
670+ {
671+ mi. meta_item_list ( ) . and_then ( |list| {
672+ for meta in list {
673+ if meta. check_name ( "include" ) {
674+ // the actual compiled `#[doc(include="filename")]` gets expanded to
675+ // `#[doc(include(file="filename", contents="file contents")]` so we need to
676+ // look for that instead
677+ return meta. meta_item_list ( ) . and_then ( |list| {
678+ let mut filename: Option < String > = None ;
679+ let mut contents: Option < String > = None ;
680+
681+ for it in list {
682+ if it. check_name ( "file" ) {
683+ if let Some ( name) = it. value_str ( ) {
684+ filename = Some ( name. to_string ( ) ) ;
685+ }
686+ } else if it. check_name ( "contents" ) {
687+ if let Some ( docs) = it. value_str ( ) {
688+ contents = Some ( docs. to_string ( ) ) ;
689+ }
690+ }
691+ }
692+
693+ if let ( Some ( filename) , Some ( contents) ) = ( filename, contents) {
694+ Some ( ( filename, contents) )
695+ } else {
696+ None
697+ }
698+ } ) ;
699+ }
700+ }
701+
702+ None
703+ } )
704+ }
705+
599706 pub fn has_doc_flag ( & self , flag : & str ) -> bool {
600707 for attr in & self . other_attrs {
601708 if !attr. check_name ( "doc" ) { continue ; }
@@ -610,18 +717,29 @@ impl Attributes {
610717 false
611718 }
612719
613- pub fn from_ast ( diagnostic : & :: errors:: Handler , attrs : & [ ast:: Attribute ] ) -> Attributes {
720+ pub fn from_ast ( diagnostic : & :: errors:: Handler ,
721+ attrs : & [ ast:: Attribute ] ) -> Attributes {
614722 let mut doc_strings = vec ! [ ] ;
615723 let mut sp = None ;
616724 let mut cfg = Cfg :: True ;
725+ let mut doc_line = 0 ;
617726
618727 let other_attrs = attrs. iter ( ) . filter_map ( |attr| {
619728 attr. with_desugared_doc ( |attr| {
620729 if attr. check_name ( "doc" ) {
621730 if let Some ( mi) = attr. meta ( ) {
622731 if let Some ( value) = mi. value_str ( ) {
623732 // Extracted #[doc = "..."]
624- doc_strings. push ( value. to_string ( ) ) ;
733+ let value = value. to_string ( ) ;
734+ let line = doc_line;
735+ doc_line += value. lines ( ) . count ( ) ;
736+
737+ if attr. is_sugared_doc {
738+ doc_strings. push ( DocFragment :: SugaredDoc ( line, attr. span , value) ) ;
739+ } else {
740+ doc_strings. push ( DocFragment :: RawDoc ( line, attr. span , value) ) ;
741+ }
742+
625743 if sp. is_none ( ) {
626744 sp = Some ( attr. span ) ;
627745 }
@@ -633,6 +751,14 @@ impl Attributes {
633751 Err ( e) => diagnostic. span_err ( e. span , e. msg ) ,
634752 }
635753 return None ;
754+ } else if let Some ( ( filename, contents) ) = Attributes :: extract_include ( & mi)
755+ {
756+ let line = doc_line;
757+ doc_line += contents. lines ( ) . count ( ) ;
758+ doc_strings. push ( DocFragment :: Include ( line,
759+ attr. span ,
760+ filename,
761+ contents) ) ;
636762 }
637763 }
638764 }
@@ -650,7 +776,17 @@ impl Attributes {
650776 /// Finds the `doc` attribute as a NameValue and returns the corresponding
651777 /// value found.
652778 pub fn doc_value < ' a > ( & ' a self ) -> Option < & ' a str > {
653- self . doc_strings . first ( ) . map ( |s| & s[ ..] )
779+ self . doc_strings . first ( ) . map ( |s| s. as_str ( ) )
780+ }
781+
782+ /// Finds all `doc` attributes as NameValues and returns their corresponding values, joined
783+ /// with newlines.
784+ pub fn collapsed_doc_value ( & self ) -> Option < String > {
785+ if !self . doc_strings . is_empty ( ) {
786+ Some ( self . doc_strings . iter ( ) . collect ( ) )
787+ } else {
788+ None
789+ }
654790 }
655791}
656792
0 commit comments