turn intra-doc-link collection into an early pass
This commit is contained in:
parent
d3d6f76887
commit
9e4e266877
@ -19,14 +19,12 @@ pub use self::FunctionRetTy::*;
|
||||
pub use self::Visibility::{Public, Inherited};
|
||||
|
||||
use rustc_target::spec::abi::Abi;
|
||||
use syntax;
|
||||
use syntax::ast::{self, AttrStyle, Name, NodeId, Ident};
|
||||
use syntax::ast::{self, AttrStyle, Ident};
|
||||
use syntax::attr;
|
||||
use syntax::codemap::{dummy_spanned, Spanned};
|
||||
use syntax::feature_gate::UnstableFeatures;
|
||||
use syntax::ptr::P;
|
||||
use syntax::symbol::keywords::{self, Keyword};
|
||||
use syntax::symbol::{Symbol, InternedString};
|
||||
use syntax::symbol::InternedString;
|
||||
use syntax_pos::{self, DUMMY_SP, Pos, FileName};
|
||||
|
||||
use rustc::mir::interpret::ConstValue;
|
||||
@ -38,14 +36,12 @@ use rustc::mir::interpret::GlobalId;
|
||||
use rustc::hir::{self, GenericArg, HirVec};
|
||||
use rustc::hir::def::{self, Def, CtorKind};
|
||||
use rustc::hir::def_id::{CrateNum, DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
|
||||
use rustc::hir::map::Node;
|
||||
use rustc::ty::subst::Substs;
|
||||
use rustc::ty::{self, TyCtxt, Region, RegionVid, Ty, AdtKind};
|
||||
use rustc::middle::stability;
|
||||
use rustc::util::nodemap::{FxHashMap, FxHashSet};
|
||||
use rustc_typeck::hir_ty_to_ty;
|
||||
use rustc::infer::region_constraints::{RegionConstraintData, Constraint};
|
||||
use rustc::lint as lint;
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::fmt;
|
||||
@ -59,14 +55,12 @@ use std::str::FromStr;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use std::u32;
|
||||
use std::ops::Range;
|
||||
|
||||
use core::{self, DocContext};
|
||||
use doctree;
|
||||
use visit_ast;
|
||||
use html::render::{cache, ExternalLocation};
|
||||
use html::item_type::ItemType;
|
||||
use html::markdown::markdown_links;
|
||||
|
||||
pub mod inline;
|
||||
pub mod cfg;
|
||||
@ -580,32 +574,7 @@ impl Clean<Item> for doctree::Module {
|
||||
// maintain a stack of mod ids, for doc comment path resolution
|
||||
// but we also need to resolve the module's own docs based on whether its docs were written
|
||||
// inside or outside the module, so check for that
|
||||
let attrs = if self.attrs.iter()
|
||||
.filter(|a| a.check_name("doc"))
|
||||
.next()
|
||||
.map_or(true, |a| a.style == AttrStyle::Inner) {
|
||||
// inner doc comment, use the module's own scope for resolution
|
||||
if self.id != NodeId::new(0) {
|
||||
*cx.current_item_name.borrow_mut() = Some(cx.tcx.hir.name(self.id));
|
||||
} else {
|
||||
*cx.current_item_name.borrow_mut() = None;
|
||||
}
|
||||
cx.mod_ids.borrow_mut().push(self.id);
|
||||
self.attrs.clean(cx)
|
||||
} else {
|
||||
// outer doc comment, use its parent's scope
|
||||
match cx.mod_ids.borrow().last() {
|
||||
Some(parent) if *parent != NodeId::new(0) => {
|
||||
*cx.current_item_name.borrow_mut() = Some(cx.tcx.hir.name(*parent));
|
||||
}
|
||||
_ => {
|
||||
*cx.current_item_name.borrow_mut() = None;
|
||||
}
|
||||
}
|
||||
let attrs = self.attrs.clean(cx);
|
||||
cx.mod_ids.borrow_mut().push(self.id);
|
||||
attrs
|
||||
};
|
||||
let attrs = self.attrs.clean(cx);
|
||||
|
||||
let mut items: Vec<Item> = vec![];
|
||||
items.extend(self.extern_crates.iter().map(|x| x.clean(cx)));
|
||||
@ -624,8 +593,6 @@ impl Clean<Item> for doctree::Module {
|
||||
items.extend(self.impls.iter().flat_map(|x| x.clean(cx)));
|
||||
items.extend(self.macros.iter().map(|x| x.clean(cx)));
|
||||
|
||||
cx.mod_ids.borrow_mut().pop();
|
||||
|
||||
// determine if we should display the inner contents or
|
||||
// the outer `mod` item for the source code.
|
||||
let whence = {
|
||||
@ -785,6 +752,7 @@ pub struct Attributes {
|
||||
pub span: Option<syntax_pos::Span>,
|
||||
/// map from Rust paths to resolved defs and potential URL fragments
|
||||
pub links: Vec<(String, Option<DefId>, Option<String>)>,
|
||||
pub inner_docs: bool,
|
||||
}
|
||||
|
||||
impl Attributes {
|
||||
@ -929,12 +897,18 @@ impl Attributes {
|
||||
}
|
||||
}
|
||||
|
||||
let inner_docs = attrs.iter()
|
||||
.filter(|a| a.check_name("doc"))
|
||||
.next()
|
||||
.map_or(true, |a| a.style == AttrStyle::Inner);
|
||||
|
||||
Attributes {
|
||||
doc_strings,
|
||||
other_attrs,
|
||||
cfg: if cfg == Cfg::True { None } else { Some(Arc::new(cfg)) },
|
||||
span: sp,
|
||||
links: vec![],
|
||||
inner_docs,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1027,487 +1001,9 @@ impl AttributesExt for Attributes {
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a def, returns its name and disambiguator
|
||||
/// for a value namespace
|
||||
///
|
||||
/// Returns None for things which cannot be ambiguous since
|
||||
/// they exist in both namespaces (structs and modules)
|
||||
fn value_ns_kind(def: Def, path_str: &str) -> Option<(&'static str, String)> {
|
||||
match def {
|
||||
// structs, variants, and mods exist in both namespaces. skip them
|
||||
Def::StructCtor(..) | Def::Mod(..) | Def::Variant(..) | Def::VariantCtor(..) => None,
|
||||
Def::Fn(..)
|
||||
=> Some(("function", format!("{}()", path_str))),
|
||||
Def::Method(..)
|
||||
=> Some(("method", format!("{}()", path_str))),
|
||||
Def::Const(..)
|
||||
=> Some(("const", format!("const@{}", path_str))),
|
||||
Def::Static(..)
|
||||
=> Some(("static", format!("static@{}", path_str))),
|
||||
_ => Some(("value", format!("value@{}", path_str))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a def, returns its name, the article to be used, and a disambiguator
|
||||
/// for the type namespace
|
||||
fn type_ns_kind(def: Def, path_str: &str) -> (&'static str, &'static str, String) {
|
||||
let (kind, article) = match def {
|
||||
// we can still have non-tuple structs
|
||||
Def::Struct(..) => ("struct", "a"),
|
||||
Def::Enum(..) => ("enum", "an"),
|
||||
Def::Trait(..) => ("trait", "a"),
|
||||
Def::Union(..) => ("union", "a"),
|
||||
_ => ("type", "a"),
|
||||
};
|
||||
(kind, article, format!("{}@{}", kind, path_str))
|
||||
}
|
||||
|
||||
fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
|
||||
if attrs.doc_strings.is_empty() {
|
||||
return DUMMY_SP;
|
||||
}
|
||||
let start = attrs.doc_strings[0].span();
|
||||
let end = attrs.doc_strings.last().expect("No doc strings provided").span();
|
||||
start.to(end)
|
||||
}
|
||||
|
||||
fn ambiguity_error(cx: &DocContext, attrs: &Attributes,
|
||||
path_str: &str,
|
||||
article1: &str, kind1: &str, disambig1: &str,
|
||||
article2: &str, kind2: &str, disambig2: &str) {
|
||||
let sp = span_of_attrs(attrs);
|
||||
cx.sess()
|
||||
.struct_span_warn(sp,
|
||||
&format!("`{}` is both {} {} and {} {}",
|
||||
path_str, article1, kind1,
|
||||
article2, kind2))
|
||||
.help(&format!("try `{}` if you want to select the {}, \
|
||||
or `{}` if you want to \
|
||||
select the {}",
|
||||
disambig1, kind1, disambig2,
|
||||
kind2))
|
||||
.emit();
|
||||
}
|
||||
|
||||
/// Given an enum variant's def, return the def of its enum and the associated fragment
|
||||
fn handle_variant(cx: &DocContext, def: Def) -> Result<(Def, Option<String>), ()> {
|
||||
use rustc::ty::DefIdTree;
|
||||
|
||||
let parent = if let Some(parent) = cx.tcx.parent(def.def_id()) {
|
||||
parent
|
||||
} else {
|
||||
return Err(())
|
||||
};
|
||||
let parent_def = Def::Enum(parent);
|
||||
let variant = cx.tcx.expect_variant_def(def);
|
||||
Ok((parent_def, Some(format!("{}.v", variant.name))))
|
||||
}
|
||||
|
||||
const PRIMITIVES: &[(&str, Def)] = &[
|
||||
("u8", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U8))),
|
||||
("u16", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U16))),
|
||||
("u32", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U32))),
|
||||
("u64", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U64))),
|
||||
("u128", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U128))),
|
||||
("usize", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::Usize))),
|
||||
("i8", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I8))),
|
||||
("i16", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I16))),
|
||||
("i32", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I32))),
|
||||
("i64", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I64))),
|
||||
("i128", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I128))),
|
||||
("isize", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::Isize))),
|
||||
("f32", Def::PrimTy(hir::PrimTy::TyFloat(syntax::ast::FloatTy::F32))),
|
||||
("f64", Def::PrimTy(hir::PrimTy::TyFloat(syntax::ast::FloatTy::F64))),
|
||||
("str", Def::PrimTy(hir::PrimTy::TyStr)),
|
||||
("bool", Def::PrimTy(hir::PrimTy::TyBool)),
|
||||
("char", Def::PrimTy(hir::PrimTy::TyChar)),
|
||||
];
|
||||
|
||||
fn is_primitive(path_str: &str, is_val: bool) -> Option<Def> {
|
||||
if is_val {
|
||||
None
|
||||
} else {
|
||||
PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a given string as a path, along with whether or not it is
|
||||
/// in the value namespace. Also returns an optional URL fragment in the case
|
||||
/// of variants and methods
|
||||
fn resolve(cx: &DocContext, path_str: &str, is_val: bool) -> Result<(Def, Option<String>), ()> {
|
||||
// In case we're in a module, try to resolve the relative
|
||||
// path
|
||||
if let Some(id) = cx.mod_ids.borrow().last() {
|
||||
let result = cx.resolver.borrow_mut()
|
||||
.with_scope(*id,
|
||||
|resolver| {
|
||||
resolver.resolve_str_path_error(DUMMY_SP,
|
||||
&path_str, is_val)
|
||||
});
|
||||
|
||||
if let Ok(result) = result {
|
||||
// In case this is a trait item, skip the
|
||||
// early return and try looking for the trait
|
||||
let value = match result.def {
|
||||
Def::Method(_) | Def::AssociatedConst(_) => true,
|
||||
Def::AssociatedTy(_) => false,
|
||||
Def::Variant(_) => return handle_variant(cx, result.def),
|
||||
// not a trait item, just return what we found
|
||||
_ => return Ok((result.def, None))
|
||||
};
|
||||
|
||||
if value != is_val {
|
||||
return Err(())
|
||||
}
|
||||
} else if let Some(prim) = is_primitive(path_str, is_val) {
|
||||
return Ok((prim, Some(path_str.to_owned())))
|
||||
} else {
|
||||
// If resolution failed, it may still be a method
|
||||
// because methods are not handled by the resolver
|
||||
// If so, bail when we're not looking for a value
|
||||
if !is_val {
|
||||
return Err(())
|
||||
}
|
||||
}
|
||||
|
||||
// Try looking for methods and associated items
|
||||
let mut split = path_str.rsplitn(2, "::");
|
||||
let item_name = if let Some(first) = split.next() {
|
||||
first
|
||||
} else {
|
||||
return Err(())
|
||||
};
|
||||
|
||||
let mut path = if let Some(second) = split.next() {
|
||||
second.to_owned()
|
||||
} else {
|
||||
return Err(())
|
||||
};
|
||||
|
||||
if path == "self" || path == "Self" {
|
||||
if let Some(name) = *cx.current_item_name.borrow() {
|
||||
path = name.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
let ty = cx.resolver.borrow_mut()
|
||||
.with_scope(*id,
|
||||
|resolver| {
|
||||
resolver.resolve_str_path_error(DUMMY_SP, &path, false)
|
||||
})?;
|
||||
match ty.def {
|
||||
Def::Struct(did) | Def::Union(did) | Def::Enum(did) | Def::TyAlias(did) => {
|
||||
let item = cx.tcx.inherent_impls(did)
|
||||
.iter()
|
||||
.flat_map(|imp| cx.tcx.associated_items(*imp))
|
||||
.find(|item| item.ident.name == item_name);
|
||||
if let Some(item) = item {
|
||||
let out = match item.kind {
|
||||
ty::AssociatedKind::Method if is_val => "method",
|
||||
ty::AssociatedKind::Const if is_val => "associatedconstant",
|
||||
_ => return Err(())
|
||||
};
|
||||
Ok((ty.def, Some(format!("{}.{}", out, item_name))))
|
||||
} else {
|
||||
match cx.tcx.type_of(did).sty {
|
||||
ty::TyAdt(def, _) => {
|
||||
if let Some(item) = if def.is_enum() {
|
||||
def.all_fields().find(|item| item.ident.name == item_name)
|
||||
} else {
|
||||
def.non_enum_variant()
|
||||
.fields
|
||||
.iter()
|
||||
.find(|item| item.ident.name == item_name)
|
||||
} {
|
||||
Ok((ty.def,
|
||||
Some(format!("{}.{}",
|
||||
if def.is_enum() {
|
||||
"variant"
|
||||
} else {
|
||||
"structfield"
|
||||
},
|
||||
item.ident))))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Def::Trait(did) => {
|
||||
let item = cx.tcx.associated_item_def_ids(did).iter()
|
||||
.map(|item| cx.tcx.associated_item(*item))
|
||||
.find(|item| item.ident.name == item_name);
|
||||
if let Some(item) = item {
|
||||
let kind = match item.kind {
|
||||
ty::AssociatedKind::Const if is_val => "associatedconstant",
|
||||
ty::AssociatedKind::Type if !is_val => "associatedtype",
|
||||
ty::AssociatedKind::Method if is_val => {
|
||||
if item.defaultness.has_value() {
|
||||
"method"
|
||||
} else {
|
||||
"tymethod"
|
||||
}
|
||||
}
|
||||
_ => return Err(())
|
||||
};
|
||||
|
||||
Ok((ty.def, Some(format!("{}.{}", kind, item_name))))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
_ => Err(())
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a string as a macro
|
||||
fn macro_resolve(cx: &DocContext, path_str: &str) -> Option<Def> {
|
||||
use syntax::ext::base::{MacroKind, SyntaxExtension};
|
||||
use syntax::ext::hygiene::Mark;
|
||||
let segment = ast::PathSegment::from_ident(Ident::from_str(path_str));
|
||||
let path = ast::Path { segments: vec![segment], span: DUMMY_SP };
|
||||
let mut resolver = cx.resolver.borrow_mut();
|
||||
let mark = Mark::root();
|
||||
let res = resolver
|
||||
.resolve_macro_to_def_inner(mark, &path, MacroKind::Bang, false);
|
||||
if let Ok(def) = res {
|
||||
if let SyntaxExtension::DeclMacro { .. } = *resolver.get_macro(def) {
|
||||
return Some(def);
|
||||
}
|
||||
}
|
||||
if let Some(def) = resolver.all_macros.get(&Symbol::intern(path_str)) {
|
||||
return Some(*def);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PathKind {
|
||||
/// can be either value or type, not a macro
|
||||
Unknown,
|
||||
/// macro
|
||||
Macro,
|
||||
/// values, functions, consts, statics, everything in the value namespace
|
||||
Value,
|
||||
/// types, traits, everything in the type namespace
|
||||
Type,
|
||||
}
|
||||
|
||||
fn resolution_failure(
|
||||
cx: &DocContext,
|
||||
attrs: &Attributes,
|
||||
path_str: &str,
|
||||
dox: &str,
|
||||
link_range: Option<Range<usize>>,
|
||||
) {
|
||||
let sp = span_of_attrs(attrs);
|
||||
let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str);
|
||||
|
||||
let code_dox = sp.to_src(cx);
|
||||
|
||||
let doc_comment_padding = 3;
|
||||
let mut diag = if let Some(link_range) = link_range {
|
||||
// blah blah blah\nblah\nblah [blah] blah blah\nblah blah
|
||||
// ^ ~~~~~~
|
||||
// | link_range
|
||||
// last_new_line_offset
|
||||
|
||||
let mut diag;
|
||||
if dox.lines().count() == code_dox.lines().count() {
|
||||
let line_offset = dox[..link_range.start].lines().count();
|
||||
// The span starts in the `///`, so we don't have to account for the leading whitespace
|
||||
let code_dox_len = if line_offset <= 1 {
|
||||
doc_comment_padding
|
||||
} else {
|
||||
// The first `///`
|
||||
doc_comment_padding +
|
||||
// Each subsequent leading whitespace and `///`
|
||||
code_dox.lines().skip(1).take(line_offset - 1).fold(0, |sum, line| {
|
||||
sum + doc_comment_padding + line.len() - line.trim().len()
|
||||
})
|
||||
};
|
||||
|
||||
// Extract the specific span
|
||||
let sp = sp.from_inner_byte_pos(
|
||||
link_range.start + code_dox_len,
|
||||
link_range.end + code_dox_len,
|
||||
);
|
||||
|
||||
diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
|
||||
NodeId::new(0),
|
||||
sp,
|
||||
&msg);
|
||||
diag.span_label(sp, "cannot be resolved, ignoring");
|
||||
} else {
|
||||
diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
|
||||
NodeId::new(0),
|
||||
sp,
|
||||
&msg);
|
||||
|
||||
let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
|
||||
let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
|
||||
|
||||
// Print the line containing the `link_range` and manually mark it with '^'s
|
||||
diag.note(&format!(
|
||||
"the link appears in this line:\n\n{line}\n\
|
||||
{indicator: <before$}{indicator:^<found$}",
|
||||
line=line,
|
||||
indicator="",
|
||||
before=link_range.start - last_new_line_offset,
|
||||
found=link_range.len(),
|
||||
));
|
||||
}
|
||||
diag
|
||||
} else {
|
||||
cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
|
||||
NodeId::new(0),
|
||||
sp,
|
||||
&msg)
|
||||
};
|
||||
diag.help("to escape `[` and `]` characters, just add '\\' before them like \
|
||||
`\\[` or `\\]`");
|
||||
diag.emit();
|
||||
}
|
||||
|
||||
impl Clean<Attributes> for [ast::Attribute] {
|
||||
fn clean(&self, cx: &DocContext) -> Attributes {
|
||||
let mut attrs = Attributes::from_ast(cx.sess().diagnostic(), self);
|
||||
|
||||
if UnstableFeatures::from_environment().is_nightly_build() {
|
||||
let dox = attrs.collapsed_doc_value().unwrap_or_else(String::new);
|
||||
for (ori_link, link_range) in markdown_links(&dox) {
|
||||
// bail early for real links
|
||||
if ori_link.contains('/') {
|
||||
continue;
|
||||
}
|
||||
let link = ori_link.replace("`", "");
|
||||
let (def, fragment) = {
|
||||
let mut kind = PathKind::Unknown;
|
||||
let path_str = if let Some(prefix) =
|
||||
["struct@", "enum@", "type@",
|
||||
"trait@", "union@"].iter()
|
||||
.find(|p| link.starts_with(**p)) {
|
||||
kind = PathKind::Type;
|
||||
link.trim_left_matches(prefix)
|
||||
} else if let Some(prefix) =
|
||||
["const@", "static@",
|
||||
"value@", "function@", "mod@",
|
||||
"fn@", "module@", "method@"]
|
||||
.iter().find(|p| link.starts_with(**p)) {
|
||||
kind = PathKind::Value;
|
||||
link.trim_left_matches(prefix)
|
||||
} else if link.ends_with("()") {
|
||||
kind = PathKind::Value;
|
||||
link.trim_right_matches("()")
|
||||
} else if link.starts_with("macro@") {
|
||||
kind = PathKind::Macro;
|
||||
link.trim_left_matches("macro@")
|
||||
} else if link.ends_with('!') {
|
||||
kind = PathKind::Macro;
|
||||
link.trim_right_matches('!')
|
||||
} else {
|
||||
&link[..]
|
||||
}.trim();
|
||||
|
||||
if path_str.contains(|ch: char| !(ch.is_alphanumeric() ||
|
||||
ch == ':' || ch == '_')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match kind {
|
||||
PathKind::Value => {
|
||||
if let Ok(def) = resolve(cx, path_str, true) {
|
||||
def
|
||||
} else {
|
||||
resolution_failure(cx, &attrs, path_str, &dox, link_range);
|
||||
// this could just be a normal link or a broken link
|
||||
// we could potentially check if something is
|
||||
// "intra-doc-link-like" and warn in that case
|
||||
continue;
|
||||
}
|
||||
}
|
||||
PathKind::Type => {
|
||||
if let Ok(def) = resolve(cx, path_str, false) {
|
||||
def
|
||||
} else {
|
||||
resolution_failure(cx, &attrs, path_str, &dox, link_range);
|
||||
// this could just be a normal link
|
||||
continue;
|
||||
}
|
||||
}
|
||||
PathKind::Unknown => {
|
||||
// try everything!
|
||||
if let Some(macro_def) = macro_resolve(cx, path_str) {
|
||||
if let Ok(type_def) = resolve(cx, path_str, false) {
|
||||
let (type_kind, article, type_disambig)
|
||||
= type_ns_kind(type_def.0, path_str);
|
||||
ambiguity_error(cx, &attrs, path_str,
|
||||
article, type_kind, &type_disambig,
|
||||
"a", "macro", &format!("macro@{}", path_str));
|
||||
continue;
|
||||
} else if let Ok(value_def) = resolve(cx, path_str, true) {
|
||||
let (value_kind, value_disambig)
|
||||
= value_ns_kind(value_def.0, path_str)
|
||||
.expect("struct and mod cases should have been \
|
||||
caught in previous branch");
|
||||
ambiguity_error(cx, &attrs, path_str,
|
||||
"a", value_kind, &value_disambig,
|
||||
"a", "macro", &format!("macro@{}", path_str));
|
||||
}
|
||||
(macro_def, None)
|
||||
} else if let Ok(type_def) = resolve(cx, path_str, false) {
|
||||
// It is imperative we search for not-a-value first
|
||||
// Otherwise we will find struct ctors for when we are looking
|
||||
// for structs, and the link won't work.
|
||||
// if there is something in both namespaces
|
||||
if let Ok(value_def) = resolve(cx, path_str, true) {
|
||||
let kind = value_ns_kind(value_def.0, path_str);
|
||||
if let Some((value_kind, value_disambig)) = kind {
|
||||
let (type_kind, article, type_disambig)
|
||||
= type_ns_kind(type_def.0, path_str);
|
||||
ambiguity_error(cx, &attrs, path_str,
|
||||
article, type_kind, &type_disambig,
|
||||
"a", value_kind, &value_disambig);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
type_def
|
||||
} else if let Ok(value_def) = resolve(cx, path_str, true) {
|
||||
value_def
|
||||
} else {
|
||||
resolution_failure(cx, &attrs, path_str, &dox, link_range);
|
||||
// this could just be a normal link
|
||||
continue;
|
||||
}
|
||||
}
|
||||
PathKind::Macro => {
|
||||
if let Some(def) = macro_resolve(cx, path_str) {
|
||||
(def, None)
|
||||
} else {
|
||||
resolution_failure(cx, &attrs, path_str, &dox, link_range);
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Def::PrimTy(_) = def {
|
||||
attrs.links.push((ori_link, None, fragment));
|
||||
} else {
|
||||
let id = register_def(cx, def);
|
||||
attrs.links.push((ori_link, Some(id), fragment));
|
||||
}
|
||||
}
|
||||
|
||||
cx.sess().abort_if_errors();
|
||||
}
|
||||
|
||||
attrs
|
||||
Attributes::from_ast(cx.sess().diagnostic(), self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2165,7 +1661,6 @@ impl Clean<Item> for doctree::Function {
|
||||
(self.generics.clean(cx), (&self.decl, self.body).clean(cx))
|
||||
});
|
||||
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
Item {
|
||||
name: Some(self.name.clean(cx)),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -2340,7 +1835,6 @@ pub struct Trait {
|
||||
|
||||
impl Clean<Item> for doctree::Trait {
|
||||
fn clean(&self, cx: &DocContext) -> Item {
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
let attrs = self.attrs.clean(cx);
|
||||
let is_spotlight = attrs.has_doc_flag("spotlight");
|
||||
Item {
|
||||
@ -2412,7 +1906,6 @@ impl Clean<Item> for hir::TraitItem {
|
||||
AssociatedTypeItem(bounds.clean(cx), default.clean(cx))
|
||||
}
|
||||
};
|
||||
*cx.current_item_name.borrow_mut() = Some(self.ident.name);
|
||||
Item {
|
||||
name: Some(self.ident.name.clean(cx)),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -2445,7 +1938,6 @@ impl Clean<Item> for hir::ImplItem {
|
||||
generics: Generics::default(),
|
||||
}, true),
|
||||
};
|
||||
*cx.current_item_name.borrow_mut() = Some(self.ident.name);
|
||||
Item {
|
||||
name: Some(self.ident.name.clean(cx)),
|
||||
source: self.span.clean(cx),
|
||||
@ -3239,7 +2731,6 @@ impl<'tcx> Clean<Type> for Ty<'tcx> {
|
||||
|
||||
impl Clean<Item> for hir::StructField {
|
||||
fn clean(&self, cx: &DocContext) -> Item {
|
||||
*cx.current_item_name.borrow_mut() = Some(self.ident.name);
|
||||
Item {
|
||||
name: Some(self.ident.name).clean(cx),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -3319,7 +2810,6 @@ impl Clean<Vec<Item>> for doctree::Struct {
|
||||
let mut ret = get_auto_traits_with_node_id(cx, self.id, name.clone());
|
||||
ret.extend(get_blanket_impls_with_node_id(cx, self.id, name.clone()));
|
||||
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
ret.push(Item {
|
||||
name: Some(name),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -3346,7 +2836,6 @@ impl Clean<Vec<Item>> for doctree::Union {
|
||||
let mut ret = get_auto_traits_with_node_id(cx, self.id, name.clone());
|
||||
ret.extend(get_blanket_impls_with_node_id(cx, self.id, name.clone()));
|
||||
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
ret.push(Item {
|
||||
name: Some(name),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -3400,7 +2889,6 @@ impl Clean<Vec<Item>> for doctree::Enum {
|
||||
let mut ret = get_auto_traits_with_node_id(cx, self.id, name.clone());
|
||||
ret.extend(get_blanket_impls_with_node_id(cx, self.id, name.clone()));
|
||||
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
ret.push(Item {
|
||||
name: Some(name),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -3427,7 +2915,6 @@ pub struct Variant {
|
||||
|
||||
impl Clean<Item> for doctree::Variant {
|
||||
fn clean(&self, cx: &DocContext) -> Item {
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
Item {
|
||||
name: Some(self.name.clean(cx)),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -3708,7 +3195,6 @@ pub struct Typedef {
|
||||
|
||||
impl Clean<Item> for doctree::Typedef {
|
||||
fn clean(&self, cx: &DocContext) -> Item {
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
Item {
|
||||
name: Some(self.name.clean(cx)),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -3784,7 +3270,6 @@ pub struct Static {
|
||||
impl Clean<Item> for doctree::Static {
|
||||
fn clean(&self, cx: &DocContext) -> Item {
|
||||
debug!("cleaning static {}: {:?}", self.name.clean(cx), self);
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
Item {
|
||||
name: Some(self.name.clean(cx)),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -3810,7 +3295,6 @@ pub struct Constant {
|
||||
|
||||
impl Clean<Item> for doctree::Constant {
|
||||
fn clean(&self, cx: &DocContext) -> Item {
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
Item {
|
||||
name: Some(self.name.clean(cx)),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -3892,23 +3376,6 @@ pub fn get_blanket_impls_with_def_id(cx: &DocContext, id: DefId) -> Vec<Item> {
|
||||
finder.get_with_def_id(id)
|
||||
}
|
||||
|
||||
fn get_name_if_possible(cx: &DocContext, node: NodeId) -> Option<Name> {
|
||||
match cx.tcx.hir.get(node) {
|
||||
Node::NodeItem(_) |
|
||||
Node::NodeForeignItem(_) |
|
||||
Node::NodeImplItem(_) |
|
||||
Node::NodeTraitItem(_) |
|
||||
Node::NodeVariant(_) |
|
||||
Node::NodeField(_) |
|
||||
Node::NodeLifetime(_) |
|
||||
Node::NodeGenericParam(_) |
|
||||
Node::NodeBinding(&hir::Pat { node: hir::PatKind::Binding(_,_,_,_), .. }) |
|
||||
Node::NodeStructCtor(_) => {}
|
||||
_ => return None,
|
||||
}
|
||||
Some(cx.tcx.hir.name(node))
|
||||
}
|
||||
|
||||
impl Clean<Vec<Item>> for doctree::Impl {
|
||||
fn clean(&self, cx: &DocContext) -> Vec<Item> {
|
||||
let mut ret = Vec::new();
|
||||
@ -3928,7 +3395,6 @@ impl Clean<Vec<Item>> for doctree::Impl {
|
||||
.collect()
|
||||
}).unwrap_or(FxHashSet());
|
||||
|
||||
*cx.current_item_name.borrow_mut() = get_name_if_possible(cx, self.for_.id);
|
||||
ret.push(Item {
|
||||
name: None,
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -4016,7 +3482,6 @@ fn build_deref_target_impls(cx: &DocContext,
|
||||
|
||||
impl Clean<Item> for doctree::ExternCrate {
|
||||
fn clean(&self, cx: &DocContext) -> Item {
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
Item {
|
||||
name: None,
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -4064,7 +3529,6 @@ impl Clean<Vec<Item>> for doctree::Import {
|
||||
Import::Simple(name.clean(cx), resolve_use_source(cx, path))
|
||||
};
|
||||
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
vec![Item {
|
||||
name: None,
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -4134,7 +3598,6 @@ impl Clean<Item> for hir::ForeignItem {
|
||||
}
|
||||
};
|
||||
|
||||
*cx.current_item_name.borrow_mut() = Some(self.name);
|
||||
Item {
|
||||
name: Some(self.name.clean(cx)),
|
||||
attrs: self.attrs.clean(cx),
|
||||
@ -4150,7 +3613,7 @@ impl Clean<Item> for hir::ForeignItem {
|
||||
|
||||
// Utilities
|
||||
|
||||
trait ToSource {
|
||||
pub trait ToSource {
|
||||
fn to_src(&self, cx: &DocContext) -> String;
|
||||
}
|
||||
|
||||
@ -4260,7 +3723,7 @@ fn resolve_type(cx: &DocContext,
|
||||
ResolvedPath { path: path, typarams: None, did: did, is_generic: is_generic }
|
||||
}
|
||||
|
||||
fn register_def(cx: &DocContext, def: Def) -> DefId {
|
||||
pub fn register_def(cx: &DocContext, def: Def) -> DefId {
|
||||
debug!("register_def({:?})", def);
|
||||
|
||||
let (did, kind) = match def {
|
||||
@ -4311,7 +3774,6 @@ pub struct Macro {
|
||||
impl Clean<Item> for doctree::Macro {
|
||||
fn clean(&self, cx: &DocContext) -> Item {
|
||||
let name = self.name.clean(cx);
|
||||
*cx.current_item_name.borrow_mut() = None;
|
||||
Item {
|
||||
name: Some(name.clone()),
|
||||
attrs: self.attrs.clean(cx),
|
||||
|
@ -26,7 +26,7 @@ use rustc_metadata::creader::CrateLoader;
|
||||
use rustc_metadata::cstore::CStore;
|
||||
use rustc_target::spec::TargetTriple;
|
||||
|
||||
use syntax::ast::{self, Ident, Name, NodeId};
|
||||
use syntax::ast::{self, Ident};
|
||||
use syntax::codemap;
|
||||
use syntax::edition::Edition;
|
||||
use syntax::feature_gate::UnstableFeatures;
|
||||
@ -58,7 +58,6 @@ pub struct DocContext<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> {
|
||||
pub tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
pub resolver: &'a RefCell<resolve::Resolver<'rcx, 'cstore>>,
|
||||
/// The stack of module NodeIds up till this point
|
||||
pub mod_ids: RefCell<Vec<NodeId>>,
|
||||
pub crate_name: Option<String>,
|
||||
pub cstore: Rc<CStore>,
|
||||
pub populated_all_crate_impls: Cell<bool>,
|
||||
@ -88,7 +87,6 @@ pub struct DocContext<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> {
|
||||
pub all_fake_def_ids: RefCell<FxHashSet<DefId>>,
|
||||
/// Maps (type_id, trait_id) -> auto trait impl
|
||||
pub generated_synthetics: RefCell<FxHashSet<(DefId, DefId)>>,
|
||||
pub current_item_name: RefCell<Option<Name>>,
|
||||
pub all_traits: Vec<DefId>,
|
||||
}
|
||||
|
||||
@ -325,7 +323,8 @@ pub fn run_core(search_paths: SearchPaths,
|
||||
lint_cap: Option<lint::Level>,
|
||||
describe_lints: bool,
|
||||
mut manual_passes: Vec<String>,
|
||||
mut default_passes: passes::DefaultPassOption) -> (clean::Crate, RenderInfo, Vec<String>)
|
||||
mut default_passes: passes::DefaultPassOption)
|
||||
-> (clean::Crate, RenderInfo, Vec<String>)
|
||||
{
|
||||
// Parse, resolve, and typecheck the given crate.
|
||||
|
||||
@ -520,12 +519,10 @@ pub fn run_core(search_paths: SearchPaths,
|
||||
ty_substs: Default::default(),
|
||||
lt_substs: Default::default(),
|
||||
impl_trait_bounds: Default::default(),
|
||||
mod_ids: Default::default(),
|
||||
send_trait: send_trait,
|
||||
fake_def_ids: RefCell::new(FxHashMap()),
|
||||
all_fake_def_ids: RefCell::new(FxHashSet()),
|
||||
generated_synthetics: RefCell::new(FxHashSet()),
|
||||
current_item_name: RefCell::new(None),
|
||||
all_traits: tcx.all_traits(LOCAL_CRATE).to_vec(),
|
||||
};
|
||||
debug!("crate: {:?}", tcx.hir.krate());
|
||||
|
598
src/librustdoc/passes/collect_intra_doc_links.rs
Normal file
598
src/librustdoc/passes/collect_intra_doc_links.rs
Normal file
@ -0,0 +1,598 @@
|
||||
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use clean::*;
|
||||
|
||||
use rustc::lint as lint;
|
||||
use rustc::hir;
|
||||
use rustc::hir::def::Def;
|
||||
use rustc::ty;
|
||||
use syntax;
|
||||
use syntax::ast::{self, Ident, NodeId};
|
||||
use syntax::feature_gate::UnstableFeatures;
|
||||
use syntax::symbol::Symbol;
|
||||
use syntax_pos::{self, DUMMY_SP};
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use core::DocContext;
|
||||
use fold::DocFolder;
|
||||
use html::markdown::markdown_links;
|
||||
use passes::Pass;
|
||||
|
||||
pub const COLLECT_INTRA_DOC_LINKS: Pass =
|
||||
Pass::early("collect-intra-doc-links", collect_intra_doc_links,
|
||||
"reads a crate's documentation to resolve intra-doc-links");
|
||||
|
||||
pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext) -> Crate {
|
||||
if !UnstableFeatures::from_environment().is_nightly_build() {
|
||||
krate
|
||||
} else {
|
||||
let mut coll = LinkCollector::new(cx);
|
||||
|
||||
coll.fold_crate(krate)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PathKind {
|
||||
/// can be either value or type, not a macro
|
||||
Unknown,
|
||||
/// macro
|
||||
Macro,
|
||||
/// values, functions, consts, statics, everything in the value namespace
|
||||
Value,
|
||||
/// types, traits, everything in the type namespace
|
||||
Type,
|
||||
}
|
||||
|
||||
struct LinkCollector<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> {
|
||||
cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>,
|
||||
mod_ids: Vec<NodeId>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx, 'rcx, 'cstore> LinkCollector<'a, 'tcx, 'rcx, 'cstore> {
|
||||
fn new(cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>) -> Self {
|
||||
LinkCollector {
|
||||
cx,
|
||||
mod_ids: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a given string as a path, along with whether or not it is
|
||||
/// in the value namespace. Also returns an optional URL fragment in the case
|
||||
/// of variants and methods
|
||||
fn resolve(&self, path_str: &str, is_val: bool, current_item: &Option<String>)
|
||||
-> Result<(Def, Option<String>), ()>
|
||||
{
|
||||
let cx = self.cx;
|
||||
|
||||
// In case we're in a module, try to resolve the relative
|
||||
// path
|
||||
if let Some(id) = self.mod_ids.last() {
|
||||
let result = cx.resolver.borrow_mut()
|
||||
.with_scope(*id,
|
||||
|resolver| {
|
||||
resolver.resolve_str_path_error(DUMMY_SP,
|
||||
&path_str, is_val)
|
||||
});
|
||||
|
||||
if let Ok(result) = result {
|
||||
// In case this is a trait item, skip the
|
||||
// early return and try looking for the trait
|
||||
let value = match result.def {
|
||||
Def::Method(_) | Def::AssociatedConst(_) => true,
|
||||
Def::AssociatedTy(_) => false,
|
||||
Def::Variant(_) => return handle_variant(cx, result.def),
|
||||
// not a trait item, just return what we found
|
||||
_ => return Ok((result.def, None))
|
||||
};
|
||||
|
||||
if value != is_val {
|
||||
return Err(())
|
||||
}
|
||||
} else if let Some(prim) = is_primitive(path_str, is_val) {
|
||||
return Ok((prim, Some(path_str.to_owned())))
|
||||
} else {
|
||||
// If resolution failed, it may still be a method
|
||||
// because methods are not handled by the resolver
|
||||
// If so, bail when we're not looking for a value
|
||||
if !is_val {
|
||||
return Err(())
|
||||
}
|
||||
}
|
||||
|
||||
// Try looking for methods and associated items
|
||||
let mut split = path_str.rsplitn(2, "::");
|
||||
let item_name = if let Some(first) = split.next() {
|
||||
first
|
||||
} else {
|
||||
return Err(())
|
||||
};
|
||||
|
||||
let mut path = if let Some(second) = split.next() {
|
||||
second.to_owned()
|
||||
} else {
|
||||
return Err(())
|
||||
};
|
||||
|
||||
if path == "self" || path == "Self" {
|
||||
if let Some(name) = current_item.as_ref() {
|
||||
path = name.clone();
|
||||
}
|
||||
}
|
||||
|
||||
let ty = cx.resolver.borrow_mut()
|
||||
.with_scope(*id,
|
||||
|resolver| {
|
||||
resolver.resolve_str_path_error(DUMMY_SP, &path, false)
|
||||
})?;
|
||||
match ty.def {
|
||||
Def::Struct(did) | Def::Union(did) | Def::Enum(did) | Def::TyAlias(did) => {
|
||||
let item = cx.tcx.inherent_impls(did)
|
||||
.iter()
|
||||
.flat_map(|imp| cx.tcx.associated_items(*imp))
|
||||
.find(|item| item.ident.name == item_name);
|
||||
if let Some(item) = item {
|
||||
let out = match item.kind {
|
||||
ty::AssociatedKind::Method if is_val => "method",
|
||||
ty::AssociatedKind::Const if is_val => "associatedconstant",
|
||||
_ => return Err(())
|
||||
};
|
||||
Ok((ty.def, Some(format!("{}.{}", out, item_name))))
|
||||
} else {
|
||||
match cx.tcx.type_of(did).sty {
|
||||
ty::TyAdt(def, _) => {
|
||||
if let Some(item) = if def.is_enum() {
|
||||
def.all_fields().find(|item| item.ident.name == item_name)
|
||||
} else {
|
||||
def.non_enum_variant()
|
||||
.fields
|
||||
.iter()
|
||||
.find(|item| item.ident.name == item_name)
|
||||
} {
|
||||
Ok((ty.def,
|
||||
Some(format!("{}.{}",
|
||||
if def.is_enum() {
|
||||
"variant"
|
||||
} else {
|
||||
"structfield"
|
||||
},
|
||||
item.ident))))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Def::Trait(did) => {
|
||||
let item = cx.tcx.associated_item_def_ids(did).iter()
|
||||
.map(|item| cx.tcx.associated_item(*item))
|
||||
.find(|item| item.ident.name == item_name);
|
||||
if let Some(item) = item {
|
||||
let kind = match item.kind {
|
||||
ty::AssociatedKind::Const if is_val => "associatedconstant",
|
||||
ty::AssociatedKind::Type if !is_val => "associatedtype",
|
||||
ty::AssociatedKind::Method if is_val => {
|
||||
if item.defaultness.has_value() {
|
||||
"method"
|
||||
} else {
|
||||
"tymethod"
|
||||
}
|
||||
}
|
||||
_ => return Err(())
|
||||
};
|
||||
|
||||
Ok((ty.def, Some(format!("{}.{}", kind, item_name))))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
_ => Err(())
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx, 'rcx, 'cstore> DocFolder for LinkCollector<'a, 'tcx, 'rcx, 'cstore> {
|
||||
fn fold_item(&mut self, mut item: Item) -> Option<Item> {
|
||||
let item_node_id = if item.is_mod() {
|
||||
if let Some(id) = self.cx.tcx.hir.as_local_node_id(item.def_id) {
|
||||
Some(id)
|
||||
} else {
|
||||
debug!("attempting to fold on a non-local item: {:?}", item);
|
||||
return self.fold_item_recur(item);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let current_item = match item.inner {
|
||||
ModuleItem(..) => {
|
||||
if item.attrs.inner_docs {
|
||||
if item_node_id.unwrap() != NodeId::new(0) {
|
||||
item.name.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
match self.mod_ids.last() {
|
||||
Some(parent) if *parent != NodeId::new(0) => {
|
||||
//FIXME: can we pull the parent module's name from elsewhere?
|
||||
Some(self.cx.tcx.hir.name(*parent).to_string())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
ImplItem(Impl { ref for_, .. }) => {
|
||||
for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string())
|
||||
}
|
||||
ExternCrateItem(ref name, ..) => Some(name.clone()),
|
||||
ImportItem(Import::Simple(ref name, ..)) => Some(name.clone()),
|
||||
MacroItem(..) => None,
|
||||
_ => item.name.clone(),
|
||||
};
|
||||
|
||||
if item.is_mod() && item.attrs.inner_docs {
|
||||
self.mod_ids.push(item_node_id.unwrap());
|
||||
}
|
||||
|
||||
let cx = self.cx;
|
||||
let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
|
||||
|
||||
for (ori_link, link_range) in markdown_links(&dox) {
|
||||
// bail early for real links
|
||||
if ori_link.contains('/') {
|
||||
continue;
|
||||
}
|
||||
let link = ori_link.replace("`", "");
|
||||
let (def, fragment) = {
|
||||
let mut kind = PathKind::Unknown;
|
||||
let path_str = if let Some(prefix) =
|
||||
["struct@", "enum@", "type@",
|
||||
"trait@", "union@"].iter()
|
||||
.find(|p| link.starts_with(**p)) {
|
||||
kind = PathKind::Type;
|
||||
link.trim_left_matches(prefix)
|
||||
} else if let Some(prefix) =
|
||||
["const@", "static@",
|
||||
"value@", "function@", "mod@",
|
||||
"fn@", "module@", "method@"]
|
||||
.iter().find(|p| link.starts_with(**p)) {
|
||||
kind = PathKind::Value;
|
||||
link.trim_left_matches(prefix)
|
||||
} else if link.ends_with("()") {
|
||||
kind = PathKind::Value;
|
||||
link.trim_right_matches("()")
|
||||
} else if link.starts_with("macro@") {
|
||||
kind = PathKind::Macro;
|
||||
link.trim_left_matches("macro@")
|
||||
} else if link.ends_with('!') {
|
||||
kind = PathKind::Macro;
|
||||
link.trim_right_matches('!')
|
||||
} else {
|
||||
&link[..]
|
||||
}.trim();
|
||||
|
||||
if path_str.contains(|ch: char| !(ch.is_alphanumeric() ||
|
||||
ch == ':' || ch == '_')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match kind {
|
||||
PathKind::Value => {
|
||||
if let Ok(def) = self.resolve(path_str, true, ¤t_item) {
|
||||
def
|
||||
} else {
|
||||
resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
|
||||
// this could just be a normal link or a broken link
|
||||
// we could potentially check if something is
|
||||
// "intra-doc-link-like" and warn in that case
|
||||
continue;
|
||||
}
|
||||
}
|
||||
PathKind::Type => {
|
||||
if let Ok(def) = self.resolve(path_str, false, ¤t_item) {
|
||||
def
|
||||
} else {
|
||||
resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
|
||||
// this could just be a normal link
|
||||
continue;
|
||||
}
|
||||
}
|
||||
PathKind::Unknown => {
|
||||
// try everything!
|
||||
if let Some(macro_def) = macro_resolve(cx, path_str) {
|
||||
if let Ok(type_def) = self.resolve(path_str, false, ¤t_item) {
|
||||
let (type_kind, article, type_disambig)
|
||||
= type_ns_kind(type_def.0, path_str);
|
||||
ambiguity_error(cx, &item.attrs, path_str,
|
||||
article, type_kind, &type_disambig,
|
||||
"a", "macro", &format!("macro@{}", path_str));
|
||||
continue;
|
||||
} else if let Ok(value_def) = self.resolve(path_str,
|
||||
true,
|
||||
¤t_item) {
|
||||
let (value_kind, value_disambig)
|
||||
= value_ns_kind(value_def.0, path_str)
|
||||
.expect("struct and mod cases should have been \
|
||||
caught in previous branch");
|
||||
ambiguity_error(cx, &item.attrs, path_str,
|
||||
"a", value_kind, &value_disambig,
|
||||
"a", "macro", &format!("macro@{}", path_str));
|
||||
}
|
||||
(macro_def, None)
|
||||
} else if let Ok(type_def) = self.resolve(path_str, false, ¤t_item) {
|
||||
// It is imperative we search for not-a-value first
|
||||
// Otherwise we will find struct ctors for when we are looking
|
||||
// for structs, and the link won't work.
|
||||
// if there is something in both namespaces
|
||||
if let Ok(value_def) = self.resolve(path_str, true, ¤t_item) {
|
||||
let kind = value_ns_kind(value_def.0, path_str);
|
||||
if let Some((value_kind, value_disambig)) = kind {
|
||||
let (type_kind, article, type_disambig)
|
||||
= type_ns_kind(type_def.0, path_str);
|
||||
ambiguity_error(cx, &item.attrs, path_str,
|
||||
article, type_kind, &type_disambig,
|
||||
"a", value_kind, &value_disambig);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
type_def
|
||||
} else if let Ok(value_def) = self.resolve(path_str, true, ¤t_item) {
|
||||
value_def
|
||||
} else {
|
||||
resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
|
||||
// this could just be a normal link
|
||||
continue;
|
||||
}
|
||||
}
|
||||
PathKind::Macro => {
|
||||
if let Some(def) = macro_resolve(cx, path_str) {
|
||||
(def, None)
|
||||
} else {
|
||||
resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Def::PrimTy(_) = def {
|
||||
item.attrs.links.push((ori_link, None, fragment));
|
||||
} else {
|
||||
let id = register_def(cx, def);
|
||||
item.attrs.links.push((ori_link, Some(id), fragment));
|
||||
}
|
||||
}
|
||||
|
||||
cx.sess().abort_if_errors();
|
||||
|
||||
if item.is_mod() && !item.attrs.inner_docs {
|
||||
self.mod_ids.push(item_node_id.unwrap());
|
||||
}
|
||||
|
||||
if item.is_mod() {
|
||||
let ret = self.fold_item_recur(item);
|
||||
|
||||
self.mod_ids.pop();
|
||||
|
||||
ret
|
||||
} else {
|
||||
self.fold_item_recur(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a string as a macro
|
||||
fn macro_resolve(cx: &DocContext, path_str: &str) -> Option<Def> {
|
||||
use syntax::ext::base::{MacroKind, SyntaxExtension};
|
||||
use syntax::ext::hygiene::Mark;
|
||||
let segment = ast::PathSegment::from_ident(Ident::from_str(path_str));
|
||||
let path = ast::Path { segments: vec![segment], span: DUMMY_SP };
|
||||
let mut resolver = cx.resolver.borrow_mut();
|
||||
let mark = Mark::root();
|
||||
let res = resolver
|
||||
.resolve_macro_to_def_inner(mark, &path, MacroKind::Bang, false);
|
||||
if let Ok(def) = res {
|
||||
if let SyntaxExtension::DeclMacro { .. } = *resolver.get_macro(def) {
|
||||
return Some(def);
|
||||
}
|
||||
}
|
||||
if let Some(def) = resolver.all_macros.get(&Symbol::intern(path_str)) {
|
||||
return Some(*def);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
|
||||
if attrs.doc_strings.is_empty() {
|
||||
return DUMMY_SP;
|
||||
}
|
||||
let start = attrs.doc_strings[0].span();
|
||||
let end = attrs.doc_strings.last().expect("No doc strings provided").span();
|
||||
start.to(end)
|
||||
}
|
||||
|
||||
fn resolution_failure(
|
||||
cx: &DocContext,
|
||||
attrs: &Attributes,
|
||||
path_str: &str,
|
||||
dox: &str,
|
||||
link_range: Option<Range<usize>>,
|
||||
) {
|
||||
let sp = span_of_attrs(attrs);
|
||||
let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str);
|
||||
|
||||
let code_dox = sp.to_src(cx);
|
||||
|
||||
let doc_comment_padding = 3;
|
||||
let mut diag = if let Some(link_range) = link_range {
|
||||
// blah blah blah\nblah\nblah [blah] blah blah\nblah blah
|
||||
// ^ ~~~~~~
|
||||
// | link_range
|
||||
// last_new_line_offset
|
||||
|
||||
let mut diag;
|
||||
if dox.lines().count() == code_dox.lines().count() {
|
||||
let line_offset = dox[..link_range.start].lines().count();
|
||||
// The span starts in the `///`, so we don't have to account for the leading whitespace
|
||||
let code_dox_len = if line_offset <= 1 {
|
||||
doc_comment_padding
|
||||
} else {
|
||||
// The first `///`
|
||||
doc_comment_padding +
|
||||
// Each subsequent leading whitespace and `///`
|
||||
code_dox.lines().skip(1).take(line_offset - 1).fold(0, |sum, line| {
|
||||
sum + doc_comment_padding + line.len() - line.trim().len()
|
||||
})
|
||||
};
|
||||
|
||||
// Extract the specific span
|
||||
let sp = sp.from_inner_byte_pos(
|
||||
link_range.start + code_dox_len,
|
||||
link_range.end + code_dox_len,
|
||||
);
|
||||
|
||||
diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
|
||||
NodeId::new(0),
|
||||
sp,
|
||||
&msg);
|
||||
diag.span_label(sp, "cannot be resolved, ignoring");
|
||||
} else {
|
||||
diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
|
||||
NodeId::new(0),
|
||||
sp,
|
||||
&msg);
|
||||
|
||||
let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
|
||||
let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
|
||||
|
||||
// Print the line containing the `link_range` and manually mark it with '^'s
|
||||
diag.note(&format!(
|
||||
"the link appears in this line:\n\n{line}\n\
|
||||
{indicator: <before$}{indicator:^<found$}",
|
||||
line=line,
|
||||
indicator="",
|
||||
before=link_range.start - last_new_line_offset,
|
||||
found=link_range.len(),
|
||||
));
|
||||
}
|
||||
diag
|
||||
} else {
|
||||
cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
|
||||
NodeId::new(0),
|
||||
sp,
|
||||
&msg)
|
||||
};
|
||||
diag.help("to escape `[` and `]` characters, just add '\\' before them like \
|
||||
`\\[` or `\\]`");
|
||||
diag.emit();
|
||||
}
|
||||
|
||||
fn ambiguity_error(cx: &DocContext, attrs: &Attributes,
|
||||
path_str: &str,
|
||||
article1: &str, kind1: &str, disambig1: &str,
|
||||
article2: &str, kind2: &str, disambig2: &str) {
|
||||
let sp = span_of_attrs(attrs);
|
||||
cx.sess()
|
||||
.struct_span_warn(sp,
|
||||
&format!("`{}` is both {} {} and {} {}",
|
||||
path_str, article1, kind1,
|
||||
article2, kind2))
|
||||
.help(&format!("try `{}` if you want to select the {}, \
|
||||
or `{}` if you want to \
|
||||
select the {}",
|
||||
disambig1, kind1, disambig2,
|
||||
kind2))
|
||||
.emit();
|
||||
}
|
||||
|
||||
/// Given a def, returns its name and disambiguator
|
||||
/// for a value namespace
|
||||
///
|
||||
/// Returns None for things which cannot be ambiguous since
|
||||
/// they exist in both namespaces (structs and modules)
|
||||
fn value_ns_kind(def: Def, path_str: &str) -> Option<(&'static str, String)> {
|
||||
match def {
|
||||
// structs, variants, and mods exist in both namespaces. skip them
|
||||
Def::StructCtor(..) | Def::Mod(..) | Def::Variant(..) | Def::VariantCtor(..) => None,
|
||||
Def::Fn(..)
|
||||
=> Some(("function", format!("{}()", path_str))),
|
||||
Def::Method(..)
|
||||
=> Some(("method", format!("{}()", path_str))),
|
||||
Def::Const(..)
|
||||
=> Some(("const", format!("const@{}", path_str))),
|
||||
Def::Static(..)
|
||||
=> Some(("static", format!("static@{}", path_str))),
|
||||
_ => Some(("value", format!("value@{}", path_str))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a def, returns its name, the article to be used, and a disambiguator
|
||||
/// for the type namespace
|
||||
fn type_ns_kind(def: Def, path_str: &str) -> (&'static str, &'static str, String) {
|
||||
let (kind, article) = match def {
|
||||
// we can still have non-tuple structs
|
||||
Def::Struct(..) => ("struct", "a"),
|
||||
Def::Enum(..) => ("enum", "an"),
|
||||
Def::Trait(..) => ("trait", "a"),
|
||||
Def::Union(..) => ("union", "a"),
|
||||
_ => ("type", "a"),
|
||||
};
|
||||
(kind, article, format!("{}@{}", kind, path_str))
|
||||
}
|
||||
|
||||
/// Given an enum variant's def, return the def of its enum and the associated fragment
|
||||
fn handle_variant(cx: &DocContext, def: Def) -> Result<(Def, Option<String>), ()> {
|
||||
use rustc::ty::DefIdTree;
|
||||
|
||||
let parent = if let Some(parent) = cx.tcx.parent(def.def_id()) {
|
||||
parent
|
||||
} else {
|
||||
return Err(())
|
||||
};
|
||||
let parent_def = Def::Enum(parent);
|
||||
let variant = cx.tcx.expect_variant_def(def);
|
||||
Ok((parent_def, Some(format!("{}.v", variant.name))))
|
||||
}
|
||||
|
||||
const PRIMITIVES: &[(&str, Def)] = &[
|
||||
("u8", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U8))),
|
||||
("u16", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U16))),
|
||||
("u32", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U32))),
|
||||
("u64", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U64))),
|
||||
("u128", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U128))),
|
||||
("usize", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::Usize))),
|
||||
("i8", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I8))),
|
||||
("i16", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I16))),
|
||||
("i32", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I32))),
|
||||
("i64", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I64))),
|
||||
("i128", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I128))),
|
||||
("isize", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::Isize))),
|
||||
("f32", Def::PrimTy(hir::PrimTy::TyFloat(syntax::ast::FloatTy::F32))),
|
||||
("f64", Def::PrimTy(hir::PrimTy::TyFloat(syntax::ast::FloatTy::F64))),
|
||||
("str", Def::PrimTy(hir::PrimTy::TyStr)),
|
||||
("bool", Def::PrimTy(hir::PrimTy::TyBool)),
|
||||
("char", Def::PrimTy(hir::PrimTy::TyChar)),
|
||||
];
|
||||
|
||||
fn is_primitive(path_str: &str, is_val: bool) -> Option<Def> {
|
||||
if is_val {
|
||||
None
|
||||
} else {
|
||||
PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1)
|
||||
}
|
||||
}
|
@ -40,6 +40,9 @@ pub use self::unindent_comments::UNINDENT_COMMENTS;
|
||||
mod propagate_doc_cfg;
|
||||
pub use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
|
||||
|
||||
mod collect_intra_doc_links;
|
||||
pub use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;
|
||||
|
||||
/// Represents a single pass.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Pass {
|
||||
@ -128,12 +131,14 @@ pub const PASSES: &'static [Pass] = &[
|
||||
STRIP_PRIVATE,
|
||||
STRIP_PRIV_IMPORTS,
|
||||
PROPAGATE_DOC_CFG,
|
||||
COLLECT_INTRA_DOC_LINKS,
|
||||
];
|
||||
|
||||
/// The list of passes run by default.
|
||||
pub const DEFAULT_PASSES: &'static [&'static str] = &[
|
||||
"strip-hidden",
|
||||
"strip-private",
|
||||
"collect-intra-doc-links",
|
||||
"collapse-docs",
|
||||
"unindent-comments",
|
||||
"propagate-doc-cfg",
|
||||
@ -142,6 +147,7 @@ pub const DEFAULT_PASSES: &'static [&'static str] = &[
|
||||
/// The list of default passes run with `--document-private-items` is passed to rustdoc.
|
||||
pub const DEFAULT_PRIVATE_PASSES: &'static [&'static str] = &[
|
||||
"strip-priv-imports",
|
||||
"collect-intra-doc-links",
|
||||
"collapse-docs",
|
||||
"unindent-comments",
|
||||
"propagate-doc-cfg",
|
||||
|
Loading…
Reference in New Issue
Block a user