Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cad609d

Browse files
authoredJan 19, 2024
Rollup merge of #117662 - GuillaumeGomez:links-in-headings, r=notriddle
[rustdoc] Allows links in headings Reopening of #94360. # Explanations Rustdoc currently doesn't follow the markdown spec on headings: we don't allow links in them. So instead of having headings linking to themselves, this PR generates an anchor on the left side like this: ![image](https://github.com/rust-lang/rust/assets/3050060/a118a7e9-5ef8-4d07-914f-46defc3245c3) <details> <summary>previous version</summary> ![image](https://github.com/rust-lang/rust/assets/3050060/c34fa844-9cd4-47dc-bb51-b37f5f66afee) </details> Having the anchor always displayed allows for mobile devices users to be able to have a link to the anchor. The different color used for the anchor itself is the same as links so people notice when looking at it that they can click on it. You can test it [here](https://rustdoc.crud.net/imperio/links-in-headings/std/index.html). cc `@camelid` r? `@notriddle`
2 parents 64461da + bf4a20c commit cad609d

File tree

12 files changed

+211
-99
lines changed

12 files changed

+211
-99
lines changed
 

‎src/librustdoc/html/markdown.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,6 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
530530
for event in &mut self.inner {
531531
match &event.0 {
532532
Event::End(Tag::Heading(..)) => break,
533-
Event::Start(Tag::Link(_, _, _)) | Event::End(Tag::Link(..)) => {}
534533
Event::Text(text) | Event::Code(text) => {
535534
id.extend(text.chars().filter_map(slugify));
536535
self.buf.push_back(event);
@@ -549,12 +548,10 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
549548

550549
let level =
551550
std::cmp::min(level as u32 + (self.heading_offset as u32), MAX_HEADER_LEVEL);
552-
self.buf.push_back((Event::Html(format!("</a></h{level}>").into()), 0..0));
551+
self.buf.push_back((Event::Html(format!("</h{level}>").into()), 0..0));
553552

554-
let start_tags = format!(
555-
"<h{level} id=\"{id}\">\
556-
<a href=\"#{id}\">",
557-
);
553+
let start_tags =
554+
format!("<h{level} id=\"{id}\"><a class=\"doc-anchor\" href=\"#{id}\">§</a>");
558555
return Some((Event::Html(start_tags.into()), 0..0));
559556
}
560557
event

‎src/librustdoc/html/markdown/tests.rs

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -311,26 +311,38 @@ fn test_header() {
311311
assert_eq!(output, expect, "original: {}", input);
312312
}
313313

314-
t("# Foo bar", "<h2 id=\"foo-bar\"><a href=\"#foo-bar\">Foo bar</a></h2>");
314+
t(
315+
"# Foo bar",
316+
"<h2 id=\"foo-bar\"><a class=\"doc-anchor\" href=\"#foo-bar\">§</a>Foo bar</h2>",
317+
);
315318
t(
316319
"## Foo-bar_baz qux",
317320
"<h3 id=\"foo-bar_baz-qux\">\
318-
<a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h3>",
321+
<a class=\"doc-anchor\" href=\"#foo-bar_baz-qux\">§</a>\
322+
Foo-bar_baz qux\
323+
</h3>",
319324
);
320325
t(
321326
"### **Foo** *bar* baz!?!& -_qux_-%",
322327
"<h4 id=\"foo-bar-baz--qux-\">\
323-
<a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
324-
<em>bar</em> baz!?!&amp; -<em>qux</em>-%</a>\
328+
<a class=\"doc-anchor\" href=\"#foo-bar-baz--qux-\">§</a>\
329+
<strong>Foo</strong> <em>bar</em> baz!?!&amp; -<em>qux</em>-%\
325330
</h4>",
326331
);
327332
t(
328333
"#### **Foo?** & \\*bar?!* _`baz`_ ❤ #qux",
329334
"<h5 id=\"foo--bar--baz--qux\">\
330-
<a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!* \
331-
<em><code>baz</code></em> ❤ #qux</a>\
335+
<a class=\"doc-anchor\" href=\"#foo--bar--baz--qux\">§</a>\
336+
<strong>Foo?</strong> &amp; *bar?!* <em><code>baz</code></em> ❤ #qux\
332337
</h5>",
333338
);
339+
t(
340+
"# Foo [bar](https://hello.yo)",
341+
"<h2 id=\"foo-bar\">\
342+
<a class=\"doc-anchor\" href=\"#foo-bar\">§</a>\
343+
Foo <a href=\"https://hello.yo\">bar</a>\
344+
</h2>",
345+
);
334346
}
335347

336348
#[test]
@@ -351,12 +363,36 @@ fn test_header_ids_multiple_blocks() {
351363
assert_eq!(output, expect, "original: {}", input);
352364
}
353365

354-
t(&mut map, "# Example", "<h2 id=\"example\"><a href=\"#example\">Example</a></h2>");
355-
t(&mut map, "# Panics", "<h2 id=\"panics\"><a href=\"#panics\">Panics</a></h2>");
356-
t(&mut map, "# Example", "<h2 id=\"example-1\"><a href=\"#example-1\">Example</a></h2>");
357-
t(&mut map, "# Search", "<h2 id=\"search-1\"><a href=\"#search-1\">Search</a></h2>");
358-
t(&mut map, "# Example", "<h2 id=\"example-2\"><a href=\"#example-2\">Example</a></h2>");
359-
t(&mut map, "# Panics", "<h2 id=\"panics-1\"><a href=\"#panics-1\">Panics</a></h2>");
366+
t(
367+
&mut map,
368+
"# Example",
369+
"<h2 id=\"example\"><a class=\"doc-anchor\" href=\"#example\">§</a>Example</h2>",
370+
);
371+
t(
372+
&mut map,
373+
"# Panics",
374+
"<h2 id=\"panics\"><a class=\"doc-anchor\" href=\"#panics\">§</a>Panics</h2>",
375+
);
376+
t(
377+
&mut map,
378+
"# Example",
379+
"<h2 id=\"example-1\"><a class=\"doc-anchor\" href=\"#example-1\">§</a>Example</h2>",
380+
);
381+
t(
382+
&mut map,
383+
"# Search",
384+
"<h2 id=\"search-1\"><a class=\"doc-anchor\" href=\"#search-1\">§</a>Search</h2>",
385+
);
386+
t(
387+
&mut map,
388+
"# Example",
389+
"<h2 id=\"example-2\"><a class=\"doc-anchor\" href=\"#example-2\">§</a>Example</h2>",
390+
);
391+
t(
392+
&mut map,
393+
"# Panics",
394+
"<h2 id=\"panics-1\"><a class=\"doc-anchor\" href=\"#panics-1\">§</a>Panics</h2>",
395+
);
360396
}
361397

