]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/collect_intra_doc_links.rs
Various minor/cosmetic improvements to code
[rust.git] / src / librustdoc / passes / collect_intra_doc_links.rs
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.
4 //
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.
10
11 use rustc::lint as lint;
12 use rustc::hir;
13 use rustc::hir::def::Def;
14 use rustc::ty;
15 use syntax;
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};
20
21 use std::ops::Range;
22
23 use core::DocContext;
24 use fold::DocFolder;
25 use html::markdown::markdown_links;
26
27 use clean::*;
28 use passes::{look_for_tests, Pass};
29
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");
33
34 pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext) -> Crate {
35     if !UnstableFeatures::from_environment().is_nightly_build() {
36         krate
37     } else {
38         let mut coll = LinkCollector::new(cx);
39
40         coll.fold_crate(krate)
41     }
42 }
43
44 #[derive(Debug)]
45 enum PathKind {
46     /// Either a value or type, but not a macro
47     Unknown,
48     /// Macro
49     Macro,
50     /// Values, functions, consts, statics (everything in the value namespace)
51     Value,
52     /// Types, traits (everything in the type namespace)
53     Type,
54 }
55
56 struct LinkCollector<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> {
57     cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>,
58     mod_ids: Vec<NodeId>,
59     is_nightly_build: bool,
60 }
61
62 impl<'a, 'tcx, 'rcx, 'cstore> LinkCollector<'a, 'tcx, 'rcx, 'cstore> {
63     fn new(cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>) -> Self {
64         LinkCollector {
65             cx,
66             mod_ids: Vec::new(),
67             is_nightly_build: UnstableFeatures::from_environment().is_nightly_build(),
68         }
69     }
70
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.
74     fn resolve(&self,
75                path_str: &str,
76                is_val: bool,
77                current_item: &Option<String>,
78                parent_id: Option<NodeId>)
79         -> Result<(Def, Option<String>), ()>
80     {
81         let cx = self.cx;
82
83         // In case we're in a module, try to resolve the relative
84         // path.
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()
88                                     .with_scope(id,
89                 |resolver| {
90                     resolver.resolve_str_path_error(DUMMY_SP,
91                                                     &path_str, is_val)
92             });
93
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))
103                 };
104
105                 if value != is_val {
106                     return Err(())
107                 }
108             } else if let Some(prim) = is_primitive(path_str, is_val) {
109                 return Ok((prim, Some(path_str.to_owned())))
110             } else {
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.
114                 if !is_val {
115                     return Err(())
116                 }
117             }
118
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() {
122                 first
123             } else {
124                 return Err(())
125             };
126
127             let mut path = if let Some(second) = split.next() {
128                 second.to_owned()
129             } else {
130                 return Err(())
131             };
132
133             if path == "self" || path == "Self" {
134                 if let Some(name) = current_item.as_ref() {
135                     path = name.clone();
136                 }
137             }
138
139             // FIXME: `with_scope` requires the `NodeId` of a module.
140             let ty = cx.resolver.borrow_mut()
141                                 .with_scope(id,
142                 |resolver| {
143                     resolver.resolve_str_path_error(DUMMY_SP, &path, false)
144             })?;
145             match ty.def {
146                 Def::Struct(did) | Def::Union(did) | Def::Enum(did) | Def::TyAlias(did) => {
147                     let item = cx.tcx.inherent_impls(did)
148                                      .iter()
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",
155                             _ => return Err(())
156                         };
157                         Ok((ty.def, Some(format!("{}.{}", out, item_name))))
158                     } else {
159                         match cx.tcx.type_of(did).sty {
160                             ty::Adt(def, _) => {
161                                 if let Some(item) = if def.is_enum() {
162                                     def.all_fields().find(|item| item.ident.name == item_name)
163                                 } else {
164                                     def.non_enum_variant()
165                                        .fields
166                                        .iter()
167                                        .find(|item| item.ident.name == item_name)
168                                 } {
169                                     Ok((ty.def,
170                                         Some(format!("{}.{}",
171                                                      if def.is_enum() {
172                                                          "variant"
173                                                      } else {
174                                                          "structfield"
175                                                      },
176                                                      item.ident))))
177                                 } else {
178                                     Err(())
179                                 }
180                             }
181                             _ => Err(()),
182                         }
183                     }
184                 }
185                 Def::Trait(did) => {
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() {
195                                     "method"
196                                 } else {
197                                     "tymethod"
198                                 }
199                             }
200                             _ => return Err(())
201                         };
202
203                         Ok((ty.def, Some(format!("{}.{}", kind, item_name))))
204                     } else {
205                         Err(())
206                     }
207                 }
208                 _ => Err(())
209             }
210         } else {
211             Err(())
212         }
213     }
214 }
215
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) {
220                 Some(id)
221             } else {
222                 debug!("attempting to fold on a non-local item: {:?}", item);
223                 return self.fold_item_recur(item);
224             }
225         } else {
226             None
227         };
228
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),
235                 _ => None,
236             }
237         });
238
239         if parent_node.is_some() {
240             debug!("got parent node for {} {:?}, id {:?}", item.type_(), item.name, item.def_id);
241         }
242
243         let current_item = match item.inner {
244             ModuleItem(..) => {
245                 if item.attrs.inner_docs {
246                     if item_node_id.unwrap() != NodeId::from_u32(0) {
247                         item.name.clone()
248                     } else {
249                         None
250                     }
251                 } else {
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())
256                         }
257                         _ => None,
258                     }
259                 }
260             }
261             ImplItem(Impl { ref for_, .. }) => {
262                 for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string())
263             }
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(),
269         };
270
271         if item.is_mod() && item.attrs.inner_docs {
272             self.mod_ids.push(item_node_id.unwrap());
273         }
274
275         let cx = self.cx;
276         let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
277
278         look_for_tests(&cx, &dox, &item, true);
279
280         if !self.is_nightly_build {
281             return None;
282         }
283
284         for (ori_link, link_range) in markdown_links(&dox) {
285             // Bail early for real links.
286             if ori_link.contains('/') {
287                 continue;
288             }
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('!')
314                 } else {
315                     &link[..]
316                 }.trim();
317
318                 if path_str.contains(|ch: char| !(ch.is_alphanumeric() ||
319                                                   ch == ':' || ch == '_')) {
320                     continue;
321                 }
322
323                 match kind {
324                     PathKind::Value => {
325                         if let Ok(def) = self.resolve(path_str, true, &current_item, parent_node) {
326                             def
327                         } else {
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.
332                             continue;
333                         }
334                     }
335                     PathKind::Type => {
336                         if let Ok(def) = self.resolve(path_str, false, &current_item, parent_node) {
337                             def
338                         } else {
339                             resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
340                             // This could just be a normal link.
341                             continue;
342                         }
343                     }
344                     PathKind::Unknown => {
345                         // Try everything!
346                         if let Some(macro_def) = macro_resolve(cx, path_str) {
347                             if let Ok(type_def) =
348                                 self.resolve(path_str, false, &current_item, parent_node)
349                             {
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));
355                                 continue;
356                             } else if let Ok(value_def) =
357                                 self.resolve(path_str, true, &current_item, parent_node)
358                             {
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));
366                             }
367                             (macro_def, None)
368                         } else if let Ok(type_def) =
369                             self.resolve(path_str, false, &current_item, parent_node)
370                         {
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
374                             // both namespaces.
375                             if let Ok(value_def) =
376                                 self.resolve(path_str, true, &current_item, parent_node)
377                             {
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);
385                                     continue;
386                                 }
387                             }
388                             type_def
389                         } else if let Ok(value_def) =
390                             self.resolve(path_str, true, &current_item, parent_node)
391                         {
392                             value_def
393                         } else {
394                             resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
395                             // this could just be a normal link
396                             continue;
397                         }
398                     }
399                     PathKind::Macro => {
400                         if let Some(def) = macro_resolve(cx, path_str) {
401                             (def, None)
402                         } else {
403                             resolution_failure(cx, &item.attrs, path_str, &dox, link_range);
404                             continue
405                         }
406                     }
407                 }
408             };
409
410             if let Def::PrimTy(_) = def {
411                 item.attrs.links.push((ori_link, None, fragment));
412             } else {
413                 let id = register_def(cx, def);
414                 item.attrs.links.push((ori_link, Some(id), fragment));
415             }
416         }
417
418         if item.is_mod() && !item.attrs.inner_docs {
419             self.mod_ids.push(item_node_id.unwrap());
420         }
421
422         if item.is_mod() {
423             let ret = self.fold_item_recur(item);
424
425             self.mod_ids.pop();
426
427             ret
428         } else {
429             self.fold_item_recur(item)
430         }
431     }
432 }
433
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) {
444             return Some(def);
445         }
446     }
447     if let Some(def) = resolver.all_macros.get(&Symbol::intern(path_str)) {
448         return Some(*def);
449     }
450     None
451 }
452
453 pub fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
454     if attrs.doc_strings.is_empty() {
455         return DUMMY_SP;
456     }
457     let start = attrs.doc_strings[0].span();
458     let end = attrs.doc_strings.last().expect("No doc strings provided").span();
459     start.to(end)
460 }
461
462 fn resolution_failure(
463     cx: &DocContext,
464     attrs: &Attributes,
465     path_str: &str,
466     dox: &str,
467     link_range: Option<Range<usize>>,
468 ) {
469     let sp = span_of_attrs(attrs);
470     let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str);
471
472     let code_dox = sp.to_src(cx);
473
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
477         //                       ^    ~~~~~~
478         //                       |    link_range
479         //                       last_new_line_offset
480
481         let mut diag;
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 {
486                 doc_comment_padding
487             } else {
488                 // The first `///`.
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()
493                     })
494             };
495
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,
500             );
501
502             diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
503                                                 NodeId::from_u32(0),
504                                                 sp,
505                                                 &msg);
506             diag.span_label(sp, "cannot be resolved, ignoring");
507         } else {
508             diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
509                                                 NodeId::from_u32(0),
510                                                 sp,
511                                                 &msg);
512
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("");
515
516             // Print the line containing the `link_range` and manually mark it with '^'s.
517             diag.note(&format!(
518                 "the link appears in this line:\n\n{line}\n\
519                  {indicator: <before$}{indicator:^<found$}",
520                 line=line,
521                 indicator="",
522                 before=link_range.start - last_new_line_offset,
523                 found=link_range.len(),
524             ));
525         }
526         diag
527     } else {
528         cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
529                                      NodeId::from_u32(0),
530                                      sp,
531                                      &msg)
532     };
533     diag.help("to escape `[` and `]` characters, just add '\\' before them like \
534                `\\[` or `\\]`");
535     diag.emit();
536 }
537
538 fn ambiguity_error(cx: &DocContext, attrs: &Attributes,
539                    path_str: &str,
540                    article1: &str, kind1: &str, disambig1: &str,
541                    article2: &str, kind2: &str, disambig2: &str) {
542     let sp = span_of_attrs(attrs);
543     cx.sess()
544       .struct_span_warn(sp,
545                         &format!("`{}` is both {} {} and {} {}",
546                                  path_str, article1, kind1,
547                                  article2, kind2))
548       .help(&format!("try `{}` if you want to select the {}, \
549                       or `{}` if you want to \
550                       select the {}",
551                       disambig1, kind1, disambig2,
552                       kind2))
553       .emit();
554 }
555
556 /// Given a def, returns its name and disambiguator
557 /// for a value namespace.
558 ///
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)> {
562     match def {
563         // Structs, variants, and mods exist in both namespaces; skip them.
564         Def::StructCtor(..) | Def::Mod(..) | Def::Variant(..) |
565         Def::VariantCtor(..) | Def::SelfCtor(..)
566             => None,
567         Def::Fn(..)
568             => Some(("function", format!("{}()", path_str))),
569         Def::Method(..)
570             => Some(("method", format!("{}()", path_str))),
571         Def::Const(..)
572             => Some(("const", format!("const@{}", path_str))),
573         Def::Static(..)
574             => Some(("static", format!("static@{}", path_str))),
575         _ => Some(("value", format!("value@{}", path_str))),
576     }
577 }
578
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"),
588         _ => ("type", "a"),
589     };
590     (kind, article, format!("{}@{}", kind, path_str))
591 }
592
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;
596
597     let parent = if let Some(parent) = cx.tcx.parent(def.def_id()) {
598         parent
599     } else {
600         return Err(())
601     };
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))))
605 }
606
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)),
625 ];
626
627 fn is_primitive(path_str: &str, is_val: bool) -> Option<Def> {
628     if is_val {
629         None
630     } else {
631         PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1)
632     }
633 }