From 17d40c6057c88f4c432b0d7bac88e1b84cb7e67f Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:03:36 +0200 Subject: Adding upstream version 1.65.0+dfsg1. Signed-off-by: Daniel Baumann --- src/librustdoc/html/format.rs | 45 +- src/librustdoc/html/highlight.rs | 355 +++++++++--- .../html/highlight/fixtures/decorations.html | 6 +- .../html/highlight/fixtures/dos_line.html | 2 +- .../html/highlight/fixtures/highlight.html | 8 +- src/librustdoc/html/highlight/fixtures/sample.html | 39 +- src/librustdoc/html/highlight/fixtures/sample.rs | 1 + src/librustdoc/html/highlight/fixtures/union.html | 10 +- src/librustdoc/html/highlight/tests.rs | 18 +- src/librustdoc/html/markdown.rs | 32 +- src/librustdoc/html/render/context.rs | 37 +- src/librustdoc/html/render/mod.rs | 399 ++++++++------ src/librustdoc/html/render/print_item.rs | 95 ++-- src/librustdoc/html/render/search_index.rs | 13 +- src/librustdoc/html/render/span_map.rs | 34 +- src/librustdoc/html/render/write_shared.rs | 79 ++- src/librustdoc/html/sources.rs | 22 +- src/librustdoc/html/static/css/rustdoc.css | 597 +++++++++------------ src/librustdoc/html/static/css/themes/ayu.css | 279 ++-------- src/librustdoc/html/static/css/themes/dark.css | 216 ++------ src/librustdoc/html/static/css/themes/light.css | 214 ++------ src/librustdoc/html/static/images/down-arrow.svg | 2 +- src/librustdoc/html/static/js/main.js | 89 +-- src/librustdoc/html/static/js/search.js | 31 +- src/librustdoc/html/static/js/source-script.js | 20 +- src/librustdoc/html/templates/page.html | 8 +- 26 files changed, 1243 insertions(+), 1408 deletions(-) (limited to 'src/librustdoc/html') diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 36a47b05c..b499e186c 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -152,7 +152,7 @@ impl Buffer { } } -fn comma_sep( +pub(crate) fn comma_sep( items: impl Iterator, space_after_comma: bool, ) -> impl fmt::Display { @@ -349,8 +349,7 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( let where_preds = comma_sep(where_predicates, false); let clause = if f.alternate() { if ending == Ending::Newline { - // add a space so stripping
tags and breaking spaces still renders properly - format!(" where{where_preds}, ") + format!(" where{where_preds},") } else { format!(" where{where_preds}") } @@ -364,20 +363,16 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( if ending == Ending::Newline { let mut clause = " ".repeat(indent.saturating_sub(1)); - // add a space so stripping
tags and breaking spaces still renders properly - write!( - clause, - " where{where_preds}, " - )?; + write!(clause, "where{where_preds},")?; clause } else { // insert a
tag after a single space but before multiple spaces at the start if indent == 0 { - format!("
where{where_preds}") + format!("
where{where_preds}") } else { let mut clause = br_with_padding; - clause.truncate(clause.len() - 5 * " ".len()); - write!(clause, " where{where_preds}")?; + clause.truncate(clause.len() - 4 * " ".len()); + write!(clause, "where{where_preds}")?; clause } } @@ -592,7 +587,7 @@ fn generate_macro_def_id_path( } }) .collect(); - let relative = fqp.iter().map(|elem| elem.to_string()); + let mut relative = fqp.iter().map(|elem| elem.to_string()); let cstore = CStore::from_tcx(tcx); // We need this to prevent a `panic` when this function is used from intra doc links... if !cstore.has_crate_data(def_id.krate) { @@ -612,7 +607,7 @@ fn generate_macro_def_id_path( let mut path = if is_macro_2 { once(crate_name.clone()).chain(relative).collect() } else { - vec![crate_name.clone(), relative.last().unwrap()] + vec![crate_name.clone(), relative.next_back().unwrap()] }; if path.len() < 2 { // The minimum we can have is the crate name followed by the macro name. If shorter, then @@ -1079,7 +1074,12 @@ fn fmt_type<'cx>( write!(f, "impl {}", print_generic_bounds(bounds, cx)) } } - clean::QPath { ref assoc, ref self_type, ref trait_, should_show_cast } => { + clean::QPath(box clean::QPathData { + ref assoc, + ref self_type, + ref trait_, + should_show_cast, + }) => { if f.alternate() { if should_show_cast { write!(f, "<{:#} as {:#}>::", self_type.print(cx), trait_.print(cx))? @@ -1305,22 +1305,19 @@ impl clean::FnDecl { ///
Used to determine line-wrapping. /// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is /// necessary. - /// * `asyncness`: Whether the function is async or not. pub(crate) fn full_print<'a, 'tcx: 'a>( &'a self, header_len: usize, indent: usize, - asyncness: hir::IsAsync, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx> { - display_fn(move |f| self.inner_full_print(header_len, indent, asyncness, f, cx)) + display_fn(move |f| self.inner_full_print(header_len, indent, f, cx)) } fn inner_full_print( &self, header_len: usize, indent: usize, - asyncness: hir::IsAsync, f: &mut fmt::Formatter<'_>, cx: &Context<'_>, ) -> fmt::Result { @@ -1385,15 +1382,9 @@ impl clean::FnDecl { args_plain.push_str(", ..."); } - let arrow_plain; - let arrow = if let hir::IsAsync::Async = asyncness { - let output = self.sugared_async_return_type(); - arrow_plain = format!("{:#}", output.print(cx)); - if f.alternate() { arrow_plain.clone() } else { format!("{}", output.print(cx)) } - } else { - arrow_plain = format!("{:#}", self.output.print(cx)); - if f.alternate() { arrow_plain.clone() } else { format!("{}", self.output.print(cx)) } - }; + let arrow_plain = format!("{:#}", self.output.print(cx)); + let arrow = + if f.alternate() { arrow_plain.clone() } else { format!("{}", self.output.print(cx)) }; let declaration_len = header_len + args_plain.len() + arrow_plain.len(); let output = if declaration_len > 80 { diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 05547ea15..8922bf377 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -29,31 +29,74 @@ pub(crate) struct HrefContext<'a, 'b, 'c> { /// This field is used to know "how far" from the top of the directory we are to link to either /// documentation pages or other source pages. pub(crate) root_path: &'c str, + /// This field is used to calculate precise local URLs. + pub(crate) current_href: &'c str, } /// Decorations are represented as a map from CSS class to vector of character ranges. /// Each range will be wrapped in a span with that class. +#[derive(Default)] pub(crate) struct DecorationInfo(pub(crate) FxHashMap<&'static str, Vec<(u32, u32)>>); -/// Highlights `src`, returning the HTML output. -pub(crate) fn render_with_highlighting( +#[derive(Eq, PartialEq, Clone, Copy)] +pub(crate) enum Tooltip { + Ignore, + CompileFail, + ShouldPanic, + Edition(Edition), + None, +} + +/// Highlights `src` as an inline example, returning the HTML output. +pub(crate) fn render_example_with_highlighting( src: &str, out: &mut Buffer, - class: Option<&str>, + tooltip: Tooltip, playground_button: Option<&str>, - tooltip: Option<(Option, &str)>, - edition: Edition, - extra_content: Option, - href_context: Option>, - decoration_info: Option, ) { - debug!("highlighting: ================\n{}\n==============", src); - if let Some((edition_info, class)) = tooltip { + write_header(out, "rust-example-rendered", None, tooltip); + write_code(out, src, None, None); + write_footer(out, playground_button); +} + +/// Highlights `src` as a macro, returning the HTML output. +pub(crate) fn render_macro_with_highlighting(src: &str, out: &mut Buffer) { + write_header(out, "macro", None, Tooltip::None); + write_code(out, src, None, None); + write_footer(out, None); +} + +/// Highlights `src` as a source code page, returning the HTML output. +pub(crate) fn render_source_with_highlighting( + src: &str, + out: &mut Buffer, + line_numbers: Buffer, + href_context: HrefContext<'_, '_, '_>, + decoration_info: DecorationInfo, +) { + write_header(out, "", Some(line_numbers), Tooltip::None); + write_code(out, src, Some(href_context), Some(decoration_info)); + write_footer(out, None); +} + +fn write_header(out: &mut Buffer, class: &str, extra_content: Option, tooltip: Tooltip) { + write!( + out, + "
", + match tooltip { + Tooltip::Ignore => " ignore", + Tooltip::CompileFail => " compile_fail", + Tooltip::ShouldPanic => " should_panic", + Tooltip::Edition(_) => " edition", + Tooltip::None => "", + }, + ); + + if tooltip != Tooltip::None { write!( out, - "
โ“˜
", - class, - if let Some(edition_info) = edition_info { + "
โ“˜
", + if let Tooltip::Edition(edition_info) = tooltip { format!(" data-edition=\"{}\"", edition_info) } else { String::new() @@ -61,24 +104,115 @@ pub(crate) fn render_with_highlighting( ); } - write_header(out, class, extra_content); - write_code(out, src, edition, href_context, decoration_info); - write_footer(out, playground_button); -} - -fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option) { - write!(out, "
"); if let Some(extra) = extra_content { out.push_buffer(extra); } - if let Some(class) = class { - write!(out, "
", class);
-    } else {
+    if class.is_empty() {
         write!(out, "
");
+    } else {
+        write!(out, "
");
     }
     write!(out, "");
 }
 
+/// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
+/// basically (since it's `Option`). The following rules apply:
+///
+/// * If two `Class` have the same variant, then they can be merged.
+/// * If the other `Class` is unclassified and only contains white characters (backline,
+///   whitespace, etc), it can be merged.
+/// * `Class::Ident` is considered the same as unclassified (because it doesn't have an associated
+///    CSS class).
+fn can_merge(class1: Option, class2: Option, text: &str) -> bool {
+    match (class1, class2) {
+        (Some(c1), Some(c2)) => c1.is_equal_to(c2),
+        (Some(Class::Ident(_)), None) | (None, Some(Class::Ident(_))) => true,
+        (Some(_), None) | (None, Some(_)) => text.trim().is_empty(),
+        (None, None) => true,
+    }
+}
+
+/// This type is used as a conveniency to prevent having to pass all its fields as arguments into
+/// the various functions (which became its methods).
+struct TokenHandler<'a, 'b, 'c, 'd, 'e> {
+    out: &'a mut Buffer,
+    /// It contains the closing tag and the associated `Class`.
+    closing_tags: Vec<(&'static str, Class)>,
+    /// This is used because we don't automatically generate the closing tag on `ExitSpan` in
+    /// case an `EnterSpan` event with the same class follows.
+    pending_exit_span: Option,
+    /// `current_class` and `pending_elems` are used to group HTML elements with same `class`
+    /// attributes to reduce the DOM size.
+    current_class: Option,
+    /// We need to keep the `Class` for each element because it could contain a `Span` which is
+    /// used to generate links.
+    pending_elems: Vec<(&'b str, Option)>,
+    href_context: Option>,
+}
+
+impl<'a, 'b, 'c, 'd, 'e> TokenHandler<'a, 'b, 'c, 'd, 'e> {
+    fn handle_exit_span(&mut self) {
+        // We can't get the last `closing_tags` element using `pop()` because `closing_tags` is
+        // being used in `write_pending_elems`.
+        let class = self.closing_tags.last().expect("ExitSpan without EnterSpan").1;
+        // We flush everything just in case...
+        self.write_pending_elems(Some(class));
+
+        exit_span(self.out, self.closing_tags.pop().expect("ExitSpan without EnterSpan").0);
+        self.pending_exit_span = None;
+    }
+
+    /// Write all the pending elements sharing a same (or at mergeable) `Class`.
+    ///
+    /// If there is a "parent" (if a `EnterSpan` event was encountered) and the parent can be merged
+    /// with the elements' class, then we simply write the elements since the `ExitSpan` event will
+    /// close the tag.
+    ///
+    /// Otherwise, if there is only one pending element, we let the `string` function handle both
+    /// opening and closing the tag, otherwise we do it into this function.
+    ///
+    /// It returns `true` if `current_class` must be set to `None` afterwards.
+    fn write_pending_elems(&mut self, current_class: Option) -> bool {
+        if self.pending_elems.is_empty() {
+            return false;
+        }
+        if let Some((_, parent_class)) = self.closing_tags.last() &&
+            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);
+            }
+        } else {
+            // We only want to "open" the tag ourselves if we have more than one pending and if the
+            // current parent tag is not the same as our pending content.
+            let close_tag = if self.pending_elems.len() > 1 && current_class.is_some() {
+                Some(enter_span(self.out, current_class.unwrap(), &self.href_context))
+            } else {
+                None
+            };
+            for (text, class) in self.pending_elems.iter() {
+                string(self.out, Escape(text), *class, &self.href_context, close_tag.is_none());
+            }
+            if let Some(close_tag) = close_tag {
+                exit_span(self.out, close_tag);
+            }
+        }
+        self.pending_elems.clear();
+        true
+    }
+}
+
+impl<'a, 'b, 'c, 'd, 'e> Drop for TokenHandler<'a, 'b, 'c, 'd, 'e> {
+    /// When leaving, we need to flush all pending data to not have missing content.
+    fn drop(&mut self) {
+        if self.pending_exit_span.is_some() {
+            self.handle_exit_span();
+        } else {
+            self.write_pending_elems(self.current_class);
+        }
+    }
+}
+
 /// Convert the given `src` source code into HTML by adding classes for highlighting.
 ///
 /// This code is used to render code blocks (in the documentation) as well as the source code pages.
@@ -93,27 +227,74 @@ fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option>,
     decoration_info: Option,
 ) {
     // This replace allows to fix how the code source with DOS backline characters is displayed.
     let src = src.replace("\r\n", "\n");
-    let mut closing_tags: Vec<&'static str> = Vec::new();
+    let mut token_handler = TokenHandler {
+        out,
+        closing_tags: Vec::new(),
+        pending_exit_span: None,
+        current_class: None,
+        pending_elems: Vec::new(),
+        href_context,
+    };
+
     Classifier::new(
         &src,
-        edition,
-        href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
+        token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
         decoration_info,
     )
     .highlight(&mut |highlight| {
         match highlight {
-            Highlight::Token { text, class } => string(out, Escape(text), class, &href_context),
+            Highlight::Token { text, class } => {
+                // If we received a `ExitSpan` event and then have a non-compatible `Class`, we
+                // need to close the ``.
+                let need_current_class_update = if let Some(pending) = token_handler.pending_exit_span &&
+                    !can_merge(Some(pending), class, text) {
+                        token_handler.handle_exit_span();
+                        true
+                // If the two `Class` are different, time to flush the current content and start
+                // a new one.
+                } else if !can_merge(token_handler.current_class, class, text) {
+                    token_handler.write_pending_elems(token_handler.current_class);
+                    true
+                } else {
+                    token_handler.current_class.is_none()
+                };
+
+                if need_current_class_update {
+                    token_handler.current_class = class.map(Class::dummy);
+                }
+                token_handler.pending_elems.push((text, class));
+            }
             Highlight::EnterSpan { class } => {
-                closing_tags.push(enter_span(out, class, &href_context))
+                let mut should_add = true;
+                if let Some(pending_exit_span) = token_handler.pending_exit_span {
+                    if class.is_equal_to(pending_exit_span) {
+                        should_add = false;
+                    } else {
+                        token_handler.handle_exit_span();
+                    }
+                } else {
+                    // We flush everything just in case...
+                    if token_handler.write_pending_elems(token_handler.current_class) {
+                        token_handler.current_class = None;
+                    }
+                }
+                if should_add {
+                    let closing_tag = enter_span(token_handler.out, class, &token_handler.href_context);
+                    token_handler.closing_tags.push((closing_tag, class));
+                }
+
+                token_handler.current_class = None;
+                token_handler.pending_exit_span = None;
             }
             Highlight::ExitSpan => {
-                exit_span(out, closing_tags.pop().expect("ExitSpan without EnterSpan"))
+                token_handler.current_class = None;
+                token_handler.pending_exit_span =
+                    Some(token_handler.closing_tags.last().as_ref().expect("ExitSpan without EnterSpan").1);
             }
         };
     });
@@ -130,15 +311,15 @@ enum Class {
     DocComment,
     Attribute,
     KeyWord,
-    // Keywords that do pointer/reference stuff.
+    /// Keywords that do pointer/reference stuff.
     RefKeyWord,
     Self_(Span),
-    Op,
     Macro(Span),
     MacroNonTerminal,
     String,
     Number,
     Bool,
+    /// `Ident` isn't rendered in the HTML but we still need it for the `Span` it contains.
     Ident(Span),
     Lifetime,
     PreludeTy,
@@ -148,6 +329,31 @@ enum Class {
 }
 
 impl Class {
+    /// It is only looking at the variant, not the variant content.
+    ///
+    /// It is used mostly to group multiple similar HTML elements into one `` instead of
+    /// multiple ones.
+    fn is_equal_to(self, other: Self) -> bool {
+        match (self, other) {
+            (Self::Self_(_), Self::Self_(_))
+            | (Self::Macro(_), Self::Macro(_))
+            | (Self::Ident(_), Self::Ident(_)) => true,
+            (Self::Decoration(c1), Self::Decoration(c2)) => c1 == c2,
+            (x, y) => x == y,
+        }
+    }
+
+    /// If `self` contains a `Span`, it'll be replaced with `DUMMY_SP` to prevent creating links
+    /// on "empty content" (because of the attributes merge).
+    fn dummy(self) -> Self {
+        match self {
+            Self::Self_(_) => Self::Self_(DUMMY_SP),
+            Self::Macro(_) => Self::Macro(DUMMY_SP),
+            Self::Ident(_) => Self::Ident(DUMMY_SP),
+            s => s,
+        }
+    }
+
     /// Returns the css class expected by rustdoc for each `Class`.
     fn as_html(self) -> &'static str {
         match self {
@@ -157,13 +363,12 @@ impl Class {
             Class::KeyWord => "kw",
             Class::RefKeyWord => "kw-2",
             Class::Self_(_) => "self",
-            Class::Op => "op",
             Class::Macro(_) => "macro",
             Class::MacroNonTerminal => "macro-nonterminal",
             Class::String => "string",
             Class::Number => "number",
             Class::Bool => "bool-val",
-            Class::Ident(_) => "ident",
+            Class::Ident(_) => "",
             Class::Lifetime => "lifetime",
             Class::PreludeTy => "prelude-ty",
             Class::PreludeVal => "prelude-val",
@@ -182,7 +387,6 @@ impl Class {
             | Self::Attribute
             | Self::KeyWord
             | Self::RefKeyWord
-            | Self::Op
             | Self::MacroNonTerminal
             | Self::String
             | Self::Number
@@ -220,7 +424,7 @@ impl<'a> Iterator for TokenIter<'a> {
 }
 
 /// Classifies into identifier class; returns `None` if this is a non-keyword identifier.
-fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool) -> Option {
+fn get_real_ident_class(text: &str, allow_path_keywords: bool) -> Option {
     let ignore: &[&str] =
         if allow_path_keywords { &["self", "Self", "super", "crate"] } else { &["self", "Self"] };
     if ignore.iter().any(|k| *k == text) {
@@ -229,7 +433,7 @@ fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool)
     Some(match text {
         "ref" | "mut" => Class::RefKeyWord,
         "false" | "true" => Class::Bool,
-        _ if Symbol::intern(text).is_reserved(|| edition) => Class::KeyWord,
+        _ if Symbol::intern(text).is_reserved(|| Edition::Edition2021) => Class::KeyWord,
         _ => return None,
     })
 }
@@ -250,7 +454,7 @@ impl<'a> PeekIter<'a> {
     fn new(iter: TokenIter<'a>) -> Self {
         Self { stored: VecDeque::new(), peek_pos: 0, iter }
     }
-    /// Returns the next item after the current one. It doesn't interfer with `peek_next` output.
+    /// Returns the next item after the current one. It doesn't interfere with `peek_next` output.
     fn peek(&mut self) -> Option<&(TokenKind, &'a str)> {
         if self.stored.is_empty() {
             if let Some(next) = self.iter.next() {
@@ -259,7 +463,7 @@ impl<'a> PeekIter<'a> {
         }
         self.stored.front()
     }
-    /// Returns the next item after the last one peeked. It doesn't interfer with `peek` output.
+    /// Returns the next item after the last one peeked. It doesn't interfere with `peek` output.
     fn peek_next(&mut self) -> Option<&(TokenKind, &'a str)> {
         self.peek_pos += 1;
         if self.peek_pos - 1 < self.stored.len() {
@@ -311,7 +515,6 @@ struct Classifier<'a> {
     in_attribute: bool,
     in_macro: bool,
     in_macro_nonterminal: bool,
-    edition: Edition,
     byte_pos: u32,
     file_span: Span,
     src: &'a str,
@@ -321,12 +524,7 @@ struct Classifier<'a> {
 impl<'a> Classifier<'a> {
     /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code
     /// file span which will be used later on by the `span_correspondance_map`.
-    fn new(
-        src: &str,
-        edition: Edition,
-        file_span: Span,
-        decoration_info: Option,
-    ) -> Classifier<'_> {
+    fn new(src: &str, file_span: Span, decoration_info: Option) -> Classifier<'_> {
         let tokens = PeekIter::new(TokenIter { src });
         let decorations = decoration_info.map(Decorations::new);
         Classifier {
@@ -334,7 +532,6 @@ impl<'a> Classifier<'a> {
             in_attribute: false,
             in_macro: false,
             in_macro_nonterminal: false,
-            edition,
             byte_pos: 0,
             file_span,
             src,
@@ -354,7 +551,6 @@ impl<'a> Classifier<'a> {
         let start = self.byte_pos as usize;
         let mut pos = start;
         let mut has_ident = false;
-        let edition = self.edition;
 
         loop {
             let mut nb = 0;
@@ -376,7 +572,7 @@ impl<'a> Classifier<'a> {
 
             if let Some((None, text)) = self.tokens.peek().map(|(token, text)| {
                 if *token == TokenKind::Ident {
-                    let class = get_real_ident_class(text, edition, true);
+                    let class = get_real_ident_class(text, true);
                     (class, text)
                 } else {
                     // Doesn't matter which Class we put in here...
@@ -494,7 +690,7 @@ impl<'a> Classifier<'a> {
             // or a reference or pointer type. Unless, of course, it looks like
             // a logical and or a multiplication operator: `&&` or `* `.
             TokenKind::Star => match self.tokens.peek() {
-                Some((TokenKind::Whitespace, _)) => Class::Op,
+                Some((TokenKind::Whitespace, _)) => return no_highlight(sink),
                 Some((TokenKind::Ident, "mut")) => {
                     self.next();
                     sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) });
@@ -510,15 +706,15 @@ impl<'a> Classifier<'a> {
             TokenKind::And => match self.tokens.peek() {
                 Some((TokenKind::And, _)) => {
                     self.next();
-                    sink(Highlight::Token { text: "&&", class: Some(Class::Op) });
+                    sink(Highlight::Token { text: "&&", class: None });
                     return;
                 }
                 Some((TokenKind::Eq, _)) => {
                     self.next();
-                    sink(Highlight::Token { text: "&=", class: Some(Class::Op) });
+                    sink(Highlight::Token { text: "&=", class: None });
                     return;
                 }
-                Some((TokenKind::Whitespace, _)) => Class::Op,
+                Some((TokenKind::Whitespace, _)) => return no_highlight(sink),
                 Some((TokenKind::Ident, "mut")) => {
                     self.next();
                     sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) });
@@ -531,7 +727,7 @@ impl<'a> Classifier<'a> {
             TokenKind::Eq => match lookahead {
                 Some(TokenKind::Eq) => {
                     self.next();
-                    sink(Highlight::Token { text: "==", class: Some(Class::Op) });
+                    sink(Highlight::Token { text: "==", class: None });
                     return;
                 }
                 Some(TokenKind::Gt) => {
@@ -539,7 +735,7 @@ impl<'a> Classifier<'a> {
                     sink(Highlight::Token { text: "=>", class: None });
                     return;
                 }
-                _ => Class::Op,
+                _ => return no_highlight(sink),
             },
             TokenKind::Minus if lookahead == Some(TokenKind::Gt) => {
                 self.next();
@@ -556,7 +752,7 @@ impl<'a> Classifier<'a> {
             | TokenKind::Percent
             | TokenKind::Bang
             | TokenKind::Lt
-            | TokenKind::Gt => Class::Op,
+            | TokenKind::Gt => return no_highlight(sink),
 
             // Miscellaneous, no highlighting.
             TokenKind::Dot
@@ -634,7 +830,7 @@ impl<'a> Classifier<'a> {
                 sink(Highlight::Token { text, class: None });
                 return;
             }
-            TokenKind::Ident => match get_real_ident_class(text, self.edition, false) {
+            TokenKind::Ident => match get_real_ident_class(text, false) {
                 None => match text {
                     "Option" | "Result" => Class::PreludeTy,
                     "Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
@@ -682,7 +878,7 @@ fn enter_span(
     klass: Class,
     href_context: &Option>,
 ) -> &'static str {
-    string_without_closing_tag(out, "", Some(klass), href_context).expect(
+    string_without_closing_tag(out, "", Some(klass), href_context, true).expect(
         "internal error: enter_span was called with Some(klass) but did not return a \
             closing HTML tag",
     )
@@ -714,8 +910,10 @@ fn string(
     text: T,
     klass: Option,
     href_context: &Option>,
+    open_tag: bool,
 ) {
-    if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context) {
+    if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context, open_tag)
+    {
         out.write_str(closing_tag);
     }
 }
@@ -734,6 +932,7 @@ fn string_without_closing_tag(
     text: T,
     klass: Option,
     href_context: &Option>,
+    open_tag: bool,
 ) -> Option<&'static str> {
     let Some(klass) = klass
     else {
@@ -742,6 +941,10 @@ fn string_without_closing_tag(
     };
     let Some(def_span) = klass.get_span()
     else {
+        if !open_tag {
+            write!(out, "{}", text);
+            return None;
+        }
         write!(out, "{}", klass.as_html(), text);
         return Some("");
     };
@@ -765,6 +968,7 @@ fn string_without_closing_tag(
             path
         });
     }
+
     if let Some(href_context) = href_context {
         if let Some(href) =
             href_context.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
@@ -775,9 +979,9 @@ fn string_without_closing_tag(
                 // a link to their definition can be generated using this:
                 // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
                 match href {
-                    LinkFromSrc::Local(span) => context
-                        .href_from_span(*span, true)
-                        .map(|s| format!("{}{}", href_context.root_path, s)),
+                    LinkFromSrc::Local(span) => {
+                        context.href_from_span_relative(*span, href_context.current_href)
+                    }
                     LinkFromSrc::External(def_id) => {
                         format::href_with_root_path(*def_id, context, Some(href_context.root_path))
                             .ok()
@@ -793,12 +997,33 @@ fn string_without_closing_tag(
                 }
             })
         {
-            write!(out, "{}", klass.as_html(), href, text_s);
+            if !open_tag {
+                // We're already inside an element which has the same klass, no need to give it
+                // again.
+                write!(out, "{}", href, text_s);
+            } else {
+                let klass_s = klass.as_html();
+                if klass_s.is_empty() {
+                    write!(out, "{}", href, text_s);
+                } else {
+                    write!(out, "{}", klass_s, href, text_s);
+                }
+            }
             return Some("");
         }
     }
-    write!(out, "{}", klass.as_html(), text_s);
-    Some("")
+    if !open_tag {
+        write!(out, "{}", text_s);
+        return None;
+    }
+    let klass_s = klass.as_html();
+    if klass_s.is_empty() {
+        write!(out, "{}", text_s);
+        Some("")
+    } else {
+        write!(out, "{}", klass_s, text_s);
+        Some("")
+    }
 }
 
 #[cfg(test)]
diff --git a/src/librustdoc/html/highlight/fixtures/decorations.html b/src/librustdoc/html/highlight/fixtures/decorations.html
index 45f567880..ebf29f9cb 100644
--- a/src/librustdoc/html/highlight/fixtures/decorations.html
+++ b/src/librustdoc/html/highlight/fixtures/decorations.html
@@ -1,2 +1,4 @@
-let x = 1;
-let y = 2;
\ No newline at end of file
+let x = 1;
+let y = 2;
+let z = 3;
+let a = 4;
\ No newline at end of file
diff --git a/src/librustdoc/html/highlight/fixtures/dos_line.html b/src/librustdoc/html/highlight/fixtures/dos_line.html
index 1c8dbffe7..30b50ca7c 100644
--- a/src/librustdoc/html/highlight/fixtures/dos_line.html
+++ b/src/librustdoc/html/highlight/fixtures/dos_line.html
@@ -1,3 +1,3 @@
-pub fn foo() {
+pub fn foo() {
 println!("foo");
 }
diff --git a/src/librustdoc/html/highlight/fixtures/highlight.html b/src/librustdoc/html/highlight/fixtures/highlight.html
index abc2db179..9f73e03f9 100644
--- a/src/librustdoc/html/highlight/fixtures/highlight.html
+++ b/src/librustdoc/html/highlight/fixtures/highlight.html
@@ -1,4 +1,4 @@
-use crate::a::foo;
-use self::whatever;
-let x = super::b::foo;
-let y = Self::whatever;
\ No newline at end of file
+use crate::a::foo;
+use self::whatever;
+let x = super::b::foo;
+let y = Self::whatever;
\ No newline at end of file
diff --git a/src/librustdoc/html/highlight/fixtures/sample.html b/src/librustdoc/html/highlight/fixtures/sample.html
index b117a12e3..4a5a3cf60 100644
--- a/src/librustdoc/html/highlight/fixtures/sample.html
+++ b/src/librustdoc/html/highlight/fixtures/sample.html
@@ -8,30 +8,31 @@
 .lifetime { color: #B76514; }
 .question-mark { color: #ff9011; }
 
-
#![crate_type = "lib"]
+
#![crate_type = "lib"]
 
-use std::path::{Path, PathBuf};
+use std::path::{Path, PathBuf};
 
-#[cfg(target_os = "linux")]
-fn main() -> () {
-    let foo = true && false || true;
-    let _: *const () = 0;
-    let _ = &foo;
-    let _ = &&foo;
-    let _ = *foo;
-    mac!(foo, &mut bar);
-    assert!(self.length < N && index <= self.length);
-    ::std::env::var("gateau").is_ok();
-    #[rustfmt::skip]
-    let s:std::path::PathBuf = std::path::PathBuf::new();
-    let mut s = String::new();
+#[cfg(target_os = "linux")]
+#[cfg(target_os = "windows")]
+fn main() -> () {
+    let foo = true && false || true;
+    let _: *const () = 0;
+    let _ = &foo;
+    let _ = &&foo;
+    let _ = *foo;
+    mac!(foo, &mut bar);
+    assert!(self.length < N && index <= self.length);
+    ::std::env::var("gateau").is_ok();
+    #[rustfmt::skip]
+    let s:std::path::PathBuf = std::path::PathBuf::new();
+    let mut s = String::new();
 
-    match &s {
-        ref mut x => {}
+    match &s {
+        ref mut x => {}
     }
 }
 
-macro_rules! bar {
-    ($foo:tt) => {};
+macro_rules! bar {
+    ($foo:tt) => {};
 }
 
diff --git a/src/librustdoc/html/highlight/fixtures/sample.rs b/src/librustdoc/html/highlight/fixtures/sample.rs index fbfdc6767..ef85b566c 100644 --- a/src/librustdoc/html/highlight/fixtures/sample.rs +++ b/src/librustdoc/html/highlight/fixtures/sample.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; #[cfg(target_os = "linux")] +#[cfg(target_os = "windows")] fn main() -> () { let foo = true && false || true; let _: *const () = 0; diff --git a/src/librustdoc/html/highlight/fixtures/union.html b/src/librustdoc/html/highlight/fixtures/union.html index c0acf31a0..9f8915282 100644 --- a/src/librustdoc/html/highlight/fixtures/union.html +++ b/src/librustdoc/html/highlight/fixtures/union.html @@ -1,8 +1,8 @@ -union Foo { - i: i8, - u: i8, +union Foo { + i: i8, + u: i8, } -fn main() { - let union = 0; +fn main() { + let union = 0; } diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs index 1fea7e983..a5e633df4 100644 --- a/src/librustdoc/html/highlight/tests.rs +++ b/src/librustdoc/html/highlight/tests.rs @@ -3,7 +3,6 @@ use crate::html::format::Buffer; use expect_test::expect_file; use rustc_data_structures::fx::FxHashMap; use rustc_span::create_default_session_globals_then; -use rustc_span::edition::Edition; const STYLE: &str = r#"