diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs
index 4a19d0a44c365..ea4b573aeb955 100644
--- a/src/librustdoc/html/escape.rs
+++ b/src/librustdoc/html/escape.rs
@@ -38,3 +38,39 @@ impl<'a> fmt::Display for Escape<'a> {
Ok(())
}
}
+
+/// Wrapper struct which will emit the HTML-escaped version of the contained
+/// string when passed to a format string.
+///
+/// This is only safe to use for text nodes. If you need your output to be
+/// safely contained in an attribute, use [`Escape`]. If you don't know the
+/// difference, use [`Escape`].
+pub(crate) struct EscapeBodyText<'a>(pub &'a str);
+
+impl<'a> fmt::Display for EscapeBodyText<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // Because the internet is always right, turns out there's not that many
+ // characters to escape: http://stackoverflow.com/questions/7381974
+ let EscapeBodyText(s) = *self;
+ let pile_o_bits = s;
+ let mut last = 0;
+ for (i, ch) in s.char_indices() {
+ let s = match ch {
+ '>' => ">",
+ '<' => "<",
+ '&' => "&",
+ _ => continue,
+ };
+ fmt.write_str(&pile_o_bits[last..i])?;
+ fmt.write_str(s)?;
+ // NOTE: we only expect single byte characters here - which is fine as long as we
+ // only match single byte characters
+ last = i + 1;
+ }
+
+ if last < s.len() {
+ fmt.write_str(&pile_o_bits[last..])?;
+ }
+ Ok(())
+ }
+}
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index b762c8a1ce61c..2f60ae6fbb155 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -6,7 +6,7 @@
//! Use the `render_with_highlighting` to highlight some rust code.
use crate::clean::PrimitiveType;
-use crate::html::escape::Escape;
+use crate::html::escape::EscapeBodyText;
use crate::html::render::{Context, LinkFromSrc};
use std::collections::VecDeque;
@@ -189,7 +189,7 @@ impl<'a, 'tcx, F: Write> TokenHandler<'a, 'tcx, F> {
&& can_merge(current_class, Some(*parent_class), "")
{
for (text, class) in self.pending_elems.iter() {
- string(self.out, Escape(text), *class, &self.href_context, false);
+ string(self.out, EscapeBodyText(text), *class, &self.href_context, false);
}
} else {
// We only want to "open" the tag ourselves if we have more than one pending and if the
@@ -202,7 +202,7 @@ impl<'a, 'tcx, F: Write> TokenHandler<'a, 'tcx, F> {
None
};
for (text, class) in self.pending_elems.iter() {
- string(self.out, Escape(text), *class, &self.href_context, close_tag.is_none());
+ string(self.out, EscapeBodyText(text), *class, &self.href_context, close_tag.is_none());
}
if let Some(close_tag) = close_tag {
exit_span(self.out, close_tag);