From 0287b8ee0d14a480863897de5a11d7635ed53884 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Fri, 14 Feb 2025 07:31:00 +0000 Subject: [PATCH 1/4] Simplify test for escaping with tags --- src/librustdoc/html/escape/tests.rs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/librustdoc/html/escape/tests.rs b/src/librustdoc/html/escape/tests.rs index de702e1606353..0891249baf706 100644 --- a/src/librustdoc/html/escape/tests.rs +++ b/src/librustdoc/html/escape/tests.rs @@ -47,21 +47,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("")); From f401d645035e773138bfa2139cdfbef7959f29d9 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Fri, 14 Feb 2025 07:33:45 +0000 Subject: [PATCH 2/4] De-dup escaping logic --- src/librustdoc/html/escape.rs | 78 ++++++++++++++--------------------- 1 file changed, 30 insertions(+), 48 deletions(-) diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs index 88654ed32da93..de3de70ce1c2e 100644 --- a/src/librustdoc/html/escape.rs +++ b/src/librustdoc/html/escape.rs @@ -7,37 +7,41 @@ use std::fmt; use unicode_segmentation::UnicodeSegmentation; +#[inline(always)] +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(()) +} + /// 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); impl fmt::Display for Escape<'_> { 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(()) + escape(self.0, fmt, true) } } @@ -51,29 +55,7 @@ pub(crate) struct EscapeBodyText<'a>(pub &'a str); 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(()) + escape(self.0, fmt, false) } } From 896668b12a24b6bd5c65833dd65364688ed1f0ed Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Wed, 19 Feb 2025 13:34:58 +0000 Subject: [PATCH 3/4] Make `Escape` and `EscapeBodyText` support lazy processing --- src/librustdoc/html/escape.rs | 35 +++++++++++++++++++----- src/librustdoc/html/escape/tests.rs | 8 ++++++ src/librustdoc/html/format.rs | 2 +- src/librustdoc/html/render/mod.rs | 6 ++-- src/librustdoc/html/render/print_item.rs | 8 ++++-- src/librustdoc/lib.rs | 1 + 6 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs index de3de70ce1c2e..51663c7aae00f 100644 --- a/src/librustdoc/html/escape.rs +++ b/src/librustdoc/html/escape.rs @@ -7,7 +7,7 @@ use std::fmt; use unicode_segmentation::UnicodeSegmentation; -#[inline(always)] +#[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 @@ -35,13 +35,30 @@ fn escape(s: &str, mut w: impl fmt::Write, escape_quotes: bool) -> fmt::Result { 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 { - escape(self.0, fmt, true) + self.0.fmt( + &mut fmt + .options() + .create_formatter(&mut WriteEscaped { writer: fmt, escape_quotes: true }), + ) } } @@ -51,11 +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 { - escape(self.0, fmt, false) + 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 0891249baf706..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() { 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)] From 1dd2f33c6d02599aaa48c71ba6b76ff1a3cd87ec Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Wed, 19 Feb 2025 13:35:16 +0000 Subject: [PATCH 4/4] Make `render_long_plain` return an `impl fmt::Display` --- src/librustdoc/clean/cfg.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 {