Auto merge of #52800 - QuietMisdreavus:do-not-pass-go, r=GuillaumeGomez

rustdoc: refactor how passes are structured, and turn intra-doc-link collection into a pass

This builds on https://github.com/rust-lang/rust/pull/52751 and should not be merged until that finally finishes the bors queue

Part 2 of my passes refactor. This introduces the concept of an "early pass", which is run right before exiting the compiler context. This is important for passes that may want to ask the compiler about things. For example, i took out the intra-doc-link collection and turned it into a early pass. I also made the `strip-hidden`, `strip-private` and `strip-priv-imports` passes occur as early passes, so that intra-doc-link collection wouldn't run on docs that weren't getting printed.

Fixes https://github.com/rust-lang/rust/issues/51684, technically https://github.com/rust-lang/rust/issues/51468 too but that version of `h2` hits a legit intra-link error after that `>_>`

r? @rust-lang/rustdoc
This commit is contained in:
bors 2018-08-05 18:45:01 +00:00
commit 73c78734ba
15 changed files with 899 additions and 672 deletions

View File

@ -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 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),

View File

@ -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;
@ -45,8 +45,9 @@ use std::path::PathBuf;
use visit_ast::RustdocVisitor;
use clean;
use clean::{get_path_for_type, Clean, MAX_DEF_ID};
use clean::{get_path_for_type, Clean, MAX_DEF_ID, AttributesExt};
use html::render::RenderInfo;
use passes;
pub use rustc::session::config::{Input, Options, CodegenOptions};
pub use rustc::session::search_paths::SearchPaths;
@ -57,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>,
@ -87,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>,
}
@ -322,7 +321,10 @@ pub fn run_core(search_paths: SearchPaths,
error_format: ErrorOutputType,
cmd_lints: Vec<(String, lint::Level)>,
lint_cap: Option<lint::Level>,
describe_lints: bool) -> (clean::Crate, RenderInfo)
describe_lints: bool,
mut manual_passes: Vec<String>,
mut default_passes: passes::DefaultPassOption)
-> (clean::Crate, RenderInfo, Vec<String>)
{
// Parse, resolve, and typecheck the given crate.
@ -517,23 +519,86 @@ 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());
let krate = {
let mut krate = {
let mut v = RustdocVisitor::new(&ctxt);
v.visit(tcx.hir.krate());
v.clean(&ctxt)
};
(krate, ctxt.renderinfo.into_inner())
fn report_deprecated_attr(name: &str, diag: &errors::Handler) {
let mut msg = diag.struct_warn(&format!("the `#![doc({})]` attribute is \
considered deprecated", name));
msg.warn("please see https://github.com/rust-lang/rust/issues/44136");
if name == "no_default_passes" {
msg.help("you may want to use `#![doc(document_private_items)]`");
}
msg.emit();
}
// Process all of the crate attributes, extracting plugin metadata along
// with the passes which we are supposed to run.
for attr in krate.module.as_ref().unwrap().attrs.lists("doc") {
let diag = ctxt.sess().diagnostic();
let name = attr.name().map(|s| s.as_str());
let name = name.as_ref().map(|s| &s[..]);
if attr.is_word() {
if name == Some("no_default_passes") {
report_deprecated_attr("no_default_passes", diag);
if default_passes == passes::DefaultPassOption::Default {
default_passes = passes::DefaultPassOption::None;
}
}
} else if let Some(value) = attr.value_str() {
let sink = match name {
Some("passes") => {
report_deprecated_attr("passes = \"...\"", diag);
&mut manual_passes
},
Some("plugins") => {
report_deprecated_attr("plugins = \"...\"", diag);
eprintln!("WARNING: #![doc(plugins = \"...\")] no longer functions; \
see CVE-2018-1000622");
continue
},
_ => continue,
};
for p in value.as_str().split_whitespace() {
sink.push(p.to_string());
}
}
if attr.is_word() && name == Some("document_private_items") {
if default_passes == passes::DefaultPassOption::Default {
default_passes = passes::DefaultPassOption::Private;
}
}
}
let mut passes: Vec<String> =
passes::defaults(default_passes).iter().map(|p| p.to_string()).collect();
passes.extend(manual_passes);
for pass in &passes {
// the "unknown pass" error will be reported when late passes are run
if let Some(pass) = passes::find_pass(pass).and_then(|p| p.early_fn()) {
krate = pass(krate, &ctxt);
}
}
ctxt.sess().abort_if_errors();
(krate, ctxt.renderinfo.into_inner(), passes)
}), &sess)
})
}

