Skip to content

Add jump to doc #113623

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/librustdoc/html/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,11 @@ fn string_without_closing_tag<T: Display>(
)
.ok()
.map(|(url, _, _)| url),
LinkFromSrc::Doc(def_id) => {
format::href_with_root_path(*def_id, context, Some(&href_context.root_path))
.ok()
.map(|(doc_link, _, _)| doc_link)
}
}
})
{
Expand Down
7 changes: 6 additions & 1 deletion src/librustdoc/html/render/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,12 @@ impl<'tcx> Context<'tcx> {
let e = ExternalCrate { crate_num: cnum };
(e.name(self.tcx()), e.src_root(self.tcx()))
}
ExternalLocation::Unknown => return None,
ExternalLocation::Unknown => {
let e = ExternalCrate { crate_num: cnum };
let name = e.name(self.tcx());
root = name.to_string();
(name, e.src_root(self.tcx()))
}
};

let href = RefCell::new(PathBuf::new());
Expand Down
97 changes: 73 additions & 24 deletions src/librustdoc/html/render/span_map.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::clean::{self, PrimitiveType};
use crate::clean::{self, rustc_span, PrimitiveType};
use crate::html::sources;

use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{ExprKind, HirId, Mod, Node};
use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::TyCtxt;
use rustc_span::hygiene::MacroKind;
Expand All @@ -25,6 +25,7 @@ pub(crate) enum LinkFromSrc {
Local(clean::Span),
External(DefId),
Primitive(PrimitiveType),
Doc(DefId),
}

/// This function will do at most two things:
Expand Down Expand Up @@ -65,24 +66,43 @@ struct SpanMapVisitor<'tcx> {
impl<'tcx> SpanMapVisitor<'tcx> {
/// This function is where we handle `hir::Path` elements and add them into the "span map".
fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
let info = match path.res {
match path.res {
// FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
// Would be nice to support them too alongside the other `DefKind`
// (such as primitive types!).
Res::Def(kind, def_id) if kind != DefKind::TyParam => Some(def_id),
Res::Local(_) => None,
Res::Def(kind, def_id) if kind != DefKind::TyParam => {
let link = if def_id.as_local().is_some() {
LinkFromSrc::Local(rustc_span(def_id, self.tcx))
} else {
LinkFromSrc::External(def_id)
};
self.matches.insert(path.span, link);
}
Res::Local(_) => {
if let Some(span) = self.tcx.hir().res_span(path.res) {
self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
}
}
Res::PrimTy(p) => {
// FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
return;
}
Res::Err => return,
_ => return,
};
if let Some(span) = self.tcx.hir().res_span(path.res) {
self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
} else if let Some(def_id) = info {
self.matches.insert(path.span, LinkFromSrc::External(def_id));
Res::Err => {}
_ => {}
}
}

/// Used to generate links on items' definition to go to their documentation page.
pub(crate) fn extract_info_from_hir_id(&mut self, hir_id: HirId) {
if let Some(Node::Item(item)) = self.tcx.hir().find(hir_id) {
if let Some(span) = self.tcx.def_ident_span(item.owner_id) {
let cspan = clean::Span::new(span);
// If the span isn't from the current crate, we ignore it.
if cspan.inner().is_dummy() || cspan.cnum(self.tcx.sess) != LOCAL_CRATE {
return;
}
self.matches.insert(span, LinkFromSrc::Doc(item.owner_id.to_def_id()));
}
}
}

Expand Down Expand Up @@ -117,10 +137,13 @@ impl<'tcx> SpanMapVisitor<'tcx> {
_ => return true,
};
let link_from_src = match data.macro_def_id {
Some(macro_def_id) if macro_def_id.is_local() => {
LinkFromSrc::Local(clean::Span::new(data.def_site))
Some(macro_def_id) => {
if macro_def_id.is_local() {
LinkFromSrc::Local(clean::Span::new(data.def_site))
} else {
LinkFromSrc::External(macro_def_id)
}
}
Some(macro_def_id) => LinkFromSrc::External(macro_def_id),
None => return true,
};
let new_span = data.call_site;
Expand Down Expand Up @@ -160,6 +183,9 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)),
);
}
} else {
// If it's a "mod foo {}", we want to look to its documentation page.
self.extract_info_from_hir_id(id);
}
intravisit::walk_mod(self, m, id);
}
Expand All @@ -176,18 +202,41 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
.tcx
.typeck_body(hir.maybe_body_owned_by(body_id).expect("a body which isn't a body"));
if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) {
self.matches.insert(
segment.ident.span,
match hir.span_if_local(def_id) {
Some(span) => LinkFromSrc::Local(clean::Span::new(span)),
None => LinkFromSrc::External(def_id),
},
);
let link = if def_id.as_local().is_some() {
LinkFromSrc::Local(rustc_span(def_id, self.tcx))
} else {
LinkFromSrc::External(def_id)
};
self.matches.insert(segment.ident.span, link);
}
} else if self.handle_macro(expr.span) {
// We don't want to go deeper into the macro.
return;
}
intravisit::walk_expr(self, expr);
}

fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
match item.kind {
ItemKind::Static(_, _, _)
| ItemKind::Const(_, _)
| ItemKind::Fn(_, _, _)
| ItemKind::Macro(_, _)
| ItemKind::TyAlias(_, _)
| ItemKind::Enum(_, _)
| ItemKind::Struct(_, _)
| ItemKind::Union(_, _)
| ItemKind::Trait(_, _, _, _, _)
| ItemKind::TraitAlias(_, _) => self.extract_info_from_hir_id(item.hir_id()),
ItemKind::Impl(_)
| ItemKind::Use(_, _)
| ItemKind::ExternCrate(_)
| ItemKind::ForeignMod { .. }
| ItemKind::GlobalAsm(_)
| ItemKind::OpaqueTy(_)
// We already have "visit_mod" above so no need to check it here.
| ItemKind::Mod(_) => {}
}
intravisit::walk_item(self, item);
}
}
6 changes: 3 additions & 3 deletions tests/rustdoc-gui/source-anchor-scroll.goml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ set-window-size: (600, 800)
// We check that the scroll is at the top first.
assert-property: ("html", {"scrollTop": "0"})

click: '//a[text() = "barbar"]'
click: '//a[text() = "barbar" and @href="#5-7"]'
assert-property: ("html", {"scrollTop": "149"})
click: '//a[text() = "bar"]'
click: '//a[text() = "bar" and @href="#28-36"]'
assert-property: ("html", {"scrollTop": "180"})
click: '//a[text() = "sub_fn"]'
click: '//a[text() = "sub_fn" and @href="#2-4"]'
assert-property: ("html", {"scrollTop": "77"})

// We now check that clicking on lines doesn't change the scroll
Expand Down
19 changes: 10 additions & 9 deletions tests/rustdoc/check-source-code-urls-to-def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ extern crate source_code;
#[path = "auxiliary/source-code-bar.rs"]
pub mod bar;

// @count - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#5"]' 4
// @count - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#5-7"]' 4
use bar::Bar;
// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#13"]' 'self'
// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'Trait'
// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#13-17"]' 'self'
// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14-16"]' 'Trait'
use bar::sub::{self, Trait};

pub struct Foo;
Expand All @@ -32,7 +32,8 @@ fn babar() {}
// @has - '//pre[@class="rust"]//a/@href' '/primitive.u32.html'
// @has - '//pre[@class="rust"]//a/@href' '/primitive.str.html'
// @count - '//pre[@class="rust"]//a[@href="#23"]' 5
// @has - '//pre[@class="rust"]//a[@href="../../source_code/struct.SourceCode.html"]' 'source_code::SourceCode'
// @has - '//pre[@class="rust"]//a[@href="../../source_code/struct.SourceCode.html"]' \
// 'source_code::SourceCode'
pub fn foo(a: u32, b: &str, c: String, d: Foo, e: bar::Bar, f: source_code::SourceCode) {
let x = 12;
let y: Foo = Foo;
Expand All @@ -42,15 +43,15 @@ pub fn foo(a: u32, b: &str, c: String, d: Foo, e: bar::Bar, f: source_code::Sour
y.hello();
}

// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'bar::sub::Trait'
// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'Trait'
// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14-16"]' 'bar::sub::Trait'
// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14-16"]' 'Trait'
pub fn foo2<T: bar::sub::Trait, V: Trait>(t: &T, v: &V, b: bool) {}

pub trait AnotherTrait {}
pub trait WhyNot {}

// @has - '//pre[@class="rust"]//a[@href="#49"]' 'AnotherTrait'
// @has - '//pre[@class="rust"]//a[@href="#50"]' 'WhyNot'
// @has - '//pre[@class="rust"]//a[@href="#50"]' 'AnotherTrait'
// @has - '//pre[@class="rust"]//a[@href="#51"]' 'WhyNot'
pub fn foo3<T, V>(t: &T, v: &V)
where
T: AnotherTrait,
Expand All @@ -59,7 +60,7 @@ where

pub trait AnotherTrait2 {}

// @has - '//pre[@class="rust"]//a[@href="#60"]' 'AnotherTrait2'
// @has - '//pre[@class="rust"]//a[@href="#61"]' 'AnotherTrait2'
pub fn foo4() {
let x: Vec<AnotherTrait2> = Vec::new();
}
Expand Down
51 changes: 51 additions & 0 deletions tests/rustdoc/jump-to-def-doc-links.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// compile-flags: -Zunstable-options --generate-link-to-definition

#![crate_name = "foo"]

// @has 'src/foo/jump-to-def-doc-links.rs.html'

// @has - '//a[@href="../../foo/struct.Bar.html"]' 'Bar'
// @has - '//a[@href="../../foo/struct.Foo.html"]' 'Foo'
pub struct Bar; pub struct Foo;

// @has - '//a[@href="../../foo/enum.Enum.html"]' 'Enum'
pub enum Enum {
Variant1(String),
Variant2(u8),
}

// @has - '//a[@href="../../foo/struct.Struct.html"]' 'Struct'
pub struct Struct {
pub a: u8,
b: Foo,
}

impl Struct {
pub fn foo() {}
pub fn foo2(&self) {}
fn bar() {}
fn bar(&self) {}
}

// @has - '//a[@href="../../foo/trait.Trait.html"]' 'Trait'
pub trait Trait {
fn foo();
}

impl Trait for Struct {
fn foo() {}
}

// @has - '//a[@href="../../foo/union.Union.html"]' 'Union'
pub union Union {
pub a: u16,
pub f: u32,
}

// @has - '//a[@href="../../foo/fn.bar.html"]' 'bar'
pub fn bar(b: Bar) {
let x = Foo;
}

// @has - '//a[@href="../../foo/bar/index.html"]' 'bar'
pub mod bar {}