about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/html/markdown.rs9
-rw-r--r--src/librustdoc/html/markdown/tests.rs60
-rw-r--r--src/librustdoc/html/render/mod.rs20
-rw-r--r--src/librustdoc/html/render/print_item.rs112
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css21
-rw-r--r--tests/rustdoc-gui/docblock-details.goml2
-rw-r--r--tests/rustdoc-gui/headers-color.goml8
-rw-r--r--tests/rustdoc-gui/headings-anchor.goml32
-rw-r--r--tests/rustdoc/disambiguate-anchors-header-29449.rs15
-rw-r--r--tests/rustdoc/links-in-headings.rs14
-rw-r--r--tests/rustdoc/remove-url-from-headings.rs11
-rw-r--r--tests/rustdoc/short-docblock.rs6
12 files changed, 211 insertions, 99 deletions
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 642265f5f6b..bad1511dfd2 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -530,7 +530,6 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
             for event in &mut self.inner {
                 match &event.0 {
                     Event::End(Tag::Heading(..)) => break,
-                    Event::Start(Tag::Link(_, _, _)) | Event::End(Tag::Link(..)) => {}
                     Event::Text(text) | Event::Code(text) => {
                         id.extend(text.chars().filter_map(slugify));
                         self.buf.push_back(event);
@@ -549,12 +548,10 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
 
             let level =
                 std::cmp::min(level as u32 + (self.heading_offset as u32), MAX_HEADER_LEVEL);
-            self.buf.push_back((Event::Html(format!("</a></h{level}>").into()), 0..0));
+            self.buf.push_back((Event::Html(format!("</h{level}>").into()), 0..0));
 
-            let start_tags = format!(
-                "<h{level} id=\"{id}\">\
-                    <a href=\"#{id}\">",
-            );
+            let start_tags =
+                format!("<h{level} id=\"{id}\"><a class=\"doc-anchor\" href=\"#{id}\">§</a>");
             return Some((Event::Html(start_tags.into()), 0..0));
         }
         event
diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs
index 5eba1d0609f..4dd176b3a69 100644
--- a/src/librustdoc/html/markdown/tests.rs
+++ b/src/librustdoc/html/markdown/tests.rs
@@ -311,26 +311,38 @@ fn test_header() {
         assert_eq!(output, expect, "original: {}", input);
     }
 
-    t("# Foo bar", "<h2 id=\"foo-bar\"><a href=\"#foo-bar\">Foo bar</a></h2>");
+    t(
+        "# Foo bar",
+        "<h2 id=\"foo-bar\"><a class=\"doc-anchor\" href=\"#foo-bar\">§</a>Foo bar</h2>",
+    );
     t(
         "## Foo-bar_baz qux",
         "<h3 id=\"foo-bar_baz-qux\">\
-         <a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h3>",
+             <a class=\"doc-anchor\" href=\"#foo-bar_baz-qux\">§</a>\
+             Foo-bar_baz qux\
+         </h3>",
     );
     t(
         "### **Foo** *bar* baz!?!& -_qux_-%",
         "<h4 id=\"foo-bar-baz--qux-\">\
-            <a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
-            <em>bar</em> baz!?!&amp; -<em>qux</em>-%</a>\
+            <a class=\"doc-anchor\" href=\"#foo-bar-baz--qux-\">§</a>\
+            <strong>Foo</strong> <em>bar</em> baz!?!&amp; -<em>qux</em>-%\
          </h4>",
     );
     t(
         "#### **Foo?** & \\*bar?!*  _`baz`_ ❤ #qux",
         "<h5 id=\"foo--bar--baz--qux\">\
-             <a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!*  \
-             <em><code>baz</code></em> ❤ #qux</a>\
+             <a class=\"doc-anchor\" href=\"#foo--bar--baz--qux\">§</a>\
+             <strong>Foo?</strong> &amp; *bar?!*  <em><code>baz</code></em> ❤ #qux\
          </h5>",
     );
+    t(
+        "# Foo [bar](https://hello.yo)",
+        "<h2 id=\"foo-bar\">\
+             <a class=\"doc-anchor\" href=\"#foo-bar\">§</a>\
+             Foo <a href=\"https://hello.yo\">bar</a>\
+         </h2>",
+    );
 }
 
 #[test]
@@ -351,12 +363,36 @@ fn test_header_ids_multiple_blocks() {
         assert_eq!(output, expect, "original: {}", input);
     }
 
-    t(&mut map, "# Example", "<h2 id=\"example\"><a href=\"#example\">Example</a></h2>");
-    t(&mut map, "# Panics", "<h2 id=\"panics\"><a href=\"#panics\">Panics</a></h2>");
-    t(&mut map, "# Example", "<h2 id=\"example-1\"><a href=\"#example-1\">Example</a></h2>");
-    t(&mut map, "# Search", "<h2 id=\"search-1\"><a href=\"#search-1\">Search</a></h2>");
-    t(&mut map, "# Example", "<h2 id=\"example-2\"><a href=\"#example-2\">Example</a></h2>");
-    t(&mut map, "# Panics", "<h2 id=\"panics-1\"><a href=\"#panics-1\">Panics</a></h2>");
+    t(
+        &mut map,
+        "# Example",
+        "<h2 id=\"example\"><a class=\"doc-anchor\" href=\"#example\">§</a>Example</h2>",
+    );
+    t(
+        &mut map,
+        "# Panics",
+        "<h2 id=\"panics\"><a class=\"doc-anchor\" href=\"#panics\">§</a>Panics</h2>",
+    );
+    t(
+        &mut map,
+        "# Example",
+        "<h2 id=\"example-1\"><a class=\"doc-anchor\" href=\"#example-1\">§</a>Example</h2>",
+    );
+    t(
+        &mut map,
+        "# Search",
+        "<h2 id=\"search-1\"><a class=\"doc-anchor\" href=\"#search-1\">§</a>Search</h2>",
+    );
+    t(
+        &mut map,
+        "# Example",
+        "<h2 id=\"example-2\"><a class=\"doc-anchor\" href=\"#example-2\">§</a>Example</h2>",
+    );
+    t(
+        &mut map,
+        "# Panics",
+        "<h2 id=\"panics-1\"><a class=\"doc-anchor\" href=\"#panics-1\">§</a>Panics</h2>",
+    );
 }
 
 #[test]
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index bea5ccd7c86..ac7ae291d29 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -1207,17 +1207,31 @@ impl<'a> AssocItemLink<'a> {
     }
 }
 
