1 use errors::Applicability;
2 use rustc::hir::def::{Def, Namespace::{self, *}, PerNS};
3 use rustc::hir::def_id::DefId;
5 use rustc::lint as lint;
8 use syntax::ast::{self, Ident};
9 use syntax::feature_gate::UnstableFeatures;
10 use syntax::symbol::Symbol;
11 use syntax_pos::DUMMY_SP;
15 use crate::core::DocContext;
16 use crate::fold::DocFolder;
17 use crate::html::markdown::markdown_links;
19 use crate::passes::{look_for_tests, Pass};
21 use super::span_of_attrs;
23 pub const COLLECT_INTRA_DOC_LINKS: Pass = Pass {
24 name: "collect-intra-doc-links",
25 pass: collect_intra_doc_links,
26 description: "reads a crate's documentation to resolve intra-doc-links",
29 pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate {
30 if !UnstableFeatures::from_environment().is_nightly_build() {
33 let mut coll = LinkCollector::new(cx);
35 coll.fold_crate(krate)
39 struct LinkCollector<'a, 'tcx> {
40 cx: &'a DocContext<'tcx>,
41 mod_ids: Vec<ast::NodeId>,
44 impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
45 fn new(cx: &'a DocContext<'tcx>) -> Self {
52 /// Resolves a string as a path within a particular namespace. Also returns an optional
53 /// URL fragment in the case of variants and methods.
57 current_item: &Option<String>,
58 parent_id: Option<ast::NodeId>)
59 -> Result<(Def, Option<String>), ()>
63 // In case we're in a module, try to resolve the relative
65 if let Some(id) = parent_id.or(self.mod_ids.last().cloned()) {
66 // FIXME: `with_scope` requires the `NodeId` of a module.
67 let result = cx.enter_resolver(|resolver| {
68 resolver.with_scope(id, |resolver| {
69 resolver.resolve_str_path_error(DUMMY_SP, &path_str, ns == ValueNS)
73 if let Ok(result) = result {
74 // In case this is a trait item, skip the
75 // early return and try looking for the trait.
76 let value = match result.def {
77 Def::Method(_) | Def::AssociatedConst(_) => true,
78 Def::AssociatedTy(_) => false,
79 Def::Variant(_) => return handle_variant(cx, result.def),
80 // Not a trait item; just return what we found.
81 _ => return Ok((result.def, None))
84 if value != (ns == ValueNS) {
87 } else if let Some(prim) = is_primitive(path_str, ns) {
88 return Ok((prim, Some(path_str.to_owned())))
90 // If resolution failed, it may still be a method
91 // because methods are not handled by the resolver
92 // If so, bail when we're not looking for a value.
98 // Try looking for methods and associated items.
99 let mut split = path_str.rsplitn(2, "::");
100 let item_name = if let Some(first) = split.next() {
106 let mut path = if let Some(second) = split.next() {
112 if path == "self" || path == "Self" {
113 if let Some(name) = current_item.as_ref() {
117 if let Some(prim) = is_primitive(&path, TypeNS) {
118 let did = primitive_impl(cx, &path).ok_or(())?;
119 return cx.tcx.associated_items(did)
120 .find(|item| item.ident.name == item_name)
121 .and_then(|item| match item.kind {
122 ty::AssociatedKind::Method => Some("method"),
125 .map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name))))
129 // FIXME: `with_scope` requires the `NodeId` of a module.
130 let ty = cx.enter_resolver(|resolver| resolver.with_scope(id, |resolver| {
131 resolver.resolve_str_path_error(DUMMY_SP, &path, false)
134 Def::Struct(did) | Def::Union(did) | Def::Enum(did) | Def::TyAlias(did) => {
135 let item = cx.tcx.inherent_impls(did)
137 .flat_map(|imp| cx.tcx.associated_items(*imp))
138 .find(|item| item.ident.name == item_name);
139 if let Some(item) = item {
140 let out = match item.kind {
141 ty::AssociatedKind::Method if ns == ValueNS => "method",
142 ty::AssociatedKind::Const if ns == ValueNS => "associatedconstant",
145 Ok((ty.def, Some(format!("{}.{}", out, item_name))))
147 match cx.tcx.type_of(did).sty {
149 if let Some(item) = if def.is_enum() {
150 def.all_fields().find(|item| item.ident.name == item_name)
152 def.non_enum_variant()
155 .find(|item| item.ident.name == item_name)
158 Some(format!("{}.{}",
174 let item = cx.tcx.associated_item_def_ids(did).iter()
175 .map(|item| cx.tcx.associated_item(*item))
176 .find(|item| item.ident.name == item_name);
177 if let Some(item) = item {
178 let kind = match item.kind {
179 ty::AssociatedKind::Const if ns == ValueNS => "associatedconstant",
180 ty::AssociatedKind::Type if ns == TypeNS => "associatedtype",
181 ty::AssociatedKind::Method if ns == ValueNS => {
182 if item.defaultness.has_value() {
191 Ok((ty.def, Some(format!("{}.{}", kind, item_name))))
204 impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
205 fn fold_item(&mut self, mut item: Item) -> Option<Item> {
206 let item_hir_id = if item.is_mod() {
207 if let Some(id) = self.cx.tcx.hir().as_local_hir_id(item.def_id) {
210 debug!("attempting to fold on a non-local item: {:?}", item);
211 return self.fold_item_recur(item);
217 // FIXME: get the resolver to work with non-local resolve scopes.
218 let parent_node = self.cx.as_local_node_id(item.def_id).and_then(|node_id| {
219 // FIXME: this fails hard for impls in non-module scope, but is necessary for the
220 // current `resolve()` implementation.
221 match self.cx.tcx.hir().get_module_parent_node(node_id) {
222 id if id != node_id => Some(id),
227 if parent_node.is_some() {
228 debug!("got parent node for {} {:?}, id {:?}", item.type_(), item.name, item.def_id);
231 let current_item = match item.inner {
233 if item.attrs.inner_docs {
234 if item_hir_id.unwrap() != hir::CRATE_HIR_ID {
240 match parent_node.or(self.mod_ids.last().cloned()) {
241 Some(parent) if parent != ast::CRATE_NODE_ID => {
242 // FIXME: can we pull the parent module's name from elsewhere?
243 Some(self.cx.tcx.hir().name(parent).to_string())
249 ImplItem(Impl { ref for_, .. }) => {
250 for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string())
252 // we don't display docs on `extern crate` items anyway, so don't process them.
253 ExternCrateItem(..) => return self.fold_item_recur(item),
254 ImportItem(Import::Simple(ref name, ..)) => Some(name.clone()),
255 MacroItem(..) => None,
256 _ => item.name.clone(),
259 if item.is_mod() && item.attrs.inner_docs {
260 self.mod_ids.push(self.cx.tcx.hir().hir_to_node_id(item_hir_id.unwrap()));
264 let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
266 look_for_tests(&cx, &dox, &item, true);
268 for (ori_link, link_range) in markdown_links(&dox) {
269 // Bail early for real links.
270 if ori_link.contains('/') {
274 // [] is mostly likely not supposed to be a link
275 if ori_link.is_empty() {
279 let link = ori_link.replace("`", "");
280 let (def, fragment) = {
282 let path_str = if let Some(prefix) =
283 ["struct@", "enum@", "type@",
284 "trait@", "union@"].iter()
285 .find(|p| link.starts_with(**p)) {
287 link.trim_start_matches(prefix)
288 } else if let Some(prefix) =
289 ["const@", "static@",
290 "value@", "function@", "mod@",
291 "fn@", "module@", "method@"]
292 .iter().find(|p| link.starts_with(**p)) {
293 kind = Some(ValueNS);
294 link.trim_start_matches(prefix)
295 } else if link.ends_with("()") {
296 kind = Some(ValueNS);
297 link.trim_end_matches("()")
298 } else if link.starts_with("macro@") {
299 kind = Some(MacroNS);
300 link.trim_start_matches("macro@")
301 } else if link.ends_with('!') {
302 kind = Some(MacroNS);
303 link.trim_end_matches('!')
308 if path_str.contains(|ch: char| !(ch.is_alphanumeric() ||
309 ch == ':' || ch == '_')) {
314 Some(ns @ ValueNS) => {
315 if let Ok(def) = self.resolve(path_str, ns, ¤t_item, parent_node) {
318 resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
319 // This could just be a normal link or a broken link
320 // we could potentially check if something is
321 // "intra-doc-link-like" and warn in that case.
325 Some(ns @ TypeNS) => {
326 if let Ok(def) = self.resolve(path_str, ns, ¤t_item, parent_node) {
329 resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
330 // This could just be a normal link.
336 let candidates = PerNS {
337 macro_ns: macro_resolve(cx, path_str).map(|def| (def, None)),
339 .resolve(path_str, TypeNS, ¤t_item, parent_node)
342 .resolve(path_str, ValueNS, ¤t_item, parent_node)
344 .and_then(|(def, fragment)| {
345 // Constructors are picked up in the type namespace.
348 | Def::VariantCtor(..)
349 | Def::SelfCtor(..) => None,
350 _ => Some((def, fragment))
355 if candidates.is_empty() {
356 resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
357 // this could just be a normal link
361 let is_unambiguous = candidates.clone().present_items().count() == 1;
363 candidates.present_items().next().unwrap()
371 candidates.map(|candidate| candidate.map(|(def, _)| def)),
377 if let Some(def) = macro_resolve(cx, path_str) {
380 resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
387 if let Def::PrimTy(_) = def {
388 item.attrs.links.push((ori_link, None, fragment));
390 let id = register_def(cx, def);
391 item.attrs.links.push((ori_link, Some(id), fragment));
395 if item.is_mod() && !item.attrs.inner_docs {
396 self.mod_ids.push(self.cx.tcx.hir().hir_to_node_id(item_hir_id.unwrap()));
400 let ret = self.fold_item_recur(item);
406 self.fold_item_recur(item)
411 /// Resolves a string as a macro.
412 fn macro_resolve(cx: &DocContext<'_>, path_str: &str) -> Option<Def> {
413 use syntax::ext::base::{MacroKind, SyntaxExtension};
414 let segment = ast::PathSegment::from_ident(Ident::from_str(path_str));
415 let path = ast::Path { segments: vec![segment], span: DUMMY_SP };
416 cx.enter_resolver(|resolver| {
417 let parent_scope = resolver.dummy_parent_scope();
418 if let Ok(def) = resolver.resolve_macro_to_def_inner(&path, MacroKind::Bang,
419 &parent_scope, false, false) {
420 if let Def::Macro(_, MacroKind::ProcMacroStub) = def {
421 // skip proc-macro stubs, they'll cause `get_macro` to crash
423 if let SyntaxExtension::DeclMacro { .. } = *resolver.get_macro(def) {
428 if let Some(def) = resolver.all_macros.get(&Symbol::intern(path_str)) {
435 /// Reports a resolution failure diagnostic.
437 /// If we cannot find the exact source span of the resolution failure, we use the span of the
438 /// documentation attributes themselves. This is a little heavy-handed, so we display the markdown
439 /// line containing the failure as a note as well.
440 fn resolution_failure(
445 link_range: Option<Range<usize>>,
447 let sp = span_of_attrs(attrs);
449 let mut diag = cx.tcx.struct_span_lint_hir(
450 lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
453 &format!("`[{}]` cannot be resolved, ignoring it...", path_str),
455 if let Some(link_range) = link_range {
456 if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) {
458 diag.span_label(sp, "cannot be resolved, ignoring");
460 // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
463 // last_new_line_offset
464 let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
465 let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
467 // Print the line containing the `link_range` and manually mark it with '^'s.
469 "the link appears in this line:\n\n{line}\n\
470 {indicator: <before$}{indicator:^<found$}",
473 before=link_range.start - last_new_line_offset,
474 found=link_range.len(),
478 diag.help("to escape `[` and `]` characters, just add '\\' before them like \
488 link_range: Option<Range<usize>>,
489 candidates: PerNS<Option<Def>>,
491 let sp = span_of_attrs(attrs);
493 let mut msg = format!("`{}` is ", path_str);
495 let candidates = [TypeNS, ValueNS, MacroNS].iter().filter_map(|&ns| {
496 candidates[ns].map(|def| (def, ns))
497 }).collect::<Vec<_>>();
498 match candidates.as_slice() {
499 [(first_def, _), (second_def, _)] => {
501 "both {} {} and {} {}",
503 first_def.kind_name(),
504 second_def.article(),
505 second_def.kind_name(),
509 let mut candidates = candidates.iter().peekable();
510 while let Some((def, _)) = candidates.next() {
511 if candidates.peek().is_some() {
512 msg += &format!("{} {}, ", def.article(), def.kind_name());
514 msg += &format!("and {} {}", def.article(), def.kind_name());
520 let mut diag = cx.tcx.struct_span_lint_hir(
521 lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
527 if let Some(link_range) = link_range {
528 if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) {
530 diag.span_label(sp, "ambiguous link");
532 for (def, ns) in candidates {
533 let (action, mut suggestion) = match def {
534 Def::Method(..) | Def::Fn(..) => {
535 ("add parentheses", format!("{}()", path_str))
538 ("add an exclamation mark", format!("{}!", path_str))
541 let type_ = match (def, ns) {
542 (Def::Const(..), _) => "const",
543 (Def::Static(..), _) => "static",
544 (Def::Struct(..), _) => "struct",
545 (Def::Enum(..), _) => "enum",
546 (Def::Union(..), _) => "union",
547 (Def::Trait(..), _) => "trait",
548 (Def::Mod(..), _) => "module",
549 (_, TypeNS) => "type",
550 (_, ValueNS) => "value",
551 (_, MacroNS) => "macro",
554 // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
555 ("prefix with the item type", format!("{}@{}", type_, path_str))
559 if dox.bytes().nth(link_range.start) == Some(b'`') {
560 suggestion = format!("`{}`", suggestion);
563 diag.span_suggestion(
565 &format!("to link to the {}, {}", def.kind_name(), action),
567 Applicability::MaybeIncorrect,
571 // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
574 // last_new_line_offset
575 let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
576 let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
578 // Print the line containing the `link_range` and manually mark it with '^'s.
580 "the link appears in this line:\n\n{line}\n\
581 {indicator: <before$}{indicator:^<found$}",
584 before=link_range.start - last_new_line_offset,
585 found=link_range.len(),
593 /// Given an enum variant's def, return the def of its enum and the associated fragment.
594 fn handle_variant(cx: &DocContext<'_>, def: Def) -> Result<(Def, Option<String>), ()> {
595 use rustc::ty::DefIdTree;
597 let parent = if let Some(parent) = cx.tcx.parent(def.def_id()) {
602 let parent_def = Def::Enum(parent);
603 let variant = cx.tcx.expect_variant_def(def);
604 Ok((parent_def, Some(format!("{}.v", variant.ident.name))))
607 const PRIMITIVES: &[(&str, Def)] = &[
608 ("u8", Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U8))),
609 ("u16", Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U16))),
610 ("u32", Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U32))),
611 ("u64", Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U64))),
612 ("u128", Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::U128))),
613 ("usize", Def::PrimTy(hir::PrimTy::Uint(syntax::ast::UintTy::Usize))),
614 ("i8", Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I8))),
615 ("i16", Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I16))),
616 ("i32", Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I32))),
617 ("i64", Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I64))),
618 ("i128", Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::I128))),
619 ("isize", Def::PrimTy(hir::PrimTy::Int(syntax::ast::IntTy::Isize))),
620 ("f32", Def::PrimTy(hir::PrimTy::Float(syntax::ast::FloatTy::F32))),
621 ("f64", Def::PrimTy(hir::PrimTy::Float(syntax::ast::FloatTy::F64))),
622 ("str", Def::PrimTy(hir::PrimTy::Str)),
623 ("bool", Def::PrimTy(hir::PrimTy::Bool)),
624 ("char", Def::PrimTy(hir::PrimTy::Char)),
627 fn is_primitive(path_str: &str, ns: Namespace) -> Option<Def> {
629 PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1)
635 fn primitive_impl(cx: &DocContext<'_>, path_str: &str) -> Option<DefId> {
638 "u8" => tcx.lang_items().u8_impl(),
639 "u16" => tcx.lang_items().u16_impl(),
640 "u32" => tcx.lang_items().u32_impl(),
641 "u64" => tcx.lang_items().u64_impl(),
642 "u128" => tcx.lang_items().u128_impl(),
643 "usize" => tcx.lang_items().usize_impl(),
644 "i8" => tcx.lang_items().i8_impl(),
645 "i16" => tcx.lang_items().i16_impl(),
646 "i32" => tcx.lang_items().i32_impl(),
647 "i64" => tcx.lang_items().i64_impl(),
648 "i128" => tcx.lang_items().i128_impl(),
649 "isize" => tcx.lang_items().isize_impl(),
650 "f32" => tcx.lang_items().f32_impl(),
651 "f64" => tcx.lang_items().f64_impl(),
652 "str" => tcx.lang_items().str_impl(),
653 "char" => tcx.lang_items().char_impl(),