about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2017-05-07 10:52:26 +0000
committerbors <bors@rust-lang.org>2017-05-07 10:52:26 +0000
commitced823e267c132fab172b1890b24073995e79ffa (patch)
tree117de1fef83db2e8e211d08125fb33d28d3eb14d
parent892be3f30791aeec6ca408446b4505696eb21212 (diff)
parentffe12b1274732e791e117d8cc79b3db0f4161956 (diff)
downloadrust-ced823e267c132fab172b1890b24073995e79ffa.tar.gz
rust-ced823e267c132fab172b1890b24073995e79ffa.zip
Auto merge of #41785 - Mark-Simulacrum:issue-41783, r=GuillaumeGomez
Allow # to appear in rustdoc code output.

"##" at the start of a trimmed rustdoc line is now cut to "#" and then
shown. If the user wanted to show "##", they can type "###".

I'm somewhat concerned about the potential implications for users, since this does make a potentially backwards-incompatible change. Previously, `##` had no special handling, and now we do change it. However, I'm not really sure what we can do here to improve this, and I can't think of any cases where `##` would likely be correct in a code block, though of course I could be wrong.

Fixes #41783.
-rw-r--r--src/librustdoc/html/markdown.rs75
-rw-r--r--src/test/rustdoc/issue-41783.rs25
2 files changed, 73 insertions, 27 deletions
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 85a28bbfbc6..ec4a23b0417 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -66,18 +66,47 @@ pub struct MarkdownHtml<'a>(pub &'a str, pub RenderType);
 /// A unit struct like `Markdown`, that renders only the first paragraph.
 pub struct MarkdownSummaryLine<'a>(pub &'a str);
 
-/// Returns Some(code) if `s` is a line that should be stripped from
-/// documentation but used in example code. `code` is the portion of
-/// `s` that should be used in tests. (None for lines that should be
-/// left as-is.)
-fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
+/// Controls whether a line will be hidden or shown in HTML output.
+///
+/// All lines are used in documentation tests.
+enum Line<'a> {
+    Hidden(&'a str),
+    Shown(&'a str),
+}
+
+impl<'a> Line<'a> {
+    fn for_html(self) -> Option<&'a str> {
+        match self {
+            Line::Shown(l) => Some(l),
+            Line::Hidden(_) => None,
+        }
+    }
+
+    fn for_code(self) -> &'a str {
+        match self {
+            Line::Shown(l) |
+            Line::Hidden(l) => l,
+        }
+    }
+}
+
+// FIXME: There is a minor inconsistency here. For lines that start with ##, we
+// have no easy way of removing a potential single space after the hashes, which
+// is done in the single # case. This inconsistency seems okay, if non-ideal. In
+// order to fix it we'd have to iterate to find the first non-# character, and
+// then reallocate to remove it; which would make us return a String.
+fn map_line(s: &str) -> Line {
     let trimmed = s.trim();
-    if trimmed == "#" {
-        Some("")
+    if trimmed.starts_with("##") {
+        Line::Shown(&trimmed[1..])
     } else if trimmed.starts_with("# ") {
-        Some(&trimmed[2..])
+        // # text
+        Line::Hidden(&trimmed[2..])
+    } else if trimmed == "#" {
+        // We cannot handle '#text' because it could be #[attr].
+        Line::Hidden("")
     } else {
-        None
+        Line::Shown(s)
     }
 }
 
@@ -148,9 +177,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'a, I> {
                 _ => {}
             }
         }
-        let lines = origtext.lines().filter(|l| {
-            stripped_filtered_line(*l).is_none()
-        });
+        let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
         let text = lines.collect::<Vec<&str>>().join("\n");
         PLAYGROUND.with(|play| {
             // insert newline to clearly separate it from the
@@ -160,9 +187,9 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'a, I> {
                 if url.is_empty() {
                     return None;
                 }
-                let test = origtext.lines().map(|l| {
-                    stripped_filtered_line(l).unwrap_or(l)
-                }).collect::<Vec<&str>>().join("\n");
+                let test = origtext.lines()
+                    .map(|l| map_line(l).for_code())
+                    .collect::<Vec<&str>>().join("\n");
                 let krate = krate.as_ref().map(|s| &**s);
                 let test = test::maketest(&test, krate, false,
                                         &Default::default());
@@ -543,9 +570,7 @@ pub fn render(w: &mut fmt::Formatter,
                 }
             };
 
-            let lines = origtext.lines().filter(|l| {
-                stripped_filtered_line(*l).is_none()
-            });
+            let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
             let text = lines.collect::<Vec<&str>>().join("\n");
             if rendered { return }
             PLAYGROUND.with(|play| {
@@ -556,9 +581,9 @@ pub fn render(w: &mut fmt::Formatter,
                     if url.is_empty() {
                         return None;
                     }
-                    let test = origtext.lines().map(|l| {
-                        stripped_filtered_line(l).unwrap_or(l)
-                    }).collect::<Vec<&str>>().join("\n");
+                    let test = origtext.lines()
+                        .map(|l| map_line(l).for_code())
+                        .collect::<Vec<&str>>().join("\n");
                     let krate = krate.as_ref().map(|s| &**s);
                     let test = test::maketest(&test, krate, false,
                                               &Default::default());
@@ -734,9 +759,7 @@ pub fn old_find_testable_code(doc: &str, tests: &mut ::test::Collector, position
             let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
             let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
             let text = str::from_utf8(text).unwrap();
-            let lines = text.lines().map(|l| {
-                stripped_filtered_line(l).unwrap_or(l)
-            });
+            let lines = text.lines().map(|l| map_line(l).for_code());
             let text = lines.collect::<Vec<&str>>().join("\n");
             let filename = tests.get_filename();
 
@@ -827,9 +850,7 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Sp
                     }
                 }
                 let offset = offset.unwrap_or(0);
-                let lines = test_s.lines().map(|l| {
-                    stripped_filtered_line(l).unwrap_or(l)
-                });
+                let lines = test_s.lines().map(|l| map_line(l).for_code());
                 let text = lines.collect::<Vec<&str>>().join("\n");
                 nb_lines += doc[prev_offset..offset].lines().count();
                 let line = tests.get_line() + (nb_lines - 1);
diff --git a/src/test/rustdoc/issue-41783.rs b/src/test/rustdoc/issue-41783.rs
new file mode 100644
index 00000000000..3933b8bcbb8
--- /dev/null
+++ b/src/test/rustdoc/issue-41783.rs
@@ -0,0 +1,25 @@
+// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// @has issue_41783/struct.Foo.html
+// @!has - 'space'
+// @!has - 'comment'
+// @has - '# <span class="ident">single'
+// @has - '#<span class="attribute"># <span class="ident">double</span>'
+// @has - '#<span class="attribute">#<span class="attribute"># <span class="ident">triple</span>'
+
+/// ```no_run
+/// # # space
+/// # comment
+/// ## single
+/// ### double
+/// #### triple
+/// ```
+pub struct Foo;