@@ -63,7 +63,7 @@ use rustc::util::nodemap::{FxHashMap, FxHashSet};
6363use rustc:: session:: config:: nightly_options:: is_nightly_build;
6464use rustc_data_structures:: flock;
6565
66- use clean:: { self , AttributesExt , GetDefId , SelfTy , Mutability } ;
66+ use clean:: { self , AttributesExt , GetDefId , SelfTy , Mutability , Span } ;
6767use doctree;
6868use fold:: DocFolder ;
6969use html:: escape:: Escape ;
@@ -124,6 +124,9 @@ pub struct SharedContext {
124124 /// The given user css file which allow to customize the generated
125125 /// documentation theme.
126126 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 > ) > > ,
127130}
128131
129132/// Indicates where an external crate can be found.
@@ -457,6 +460,7 @@ pub fn run(mut krate: clean::Crate,
457460 krate : krate. name . clone ( ) ,
458461 } ,
459462 css_file_extension : css_file_extension. clone ( ) ,
463+ markdown_warnings : RefCell :: new ( vec ! [ ] ) ,
460464 } ;
461465
462466 // If user passed in `--playground-url` arg, we fill in crate name here
@@ -579,8 +583,102 @@ pub fn run(mut krate: clean::Crate,
579583
580584 write_shared ( & cx, & krate, & * cache, index) ?;
581585
586+ let scx = cx. shared . clone ( ) ;
587+
582588 // 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+ }
584682}
585683
586684/// Build the search index from the collected metadata
@@ -1641,47 +1739,92 @@ fn plain_summary_line(s: Option<&str>) -> String {
16411739fn document ( w : & mut fmt:: Formatter , cx : & Context , item : & clean:: Item ) -> fmt:: Result {
16421740 document_stability ( w, cx, item) ?;
16431741 let prefix = render_assoc_const_value ( item) ;
1644- document_full ( w, item, cx. render_type , & prefix) ?;
1742+ document_full ( w, item, cx, & prefix) ?;
16451743 Ok ( ( ) )
16461744}
16471745
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+
16721781 write ! ( w, "<div class='docblock'>{}{}</div>" , prefix, output)
16731782}
16741783
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+
16751818fn 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 {
16771820 if let Some ( s) = item. doc_value ( ) {
16781821 let markdown = if s. contains ( '\n' ) {
16791822 format ! ( "{} [Read more]({})" ,
16801823 & plain_summary_line( Some ( s) ) , naive_assoc_href( item, link) )
16811824 } else {
16821825 format ! ( "{}" , & plain_summary_line( Some ( s) ) )
16831826 } ;
1684- get_html_diff ( w, & markdown, render_type, prefix) ?;
1827+ render_markdown ( w, & markdown, item . source . clone ( ) , cx . render_type , prefix, & cx . shared ) ?;
16851828 } else if !prefix. is_empty ( ) {
16861829 write ! ( w, "<div class='docblock'>{}</div>" , prefix) ?;
16871830 }
@@ -1703,9 +1846,9 @@ fn render_assoc_const_value(item: &clean::Item) -> String {
17031846}
17041847
17051848fn document_full ( w : & mut fmt:: Formatter , item : & clean:: Item ,
1706- render_type : RenderType , prefix : & str ) -> fmt:: Result {
1849+ cx : & Context , prefix : & str ) -> fmt:: Result {
17071850 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 ) ?;
17091852 } else if !prefix. is_empty ( ) {
17101853 write ! ( w, "<div class='docblock'>{}</div>" , prefix) ?;
17111854 }
@@ -3104,20 +3247,20 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi
31043247 // because impls can't have a stability.
31053248 document_stability ( w, cx, it) ?;
31063249 if item. doc_value ( ) . is_some ( ) {
3107- document_full ( w, item, cx. render_type , & prefix) ?;
3250+ document_full ( w, item, cx, & prefix) ?;
31083251 } else {
31093252 // In case the item isn't documented,
31103253 // provide short documentation from the trait.
3111- document_short ( w, it, link, cx. render_type , & prefix) ?;
3254+ document_short ( w, it, link, cx, & prefix) ?;
31123255 }
31133256 }
31143257 } else {
31153258 document_stability ( w, cx, item) ?;
3116- document_full ( w, item, cx. render_type , & prefix) ?;
3259+ document_full ( w, item, cx, & prefix) ?;
31173260 }
31183261 } else {
31193262 document_stability ( w, cx, item) ?;
3120- document_short ( w, item, link, cx. render_type , & prefix) ?;
3263+ document_short ( w, item, link, cx, & prefix) ?;
31213264 }
31223265 }
31233266 Ok ( ( ) )
@@ -3586,3 +3729,35 @@ fn test_name_sorting() {
35863729 sorted. sort_by_key ( |& s| name_key ( s) ) ;
35873730 assert_eq ! ( names, sorted) ;
35883731}
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