@@ -63,7 +63,7 @@ use rustc::util::nodemap::{FxHashMap, FxHashSet};
63
63
use rustc:: session:: config:: nightly_options:: is_nightly_build;
64
64
use rustc_data_structures:: flock;
65
65
66
- use clean:: { self , AttributesExt , GetDefId , SelfTy , Mutability } ;
66
+ use clean:: { self , AttributesExt , GetDefId , SelfTy , Mutability , Span } ;
67
67
use doctree;
68
68
use fold:: DocFolder ;
69
69
use html:: escape:: Escape ;
@@ -124,6 +124,9 @@ pub struct SharedContext {
124
124
/// The given user css file which allow to customize the generated
125
125
/// documentation theme.
126
126
pub css_file_extension : Option < PathBuf > ,
127
+ /// Warnings for the user if rendering would differ using different markdown
128
+ /// parsers.
129
+ pub markdown_warnings : RefCell < Vec < ( Span , String , Vec < html_diff:: Difference > ) > > ,
127
130
}
128
131
129
132
/// Indicates where an external crate can be found.
@@ -457,6 +460,7 @@ pub fn run(mut krate: clean::Crate,
457
460
krate : krate. name . clone ( ) ,
458
461
} ,
459
462
css_file_extension : css_file_extension. clone ( ) ,
463
+ markdown_warnings : RefCell :: new ( vec ! [ ] ) ,
460
464
} ;
461
465
462
466
// If user passed in `--playground-url` arg, we fill in crate name here
@@ -579,8 +583,102 @@ pub fn run(mut krate: clean::Crate,
579
583
580
584
write_shared ( & cx, & krate, & * cache, index) ?;
581
585
586
+ let scx = cx. shared . clone ( ) ;
587
+
582
588
// And finally render the whole crate's documentation
583
- cx. krate ( krate)
589
+ let result = cx. krate ( krate) ;
590
+
591
+ let markdown_warnings = scx. markdown_warnings . borrow ( ) ;
592
+ if !markdown_warnings. is_empty ( ) {
593
+ println ! ( "WARNING: documentation for this crate may be rendered \
594
+ differently using the new Pulldown renderer.") ;
595
+ println ! ( " See https://github.com/rust-lang/rust/issues/44229 for details." ) ;
596
+ for & ( ref span, ref text, ref diffs) in & * markdown_warnings {
597
+ println ! ( "WARNING: rendering difference in `{}`" , concise_str( text) ) ;
598
+ println ! ( " --> {}:{}:{}" , span. filename, span. loline, span. locol) ;
599
+ for d in diffs {
600
+ render_difference ( d) ;
601
+ }
602
+ }
603
+ }
604
+
605
+ result
606
+ }
607
+
608
+ // A short, single-line view of `s`.
609
+ fn concise_str ( s : & str ) -> String {
610
+ if s. contains ( '\n' ) {
611
+ return format ! ( "{}..." , s. lines( ) . next( ) . expect( "Impossible! We just found a newline" ) ) ;
612
+ }
613
+ if s. len ( ) > 70 {
614
+ return format ! ( "{} ... {}" , & s[ ..50 ] , & s[ s. len( ) -20 ..] ) ;
615
+ }
616
+ s. to_owned ( )
617
+ }
618
+
619
+ // Returns short versions of s1 and s2, starting from where the strings differ.
620
+ fn concise_compared_strs ( s1 : & str , s2 : & str ) -> ( String , String ) {
621
+ let s1 = s1. trim ( ) ;
622
+ let s2 = s2. trim ( ) ;
623
+ if !s1. contains ( '\n' ) && !s2. contains ( '\n' ) && s1. len ( ) <= 70 && s2. len ( ) <= 70 {
624
+ return ( s1. to_owned ( ) , s2. to_owned ( ) ) ;
625
+ }
626
+
627
+ let mut start_byte = 0 ;
628
+ for ( c1, c2) in s1. chars ( ) . zip ( s2. chars ( ) ) {
629
+ if c1 != c2 {
630
+ break ;
631
+ }
632
+
633
+ start_byte += c1. len_utf8 ( ) ;
634
+ }
635
+
636
+ if start_byte == 0 {
637
+ return ( concise_str ( s1) , concise_str ( s2) ) ;
638
+ }
639
+
640
+ let s1 = & s1[ start_byte..] ;
641
+ let s2 = & s2[ start_byte..] ;
642
+ ( format ! ( "...{}" , concise_str( s1) ) , format ! ( "...{}" , concise_str( s2) ) )
643
+ }
644
+
645
+ fn render_difference ( diff : & html_diff:: Difference ) {
646
+ match * diff {
647
+ html_diff:: Difference :: NodeType { ref elem, ref opposite_elem } => {
648
+ println ! ( " {} Types differ: expected: `{}`, found: `{}`" ,
649
+ elem. path, elem. element_name, opposite_elem. element_name) ;
650
+ }
651
+ html_diff:: Difference :: NodeName { ref elem, ref opposite_elem } => {
652
+ println ! ( " {} Tags differ: expected: `{}`, found: `{}`" ,
653
+ elem. path, elem. element_name, opposite_elem. element_name) ;
654
+ }
655
+ html_diff:: Difference :: NodeAttributes { ref elem,
656
+ ref elem_attributes,
657
+ ref opposite_elem_attributes,
658
+ .. } => {
659
+ println ! ( " {} Attributes differ in `{}`: expected: `{:?}`, found: `{:?}`" ,
660
+ elem. path, elem. element_name, elem_attributes, opposite_elem_attributes) ;
661
+ }
662
+ html_diff:: Difference :: NodeText { ref elem, ref elem_text, ref opposite_elem_text, .. } => {
663
+ let ( s1, s2) = concise_compared_strs ( elem_text, opposite_elem_text) ;
664
+ println ! ( " {} Text differs:\n expected: `{}`\n found: `{}`" ,
665
+ elem. path, s1, s2) ;
666
+ }
667
+ html_diff:: Difference :: NotPresent { ref elem, ref opposite_elem } => {
668
+ if let Some ( ref elem) = * elem {
669
+ println ! ( " {} One element is missing: expected: `{}`" ,
670
+ elem. path, elem. element_name) ;
671
+ } else if let Some ( ref elem) = * opposite_elem {
672
+ if elem. element_name . is_empty ( ) {
673
+ println ! ( " {} Unexpected element: `{}`" ,
674
+ elem. path, concise_str( & elem. element_content) ) ;
675
+ } else {
676
+ println ! ( " {} Unexpected element `{}`: found: `{}`" ,
677
+ elem. path, elem. element_name, concise_str( & elem. element_content) ) ;
678
+ }
679
+ }
680
+ }
681
+ }
584
682
}
585
683
586
684
/// Build the search index from the collected metadata
@@ -1641,47 +1739,92 @@ fn plain_summary_line(s: Option<&str>) -> String {
1641
1739
fn document ( w : & mut fmt:: Formatter , cx : & Context , item : & clean:: Item ) -> fmt:: Result {
1642
1740
document_stability ( w, cx, item) ?;
1643
1741
let prefix = render_assoc_const_value ( item) ;
1644
- document_full ( w, item, cx. render_type , & prefix) ?;
1742
+ document_full ( w, item, cx, & prefix) ?;
1645
1743
Ok ( ( ) )
1646
1744
}
1647
1745
1648
- fn get_html_diff ( w : & mut fmt:: Formatter , md_text : & str , render_type : RenderType ,
1649
- prefix : & str ) -> fmt:: Result {
1650
- let output = format ! ( "{}" , Markdown ( md_text, render_type) ) ;
1651
- let old = format ! ( "{}" , Markdown ( md_text, match render_type {
1652
- RenderType :: Hoedown => RenderType :: Pulldown ,
1653
- RenderType :: Pulldown => RenderType :: Hoedown ,
1654
- } ) ) ;
1655
- let differences = html_diff:: get_differences ( & output, & old) ;
1656
- if !differences. is_empty ( ) {
1657
- println ! ( "Differences spotted in {:?}:\n {}" ,
1658
- md_text,
1659
- differences. iter( )
1660
- . filter_map( |s| {
1661
- match * s {
1662
- html_diff:: Difference :: NodeText { ref elem_text,
1663
- ref opposite_elem_text,
1664
- .. }
1665
- if elem_text. trim( ) == opposite_elem_text. trim( ) => None ,
1666
- _ => Some ( format!( "=> {}" , s. to_string( ) ) ) ,
1667
- }
1668
- } )
1669
- . collect:: <Vec <String >>( )
1670
- . join( "\n " ) ) ;
1671
- }
1746
+ /// Render md_text as markdown. Warns the user if there are difference in
1747
+ /// rendering between Pulldown and Hoedown.
1748
+ fn render_markdown ( w : & mut fmt:: Formatter ,
1749
+ md_text : & str ,
1750
+ span : Span ,
1751
+ render_type : RenderType ,
1752
+ prefix : & str ,
1753
+ scx : & SharedContext )
1754
+ -> fmt:: Result {
1755
+ let hoedown_output = format ! ( "{}" , Markdown ( md_text, RenderType :: Hoedown ) ) ;
1756
+ // We only emit warnings if the user has opted-in to Pulldown rendering.
1757
+ let output = if render_type == RenderType :: Pulldown {
1758
+ let pulldown_output = format ! ( "{}" , Markdown ( md_text, RenderType :: Pulldown ) ) ;
1759
+ let differences = html_diff:: get_differences ( & pulldown_output, & hoedown_output) ;
1760
+ let differences = differences. into_iter ( )
1761
+ . filter ( |s| {
1762
+ match * s {
1763
+ html_diff:: Difference :: NodeText { ref elem_text,
1764
+ ref opposite_elem_text,
1765
+ .. }
1766
+ if match_non_whitespace ( elem_text, opposite_elem_text) => false ,
1767
+ _ => true ,
1768
+ }
1769
+ } )
1770
+ . collect :: < Vec < _ > > ( ) ;
1771
+
1772
+ if !differences. is_empty ( ) {
1773
+ scx. markdown_warnings . borrow_mut ( ) . push ( ( span, md_text. to_owned ( ) , differences) ) ;
1774
+ }
1775
+
1776
+ pulldown_output
1777
+ } else {
1778
+ hoedown_output
1779
+ } ;
1780
+
1672
1781
write ! ( w, "<div class='docblock'>{}{}</div>" , prefix, output)
1673
1782
}
1674
1783
1784
+ // Returns true iff s1 and s2 match, ignoring whitespace.
1785
+ fn match_non_whitespace ( s1 : & str , s2 : & str ) -> bool {
1786
+ let s1 = s1. trim ( ) ;
1787
+ let s2 = s2. trim ( ) ;
1788
+ let mut cs1 = s1. chars ( ) ;
1789
+ let mut cs2 = s2. chars ( ) ;
1790
+ while let Some ( c1) = cs1. next ( ) {
1791
+ if c1. is_whitespace ( ) {
1792
+ continue ;
1793
+ }
1794
+
1795
+ loop {
1796
+ if let Some ( c2) = cs2. next ( ) {
1797
+ if !c2. is_whitespace ( ) {
1798
+ if c1 != c2 {
1799
+ return false ;
1800
+ }
1801
+ break ;
1802
+ }
1803
+ } else {
1804
+ return false ;
1805
+ }
1806
+ }
1807
+ }
1808
+
1809
+ while let Some ( c2) = cs2. next ( ) {
1810
+ if !c2. is_whitespace ( ) {
1811
+ return false ;
1812
+ }
1813
+ }
1814
+
1815
+ true
1816
+ }
1817
+
1675
1818
fn document_short ( w : & mut fmt:: Formatter , item : & clean:: Item , link : AssocItemLink ,
1676
- render_type : RenderType , prefix : & str ) -> fmt:: Result {
1819
+ cx : & Context , prefix : & str ) -> fmt:: Result {
1677
1820
if let Some ( s) = item. doc_value ( ) {
1678
1821
let markdown = if s. contains ( '\n' ) {
1679
1822
format ! ( "{} [Read more]({})" ,
1680
1823
& plain_summary_line( Some ( s) ) , naive_assoc_href( item, link) )
1681
1824
} else {
1682
1825
format ! ( "{}" , & plain_summary_line( Some ( s) ) )
1683
1826
} ;
1684
- get_html_diff ( w, & markdown, render_type, prefix) ?;
1827
+ render_markdown ( w, & markdown, item . source . clone ( ) , cx . render_type , prefix, & cx . shared ) ?;
1685
1828
} else if !prefix. is_empty ( ) {
1686
1829
write ! ( w, "<div class='docblock'>{}</div>" , prefix) ?;
1687
1830
}
@@ -1703,9 +1846,9 @@ fn render_assoc_const_value(item: &clean::Item) -> String {
1703
1846
}
1704
1847
1705
1848
fn document_full ( w : & mut fmt:: Formatter , item : & clean:: Item ,
1706
- render_type : RenderType , prefix : & str ) -> fmt:: Result {
1849
+ cx : & Context , prefix : & str ) -> fmt:: Result {
1707
1850
if let Some ( s) = item. doc_value ( ) {
1708
- get_html_diff ( w, s, render_type, prefix) ?;
1851
+ render_markdown ( w, s, item . source . clone ( ) , cx . render_type , prefix, & cx . shared ) ?;
1709
1852
} else if !prefix. is_empty ( ) {
1710
1853
write ! ( w, "<div class='docblock'>{}</div>" , prefix) ?;
1711
1854
}
@@ -3104,20 +3247,20 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi
3104
3247
// because impls can't have a stability.
3105
3248
document_stability ( w, cx, it) ?;
3106
3249
if item. doc_value ( ) . is_some ( ) {
3107
- document_full ( w, item, cx. render_type , & prefix) ?;
3250
+ document_full ( w, item, cx, & prefix) ?;
3108
3251
} else {
3109
3252
// In case the item isn't documented,
3110
3253
// provide short documentation from the trait.
3111
- document_short ( w, it, link, cx. render_type , & prefix) ?;
3254
+ document_short ( w, it, link, cx, & prefix) ?;
3112
3255
}
3113
3256
}
3114
3257
} else {
3115
3258
document_stability ( w, cx, item) ?;
3116
- document_full ( w, item, cx. render_type , & prefix) ?;
3259
+ document_full ( w, item, cx, & prefix) ?;
3117
3260
}
3118
3261
} else {
3119
3262
document_stability ( w, cx, item) ?;
3120
- document_short ( w, item, link, cx. render_type , & prefix) ?;
3263
+ document_short ( w, item, link, cx, & prefix) ?;
3121
3264
}
3122
3265
}
3123
3266
Ok ( ( ) )
@@ -3586,3 +3729,35 @@ fn test_name_sorting() {
3586
3729
sorted. sort_by_key ( |& s| name_key ( s) ) ;
3587
3730
assert_eq ! ( names, sorted) ;
3588
3731
}
3732
+
3733
+ #[ cfg( test) ]
3734
+ #[ test]
3735
+ fn test_match_non_whitespace ( ) {
3736
+ assert ! ( match_non_whitespace( "" , "" ) ) ;
3737
+ assert ! ( match_non_whitespace( " " , "" ) ) ;
3738
+ assert ! ( match_non_whitespace( "" , " " ) ) ;
3739
+
3740
+ assert ! ( match_non_whitespace( "a" , "a" ) ) ;
3741
+ assert ! ( match_non_whitespace( " a " , "a" ) ) ;
3742
+ assert ! ( match_non_whitespace( "a" , " a" ) ) ;
3743
+ assert ! ( match_non_whitespace( "abc" , "abc" ) ) ;
3744
+ assert ! ( match_non_whitespace( "abc" , " abc " ) ) ;
3745
+ assert ! ( match_non_whitespace( "abc " , "abc" ) ) ;
3746
+ assert ! ( match_non_whitespace( "abc xyz" , "abc xyz" ) ) ;
3747
+ assert ! ( match_non_whitespace( "abc xyz" , "abc\n xyz" ) ) ;
3748
+ assert ! ( match_non_whitespace( "abc xyz" , "abcxyz" ) ) ;
3749
+ assert ! ( match_non_whitespace( "abcxyz" , "abc xyz" ) ) ;
3750
+ assert ! ( match_non_whitespace( "abc xyz " , " abc xyz\n " ) ) ;
3751
+
3752
+ assert ! ( !match_non_whitespace( "a" , "b" ) ) ;
3753
+ assert ! ( !match_non_whitespace( " a " , "c" ) ) ;
3754
+ assert ! ( !match_non_whitespace( "a" , " aa" ) ) ;
3755
+ assert ! ( !match_non_whitespace( "abc" , "ac" ) ) ;
3756
+ assert ! ( !match_non_whitespace( "abc" , " adc " ) ) ;
3757
+ assert ! ( !match_non_whitespace( "abc " , "abca" ) ) ;
3758
+ assert ! ( !match_non_whitespace( "abc xyz" , "abc xy" ) ) ;
3759
+ assert ! ( !match_non_whitespace( "abc xyz" , "bc\n xyz" ) ) ;
3760
+ assert ! ( !match_non_whitespace( "abc xyz" , "abc.xyz" ) ) ;
3761
+ assert ! ( !match_non_whitespace( "abcxyz" , "abc.xyz" ) ) ;
3762
+ assert ! ( !match_non_whitespace( "abc xyz " , " abc xyz w" ) ) ;
3763
+ }
0 commit comments