diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index bec7fbe8f52bd..3a55c94c16c1c 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -189,14 +189,16 @@ impl Cfg { } /// Renders the configuration for long display, as a long plain text description. - pub(crate) fn render_long_plain(&self) -> String { + pub(crate) fn render_long_plain(&self) -> impl fmt::Display + '_ { let on = if self.should_use_with_in_description() { "with" } else { "on" }; - let mut msg = format!("Available {on} {}", Display(self, Format::LongPlain)); - if self.should_append_only_to_description() { - msg.push_str(" only"); - } - msg + fmt::from_fn(move |f| { + write!(f, "Available {on} {}", Display(self, Format::LongPlain))?; + if self.should_append_only_to_description() { + f.write_str(" only")?; + } + Ok(()) + }) } fn should_capitalize_first_letter(&self) -> bool { diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs index 88654ed32da93..51663c7aae00f 100644 --- a/src/librustdoc/html/escape.rs +++ b/src/librustdoc/html/escape.rs @@ -7,37 +7,58 @@ use std::fmt; use unicode_segmentation::UnicodeSegmentation; +#[inline] +fn escape(s: &str, mut w: impl fmt::Write, escape_quotes: bool) -> fmt::Result { + // Because the internet is always right, turns out there's not that many + // characters to escape: http://stackoverflow.com/questions/7381974 + let pile_o_bits = s; + let mut last = 0; + for (i, ch) in s.char_indices() { + let s = match ch { + '>' => ">", + '<' => "<", + '&' => "&", + '\'' if escape_quotes => "'", + '"' if escape_quotes => """, + _ => continue, + }; + w.write_str(&pile_o_bits[last..i])?; + w.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() { + w.write_str(&pile_o_bits[last..])?; + } + Ok(()) +} + +struct WriteEscaped { + writer: W, + escape_quotes: bool, +} + +impl fmt::Write for WriteEscaped { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + escape(s, &mut self.writer, self.escape_quotes) + } +} + /// Wrapper struct which will emit the HTML-escaped version of the contained /// string when passed to a format string. -pub(crate) struct Escape<'a>(pub &'a str); +pub(crate) struct Escape(pub T); -impl fmt::Display for Escape<'_> { +impl fmt::Display for Escape { + #[inline] 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 Escape(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(()) + self.0.fmt( + &mut fmt + .options() + .create_formatter(&mut WriteEscaped { writer: fmt, escape_quotes: true }), + ) } } @@ -47,33 +68,15 @@ impl fmt::Display for Escape<'_> { /// 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); +pub(crate) struct EscapeBodyText(pub T); -impl fmt::Display for EscapeBodyText<'_> { +impl fmt::Display for EscapeBodyText { 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(()) + self.0.fmt( + &mut fmt + .options() + .create_formatter(&mut WriteEscaped { writer: fmt, escape_quotes: false }), + ) } } diff --git a/src/librustdoc/html/escape/tests.rs b/src/librustdoc/html/escape/tests.rs index de702e1606353..9beb137f973eb 100644 --- a/src/librustdoc/html/escape/tests.rs +++ b/src/librustdoc/html/escape/tests.rs @@ -1,3 +1,11 @@ +use std::iter; + +#[test] +fn escape() { + use super::Escape as E; + assert_eq!(format!(" {}", E("")), " <World>"); +} + // basic examples #[test] fn escape_body_text_with_wbr() { @@ -47,21 +55,8 @@ fn escape_body_text_with_wbr_makes_sense() { use itertools::Itertools as _; use super::EscapeBodyTextWithWbr as E; - const C: [u8; 3] = [b'a', b'A', b'_']; - for chars in [ - C.into_iter(), - C.into_iter(), - C.into_iter(), - C.into_iter(), - C.into_iter(), - C.into_iter(), - C.into_iter(), - C.into_iter(), - ] - .into_iter() - .multi_cartesian_product() - { - let s = String::from_utf8(chars).unwrap(); + for chars in iter::repeat("aA_").take(8).map(str::chars).multi_cartesian_product() { + let s = chars.into_iter().collect::(); assert_eq!(s.len(), 8); let esc = E(&s).to_string(); assert!(!esc.contains("")); diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 91b4b3ba1ebac..0220db98fd999 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -853,7 +853,7 @@ pub(crate) fn anchor<'a: 'cx, 'cx>( f, r#"{text}"#, path = join_with_double_colon(&fqp), - text = EscapeBodyText(text.as_str()), + text = EscapeBodyText(text), ) } else { f.write_str(text.as_str()) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 204631063a23a..a54d8bb8781b0 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -720,7 +720,7 @@ fn short_item_info( } DeprecatedSince::Future => String::from("Deprecating in a future version"), DeprecatedSince::NonStandard(since) => { - format!("Deprecated since {}", Escape(since.as_str())) + format!("Deprecated since {}", Escape(since)) } DeprecatedSince::Unspecified | DeprecatedSince::Err => String::from("Deprecated"), }; @@ -1518,8 +1518,8 @@ pub(crate) fn notable_traits_button<'a, 'tcx>( fmt::from_fn(|f| { write!( f, - " ", - ty = Escape(&format!("{:#}", ty.print(cx))), + " ", + ty = Escape(ty.print(cx)), ) }) }) diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index f320114703996..1dfaee7b11f85 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -534,12 +534,16 @@ fn extra_info_tags<'a, 'tcx: 'a>( import_def_id: Option, ) -> impl Display + 'a + Captures<'tcx> { fmt::from_fn(move |f| { - fn tag_html<'a>(class: &'a str, title: &'a str, contents: &'a str) -> impl Display + 'a { + fn tag_html<'a>( + class: impl fmt::Display + 'a, + title: impl fmt::Display + 'a, + contents: impl fmt::Display + 'a, + ) -> impl Display + 'a { fmt::from_fn(move |f| { write!( f, r#"{contents}"#, - title = Escape(title), + title = Escape(&title), ) }) } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index e4acbcf2c626f..5cbab1bcebe87 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -8,6 +8,7 @@ #![feature(debug_closure_helpers)] #![feature(file_buffered)] #![feature(format_args_nl)] +#![feature(formatting_options)] #![feature(if_let_guard)] #![feature(impl_trait_in_assoc_type)] #![feature(iter_intersperse)]