362398
#[test]

‎src/librustdoc/html/render/mod.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,17 +1207,31 @@ impl<'a> AssocItemLink<'a> {
12071207
}
12081208
}
12091209

1210-
fn write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str) {
1210+
pub fn write_section_heading(
1211+
w: &mut impl fmt::Write,
1212+
title: &str,
1213+
id: &str,
1214+
extra_class: Option<&str>,
1215+
extra: impl fmt::Display,
1216+
) {
1217+
let (extra_class, whitespace) = match extra_class {
1218+
Some(extra) => (extra, " "),
1219+
None => ("", ""),
1220+
};
12111221
write!(
12121222
w,
1213-
"<h2 id=\"{id}\" class=\"section-header\">\
1223+
"<h2 id=\"{id}\" class=\"{extra_class}{whitespace}section-header\">\
12141224
{title}\
12151225
<a href=\"#{id}\" class=\"anchor\">§</a>\
1216-
</h2>"
1226+
</h2>{extra}",
12171227
)
12181228
.unwrap();
12191229
}
12201230

1231+
fn write_impl_section_heading(w: &mut impl fmt::Write, title: &str, id: &str) {
1232+
write_section_heading(w, title, id, None, "")
1233+
}
1234+
12211235
pub(crate) fn render_all_impls(
12221236
mut w: impl Write,
12231237
cx: &mut Context<'_>,

‎src/librustdoc/html/render/print_item.rs

Lines changed: 51 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use super::{
1919
item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
2020
render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
2121
render_impl, render_rightside, render_stability_since_raw,
22-
render_stability_since_raw_with_extra, AssocItemLink, AssocItemRender, Context,
23-
ImplRenderingParameters, RenderMode,
22+
render_stability_since_raw_with_extra, write_section_heading, AssocItemLink, AssocItemRender,
23+
Context, ImplRenderingParameters, RenderMode,
2424
};
2525
use crate::clean;
2626
use crate::config::ModuleSorting;
@@ -425,13 +425,12 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
425425
w.write_str(ITEM_TABLE_CLOSE);
426426
}
427427
last_section = Some(my_section);
428-
write!(
428+
write_section_heading(
429429
w,
430-
"<h2 id=\"{id}\" class=\"section-header\">\
431-
<a href=\"#{id}\">{name}</a>\
432-
</h2>{ITEM_TABLE_OPEN}",
433-
id = cx.derive_id(my_section.id()),
434-
name = my_section.name(),
430+
my_section.name(),
431+
&cx.derive_id(my_section.id()),
432+
None,
433+
ITEM_TABLE_OPEN,
435434
);
436435
}
437436

@@ -814,16 +813,6 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
814813
// Trait documentation
815814
write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
816815

817-
fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) {
818-
write!(
819-
w,
820-
"<h2 id=\"{0}\" class=\"section-header\">\
821-
{1}<a href=\"#{0}\" class=\"anchor\">§</a>\
822-
</h2>{2}",
823-
id, title, extra_content
824-
)
825-
}
826-
827816
fn trait_item(w: &mut Buffer, cx: &mut Context<'_>, m: &clean::Item, t: &clean::Item) {
828817
let name = m.name.unwrap();
829818
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:
857846
}
858847

