1 // Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use rustc::lint as lint;
13 use rustc::hir::def::Def;
16 use syntax::ast::{self, Ident, NodeId};
17 use syntax::feature_gate::UnstableFeatures;
18 use syntax::symbol::Symbol;
19 use syntax_pos::{self, DUMMY_SP};
25 use html::markdown::markdown_links;
28 use passes::{look_for_tests, Pass};
30 pub const COLLECT_INTRA_DOC_LINKS: Pass =
31 Pass::early("collect-intra-doc-links", collect_intra_doc_links,
32 "reads a crate's documentation to resolve intra-doc-links");
34 pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext) -> Crate {
35 if !UnstableFeatures::from_environment().is_nightly_build() {
38 let mut coll = LinkCollector::new(cx);
40 coll.fold_crate(krate)
46 /// Either a value or type, but not a macro
50 /// Values, functions, consts, statics (everything in the value namespace)
52 /// Types, traits (everything in the type namespace)
56 struct LinkCollector<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> {
57 cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>,
59 is_nightly_build: bool,
62 impl<'a, 'tcx, 'rcx, 'cstore> LinkCollector<'a, 'tcx, 'rcx, 'cstore> {
63 fn new(cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>) -> Self {
67 is_nightly_build: UnstableFeatures::from_environment().is_nightly_build(),
71 /// Resolve a given string as a path, along with whether or not it is
72 /// in the value namespace. Also returns an optional URL fragment in the case
73 /// of variants and methods.
77 current_item: &Option<String>,
78 parent_id: Option<NodeId>)
79 -> Result<(Def, Option<String>), ()>
83 // In case we're in a module, try to resolve the relative
85 if let Some(id) = parent_id.or(self.mod_ids.last().cloned()) {
86 // FIXME: `with_scope` requires the `NodeId` of a module.
87 let result = cx.resolver.borrow_mut()
90 resolver.resolve_str_path_error(DUMMY_SP,
94 if let Ok(result) = result {
95 // In case this is a trait item, skip the
96 // early return and try looking for the trait.
97 let value = match result.def {
98 Def::Method(_) | Def::AssociatedConst(_) => true,
99 Def::AssociatedTy(_) => false,
100 Def::Variant(_) => return handle_variant(cx, result.def),
101 // Not a trait item; just return what we found.
102 _ => return Ok((result.def, None))
108 } else if let Some(prim) = is_primitive(path_str, is_val) {
109 return Ok((prim, Some(path_str.to_owned())))
111 // If resolution failed, it may still be a method
112 // because methods are not handled by the resolver
113 // If so, bail when we're not looking for a value.
119 // Try looking for methods and associated items.
120 let mut split = path_str.rsplitn(2, "::");
121 let item_name = if let Some(first) = split.next() {
127 let mut path = if let Some(second) = split.next() {
133 if path == "self" || path == "Self" {
134 if let Some(name) = current_item.as_ref() {
139 // FIXME: `with_scope` requires the `NodeId` of a module.
140 let ty = cx.resolver.borrow_mut()
143 resolver.resolve_str_path_error(DUMMY_SP, &path, false)
146 Def::Struct(did) | Def::Union(did) | Def::Enum(did) | Def::TyAlias(did) => {
147 let item = cx.tcx.inherent_impls(did)
149 .flat_map(|imp| cx.tcx.associated_items(*imp))
150 .find(|item| item.ident.name == item_name);
151 if let Some(item) = item {
152 let out = match item.kind {
153 ty::AssociatedKind::Method if is_val => "method",
154 ty::AssociatedKind::Const if is_val => "associatedconstant",
157 Ok((ty.def, Some(format!("{}.{}", out, item_name))))
159 match cx.tcx.type_of(did).sty {
161 if let Some(item) = if def.is_enum() {
162 def.all_fields().find(|item| item.ident.name == item_name)
164 def.non_enum_variant()
167 .find(|item| item.ident.name == item_name)
170 Some(format!("{}.{}",
186 let item = cx.tcx.associated_item_def_ids(did).iter()
187 .map(|item| cx.tcx.associated_item(*item))
188 .find(|item| item.ident.name == item_name);
189 if let Some(item) = item {
190 let kind = match item.kind {
191 ty::AssociatedKind::Const if is_val => "associatedconstant",
192 ty::AssociatedKind::Type if !is_val => "associatedtype",
193 ty::AssociatedKind::Method if is_val => {
194 if item.defaultness.has_value() {
203 Ok((ty.def, Some(format!("{}.{}", kind, item_name))))
216 impl<'a, 'tcx, 'rcx, 'cstore> DocFolder for LinkCollector<'a, 'tcx, 'rcx, 'cstore> {
217 fn fold_item(&mut self, mut item: Item) -> Option<Item> {
218 let item_node_id = if item.is_mod() {
219 if let Some(id) = self.cx.tcx.hir().as_local_node_id(item.def_id) {
222 debug!("attempting to fold on a non-local item: {:?}", item);
223 return self.fold_item_recur(item);
229 // FIXME: get the resolver to work with non-local resolve scopes.
230 let parent_node = self.cx.as_local_node_id(item.def_id).and_then(|node_id| {
231 // FIXME: this fails hard for impls in non-module scope, but is necessary for the
232 // current `resolve()` implementation.
233 match self.cx.tcx.hir().get_module_parent_node(node_id) {
234 id if id != node_id => Some(id),
239 if parent_node.is_some() {
240 debug!("got parent node for {} {:?}, id {:?}", item.type_(), item.name, item.def_id);
243 let current_item = match item.inner {
245 if item.attrs.inner_docs {
246 if item_node_id.unwrap() != NodeId::from_u32(0) {
252 match parent_node.or(self.mod_ids.last().cloned()) {
253 Some(parent) if parent != NodeId::from_u32(0) => {
254 // FIXME: can we pull the parent module's name from elsewhere?
255 Some(self.cx.tcx.hir().name(parent).to_string())
261 ImplItem(Impl { ref for_, .. }) => {
262 for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string())
264 // we don't display docs on `extern crate` items anyway, so don't process them.
265 ExternCrateItem(..) => return self.fold_item_recur(item),
266 ImportItem(Import::Simple(ref name, ..)) => Some(name.clone()),
267 MacroItem(..) => None,
268 _ => item.name.clone(),
271 if item.is_mod() && item.attrs.inner_docs {
272 self.mod_ids.push(item_node_id.unwrap());
276 let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
278 look_for_tests(&cx, &dox, &item, true);
280 if !self.is_nightly_build {
284 for (ori_link, link_range) in markdown_links(&dox) {
285 // Bail early for real links.
286 if ori_link.contains('/') {
289 let link = ori_link.replace("`", "");
290 let (def, fragment) = {
291 let mut kind = PathKind::Unknown;
292 let path_str = if let Some(prefix) =
293 ["struct@", "enum@", "type@",
294 "trait@", "union@"].iter()
295 .find(|p| link.starts_with(**p)) {
296 kind = PathKind::Type;
297 link.trim_left_matches(prefix)
298 } else if let Some(prefix) =
299 ["const@", "static@",
300 "value@", "function@", "mod@",
301 "fn@", "module@", "method@"]
302 .iter().find(|p| link.starts_with(**p)) {
303 kind = PathKind::Value;
304 link.trim_left_matches(prefix)
305 } else if link.ends_with("()") {
306 kind = PathKind::Value;
307 link.trim_right_matches("()")
308 } else if link.starts_with("macro@") {
309 kind = PathKind::Macro;
310 link.trim_left_matches("macro@")
311 } else if link.ends_with('!') {
312 kind = PathKind::Macro;
313 link.trim_right_matches('!')
318 if path_str.contains(|ch: char| !(ch.is_alphanumeric() ||
319 ch == ':' || ch == '_')) {
325 if let Ok(def) = self.resolve(path_str, true, ¤t_item, parent_node) {
328 resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
329 // This could just be a normal link or a broken link
330 // we could potentially check if something is
331 // "intra-doc-link-like" and warn in that case.
336 if let Ok(def) = self.resolve(path_str, false, ¤t_item, parent_node) {
339 resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
340 // This could just be a normal link.
344 PathKind::Unknown => {
346 if let Some(macro_def) = macro_resolve(cx, path_str) {
347 if let Ok(type_def) =
348 self.resolve(path_str, false, ¤t_item, parent_node)
350 let (type_kind, article, type_disambig)
351 = type_ns_kind(type_def.0, path_str);
352 ambiguity_error(cx, &item.attrs, path_str,
353 article, type_kind, &type_disambig,
354 "a", "macro", &format!("macro@{}", path_str));
356 } else if let Ok(value_def) =
357 self.resolve(path_str, true, ¤t_item, parent_node)
359 let (value_kind, value_disambig)
360 = value_ns_kind(value_def.0, path_str)
361 .expect("struct and mod cases should have been \
362 caught in previous branch");
363 ambiguity_error(cx, &item.attrs, path_str,
364 "a", value_kind, &value_disambig,
365 "a", "macro", &format!("macro@{}", path_str));
368 } else if let Ok(type_def) =
369 self.resolve(path_str, false, ¤t_item, parent_node)
371 // It is imperative we search for not-a-value first
372 // Otherwise we will find struct ctors for when we are looking
373 // for structs, and the link won't work if there is something in
375 if let Ok(value_def) =
376 self.resolve(path_str, true, ¤t_item, parent_node)
378 let kind = value_ns_kind(value_def.0, path_str);
379 if let Some((value_kind, value_disambig)) = kind {
380 let (type_kind, article, type_disambig)
381 = type_ns_kind(type_def.0, path_str);
382 ambiguity_error(cx, &item.attrs, path_str,
383 article, type_kind, &type_disambig,
384 "a", value_kind, &value_disambig);
389 } else if let Ok(value_def) =
390 self.resolve(path_str, true, ¤t_item, parent_node)
394 resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
395 // this could just be a normal link
400 if let Some(def) = macro_resolve(cx, path_str) {
403 resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
410 if let Def::PrimTy(_) = def {
411 item.attrs.links.push((ori_link, None, fragment));
413 let id = register_def(cx, def);
414 item.attrs.links.push((ori_link, Some(id), fragment));
418 if item.is_mod() && !item.attrs.inner_docs {
419 self.mod_ids.push(item_node_id.unwrap());
423 let ret = self.fold_item_recur(item);
429 self.fold_item_recur(item)
434 /// Resolve a string as a macro.
435 fn macro_resolve(cx: &DocContext, path_str: &str) -> Option<Def> {
436 use syntax::ext::base::{MacroKind, SyntaxExtension};
437 let segment = ast::PathSegment::from_ident(Ident::from_str(path_str));
438 let path = ast::Path { segments: vec![segment], span: DUMMY_SP };
439 let mut resolver = cx.resolver.borrow_mut();
440 let parent_scope = resolver.dummy_parent_scope();
441 if let Ok(def) = resolver.resolve_macro_to_def_inner(&path, MacroKind::Bang,
442 &parent_scope, false, false) {
443 if let SyntaxExtension::DeclMacro { .. } = *resolver.get_macro(def) {
447 if let Some(def) = resolver.all_macros.get(&Symbol::intern(path_str)) {
453 pub fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
454 if attrs.doc_strings.is_empty() {
457 let start = attrs.doc_strings[0].span();
458 let end = attrs.doc_strings.last().expect("No doc strings provided").span();
462 fn resolution_failure(
467 link_range: Option<Range<usize>>,
469 let sp = span_of_attrs(attrs);
470 let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str);
472 let code_dox = sp.to_src(cx);
474 let doc_comment_padding = 3;
475 let mut diag = if let Some(link_range) = link_range {
476 // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
479 // last_new_line_offset
482 if dox.lines().count() == code_dox.lines().count() {
483 let line_offset = dox[..link_range.start].lines().count();
484 // The span starts in the `///`, so we don't have to account for the leading whitespace.
485 let code_dox_len = if line_offset <= 1 {
489 doc_comment_padding +
490 // Each subsequent leading whitespace and `///`.
491 code_dox.lines().skip(1).take(line_offset - 1).fold(0, |sum, line| {
492 sum + doc_comment_padding + line.len() - line.trim_start().len()
496 // Extract the specific span.
497 let sp = sp.from_inner_byte_pos(
498 link_range.start + code_dox_len,
499 link_range.end + code_dox_len,
502 diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
506 diag.span_label(sp, "cannot be resolved, ignoring");
508 diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
513 let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
514 let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
516 // Print the line containing the `link_range` and manually mark it with '^'s.
518 "the link appears in this line:\n\n{line}\n\
519 {indicator: <before$}{indicator:^<found$}",
522 before=link_range.start - last_new_line_offset,
523 found=link_range.len(),
528 cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
533 diag.help("to escape `[` and `]` characters, just add '\\' before them like \
538 fn ambiguity_error(cx: &DocContext, attrs: &Attributes,
540 article1: &str, kind1: &str, disambig1: &str,
541 article2: &str, kind2: &str, disambig2: &str) {
542 let sp = span_of_attrs(attrs);
544 .struct_span_warn(sp,
545 &format!("`{}` is both {} {} and {} {}",
546 path_str, article1, kind1,
548 .help(&format!("try `{}` if you want to select the {}, \
549 or `{}` if you want to \
551 disambig1, kind1, disambig2,
556 /// Given a def, returns its name and disambiguator
557 /// for a value namespace.
559 /// Returns `None` for things which cannot be ambiguous since
560 /// they exist in both namespaces (structs and modules).
561 fn value_ns_kind(def: Def, path_str: &str) -> Option<(&'static str, String)> {
563 // Structs, variants, and mods exist in both namespaces; skip them.
564 Def::StructCtor(..) | Def::Mod(..) | Def::Variant(..) |
565 Def::VariantCtor(..) | Def::SelfCtor(..)
568 => Some(("function", format!("{}()", path_str))),
570 => Some(("method", format!("{}()", path_str))),
572 => Some(("const", format!("const@{}", path_str))),
574 => Some(("static", format!("static@{}", path_str))),
575 _ => Some(("value", format!("value@{}", path_str))),
579 /// Given a def, returns its name, the article to be used, and a disambiguator
580 /// for the type namespace.
581 fn type_ns_kind(def: Def, path_str: &str) -> (&'static str, &'static str, String) {
582 let (kind, article) = match def {
583 // We can still have non-tuple structs.
584 Def::Struct(..) => ("struct", "a"),
585 Def::Enum(..) => ("enum", "an"),
586 Def::Trait(..) => ("trait", "a"),
587 Def::Union(..) => ("union", "a"),
590 (kind, article, format!("{}@{}", kind, path_str))
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.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, is_val: bool) -> Option<Def> {
631 PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1)