Add support for macro in "jump to def" feature
This commit is contained in:
parent
a5c039cdb7
commit
3f12fa7fda
@ -519,6 +519,7 @@ fn print<'a, 'tcx: 'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Possible errors when computing href link source for a `DefId`
|
// Possible errors when computing href link source for a `DefId`
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
pub(crate) enum HrefError {
|
pub(crate) enum HrefError {
|
||||||
/// This item is known to rustdoc, but from a crate that does not have documentation generated.
|
/// This item is known to rustdoc, but from a crate that does not have documentation generated.
|
||||||
///
|
///
|
||||||
|
@ -5,15 +5,19 @@
|
|||||||
//!
|
//!
|
||||||
//! Use the `render_with_highlighting` to highlight some rust code.
|
//! Use the `render_with_highlighting` to highlight some rust code.
|
||||||
|
|
||||||
use crate::clean::PrimitiveType;
|
use crate::clean::{ExternalLocation, PrimitiveType};
|
||||||
use crate::html::escape::Escape;
|
use crate::html::escape::Escape;
|
||||||
use crate::html::render::Context;
|
use crate::html::render::Context;
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt::{Display, Write};
|
use std::fmt::{Display, Write};
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
use rustc_ast as ast;
|
||||||
use rustc_data_structures::fx::FxHashMap;
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_lexer::{LiteralKind, TokenKind};
|
use rustc_lexer::{LiteralKind, TokenKind};
|
||||||
|
use rustc_metadata::creader::{CStore, LoadedMacro};
|
||||||
use rustc_span::edition::Edition;
|
use rustc_span::edition::Edition;
|
||||||
use rustc_span::symbol::Symbol;
|
use rustc_span::symbol::Symbol;
|
||||||
use rustc_span::{BytePos, Span, DUMMY_SP};
|
use rustc_span::{BytePos, Span, DUMMY_SP};
|
||||||
@ -99,6 +103,7 @@ fn write_code(
|
|||||||
) {
|
) {
|
||||||
// This replace allows to fix how the code source with DOS backline characters is displayed.
|
// This replace allows to fix how the code source with DOS backline characters is displayed.
|
||||||
let src = src.replace("\r\n", "\n");
|
let src = src.replace("\r\n", "\n");
|
||||||
|
let mut closing_tag = "";
|
||||||
Classifier::new(
|
Classifier::new(
|
||||||
&src,
|
&src,
|
||||||
edition,
|
edition,
|
||||||
@ -108,8 +113,8 @@ fn write_code(
|
|||||||
.highlight(&mut |highlight| {
|
.highlight(&mut |highlight| {
|
||||||
match highlight {
|
match highlight {
|
||||||
Highlight::Token { text, class } => string(out, Escape(text), class, &context_info),
|
Highlight::Token { text, class } => string(out, Escape(text), class, &context_info),
|
||||||
Highlight::EnterSpan { class } => enter_span(out, class),
|
Highlight::EnterSpan { class } => closing_tag = enter_span(out, class, &context_info),
|
||||||
Highlight::ExitSpan => exit_span(out),
|
Highlight::ExitSpan => exit_span(out, &closing_tag),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -129,7 +134,7 @@ enum Class {
|
|||||||
RefKeyWord,
|
RefKeyWord,
|
||||||
Self_(Span),
|
Self_(Span),
|
||||||
Op,
|
Op,
|
||||||
Macro,
|
Macro(Span),
|
||||||
MacroNonTerminal,
|
MacroNonTerminal,
|
||||||
String,
|
String,
|
||||||
Number,
|
Number,
|
||||||
@ -153,7 +158,7 @@ fn as_html(self) -> &'static str {
|
|||||||
Class::RefKeyWord => "kw-2",
|
Class::RefKeyWord => "kw-2",
|
||||||
Class::Self_(_) => "self",
|
Class::Self_(_) => "self",
|
||||||
Class::Op => "op",
|
Class::Op => "op",
|
||||||
Class::Macro => "macro",
|
Class::Macro(_) => "macro",
|
||||||
Class::MacroNonTerminal => "macro-nonterminal",
|
Class::MacroNonTerminal => "macro-nonterminal",
|
||||||
Class::String => "string",
|
Class::String => "string",
|
||||||
Class::Number => "number",
|
Class::Number => "number",
|
||||||
@ -171,8 +176,22 @@ fn as_html(self) -> &'static str {
|
|||||||
/// a "span" (a tuple representing `(lo, hi)` equivalent of `Span`).
|
/// a "span" (a tuple representing `(lo, hi)` equivalent of `Span`).
|
||||||
fn get_span(self) -> Option<Span> {
|
fn get_span(self) -> Option<Span> {
|
||||||
match self {
|
match self {
|
||||||
Self::Ident(sp) | Self::Self_(sp) => Some(sp),
|
Self::Ident(sp) | Self::Self_(sp) | Self::Macro(sp) => Some(sp),
|
||||||
_ => None,
|
Self::Comment
|
||||||
|
| Self::DocComment
|
||||||
|
| Self::Attribute
|
||||||
|
| Self::KeyWord
|
||||||
|
| Self::RefKeyWord
|
||||||
|
| Self::Op
|
||||||
|
| Self::MacroNonTerminal
|
||||||
|
| Self::String
|
||||||
|
| Self::Number
|
||||||
|
| Self::Bool
|
||||||
|
| Self::Lifetime
|
||||||
|
| Self::PreludeTy
|
||||||
|
| Self::PreludeVal
|
||||||
|
| Self::QuestionMark
|
||||||
|
| Self::Decoration(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -611,7 +630,7 @@ fn advance(
|
|||||||
},
|
},
|
||||||
TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => {
|
TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => {
|
||||||
self.in_macro = true;
|
self.in_macro = true;
|
||||||
sink(Highlight::EnterSpan { class: Class::Macro });
|
sink(Highlight::EnterSpan { class: Class::Macro(self.new_span(before, text)) });
|
||||||
sink(Highlight::Token { text, class: None });
|
sink(Highlight::Token { text, class: None });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -658,13 +677,18 @@ fn check_if_is_union_keyword(&mut self) -> bool {
|
|||||||
|
|
||||||
/// Called when we start processing a span of text that should be highlighted.
|
/// Called when we start processing a span of text that should be highlighted.
|
||||||
/// The `Class` argument specifies how it should be highlighted.
|
/// The `Class` argument specifies how it should be highlighted.
|
||||||
fn enter_span(out: &mut Buffer, klass: Class) {
|
fn enter_span(
|
||||||
write!(out, "<span class=\"{}\">", klass.as_html());
|
out: &mut Buffer,
|
||||||
|
klass: Class,
|
||||||
|
context_info: &Option<ContextInfo<'_, '_, '_>>,
|
||||||
|
) -> &'static str {
|
||||||
|
string_without_closing_tag(out, "", Some(klass), context_info)
|
||||||
|
.expect("no closing tag to close wrapper...")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called at the end of a span of highlighted text.
|
/// Called at the end of a span of highlighted text.
|
||||||
fn exit_span(out: &mut Buffer) {
|
fn exit_span(out: &mut Buffer, closing_tag: &str) {
|
||||||
out.write_str("</span>");
|
out.write_str(closing_tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called for a span of text. If the text should be highlighted differently
|
/// Called for a span of text. If the text should be highlighted differently
|
||||||
@ -689,13 +713,28 @@ fn string<T: Display>(
|
|||||||
klass: Option<Class>,
|
klass: Option<Class>,
|
||||||
context_info: &Option<ContextInfo<'_, '_, '_>>,
|
context_info: &Option<ContextInfo<'_, '_, '_>>,
|
||||||
) {
|
) {
|
||||||
|
if let Some(closing_tag) = string_without_closing_tag(out, text, klass, context_info) {
|
||||||
|
out.write_str(closing_tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_without_closing_tag<T: Display>(
|
||||||
|
out: &mut Buffer,
|
||||||
|
text: T,
|
||||||
|
klass: Option<Class>,
|
||||||
|
context_info: &Option<ContextInfo<'_, '_, '_>>,
|
||||||
|
) -> Option<&'static str> {
|
||||||
let Some(klass) = klass
|
let Some(klass) = klass
|
||||||
else { return write!(out, "{}", text) };
|
else {
|
||||||
|
write!(out, "{}", text);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
let Some(def_span) = klass.get_span()
|
let Some(def_span) = klass.get_span()
|
||||||
else {
|
else {
|
||||||
write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text);
|
write!(out, "<span class=\"{}\">{}", klass.as_html(), text);
|
||||||
return;
|
return Some("</span>");
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut text_s = text.to_string();
|
let mut text_s = text.to_string();
|
||||||
if text_s.contains("::") {
|
if text_s.contains("::") {
|
||||||
text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
|
text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
|
||||||
@ -730,8 +769,17 @@ fn string<T: Display>(
|
|||||||
.map(|s| format!("{}{}", context_info.root_path, s)),
|
.map(|s| format!("{}{}", context_info.root_path, s)),
|
||||||
LinkFromSrc::External(def_id) => {
|
LinkFromSrc::External(def_id) => {
|
||||||
format::href_with_root_path(*def_id, context, Some(context_info.root_path))
|
format::href_with_root_path(*def_id, context, Some(context_info.root_path))
|
||||||
.ok()
|
|
||||||
.map(|(url, _, _)| url)
|
.map(|(url, _, _)| url)
|
||||||
|
.or_else(|e| {
|
||||||
|
if e == format::HrefError::NotInExternalCache
|
||||||
|
&& matches!(klass, Class::Macro(_))
|
||||||
|
{
|
||||||
|
Ok(generate_macro_def_id_path(context_info, *def_id))
|
||||||
|
} else {
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
}
|
}
|
||||||
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
|
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
|
||||||
PrimitiveType::primitive_locations(context.tcx())[prim],
|
PrimitiveType::primitive_locations(context.tcx())[prim],
|
||||||
@ -743,11 +791,51 @@ fn string<T: Display>(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
write!(out, "<a class=\"{}\" href=\"{}\">{}</a>", klass.as_html(), href, text_s);
|
write!(out, "<a class=\"{}\" href=\"{}\">{}", klass.as_html(), href, text_s);
|
||||||
return;
|
return Some("</a>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text_s);
|
write!(out, "<span class=\"{}\">{}", klass.as_html(), text_s);
|
||||||
|
Some("</span>")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is to get the external macro path because they are not in the cache used n
|
||||||
|
/// `href_with_root_path`.
|
||||||
|
fn generate_macro_def_id_path(context_info: &ContextInfo<'_, '_, '_>, def_id: DefId) -> String {
|
||||||
|
let tcx = context_info.context.shared.tcx;
|
||||||
|
let crate_name = tcx.crate_name(def_id.krate).to_string();
|
||||||
|
let cache = &context_info.context.cache();
|
||||||
|
|
||||||
|
let relative = tcx.def_path(def_id).data.into_iter().filter_map(|elem| {
|
||||||
|
// extern blocks have an empty name
|
||||||
|
let s = elem.data.to_string();
|
||||||
|
if !s.is_empty() { Some(s) } else { None }
|
||||||
|
});
|
||||||
|
// Check to see if it is a macro 2.0 or built-in macro
|
||||||
|
let mut path = if matches!(
|
||||||
|
CStore::from_tcx(tcx).load_macro_untracked(def_id, tcx.sess),
|
||||||
|
LoadedMacro::MacroDef(def, _)
|
||||||
|
if matches!(&def.kind, ast::ItemKind::MacroDef(ast_def)
|
||||||
|
if !ast_def.macro_rules)
|
||||||
|
) {
|
||||||
|
once(crate_name.clone()).chain(relative).collect()
|
||||||
|
} else {
|
||||||
|
vec![crate_name.clone(), relative.last().expect("relative was empty")]
|
||||||
|
};
|
||||||
|
|
||||||
|
let url_parts = match cache.extern_locations[&def_id.krate] {
|
||||||
|
ExternalLocation::Remote(ref s) => vec![s.trim_end_matches('/')],
|
||||||
|
ExternalLocation::Local => vec![context_info.root_path.trim_end_matches('/'), &crate_name],
|
||||||
|
ExternalLocation::Unknown => panic!("unknown crate"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let last = path.pop().unwrap();
|
||||||
|
let last = format!("macro.{}.html", last);
|
||||||
|
if path.is_empty() {
|
||||||
|
format!("{}/{}", url_parts.join("/"), last)
|
||||||
|
} else {
|
||||||
|
format!("{}/{}/{}", url_parts.join("/"), path.join("/"), last)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
use rustc_hir::{ExprKind, HirId, Mod, Node};
|
use rustc_hir::{ExprKind, HirId, Mod, Node};
|
||||||
use rustc_middle::hir::nested_filter;
|
use rustc_middle::hir::nested_filter;
|
||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
use rustc_span::Span;
|
use rustc_span::hygiene::MacroKind;
|
||||||
|
use rustc_span::{BytePos, ExpnKind, Span};
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@ -63,32 +64,59 @@ struct SpanMapVisitor<'tcx> {
|
|||||||
|
|
||||||
impl<'tcx> SpanMapVisitor<'tcx> {
|
impl<'tcx> SpanMapVisitor<'tcx> {
|
||||||
/// This function is where we handle `hir::Path` elements and add them into the "span map".
|
/// 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<'_>, path_span: Option<Span>) {
|
fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
|
||||||
let info = match path.res {
|
let info = match path.res {
|
||||||
// FIXME: For now, we only handle `DefKind` if it's not `DefKind::TyParam` or
|
// FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
|
||||||
// `DefKind::Macro`. Would be nice to support them too alongside the other `DefKind`
|
// Would be nice to support them too alongside the other `DefKind`
|
||||||
// (such as primitive types!).
|
// (such as primitive types!).
|
||||||
Res::Def(kind, def_id) if kind != DefKind::TyParam => {
|
Res::Def(kind, def_id) if kind != DefKind::TyParam => Some(def_id),
|
||||||
if matches!(kind, DefKind::Macro(_)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Some(def_id)
|
|
||||||
}
|
|
||||||
Res::Local(_) => None,
|
Res::Local(_) => None,
|
||||||
Res::PrimTy(p) => {
|
Res::PrimTy(p) => {
|
||||||
// FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
|
// FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
|
||||||
let span = path_span.unwrap_or(path.span);
|
self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
|
||||||
self.matches.insert(span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Res::Err => return,
|
Res::Err => return,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
if let Some(span) = self.tcx.hir().res_span(path.res) {
|
if let Some(span) = self.tcx.hir().res_span(path.res) {
|
||||||
self.matches
|
self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
|
||||||
.insert(path_span.unwrap_or(path.span), LinkFromSrc::Local(clean::Span::new(span)));
|
|
||||||
} else if let Some(def_id) = info {
|
} else if let Some(def_id) = info {
|
||||||
self.matches.insert(path_span.unwrap_or(path.span), LinkFromSrc::External(def_id));
|
self.matches.insert(path.span, LinkFromSrc::External(def_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the macro call into the span map. Returns `true` if the `span` was inside a macro
|
||||||
|
/// expansion, whether or not it was added to the span map.
|
||||||
|
fn handle_macro(&mut self, span: Span) -> bool {
|
||||||
|
if span.from_expansion() {
|
||||||
|
let mut data = span.ctxt().outer_expn_data();
|
||||||
|
let mut call_site = data.call_site;
|
||||||
|
while call_site.from_expansion() {
|
||||||
|
data = call_site.ctxt().outer_expn_data();
|
||||||
|
call_site = data.call_site;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ExpnKind::Macro(MacroKind::Bang, macro_name) = data.kind {
|
||||||
|
let link_from_src = if let Some(macro_def_id) = data.macro_def_id {
|
||||||
|
if macro_def_id.is_local() {
|
||||||
|
LinkFromSrc::Local(clean::Span::new(data.def_site))
|
||||||
|
} else {
|
||||||
|
LinkFromSrc::External(macro_def_id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
let new_span = data.call_site;
|
||||||
|
let macro_name = macro_name.as_str();
|
||||||
|
// The "call_site" includes the whole macro with its "arguments". We only want
|
||||||
|
// the macro name.
|
||||||
|
let new_span = new_span.with_hi(new_span.lo() + BytePos(macro_name.len() as u32));
|
||||||
|
self.matches.insert(new_span, link_from_src);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,7 +129,10 @@ fn nested_visit_map(&mut self) -> Self::Map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) {
|
fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) {
|
||||||
self.handle_path(path, None);
|
if self.handle_macro(path.span) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.handle_path(path);
|
||||||
intravisit::walk_path(self, path);
|
intravisit::walk_path(self, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,12 +174,18 @@ fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if self.handle_macro(expr.span) {
|
||||||
|
// We don't want to deeper into the macro.
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
intravisit::walk_expr(self, expr);
|
intravisit::walk_expr(self, expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) {
|
fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) {
|
||||||
self.handle_path(path, None);
|
if self.handle_macro(path.span) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.handle_path(path);
|
||||||
intravisit::walk_use(self, path, id);
|
intravisit::walk_use(self, path, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user