859848
if !required_types.is_empty() {
860-
write_small_section_header(
849+
write_section_heading(
861850
w,
862-
"required-associated-types",
863851
"Required Associated Types",
852+
"required-associated-types",
853+
None,
864854
"<div class=\"methods\">",
865855
);
866856
for t in required_types {
@@ -869,10 +859,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
869859
w.write_str("</div>");
870860
}
871861
if !provided_types.is_empty() {
872-
write_small_section_header(
862+
write_section_heading(
873863
w,
874-
"provided-associated-types",
875864
"Provided Associated Types",
865+
"provided-associated-types",
866+
None,
876867
"<div class=\"methods\">",
877868
);
878869
for t in provided_types {
@@ -882,10 +873,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
882873
}
883874

884875
if !required_consts.is_empty() {
885-
write_small_section_header(
876+
write_section_heading(
886877
w,
887-
"required-associated-consts",
888878
"Required Associated Constants",
879+
"required-associated-consts",
880+
None,
889881
"<div class=\"methods\">",
890882
);
891883
for t in required_consts {
@@ -894,10 +886,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
894886
w.write_str("</div>");
895887
}
896888
if !provided_consts.is_empty() {
897-
write_small_section_header(
889+
write_section_heading(
898890
w,
899-
"provided-associated-consts",
900891
"Provided Associated Constants",
892+
"provided-associated-consts",
893+
None,
901894
"<div class=\"methods\">",
902895
);
903896
for t in provided_consts {
@@ -908,10 +901,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
908901

909902
// Output the documentation for each function individually
910903
if !required_methods.is_empty() || must_implement_one_of_functions.is_some() {
911-
write_small_section_header(
904+
write_section_heading(
912905
w,
913-
"required-methods",
914906
"Required Methods",
907+
"required-methods",
908+
None,
915909
"<div class=\"methods\">",
916910
);
917911

@@ -929,10 +923,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
929923
w.write_str("</div>");
930924
}
931925
if !provided_methods.is_empty() {
932-
write_small_section_header(
926+
write_section_heading(
933927
w,
934-
"provided-methods",
935928
"Provided Methods",
929+
"provided-methods",
930+
None,
936931
"<div class=\"methods\">",
937932
);
938933
for m in provided_methods {
@@ -949,10 +944,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
949944
let mut extern_crates = FxHashSet::default();
950945

951946
if !t.is_object_safe(cx.tcx()) {
952-
write_small_section_header(
947+
write_section_heading(
953948
w,
954-
"object-safety",
955949
"Object Safety",
950+
"object-safety",
951+
None,
956952
&format!(
957953
"<div class=\"object-safety-info\">This trait is <b>not</b> \
958954
<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:
996992
foreign.sort_by_cached_key(|i| ImplString::new(i, cx));
997993

998994
if !foreign.is_empty() {
999-
write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", "");
995+
write_section_heading(w, "Implementations on Foreign Types", "foreign-impls", None, "");
1000996

1001997
for implementor in foreign {
1002998
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:
10211017
}
10221018
}
10231019

1024-
write_small_section_header(
1020+
write_section_heading(
10251021
w,
1026-
"implementors",
10271022
"Implementors",
1023+
"implementors",
1024+
None,
10281025
"<div id=\"implementors-list\">",
10291026
);
10301027
for implementor in concrete {
@@ -1033,10 +1030,11 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
10331030
w.write_str("</div>");
10341031

10351032
if t.is_auto(tcx) {
1036-
write_small_section_header(
1033+
write_section_heading(
10371034
w,
1038-
"synthetic-implementors",
10391035
"Auto implementors",
1036+
"synthetic-implementors",
1037+
None,
10401038
"<div id=\"synthetic-implementors-list\">",
10411039
);
10421040
for implementor in synthetic {
@@ -1054,18 +1052,20 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
10541052
} else {
10551053
// even without any implementations to write in, we still want the heading and list, so the
10561054
// implementors javascript file pulled in below has somewhere to write the impls into
1057-
write_small_section_header(
1055+
write_section_heading(
10581056
w,
1059-
"implementors",
10601057
"Implementors",
1058+
"implementors",
1059+
None,
10611060
"<div id=\"implementors-list\"></div>",
10621061
);
10631062

10641063
if t.is_auto(tcx) {
1065-
write_small_section_header(
1064+
write_section_heading(
10661065
w,
1067-
"synthetic-implementors",
10681066
"Auto implementors",
1067+
"synthetic-implementors",
1068+
None,
10691069
"<div id=\"synthetic-implementors-list\"></div>",
10701070
);
10711071
}
@@ -1248,11 +1248,7 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c
12481248
write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
12491249

12501250
if let Some(inner_type) = &t.inner_type {
1251-
write!(
1252-
w,
1253-
"<h2 id=\"aliased-type\" class=\"section-header\">\
1254-
Aliased Type<a href=\"#aliased-type\" class=\"anchor\">§</a></h2>"
1255-
);
1251+
write_section_heading(w, "Aliased Type", "aliased-type", None, "");
12561252

12571253
match inner_type {
12581254
clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive } => {
@@ -1673,16 +1669,14 @@ fn item_variants(
16731669
enum_def_id: DefId,
16741670
) {
16751671
let tcx = cx.tcx();
1676-
write!(
1672+
write_section_heading(
16771673
w,
1678-
"<h2 id=\"variants\" class=\"variants section-header\">\
1679-
Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\
1680-
</h2>\
1681-
{}\
1682-
<div class=\"variants\">",
1683-
document_non_exhaustive_header(it),
1684-
document_non_exhaustive(it)
1674+
&format!("Variants{}", document_non_exhaustive_header(it)),
1675+
"variants",
1676+
Some("variants"),
1677+
format!("{}<div class=\"variants\">", document_non_exhaustive(it)),
16851678
);
1679+
16861680
let should_show_enum_discriminant = should_show_enum_discriminant(cx, enum_def_id, variants);
16871681
for (index, variant) in variants.iter_enumerated() {
16881682
if variant.is_stripped() {
@@ -1930,16 +1924,12 @@ fn item_fields(
19301924
.peekable();
19311925
if let None | Some(CtorKind::Fn) = ctor_kind {
19321926
if fields.peek().is_some() {
1933-
write!(
1934-
w,
1935-
"<h2 id=\"fields\" class=\"fields section-header\">\
1936-
{}{}<a href=\"#fields\" class=\"anchor\">§</a>\
1937-
</h2>\
1938-
{}",
1927+
let title = format!(
1928+
"{}{}",
19391929
if ctor_kind.is_none() { "Fields" } else { "Tuple Fields" },
19401930
document_non_exhaustive_header(it),
1941-
document_non_exhaustive(it)
19421931
);
1932+
write_section_heading(w, &title, "fields", Some("fields"), document_non_exhaustive(it));
19431933
for (index, (field, ty)) in fields.enumerate() {
19441934
let field_name =
19451935
field.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string());

‎src/librustdoc/html/static/css/rustdoc.css

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,11 +849,30 @@ nav.sub {
849849
h2.section-header > .anchor {
850850
padding-right: 6px;
851851
}
852+
a.doc-anchor {
853+
color: var(--main-color);
854+
display: none;
855+
position: absolute;
856+
left: -17px;
857+
/* We add this padding so that when the cursor moves from the heading's text to the anchor,
858+
the anchor doesn't disappear. */
859+
padding-right: 5px;
860+
/* And this padding is used to make the anchor larger and easier to click on. */
861+
padding-left: 3px;
862+
}
863+
*:hover > .doc-anchor {
864+
display: block;
865+
}
866+
/* If the first element of the top doc block is a heading, we don't want to ever display its anchor
867+
because of the `[-]` element which would overlap with it. */
868+
.top-doc > .docblock > *:first-child > .doc-anchor {
869+
display: none !important;
870+
}
852871

853872
.main-heading a:hover,
854873
.example-wrap .rust a:hover,
855874
.all-items a:hover,
856-
.docblock a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,
875+
.docblock a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover:not(.doc-anchor),
857876
.docblock-short a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,
858877
.item-info a {
859878
text-decoration: underline;

‎tests/rustdoc-gui/docblock-details.goml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ reload:
66

77
// We first check that the headers in the `.top-doc` doc block still have their
88
// bottom border.
9-
assert-text: (".top-doc .docblock > h3", "Hello")
9+
assert-text: (".top-doc .docblock > h3", "§Hello")
1010
assert-css: (
1111
".top-doc .docblock > h3",
1212
{"border-bottom": "1px solid #d2d2d2"},

‎tests/rustdoc-gui/headers-color.goml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// This test check for headers text and background colors for the different themes.
1+
// This test check for headings text and background colors for the different themes.
22

33
define-function: (
44
"check-colors",
@@ -45,7 +45,7 @@ call-function: (
4545
"color": "#c5c5c5",
4646
"code_header_color": "#e6e1cf",
4747
"focus_background_color": "rgba(255, 236, 164, 0.06)",
48-
"headings_color": "#39afd7",
48+
"headings_color": "#c5c5c5",
4949
},
5050
)
5151
call-function: (
@@ -55,7 +55,7 @@ call-function: (
5555
"color": "#ddd",
5656
"code_header_color": "#ddd",
5757
"focus_background_color": "#494a3d",
58-
"headings_color": "#d2991d",
58+
"headings_color": "#ddd",
5959
},
6060
)
6161
call-function: (
@@ -65,6 +65,6 @@ call-function: (
6565
"color": "black",
6666
"code_header_color": "black",
6767
"focus_background_color": "#fdffd3",
68-
"headings_color": "#3873ad",
68+
"headings_color": "black",
6969
},
7070
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Test to ensure that the headings anchor behave as expected.
2+
go-to: "file://" + |DOC_PATH| + "/test_docs/struct.HeavilyDocumentedStruct.html"
3+
show-text: true
4+
5+
define-function: (
6+
"check-heading-anchor",
7+
(heading_id),
8+
block {
9+
// The anchor should not be displayed by default.
10+
assert-css: ("#" + |heading_id| + " .doc-anchor", { "display": "none" })
11+
// We ensure that hovering the heading makes the anchor visible.
12+
move-cursor-to: "#" + |heading_id|
13+
assert-css: ("#" + |heading_id| + ":hover .doc-anchor", { "display": "block" })
14+
// We then ensure that moving from the heading to the anchor doesn't make the anchor
15+
// disappear.
16+
move-cursor-to: "#" + |heading_id| + " .doc-anchor"
17+
assert-css: ("#" + |heading_id| + " .doc-anchor:hover", {
18+
"display": "block",
19+
// We also ensure that there is no underline decoration.
20+
"text-decoration-line": "none",
21+
})
22+
}
23+
)
24+
25+
move-cursor-to: "#top-doc-prose-title"
26+
// If the top documentation block first element is a heading, we should never display its anchor
27+
// to prevent it from overlapping with the `[-]` element.
28+
assert-css: ("#top-doc-prose-title:hover .doc-anchor", { "display": "none" })
29+
30+
call-function: ("check-heading-anchor", ("top-doc-prose-sub-heading"))
31+
call-function: ("check-heading-anchor", ("top-doc-prose-sub-sub-heading"))
32+
call-function: ("check-heading-anchor", ("you-know-the-drill"))

‎tests/rustdoc/disambiguate-anchors-header-29449.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,23 @@
55
pub struct Foo;
66

77
impl Foo {
8-
// @has - '//*[@id="examples"]//a' 'Examples'
9-
// @has - '//*[@id="panics"]//a' 'Panics'
8+
// @has - '//*[@id="examples"]' 'Examples'
9+
// @has - '//*[@id="examples"]/a[@href="#examples"]' '§'
10+
// @has - '//*[@id="panics"]' 'Panics'
11+
// @has - '//*[@id="panics"]/a[@href="#panics"]' '§'
1012
/// # Examples
1113
/// # Panics
1214
pub fn bar() {}
1315

14-
// @has - '//*[@id="examples-1"]//a' 'Examples'
16+
// @has - '//*[@id="examples-1"]' 'Examples'
17+
// @has - '//*[@id="examples-1"]/a[@href="#examples-1"]' '§'
1518
/// # Examples
1619
pub fn bar_1() {}
1720

18-
// @has - '//*[@id="examples-2"]//a' 'Examples'
19-
// @has - '//*[@id="panics-1"]//a' 'Panics'
21+
// @has - '//*[@id="examples-2"]' 'Examples'
22+
// @has - '//*[@id="examples-2"]/a[@href="#examples-2"]' '§'
23+
// @has - '//*[@id="panics-1"]' 'Panics'
24+
// @has - '//*[@id="panics-1"]/a[@href="#panics-1"]' '§'
2025
/// # Examples
2126
/// # Panics
2227
pub fn bar_2() {}

‎tests/rustdoc/links-in-headings.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![crate_name = "foo"]
2+
3+
//! # Heading with [a link](https://a.com) inside
4+
//!
5+
//! And even with
6+
//!
7+
//! ## [multiple](https://b.com) [links](https://c.com)
8+
//!
9+
//! !
10+
11+
// @has 'foo/index.html'
12+
// @has - '//h2/a[@href="https://a.com"]' 'a link'
13+
// @has - '//h3/a[@href="https://b.com"]' 'multiple'
14+
// @has - '//h3/a[@href="https://c.com"]' 'links'
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
// It actually checks that the link is kept in the headings as expected now.
2+
13
#![crate_name = "foo"]
24

35
// @has foo/fn.foo.html
4-
// @!has - '//a[@href="http://a.a"]' ''
5-
// @has - '//a[@href="#implementing-stuff-somewhere"]' 'Implementing stuff somewhere'
6-
// @has - '//a[@href="#another-one-urg"]' 'Another one urg'
6+
// @has - '//a[@href="http://a.a"]' 'stuff'
7+
// @has - '//*[@id="implementing-stuff-somewhere"]' 'Implementing stuff somewhere'
8+
// @has - '//a[@href="http://b.b"]' 'one'
9+
// @has - '//*[@id="another-one-urg"]' 'Another one urg'
710

811
/// fooo
912
///
@@ -13,5 +16,5 @@
1316
///
1417
/// # Another [one][two] urg
1518
///
16-
/// [two]: http://a.a
19+
/// [two]: http://b.b
1720
pub fn foo() {}

‎tests/rustdoc/short-docblock.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22

33
// @has foo/index.html '//*[@class="desc docblock-short"]' 'fooo'
44
// @!has foo/index.html '//*[@class="desc docblock-short"]/h1' 'fooo'
5-
// @has foo/fn.foo.html '//h2[@id="fooo"]/a[@href="#fooo"]' 'fooo'
65

6+
// @has foo/fn.foo.html '//h2[@id="fooo"]' 'fooo'
7+
// @has foo/fn.foo.html '//h2[@id="fooo"]/a[@href="#fooo"]' '§'
78
/// # fooo
89
///
910
/// foo
1011
pub fn foo() {}
1112

1213
// @has foo/index.html '//*[@class="desc docblock-short"]' 'mooood'
1314
// @!has foo/index.html '//*[@class="desc docblock-short"]/h2' 'mooood'
14-
// @has foo/foo/index.html '//h3[@id="mooood"]/a[@href="#mooood"]' 'mooood'
1515

16+
// @has foo/foo/index.html '//h3[@id="mooood"]' 'mooood'
17+
// @has foo/foo/index.html '//h3[@id="mooood"]/a[@href="#mooood"]' '§'
1618
/// ## mooood
1719
///
1820
/// foo mod

0 commit comments

Comments
 (0)
Please sign in to comment.