diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 5ce62224d35e5..9450262b0af1f 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -223,6 +223,71 @@ impl<'p, 'a, I: Iterator- >> CodeBlocks<'p, 'a, I> {
}
}
+/// This iterator strips all the leading and trailing empty lines in the code block. For example:
+///
+/// ```
+///
+/// let x = 12;
+///
+/// // hello
+///
+/// ```
+///
+/// It'll only keep:
+///
+/// ```
+/// let x = 12;
+///
+/// // hello
+/// ```
+struct CodeblockTextIter<'a, T: Iterator
- >> {
+ iter: T,
+ found_first_non_empty: bool,
+ pending_content: VecDeque>,
+}
+
+impl<'a, T: Iterator
- >> CodeblockTextIter<'a, T> {
+ fn new(iter: T) -> Self {
+ Self { iter, found_first_non_empty: false, pending_content: VecDeque::new() }
+ }
+}
+
+impl<'a, T: Iterator
- >> Iterator for CodeblockTextIter<'a, T> {
+ type Item = Cow<'a, str>;
+
+ fn next(&mut self) -> Option {
+ // If there are still stored content because there were empty lines before a non-empty one,
+ // we need to provide all of them too.
+ if let Some(next) = self.pending_content.pop_front() {
+ return Some(next);
+ }
+ while let Some(next) = self.iter.next() {
+ // As long as we don't encounter the first non-empty lines, we skip all of them.
+ if !self.found_first_non_empty {
+ if !next.trim().is_empty() {
+ self.found_first_non_empty = true;
+ return Some(next);
+ }
+ } else if !next.trim().is_empty() {
+ // We need to check the buffer since it could have been filled in the meantime
+ // if empty lines were encountered.
+ if let Some(front) = self.pending_content.pop_front() {
+ self.pending_content.push_back(next);
+ return Some(front);
+ }
+ return Some(next);
+ } else {
+ // We encountered an empty line but it's maybe not the last line so we need to
+ // store it just in case.
+ self.pending_content.push_back(next);
+ }
+ }
+ // We clear the content in case `next` is called afterwards.
+ self.pending_content.clear();
+ None
+ }
+}
+
impl<'a, I: Iterator
- >> Iterator for CodeBlocks<'_, 'a, I> {
type Item = Event<'a>;
@@ -246,8 +311,8 @@ impl<'a, I: Iterator
- >> Iterator for CodeBlocks<'_, 'a, I> {
_ => {}
}
}
- let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
- let text = lines.intersperse("\n".into()).collect::();
+ let lines = CodeblockTextIter::new(origtext.lines().filter_map(|l| map_line(l).for_html()));
+ let text = lines.intersperse(Cow::Borrowed("\n")).collect::();
let parse_result = match kind {
CodeBlockKind::Fenced(ref lang) => {
diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs
index e4f72a057892f..e74bfafacd5d8 100644
--- a/src/librustdoc/html/markdown/tests.rs
+++ b/src/librustdoc/html/markdown/tests.rs
@@ -1,5 +1,6 @@
use super::{find_testable_code, plain_text_summary, short_markdown_summary};
use super::{ErrorCodes, HeadingOffset, IdMap, Ignore, LangString, Markdown, MarkdownItemInfo};
+use rustc_span::create_default_session_globals_then;
use rustc_span::edition::{Edition, DEFAULT_EDITION};
#[test]
@@ -309,3 +310,65 @@ fn test_find_testable_code_line() {
t("```rust\n```\n```rust\n```", &[1, 3]);
t("```rust\n```\n ```rust\n```", &[1, 3]);
}
+
+#[test]
+fn test_handling_of_starting_and_trailing_empty_lines() {
+ fn t(input: &str, expect: &str) {
+ let mut map = IdMap::new();
+ let output = Markdown {
+ content: input,
+ links: &[],
+ ids: &mut map,
+ error_codes: ErrorCodes::Yes,
+ edition: DEFAULT_EDITION,
+ playground: &None,
+ heading_offset: HeadingOffset::H2,
+ }
+ .into_string();
+ assert_eq!(output, expect, "original: {}", input);
+ }
+
+ create_default_session_globals_then(|| {
+ t(
+ "\
+```
+// hello
+
+
+f();
+
+// bye
+```",
+ r#"
+
+"#,
+ );
+ t(
+ "\
+```
+
+
+// hello
+
+f();
+
+// bye
+
+
+
+```",
+ r#"
+
+"#,
+ );
+ });
+}