about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/clean/mod.rs564
-rw-r--r--src/librustdoc/core.rs83
-rw-r--r--src/librustdoc/lib.rs78
-rw-r--r--src/librustdoc/passes/collapse_docs.rs5
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs597
-rw-r--r--src/librustdoc/passes/mod.rs155
-rw-r--r--src/librustdoc/passes/propagate_doc_cfg.rs5
-rw-r--r--src/librustdoc/passes/strip_hidden.rs9
-rw-r--r--src/librustdoc/passes/strip_priv_imports.rs8
-rw-r--r--src/librustdoc/passes/strip_private.rs10
-rw-r--r--src/librustdoc/passes/unindent_comments.rs5
-rw-r--r--src/test/run-make-fulldeps/exit-code/lint-failure.rs2
-rw-r--r--src/test/rustdoc/auxiliary/intra-link-extern-crate.rs13
-rw-r--r--src/test/rustdoc/intra-link-extern-crate.rs19
-rw-r--r--src/test/rustdoc/intra-link-private.rs18
15 files changed, 899 insertions, 672 deletions
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 9dbd7d7b260..ad774f98602 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -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),
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 88faea9514f..86e5bbeab70 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -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)
     })
 }
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index dda97cfdb2c..bd7f7386fd1 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -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);
 
diff --git a/src/librustdoc/passes/collapse_docs.rs b/src/librustdoc/passes/collapse_docs.rs
index 6f70fcf1099..33d052775ba 100644
--- a/src/librustdoc/passes/collapse_docs.rs
+++ b/src/librustdoc/passes/collapse_docs.rs
@@ -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,
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
new file mode 100644
index 00000000000..28d504c2ee0
--- /dev/null
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -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)
+    }
+}
diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs
index aa4acaf75bf..16251877bb1 100644
--- a/src/librustdoc/passes/mod.rs
+++ b/src/librustdoc/passes/mod.rs
@@ -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;
+
+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()
+    }
+}
 
-type Pass = (
-    &'static str,                     // name
-    fn(clean::Crate) -> clean::Crate, // fn
-    &'static str,
-); // description
+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>,
diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs
index 572a8d3f470..69093846302 100644
--- a/src/librustdoc/passes/propagate_doc_cfg.rs
+++ b/src/librustdoc/passes/propagate_doc_cfg.rs
@@ -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)
diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs
index 279c9603703..cc0b6fb6d67 100644
--- a/src/librustdoc/passes/strip_hidden.rs
+++ b/src/librustdoc/passes/strip_hidden.rs
@@ -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
diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs
index c4640839923..f01c333d742 100644
--- a/src/librustdoc/passes/strip_priv_imports.rs
+++ b/src/librustdoc/passes/strip_priv_imports.rs
@@ -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)
 }
diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs
index 45f706590e3..3b17a768ffd 100644
--- a/src/librustdoc/passes/strip_private.rs
+++ b/src/librustdoc/passes/strip_private.rs
@@ -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();
diff --git a/src/librustdoc/passes/unindent_comments.rs b/src/librustdoc/passes/unindent_comments.rs
index 2510ec011b6..6d875c107c8 100644
--- a/src/librustdoc/passes/unindent_comments.rs
+++ b/src/librustdoc/passes/unindent_comments.rs
@@ -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)
diff --git a/src/test/run-make-fulldeps/exit-code/lint-failure.rs b/src/test/run-make-fulldeps/exit-code/lint-failure.rs
index 3bf40b753c1..910abfd5d7e 100644
--- a/src/test/run-make-fulldeps/exit-code/lint-failure.rs
+++ b/src/test/run-make-fulldeps/exit-code/lint-failure.rs
@@ -11,6 +11,6 @@
 #![deny(intra_doc_link_resolution_failure)]
 
 /// [intradoc::failure]
-fn main() {
+pub fn main() {
     println!("Hello, world!");
 }
diff --git a/src/test/rustdoc/auxiliary/intra-link-extern-crate.rs b/src/test/rustdoc/auxiliary/intra-link-extern-crate.rs
new file mode 100644
index 00000000000..e4a194466cc
--- /dev/null
+++ b/src/test/rustdoc/auxiliary/intra-link-extern-crate.rs
@@ -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]
diff --git a/src/test/rustdoc/intra-link-extern-crate.rs b/src/test/rustdoc/intra-link-extern-crate.rs
new file mode 100644
index 00000000000..5666f3bd2f2
--- /dev/null
+++ b/src/test/rustdoc/intra-link-extern-crate.rs
@@ -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;
diff --git a/src/test/rustdoc/intra-link-private.rs b/src/test/rustdoc/intra-link-private.rs
new file mode 100644
index 00000000000..dbdfbc4e5ad
--- /dev/null
+++ b/src/test/rustdoc/intra-link-private.rs
@@ -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;