-fn write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str) {
+pub fn write_section_heading(
+    w: &mut impl fmt::Write,
+    title: &str,
+    id: &str,
+    extra_class: Option<&str>,
+    extra: impl fmt::Display,
+) {
+    let (extra_class, whitespace) = match extra_class {
+        Some(extra) => (extra, " "),
+        None => ("", ""),
+    };
     write!(
         w,
-        "<h2 id=\"{id}\" class=\"section-header\">\
+        "<h2 id=\"{id}\" class=\"{extra_class}{whitespace}section-header\">\
             {title}\
             <a href=\"#{id}\" class=\"anchor\">§</a>\
-         </h2>"
+         </h2>{extra}",
     )
     .unwrap();
 }
 
+fn write_impl_section_heading(w: &mut impl fmt::Write, title: &str, id: &str) {
+    write_section_heading(w, title, id, None, "")
+}
+
 pub(crate) fn render_all_impls(
     mut w: impl Write,
     cx: &mut Context<'_>,
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 3b91fbdcb29..71186319e07 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -19,8 +19,8 @@ use super::{
     item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
     render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
     render_impl, render_rightside, render_stability_since_raw,
-    render_stability_since_raw_with_extra, AssocItemLink, AssocItemRender, Context,
-    ImplRenderingParameters, RenderMode,
+    render_stability_since_raw_with_extra, write_section_heading, AssocItemLink, AssocItemRender,
+    Context, ImplRenderingParameters, RenderMode,
 };
 use crate::clean;
 use crate::config::ModuleSorting;
@@ -425,13 +425,12 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
                 w.write_str(ITEM_TABLE_CLOSE);
             }
             last_section = Some(my_section);
-            write!(
+            write_section_heading(
                 w,
-                "<h2 id=\"{id}\" class=\"section-header\">\
-                    <a href=\"#{id}\">{name}</a>\
-                 </h2>{ITEM_TABLE_OPEN}",
-                id = cx.derive_id(my_section.id()),
-                name = my_section.name(),
+                my_section.name(),
+                &cx.derive_id(my_section.id()),
+                None,
+                ITEM_TABLE_OPEN,
             );
         }
 
@@ -814,16 +813,6 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
     // Trait documentation
     write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
 
-    fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) {
-        write!(
-            w,
-            "<h2 id=\"{0}\" class=\"section-header\">\
-                {1}<a href=\"#{0}\" class=\"anchor\">§</a>\
-             </h2>{2}",
-            id, title, extra_content
-        )
-    }
-
     fn trait_item(w: &mut Buffer, cx: &mut Context<'_>, m: &clean::Item, t: &clean::Item) {
         let name = m.name.unwrap();
         info!("Documenting {name} on {ty_name:?}", ty_name = t.name);
@@ -857,10 +846,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
     }
 
     if !required_types.is_empty() {
-        write_small_section_header(
+        write_section_heading(
             w,
-            "required-associated-types",
             "Required Associated Types",
+            "required-associated-types",
+            None,
             "<div class=\"methods\">",
         );
         for t in required_types {
@@ -869,10 +859,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
         w.write_str("</div>");
     }
     if !provided_types.is_empty() {
-        write_small_section_header(
+        write_section_heading(
             w,
-            "provided-associated-types",
             "Provided Associated Types",
+            "provided-associated-types",
+            None,
             "<div class=\"methods\">",
         );
         for t in provided_types {
@@ -882,10 +873,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
     }
 
     if !required_consts.is_empty() {
-        write_small_section_header(
+        write_section_heading(
             w,
-            "required-associated-consts",
             "Required Associated Constants",
+            "required-associated-consts",
+            None,
             "<div class=\"methods\">",
         );
         for t in required_consts {
@@ -894,10 +886,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
         w.write_str("</div>");
     }
     if !provided_consts.is_empty() {
-        write_small_section_header(
+        write_section_heading(
             w,
-            "provided-associated-consts",
             "Provided Associated Constants",
+            "provided-associated-consts",
+            None,
             "<div class=\"methods\">",
         );
         for t in provided_consts {
@@ -908,10 +901,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
 
     // Output the documentation for each function individually
     if !required_methods.is_empty() || must_implement_one_of_functions.is_some() {
-        write_small_section_header(
+        write_section_heading(
             w,
-            "required-methods",
             "Required Methods",
+            "required-methods",
+            None,
             "<div class=\"methods\">",
         );
 
@@ -929,10 +923,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
         w.write_str("</div>");
     }
     if !provided_methods.is_empty() {
-        write_small_section_header(
+        write_section_heading(
             w,
-            "provided-methods",
             "Provided Methods",
+            "provided-methods",
+            None,
             "<div class=\"methods\">",
         );
         for m in provided_methods {
@@ -949,10 +944,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
     let mut extern_crates = FxHashSet::default();
 
     if !t.is_object_safe(cx.tcx()) {
-        write_small_section_header(
+        write_section_heading(
             w,
-            "object-safety",
             "Object Safety",
+            "object-safety",
+            None,
             &format!(
                 "<div class=\"object-safety-info\">This trait is <b>not</b> \
                 <a href=\"{base}/reference/items/traits.html#object-safety\">\
@@ -996,7 +992,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
         foreign.sort_by_cached_key(|i| ImplString::new(i, cx));
 
         if !foreign.is_empty() {
-            write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", "");
+            write_section_heading(w, "Implementations on Foreign Types", "foreign-impls", None, "");
 
             for implementor in foreign {
                 let provided_methods = implementor.inner_impl().provided_trait_methods(tcx);
@@ -1021,10 +1017,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
             }
         }
 
-        write_small_section_header(
+        write_section_heading(
             w,
-            "implementors",
             "Implementors",
+            "implementors",
+            None,
             "<div id=\"implementors-list\">",
         );
         for implementor in concrete {
@@ -1033,10 +1030,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
         w.write_str("</div>");
 
         if t.is_auto(tcx) {
-            write_small_section_header(
+            write_section_heading(
                 w,
-                "synthetic-implementors",
                 "Auto implementors",
+                "synthetic-implementors",
+                None,
                 "<div id=\"synthetic-implementors-list\">",
             );
             for implementor in synthetic {
@@ -1054,18 +1052,20 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
     } else {
         // even without any implementations to write in, we still want the heading and list, so the
         // implementors javascript file pulled in below has somewhere to write the impls into
-        write_small_section_header(
+        write_section_heading(
             w,
-            "implementors",
             "Implementors",
+            "implementors",
+            None,
             "<div id=\"implementors-list\"></div>",
         );
 
         if t.is_auto(tcx) {
-            write_small_section_header(
+            write_section_heading(
                 w,
-                "synthetic-implementors",
                 "Auto implementors",
+                "synthetic-implementors",
+                None,
                 "<div id=\"synthetic-implementors-list\"></div>",
             );
         }
@@ -1248,11 +1248,7 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c
     write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
 
     if let Some(inner_type) = &t.inner_type {
-        write!(
-            w,
-            "<h2 id=\"aliased-type\" class=\"section-header\">\
-                Aliased Type<a href=\"#aliased-type\" class=\"anchor\">§</a></h2>"
-        );
+        write_section_heading(w, "Aliased Type", "aliased-type", None, "");
 
         match inner_type {
             clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive } => {
@@ -1673,16 +1669,14 @@ fn item_variants(
     enum_def_id: DefId,
 ) {
     let tcx = cx.tcx();
-    write!(
+    write_section_heading(
         w,
-        "<h2 id=\"variants\" class=\"variants section-header\">\
-            Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\
-        </h2>\
-        {}\
-        <div class=\"variants\">",
-        document_non_exhaustive_header(it),
-        document_non_exhaustive(it)
+        &format!("Variants{}", document_non_exhaustive_header(it)),
+        "variants",
+        Some("variants"),
+        format!("{}<div class=\"variants\">", document_non_exhaustive(it)),
     );
+
     let should_show_enum_discriminant = should_show_enum_discriminant(cx, enum_def_id, variants);
     for (index, variant) in variants.iter_enumerated() {
         if variant.is_stripped() {
@@ -1930,16 +1924,12 @@ fn item_fields(
         .peekable();
     if let None | Some(CtorKind::Fn) = ctor_kind {
         if fields.peek().is_some() {
-            write!(
-                w,
-                "<h2 id=\"fields\" class=\"fields section-header\">\
-                     {}{}<a href=\"#fields\" class=\"anchor\">§</a>\
-                 </h2>\
-                 {}",
+            let title = format!(
+                "{}{}",
                 if ctor_kind.is_none() { "Fields" } else { "Tuple Fields" },
                 document_non_exhaustive_header(it),
-                document_non_exhaustive(it)
             );
+            write_section_heading(w, &title, "fields", Some("fields"), document_non_exhaustive(it));
             for (index, (field, ty)) in fields.enumerate() {
                 let field_name =
                     field.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string());
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index cd53fcb8b7c..9c593aa85d9 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -849,11 +849,30 @@ nav.sub {
 h2.section-header > .anchor {
 	padding-right: 6px;
 }
+a.doc-anchor {
+	color: var(--main-color);
+	display: none;
+	position: absolute;
+	left: -17px;
+	/* We add this padding so that when the cursor moves from the heading's text to the anchor,
+	   the anchor doesn't disappear. */
+	padding-right: 5px;
+	/* And this padding is used to make the anchor larger and easier to click on. */
+	padding-left: 3px;
+}
+*:hover > .doc-anchor {
+	display: block;
+}
+/* If the first element of the top doc block is a heading, we don't want to ever display its anchor
+because of the `[-]` element which would overlap with it. */
+.top-doc > .docblock > *:first-child > .doc-anchor {
+	display: none !important;
+}
 
 .main-heading a:hover,
 .example-wrap .rust a:hover,
 .all-items a:hover,
-.docblock a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,
+.docblock a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover:not(.doc-anchor),
 .docblock-short a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,
 .item-info a {
 	text-decoration: underline;
diff --git a/tests/rustdoc-gui/docblock-details.goml b/tests/rustdoc-gui/docblock-details.goml
index 8e6d2ba824f..4b8f5b54fac 100644
--- a/tests/rustdoc-gui/docblock-details.goml
+++ b/tests/rustdoc-gui/docblock-details.goml
@@ -6,7 +6,7 @@ reload:
 
 // We first check that the headers in the `.top-doc` doc block still have their
 // bottom border.
-assert-text: (".top-doc .docblock > h3", "Hello")
+assert-text: (".top-doc .docblock > h3", "§Hello")
 assert-css: (
     ".top-doc .docblock > h3",
     {"border-bottom": "1px solid #d2d2d2"},
diff --git a/tests/rustdoc-gui/headers-color.goml b/tests/rustdoc-gui/headers-color.goml
index 19185818f40..80d11c9c849 100644
--- a/tests/rustdoc-gui/headers-color.goml
+++ b/tests/rustdoc-gui/headers-color.goml
@@ -1,4 +1,4 @@
-// This test check for headers text and background colors for the different themes.
+// This test check for headings text and background colors for the different themes.
 
 define-function: (
     "check-colors",
@@ -45,7 +45,7 @@ call-function: (
         "color": "#c5c5c5",
         "code_header_color": "#e6e1cf",
         "focus_background_color": "rgba(255, 236, 164, 0.06)",
-        "headings_color": "#39afd7",
+        "headings_color": "#c5c5c5",
     },
 )
 call-function: (
@@ -55,7 +55,7 @@ call-function: (
         "color": "#ddd",
         "code_header_color": "#ddd",
         "focus_background_color": "#494a3d",
-        "headings_color": "#d2991d",
+        "headings_color": "#ddd",
     },
 )
 call-function: (
@@ -65,6 +65,6 @@ call-function: (
         "color": "black",
         "code_header_color": "black",
         "focus_background_color": "#fdffd3",
-        "headings_color": "#3873ad",
+        "headings_color": "black",
     },
 )
diff --git a/tests/rustdoc-gui/headings-anchor.goml b/tests/rustdoc-gui/headings-anchor.goml
new file mode 100644
index 00000000000..f568caa3b07
--- /dev/null
+++ b/tests/rustdoc-gui/headings-anchor.goml
@@ -0,0 +1,32 @@
+// Test to ensure that the headings anchor behave as expected.
+go-to: "file://" + |DOC_PATH| + "/test_docs/struct.HeavilyDocumentedStruct.html"
+show-text: true
+
+define-function: (
+    "check-heading-anchor",
+    (heading_id),
+    block {
+        // The anchor should not be displayed by default.
+        assert-css: ("#" + |heading_id| + " .doc-anchor", { "display": "none" })
+        // We ensure that hovering the heading makes the anchor visible.
+        move-cursor-to: "#" + |heading_id|
+        assert-css: ("#" + |heading_id| + ":hover .doc-anchor", { "display": "block" })
+        // We then ensure that moving from the heading to the anchor doesn't make the anchor
+        // disappear.
+        move-cursor-to: "#" + |heading_id| + " .doc-anchor"
+        assert-css: ("#" + |heading_id| + " .doc-anchor:hover", {
+            "display": "block",
+            // We also ensure that there is no underline decoration.
+            "text-decoration-line": "none",
+        })
+    }
+)
+
+move-cursor-to: "#top-doc-prose-title"
+// If the top documentation block first element is a heading, we should never display its anchor
+// to prevent it from overlapping with the `[-]` element.
+assert-css: ("#top-doc-prose-title:hover .doc-anchor", { "display": "none" })
+
+call-function: ("check-heading-anchor", ("top-doc-prose-sub-heading"))
+call-function: ("check-heading-anchor", ("top-doc-prose-sub-sub-heading"))
+call-function: ("check-heading-anchor", ("you-know-the-drill"))
diff --git a/tests/rustdoc/disambiguate-anchors-header-29449.rs b/tests/rustdoc/disambiguate-anchors-header-29449.rs
index 38a4954fc13..1388af7df4b 100644
--- a/tests/rustdoc/disambiguate-anchors-header-29449.rs
+++ b/tests/rustdoc/disambiguate-anchors-header-29449.rs
@@ -5,18 +5,23 @@
 pub struct Foo;
 
 impl Foo {
-    // @has - '//*[@id="examples"]//a' 'Examples'
-    // @has - '//*[@id="panics"]//a' 'Panics'
+    // @has - '//*[@id="examples"]' 'Examples'
+    // @has - '//*[@id="examples"]/a[@href="#examples"]' '§'
+    // @has - '//*[@id="panics"]' 'Panics'
+    // @has - '//*[@id="panics"]/a[@href="#panics"]' '§'
     /// # Examples
     /// # Panics
     pub fn bar() {}
 
-    // @has - '//*[@id="examples-1"]//a' 'Examples'
+    // @has - '//*[@id="examples-1"]' 'Examples'
+    // @has - '//*[@id="examples-1"]/a[@href="#examples-1"]' '§'
     /// # Examples
     pub fn bar_1() {}
 
-    // @has - '//*[@id="examples-2"]//a' 'Examples'
-    // @has - '//*[@id="panics-1"]//a' 'Panics'
+    // @has - '//*[@id="examples-2"]' 'Examples'
+    // @has - '//*[@id="examples-2"]/a[@href="#examples-2"]' '§'
+    // @has - '//*[@id="panics-1"]' 'Panics'
+    // @has - '//*[@id="panics-1"]/a[@href="#panics-1"]' '§'
     /// # Examples
     /// # Panics
     pub fn bar_2() {}
diff --git a/tests/rustdoc/links-in-headings.rs b/tests/rustdoc/links-in-headings.rs
new file mode 100644
index 00000000000..c5bee1a7975
--- /dev/null
+++ b/tests/rustdoc/links-in-headings.rs
@@ -0,0 +1,14 @@
+#![crate_name = "foo"]
+
+//! # Heading with [a link](https://a.com) inside
+//!
+//! And even with
+//!
+//! ## [multiple](https://b.com) [links](https://c.com)
+//!
+//! !
+
+// @has 'foo/index.html'
+// @has - '//h2/a[@href="https://a.com"]' 'a link'
+// @has - '//h3/a[@href="https://b.com"]' 'multiple'
+// @has - '//h3/a[@href="https://c.com"]' 'links'
diff --git a/tests/rustdoc/remove-url-from-headings.rs b/tests/rustdoc/remove-url-from-headings.rs
index 599c429a6e1..8f477028619 100644
--- a/tests/rustdoc/remove-url-from-headings.rs
+++ b/tests/rustdoc/remove-url-from-headings.rs
@@ -1,9 +1,12 @@
+// It actually checks that the link is kept in the headings as expected now.
+
 #![crate_name = "foo"]
 
 // @has foo/fn.foo.html
-// @!has - '//a[@href="http://a.a"]' ''
-// @has - '//a[@href="#implementing-stuff-somewhere"]' 'Implementing stuff somewhere'
-// @has - '//a[@href="#another-one-urg"]' 'Another one urg'
+// @has - '//a[@href="http://a.a"]' 'stuff'
+// @has - '//*[@id="implementing-stuff-somewhere"]' 'Implementing stuff somewhere'
+// @has - '//a[@href="http://b.b"]' 'one'
+// @has - '//*[@id="another-one-urg"]' 'Another one urg'
 
 /// fooo
 ///
@@ -13,5 +16,5 @@
 ///
 /// # Another [one][two] urg
 ///
-/// [two]: http://a.a
+/// [two]: http://b.b
 pub fn foo() {}
diff --git a/tests/rustdoc/short-docblock.rs b/tests/rustdoc/short-docblock.rs
index 791d3547c9f..151a42a9c9e 100644
--- a/tests/rustdoc/short-docblock.rs
+++ b/tests/rustdoc/short-docblock.rs
@@ -2,8 +2,9 @@
 
 // @has foo/index.html '//*[@class="desc docblock-short"]' 'fooo'
 // @!has foo/index.html '//*[@class="desc docblock-short"]/h1' 'fooo'
-// @has foo/fn.foo.html '//h2[@id="fooo"]/a[@href="#fooo"]' 'fooo'
 
+// @has foo/fn.foo.html '//h2[@id="fooo"]' 'fooo'
+// @has foo/fn.foo.html '//h2[@id="fooo"]/a[@href="#fooo"]' '§'
 /// # fooo
 ///
 /// foo
@@ -11,8 +12,9 @@ pub fn foo() {}
 
 // @has foo/index.html '//*[@class="desc docblock-short"]' 'mooood'
 // @!has foo/index.html '//*[@class="desc docblock-short"]/h2' 'mooood'
-// @has foo/foo/index.html '//h3[@id="mooood"]/a[@href="#mooood"]' 'mooood'
 
+// @has foo/foo/index.html '//h3[@id="mooood"]' 'mooood'
+// @has foo/foo/index.html '//h3[@id="mooood"]/a[@href="#mooood"]' '§'
 /// ## mooood
 ///
 /// foo mod