View File

@ -26,6 +26,7 @@
#![feature(entry_and_modify)]
#![feature(ptr_offset_from)]
#![feature(crate_visibility_modifier)]
#![feature(const_fn)]
#![recursion_limit="256"]
@ -96,8 +97,6 @@ mod visit_lib;
mod test;
mod theme;
use clean::AttributesExt;
struct Output {
krate: clean::Crate,
renderinfo: html::render::RenderInfo,
@ -367,8 +366,8 @@ fn main_args(args: &[String]) -> isize {
if matches.opt_strs("passes") == ["list"] {
println!("Available passes for running rustdoc:");
for &(name, _, description) in passes::PASSES {
println!("{:>20} - {}", name, description);
for pass in passes::PASSES {
println!("{:>20} - {}", pass.name(), pass.description());
}
println!("\nDefault passes for rustdoc:");
for &name in passes::DEFAULT_PASSES {
@ -630,7 +629,7 @@ fn rust_input<R, F>(cratefile: PathBuf,
where R: 'static + Send,
F: 'static + Send + FnOnce(Output) -> R
{
let mut default_passes = if matches.opt_present("no-defaults") {
let default_passes = if matches.opt_present("no-defaults") {
passes::DefaultPassOption::None
} else if matches.opt_present("document-private-items") {
passes::DefaultPassOption::Private
@ -638,8 +637,8 @@ where R: 'static + Send,
passes::DefaultPassOption::Default
};
let mut manual_passes = matches.opt_strs("passes");
let mut plugins = matches.opt_strs("plugins");
let manual_passes = matches.opt_strs("passes");
let plugins = matches.opt_strs("plugins");
// First, parse the crate and extract all relevant information.
let mut paths = SearchPaths::new();
@ -673,11 +672,11 @@ where R: 'static + Send,
let result = rustc_driver::monitor(move || syntax::with_globals(move || {
use rustc::session::config::Input;
let (mut krate, renderinfo) =
let (mut krate, renderinfo, passes) =
core::run_core(paths, cfgs, externs, Input::File(cratefile), triple, maybe_sysroot,
display_warnings, crate_name.clone(),
force_unstable_if_unmarked, edition, cg, error_format,
lint_opts, lint_cap, describe_lints);
lint_opts, lint_cap, describe_lints, manual_passes, default_passes);
info!("finished with rustc");
@ -687,58 +686,6 @@ where R: 'static + Send,
krate.version = crate_version;
let diag = core::new_handler(error_format, None);
fn report_deprecated_attr(name: &str, diag: &errors::Handler) {
let mut msg = diag.struct_warn(&format!("the `#![doc({})]` attribute is \
considered deprecated", name));
msg.warn("please see https://github.com/rust-lang/rust/issues/44136");
if name == "no_default_passes" {
msg.help("you may want to use `#![doc(document_private_items)]`");
}
msg.emit();
}
// Process all of the crate attributes, extracting plugin metadata along
// with the passes which we are supposed to run.
for attr in krate.module.as_ref().unwrap().attrs.lists("doc") {
let name = attr.name().map(|s| s.as_str());
let name = name.as_ref().map(|s| &s[..]);
if attr.is_word() {
if name == Some("no_default_passes") {
report_deprecated_attr("no_default_passes", &diag);
if default_passes == passes::DefaultPassOption::Default {
default_passes = passes::DefaultPassOption::None;
}
}
} else if let Some(value) = attr.value_str() {
let sink = match name {
Some("passes") => {
report_deprecated_attr("passes = \"...\"", &diag);
&mut manual_passes
},
Some("plugins") => {
report_deprecated_attr("plugins = \"...\"", &diag);
&mut plugins
},
_ => continue,
};
sink.extend(value.as_str().split_whitespace().map(|p| p.to_string()));
}
if attr.is_word() && name == Some("document_private_items") {
if default_passes == passes::DefaultPassOption::Default {
default_passes = passes::DefaultPassOption::Private;
}
}
}
let mut passes: Vec<String> =
passes::defaults(default_passes).iter().map(|p| p.to_string()).collect();
passes.extend(manual_passes);
if !plugins.is_empty() {
eprintln!("WARNING: --plugins no longer functions; see CVE-2018-1000622");
}
@ -751,8 +698,13 @@ where R: 'static + Send,
for pass in &passes {
// determine if we know about this pass
let pass = match passes::PASSES.iter().find(|(p, ..)| p == pass) {
Some(pass) => pass.1,
let pass = match passes::find_pass(pass) {
Some(pass) => if let Some(pass) = pass.late_fn() {
pass
} else {
// not a late pass, but still valid so don't report the error
continue
}
None => {
error!("unknown pass {}, skipping", *pass);

View File

@ -11,8 +11,13 @@
use clean::{self, DocFragment, Item};
use fold;
use fold::DocFolder;
use passes::Pass;
use std::mem::replace;
pub const COLLAPSE_DOCS: Pass =
Pass::late("collapse-docs", collapse_docs,
"concatenates all document attributes into one document attribute");
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum DocFragmentKind {
Sugared,

View File

@ -0,0 +1,597 @@
// 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())
}
// we don't display docs on `extern crate` items anyway, so don't process them
ExternCrateItem(..) => return self.fold_item_recur(item),
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, &current_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, &current_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, &current_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,
&current_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, &current_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, &current_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, &current_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));
}
}
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)
}
}

View File

@ -8,88 +8,153 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Contains information about "passes", used to modify crate information during the documentation
//! process.
use rustc::hir::def_id::DefId;
use rustc::middle::privacy::AccessLevels;
use rustc::util::nodemap::DefIdSet;
use std::mem;
use std::fmt;
use clean::{self, GetDefId, Item};
use core::DocContext;
use fold;
use fold::StripItem;
mod collapse_docs;
pub use self::collapse_docs::collapse_docs;
pub use self::collapse_docs::COLLAPSE_DOCS;
mod strip_hidden;
pub use self::strip_hidden::strip_hidden;
pub use self::strip_hidden::STRIP_HIDDEN;
mod strip_private;
pub use self::strip_private::strip_private;
pub use self::strip_private::STRIP_PRIVATE;
mod strip_priv_imports;
pub use self::strip_priv_imports::strip_priv_imports;
pub use self::strip_priv_imports::STRIP_PRIV_IMPORTS;
mod unindent_comments;
pub use self::unindent_comments::unindent_comments;
pub use self::unindent_comments::UNINDENT_COMMENTS;
mod propagate_doc_cfg;
pub use self::propagate_doc_cfg::propagate_doc_cfg;
pub use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
type Pass = (
&'static str, // name
fn(clean::Crate) -> clean::Crate, // fn
&'static str,
); // description
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 {
/// An "early pass" is run in the compiler context, and can gather information about types and
/// traits and the like.
EarlyPass {
name: &'static str,
pass: fn(clean::Crate, &DocContext) -> clean::Crate,
description: &'static str,
},
/// A "late pass" is run between crate cleaning and page generation.
LatePass {
name: &'static str,
pass: fn(clean::Crate) -> clean::Crate,
description: &'static str,
},
}
impl fmt::Debug for Pass {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut dbg = match *self {
Pass::EarlyPass { .. } => f.debug_struct("EarlyPass"),
Pass::LatePass { .. } => f.debug_struct("LatePass"),
};
dbg.field("name", &self.name())
.field("pass", &"...")
.field("description", &self.description())
.finish()
}
}
impl Pass {
/// Constructs a new early pass.
pub const fn early(name: &'static str,
pass: fn(clean::Crate, &DocContext) -> clean::Crate,
description: &'static str) -> Pass {
Pass::EarlyPass { name, pass, description }
}
/// Constructs a new late pass.
pub const fn late(name: &'static str,
pass: fn(clean::Crate) -> clean::Crate,
description: &'static str) -> Pass {
Pass::LatePass { name, pass, description }
}
/// Returns the name of this pass.
pub fn name(self) -> &'static str {
match self {
Pass::EarlyPass { name, .. } |
Pass::LatePass { name, .. } => name,
}
}
/// Returns the description of this pass.
pub fn description(self) -> &'static str {
match self {
Pass::EarlyPass { description, .. } |
Pass::LatePass { description, .. } => description,
}
}
/// If this pass is an early pass, returns the pointer to its function.
pub fn early_fn(self) -> Option<fn(clean::Crate, &DocContext) -> clean::Crate> {
match self {
Pass::EarlyPass { pass, .. } => Some(pass),
_ => None,
}
}
/// If this pass is a late pass, returns the pointer to its function.
pub fn late_fn(self) -> Option<fn(clean::Crate) -> clean::Crate> {
match self {
Pass::LatePass { pass, .. } => Some(pass),
_ => None,
}
}
}
/// The full list of passes.
pub const PASSES: &'static [Pass] = &[
(
"strip-hidden",
strip_hidden,
"strips all doc(hidden) items from the output",
),
(
"unindent-comments",
unindent_comments,
"removes excess indentation on comments in order for markdown to like it",
),
(
"collapse-docs",
collapse_docs,
"concatenates all document attributes into one document attribute",
),
(
"strip-private",
strip_private,
"strips all private items from a crate which cannot be seen externally, \
implies strip-priv-imports",
),
(
"strip-priv-imports",
strip_priv_imports,
"strips all private import statements (`use`, `extern crate`) from a crate",
),
(
"propagate-doc-cfg",
propagate_doc_cfg,
"propagates `#[doc(cfg(...))]` to child items",
),
STRIP_HIDDEN,
UNINDENT_COMMENTS,
COLLAPSE_DOCS,
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",
];
/// 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",
];
/// A shorthand way to refer to which set of passes to use, based on the presence of
/// `--no-defaults` or `--document-private-items`.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum DefaultPassOption {
Default,
@ -97,6 +162,7 @@ pub enum DefaultPassOption {
None,
}
/// Returns the given default set of passes.
pub fn defaults(default_set: DefaultPassOption) -> &'static [&'static str] {
match default_set {
DefaultPassOption::Default => DEFAULT_PASSES,
@ -105,6 +171,11 @@ pub fn defaults(default_set: DefaultPassOption) -> &'static [&'static str] {
}
}
/// If the given name matches a known pass, returns its information.
pub fn find_pass(pass_name: &str) -> Option<Pass> {
PASSES.iter().find(|p| p.name() == pass_name).cloned()
}
struct Stripper<'a> {
retained: &'a mut DefIdSet,
access_levels: &'a AccessLevels<DefId>,

View File

@ -13,6 +13,11 @@ use std::sync::Arc;
use clean::{Crate, Item};
use clean::cfg::Cfg;
use fold::DocFolder;
use passes::Pass;
pub const PROPAGATE_DOC_CFG: Pass =
Pass::late("propagate-doc-cfg", propagate_doc_cfg,
"propagates `#[doc(cfg(...))]` to child items");
pub fn propagate_doc_cfg(cr: Crate) -> Crate {
CfgPropagator { parent_cfg: None }.fold_crate(cr)

View File

@ -13,13 +13,18 @@ use std::mem;
use clean::{self, AttributesExt, NestedAttributesExt};
use clean::Item;
use core::DocContext;
use fold;
use fold::DocFolder;
use fold::StripItem;
use passes::ImplStripper;
use passes::{ImplStripper, Pass};
pub const STRIP_HIDDEN: Pass =
Pass::early("strip-hidden", strip_hidden,
"strips all doc(hidden) items from the output");
/// Strip items marked `#[doc(hidden)]`
pub fn strip_hidden(krate: clean::Crate) -> clean::Crate {
pub fn strip_hidden(krate: clean::Crate, _: &DocContext) -> clean::Crate {
let mut retained = DefIdSet();
// strip all #[doc(hidden)] items

View File

@ -9,9 +9,13 @@
// except according to those terms.
use clean;
use core::DocContext;
use fold::DocFolder;
use passes::ImportStripper;
use passes::{ImportStripper, Pass};
pub fn strip_priv_imports(krate: clean::Crate) -> clean::Crate {
pub const STRIP_PRIV_IMPORTS: Pass = Pass::early("strip-priv-imports", strip_priv_imports,
"strips all private import statements (`use`, `extern crate`) from a crate");
pub fn strip_priv_imports(krate: clean::Crate, _: &DocContext) -> clean::Crate {
ImportStripper.fold_crate(krate)
}

View File

@ -11,12 +11,18 @@
use rustc::util::nodemap::DefIdSet;
use clean;
use core::DocContext;
use fold::DocFolder;
use passes::{ImplStripper, ImportStripper, Stripper};
use passes::{ImplStripper, ImportStripper, Stripper, Pass};
pub const STRIP_PRIVATE: Pass =
Pass::early("strip-private", strip_private,
"strips all private items from a crate which cannot be seen externally, \
implies strip-priv-imports");
/// Strip private items from the point of view of a crate or externally from a
/// crate, specified by the `xcrate` flag.
pub fn strip_private(mut krate: clean::Crate) -> clean::Crate {
pub fn strip_private(mut krate: clean::Crate, _: &DocContext) -> clean::Crate {
// This stripper collects all *retained* nodes.
let mut retained = DefIdSet();
let access_levels = krate.access_levels.clone();

View File

@ -14,6 +14,11 @@ use std::usize;
use clean::{self, DocFragment, Item};
use fold::{self, DocFolder};
use passes::Pass;
pub const UNINDENT_COMMENTS: Pass =
Pass::late("unindent-comments", unindent_comments,
"removes excess indentation on comments in order for markdown to like it");
pub fn unindent_comments(krate: clean::Crate) -> clean::Crate {
CommentCleaner.fold_crate(krate)

View File

@ -11,6 +11,6 @@
#![deny(intra_doc_link_resolution_failure)]
/// [intradoc::failure]
fn main() {
pub fn main() {
println!("Hello, world!");
}

View File

@ -0,0 +1,13 @@
// 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.
#![crate_name="inner"]
//! ooh, i'm a rebel just for [kicks]

View File

@ -0,0 +1,19 @@
// 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.
// aux-build:intra-link-extern-crate.rs
// When loading `extern crate` statements, we would pull in their docs at the same time, even
// though they would never actually get displayed. This tripped intra-doc-link resolution failures,
// for items that aren't under our control, and not actually getting documented!
#![deny(intra_doc_link_resolution_failure)]
extern crate inner;

View File

@ -0,0 +1,18 @@
// 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.
// Rustdoc would previously report resolution failures on items that weren't in the public docs.
// These failures were legitimate, but not truly relevant - the docs in question couldn't be
// checked for accuracy anyway.
#![deny(intra_doc_link_resolution_failure)]
/// ooh, i'm a [rebel] just for kicks
struct SomeStruct;