@@ -44,6 +44,7 @@ use rustc::hir;
44
44
45
45
use rustc_const_math:: ConstInt ;
46
46
use std:: { mem, slice, vec} ;
47
+ use std:: iter:: FromIterator ;
47
48
use std:: path:: PathBuf ;
48
49
use std:: rc:: Rc ;
49
50
use std:: sync:: Arc ;
@@ -300,6 +301,11 @@ impl Item {
300
301
pub fn doc_value < ' a > ( & ' a self ) -> Option < & ' a str > {
301
302
self . attrs . doc_value ( )
302
303
}
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
+ }
303
309
pub fn is_crate ( & self ) -> bool {
304
310
match self . inner {
305
311
StrippedItem ( box ModuleItem ( Module { is_crate : true , ..} ) ) |
@@ -564,9 +570,69 @@ impl<I: IntoIterator<Item=ast::NestedMetaItem>> NestedAttributesExt for I {
564
570
}
565
571
}
566
572
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
+
567
633
#[ derive( Clone , RustcEncodable , RustcDecodable , PartialEq , Debug , Default ) ]
568
634
pub struct Attributes {
569
- pub doc_strings : Vec < String > ,
635
+ pub doc_strings : Vec < DocFragment > ,
570
636
pub other_attrs : Vec < ast:: Attribute > ,
571
637
pub cfg : Option < Rc < Cfg > > ,
572
638
pub span : Option < syntax_pos:: Span > ,
@@ -596,6 +662,47 @@ impl Attributes {
596
662
None
597
663
}
598
664
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
+
599
706
pub fn has_doc_flag ( & self , flag : & str ) -> bool {
600
707
for attr in & self . other_attrs {
601
708
if !attr. check_name ( "doc" ) { continue ; }
@@ -610,18 +717,29 @@ impl Attributes {
610
717
false
611
718
}
612
719
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 {
614
722
let mut doc_strings = vec ! [ ] ;
615
723
let mut sp = None ;
616
724
let mut cfg = Cfg :: True ;
725
+ let mut doc_line = 0 ;
617
726
618
727
let other_attrs = attrs. iter ( ) . filter_map ( |attr| {
619
728
attr. with_desugared_doc ( |attr| {
620
729
if attr. check_name ( "doc" ) {
621
730
if let Some ( mi) = attr. meta ( ) {
622
731
if let Some ( value) = mi. value_str ( ) {
623
732
// 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
+
625
743
if sp. is_none ( ) {
626
744
sp = Some ( attr. span ) ;
627
745
}
@@ -633,6 +751,14 @@ impl Attributes {
633
751
Err ( e) => diagnostic. span_err ( e. span , e. msg ) ,
634
752
}
635
753
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) ) ;
636
762
}
637
763
}
638
764
}
@@ -650,7 +776,17 @@ impl Attributes {
650
776
/// Finds the `doc` attribute as a NameValue and returns the corresponding
651
777
/// value found.
652
778
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
+ }
654
790
}
655
791
}
656
792
0 commit comments