about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2019-11-27 04:51:31 +0000
committerbors <bors@rust-lang.org>2019-11-27 04:51:31 +0000
commitb5f265eeed23ac87ec6b4a7e6bc7cb4ea3e67c31 (patch)
tree4eb21ae4fe7ba42f31aa57a5d232e529d6a2b777
parent809e180a76ce97340bf4354ff357bc59e3ca40b2 (diff)
parentc1ea1fd2b03b16bfeab01cbcd7bae976ab596923 (diff)
downloadrust-b5f265eeed23ac87ec6b4a7e6bc7cb4ea3e67c31.tar.gz
rust-b5f265eeed23ac87ec6b4a7e6bc7cb4ea3e67c31.zip
Auto merge of #66675 - GuillaumeGomez:support-anchors-intra-doc-links, r=kinnison
Support anchors intra doc links

Fixes #62833
Part of #43466.

cc @ollie27
r? @kinnison
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs289
-rw-r--r--src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr2
-rw-r--r--src/test/rustdoc-ui/intra-doc-alias-ice.stderr2
-rw-r--r--src/test/rustdoc-ui/intra-link-span-ice-55723.rs2
-rw-r--r--src/test/rustdoc-ui/intra-link-span-ice-55723.stderr2
-rw-r--r--src/test/rustdoc-ui/intra-links-anchors.rs45
-rw-r--r--src/test/rustdoc-ui/intra-links-anchors.stderr32
-rw-r--r--src/test/rustdoc-ui/intra-links-warning-crlf.stderr8
-rw-r--r--src/test/rustdoc-ui/intra-links-warning.stderr38
-rw-r--r--src/test/rustdoc-ui/lint-group.stderr2
-rw-r--r--src/test/rustdoc/intra-links-anchors.rs12
11 files changed, 328 insertions, 106 deletions
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index ab34f8daad7..783168d7bd6 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -38,6 +38,11 @@ pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate {
     }
 }
 
+enum ErrorKind {
+    ResolutionFailure,
+    AnchorFailure(&'static str),
+}
+
 struct LinkCollector<'a, 'tcx> {
     cx: &'a DocContext<'tcx>,
     mod_ids: Vec<hir::HirId>,
@@ -53,13 +58,14 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
 
     /// Resolves a string as a path within a particular namespace. Also returns an optional
     /// URL fragment in the case of variants and methods.
-    fn resolve(&self,
-               path_str: &str,
-               ns: Namespace,
-               current_item: &Option<String>,
-               parent_id: Option<hir::HirId>)
-        -> Result<(Res, Option<String>), ()>
-    {
+    fn resolve(
+        &self,
+        path_str: &str,
+        ns: Namespace,
+        current_item: &Option<String>,
+        parent_id: Option<hir::HirId>,
+        extra_fragment: &Option<String>,
+    ) -> Result<(Res, Option<String>), ErrorKind> {
         let cx = self.cx;
 
         // In case we're in a module, try to resolve the relative path.
@@ -69,8 +75,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 resolver.resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id)
             });
             let result = match result {
-                Ok((_, Res::Err)) => Err(()),
-                _ => result,
+                Ok((_, Res::Err)) => Err(ErrorKind::ResolutionFailure),
+                _ => result.map_err(|_| ErrorKind::ResolutionFailure),
             };
 
             if let Ok((_, res)) = result {
@@ -80,23 +86,36 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 let value = match res {
                     Res::Def(DefKind::Method, _) | Res::Def(DefKind::AssocConst, _) => true,
                     Res::Def(DefKind::AssocTy, _) => false,
-                    Res::Def(DefKind::Variant, _) => return handle_variant(cx, res),
+                    Res::Def(DefKind::Variant, _) => {
+                        return handle_variant(cx, res, extra_fragment);
+                    }
                     // Not a trait item; just return what we found.
-                    Res::PrimTy(..) => return Ok((res, Some(path_str.to_owned()))),
-                    _ => return Ok((res, None))
+                    Res::PrimTy(..) => {
+                        if extra_fragment.is_some() {
+                            return Err(
+                                ErrorKind::AnchorFailure(
+                                    "primitive types cannot be followed by anchors"));
+                        }
+                        return Ok((res, Some(path_str.to_owned())));
+                    }
+                    _ => return Ok((res, extra_fragment.clone()))
                 };
 
                 if value != (ns == ValueNS) {
-                    return Err(())
+                    return Err(ErrorKind::ResolutionFailure)
                 }
             } else if let Some(prim) = is_primitive(path_str, ns) {
+                if extra_fragment.is_some() {
+                    return Err(
+                        ErrorKind::AnchorFailure("primitive types cannot be followed by anchors"));
+                }
                 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 ns != ValueNS {
-                    return Err(())
+                    return Err(ErrorKind::ResolutionFailure)
                 }
             }
 
@@ -105,13 +124,13 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
             let item_name = if let Some(first) = split.next() {
                 Symbol::intern(first)
             } else {
-                return Err(())
+                return Err(ErrorKind::ResolutionFailure)
             };
 
             let mut path = if let Some(second) = split.next() {
                 second.to_owned()
             } else {
-                return Err(())
+                return Err(ErrorKind::ResolutionFailure)
             };
 
             if path == "self" || path == "Self" {
@@ -120,7 +139,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 }
             }
             if let Some(prim) = is_primitive(&path, TypeNS) {
-                let did = primitive_impl(cx, &path).ok_or(())?;
+                let did = primitive_impl(cx, &path).ok_or(ErrorKind::ResolutionFailure)?;
                 return cx.tcx.associated_items(did)
                     .find(|item| item.ident.name == item_name)
                     .and_then(|item| match item.kind {
@@ -128,14 +147,14 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         _ => None,
                     })
                     .map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name))))
-                    .ok_or(());
+                    .ok_or(ErrorKind::ResolutionFailure);
             }
 
             let (_, ty_res) = cx.enter_resolver(|resolver| {
                 resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
-            })?;
+            }).map_err(|_| ErrorKind::ResolutionFailure)?;
             if let Res::Err = ty_res {
-                return Err(());
+                return Err(ErrorKind::ResolutionFailure);
             }
             let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
             match ty_res {
@@ -151,9 +170,18 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         let out = match item.kind {
                             ty::AssocKind::Method if ns == ValueNS => "method",
                             ty::AssocKind::Const if ns == ValueNS => "associatedconstant",
-                            _ => return Err(())
+                            _ => return Err(ErrorKind::ResolutionFailure)
                         };
-                        Ok((ty_res, Some(format!("{}.{}", out, item_name))))
+                        if extra_fragment.is_some() {
+                            Err(ErrorKind::AnchorFailure(
+                                if item.kind == ty::AssocKind::Method {
+                                    "methods cannot be followed by anchors"
+                                } else {
+                                    "associated constants cannot be followed by anchors"
+                                }))
+                        } else {
+                            Ok((ty_res, Some(format!("{}.{}", out, item_name))))
+                        }
                     } else {
                         match cx.tcx.type_of(did).kind {
                             ty::Adt(def, _) => {
@@ -165,19 +193,28 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                                        .iter()
                                        .find(|item| item.ident.name == item_name)
                                 } {
-                                    Ok((ty_res,
-                                        Some(format!("{}.{}",
-                                                     if def.is_enum() {
-                                                         "variant"
-                                                     } else {
-                                                         "structfield"
-                                                     },
-                                                     item.ident))))
+                                    if extra_fragment.is_some() {
+                                        Err(ErrorKind::AnchorFailure(
+                                            if def.is_enum() {
+                                                "enum variants cannot be followed by anchors"
+                                            } else {
+                                                "struct fields cannot be followed by anchors"
+                                            }))
+                                    } else {
+                                        Ok((ty_res,
+                                            Some(format!("{}.{}",
+                                                         if def.is_enum() {
+                                                             "variant"
+                                                         } else {
+                                                             "structfield"
+                                                         },
+                                                         item.ident))))
+                                    }
                                 } else {
-                                    Err(())
+                                    Err(ErrorKind::ResolutionFailure)
                                 }
                             }
-                            _ => Err(()),
+                            _ => Err(ErrorKind::ResolutionFailure),
                         }
                     }
                 }
@@ -196,19 +233,30 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                                     "tymethod"
                                 }
                             }
-                            _ => return Err(())
+                            _ => return Err(ErrorKind::ResolutionFailure)
                         };
 
-                        Ok((ty_res, Some(format!("{}.{}", kind, item_name))))
+                        if extra_fragment.is_some() {
+                            Err(ErrorKind::AnchorFailure(
+                                if item.kind == ty::AssocKind::Const {
+                                    "associated constants cannot be followed by anchors"
+                                } else if item.kind == ty::AssocKind::Type {
+                                    "associated types cannot be followed by anchors"
+                                } else {
+                                    "methods cannot be followed by anchors"
+                                }))
+                        } else {
+                            Ok((ty_res, Some(format!("{}.{}", kind, item_name))))
+                        }
                     } else {
-                        Err(())
+                        Err(ErrorKind::ResolutionFailure)
                     }
                 }
-                _ => Err(())
+                _ => Err(ErrorKind::ResolutionFailure)
             }
         } else {
             debug!("attempting to resolve item without parent module: {}", path_str);
-            Err(())
+            Err(ErrorKind::ResolutionFailure)
         }
     }
 }
@@ -289,6 +337,22 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
             }
 
             let link = ori_link.replace("`", "");
+            let parts = link.split('#').collect::<Vec<_>>();
+            let (link, extra_fragment) = if parts.len() > 2 {
+                build_diagnostic(cx, &item, &link, &dox, link_range,
+                                 "has an issue with the link anchor.",
+                                 "only one `#` is allowed in a link",
+                                 None);
+                continue;
+            } else if parts.len() == 2 {
+                if parts[0].trim().is_empty() {
+                    // This is an anchor to an element of the current page, nothing to do in here!
+                    continue;
+                }
+                (parts[0].to_owned(), Some(parts[1].to_owned()))
+            } else {
+                (parts[0].to_owned(), None)
+            };
             let (res, fragment) = {
                 let mut kind = None;
                 let path_str = if let Some(prefix) =
@@ -341,42 +405,73 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
 
                 match kind {
                     Some(ns @ ValueNS) => {
-                        if let Ok(res) = self.resolve(path_str, ns, &current_item, base_node) {
-                            res
-                        } else {
-                            resolution_failure(cx, &item, 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;
+                        match self.resolve(path_str, ns, &current_item, base_node,
+                                           &extra_fragment) {
+                            Ok(res) => res,
+                            Err(ErrorKind::ResolutionFailure) => {
+                                resolution_failure(cx, &item, 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;
+                            }
+                            Err(ErrorKind::AnchorFailure(msg)) => {
+                                anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
+                                continue
+                            }
                         }
                     }
                     Some(ns @ TypeNS) => {
-                        if let Ok(res) = self.resolve(path_str, ns, &current_item, base_node) {
-                            res
-                        } else {
-                            resolution_failure(cx, &item, path_str, &dox, link_range);
-                            // This could just be a normal link.
-                            continue;
+                        match self.resolve(path_str, ns, &current_item, base_node,
+                                           &extra_fragment) {
+                            Ok(res) => res,
+                            Err(ErrorKind::ResolutionFailure) => {
+                                resolution_failure(cx, &item, path_str, &dox, link_range);
+                                // This could just be a normal link.
+                                continue;
+                            }
+                            Err(ErrorKind::AnchorFailure(msg)) => {
+                                anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
+                                continue
+                            }
                         }
                     }
                     None => {
                         // Try everything!
                         let candidates = PerNS {
-                            macro_ns: macro_resolve(cx, path_str).map(|res| (res, None)),
-                            type_ns: self
-                                .resolve(path_str, TypeNS, &current_item, base_node)
-                                .ok(),
-                            value_ns: self
-                                .resolve(path_str, ValueNS, &current_item, base_node)
-                                .ok()
-                                .and_then(|(res, fragment)| {
-                                    // Constructors are picked up in the type namespace.
-                                    match res {
-                                        Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => None,
-                                        _ => Some((res, fragment))
-                                    }
-                                }),
+                            macro_ns: macro_resolve(cx, path_str)
+                                        .map(|res| (res, extra_fragment.clone())),
+                            type_ns: match self.resolve(path_str, TypeNS, &current_item, base_node,
+                                                        &extra_fragment) {
+                                Err(ErrorKind::AnchorFailure(msg)) => {
+                                    anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
+                                    continue;
+                                }
+                                x => x.ok(),
+                            },
+                            value_ns: match self.resolve(path_str, ValueNS, &current_item,
+                                                         base_node, &extra_fragment) {
+                                Err(ErrorKind::AnchorFailure(msg)) => {
+                                    anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
+                                    continue;
+                                }
+                                x => x.ok(),
+                            }
+                            .and_then(|(res, fragment)| {
+                                // Constructors are picked up in the type namespace.
+                                match res {
+                                    Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => None,
+                                    _ => match (fragment, extra_fragment) {
+                                        (Some(fragment), Some(_)) => {
+                                            // Shouldn't happen but who knows?
+                                            Some((res, Some(fragment)))
+                                        }
+                                        (fragment, None) | (None, fragment) => {
+                                            Some((res, fragment))
+                                        }
+                                    },
+                                }
+                            }),
                         };
 
                         if candidates.is_empty() {
@@ -402,7 +497,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                     }
                     Some(MacroNS) => {
                         if let Some(res) = macro_resolve(cx, path_str) {
-                            (res, None)
+                            (res, extra_fragment)
                         } else {
                             resolution_failure(cx, &item, path_str, &dox, link_range);
                             continue
@@ -462,17 +557,15 @@ fn macro_resolve(cx: &DocContext<'_>, path_str: &str) -> Option<Res> {
     })
 }
 
-/// Reports a resolution failure diagnostic.
-///
-/// If we cannot find the exact source span of the resolution failure, we use the span of the
-/// documentation attributes themselves. This is a little heavy-handed, so we display the markdown
-/// line containing the failure as a note as well.
-fn resolution_failure(
+fn build_diagnostic(
     cx: &DocContext<'_>,
     item: &Item,
     path_str: &str,
     dox: &str,
     link_range: Option<Range<usize>>,
+    err_msg: &str,
+    short_err_msg: &str,
+    help_msg: Option<&str>,
 ) {
     let hir_id = match cx.as_local_hir_id(item.def_id) {
         Some(hir_id) => hir_id,
@@ -488,12 +581,12 @@ fn resolution_failure(
         lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
         hir_id,
         sp,
-        &format!("`[{}]` cannot be resolved, ignoring it...", path_str),
+        &format!("`[{}]` {}", path_str, err_msg),
     );
     if let Some(link_range) = link_range {
         if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) {
             diag.set_span(sp);
-            diag.span_label(sp, "cannot be resolved, ignoring");
+            diag.span_label(sp, short_err_msg);
         } else {
             // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
             //                       ^     ~~~~
@@ -513,11 +606,44 @@ fn resolution_failure(
             ));
         }
     };
-    diag.help("to escape `[` and `]` characters, just add '\\' before them like \
-               `\\[` or `\\]`");
+    if let Some(help_msg) = help_msg {
+        diag.help(help_msg);
+    }
     diag.emit();
 }
 
+/// Reports a resolution failure diagnostic.
+///
+/// If we cannot find the exact source span of the resolution failure, we use the span of the
+/// documentation attributes themselves. This is a little heavy-handed, so we display the markdown
+/// line containing the failure as a note as well.
+fn resolution_failure(
+    cx: &DocContext<'_>,
+    item: &Item,
+    path_str: &str,
+    dox: &str,
+    link_range: Option<Range<usize>>,
+) {
+    build_diagnostic(cx, item, path_str, dox, link_range,
+         "cannot be resolved, ignoring it.",
+         "cannot be resolved, ignoring",
+         Some("to escape `[` and `]` characters, just add '\\' before them like `\\[` or `\\]`"));
+}
+
+fn anchor_failure(
+    cx: &DocContext<'_>,
+    item: &Item,
+    path_str: &str,
+    dox: &str,
+    link_range: Option<Range<usize>>,
+    msg: &str,
+) {
+    build_diagnostic(cx, item, path_str, dox, link_range,
+         "has an issue with the link anchor.",
+         msg,
+         None);
+}
+
 fn ambiguity_error(
     cx: &DocContext<'_>,
     item: &Item,
@@ -637,13 +763,20 @@ fn ambiguity_error(
 }
 
 /// Given an enum variant's res, return the res of its enum and the associated fragment.
-fn handle_variant(cx: &DocContext<'_>, res: Res) -> Result<(Res, Option<String>), ()> {
+fn handle_variant(
+    cx: &DocContext<'_>,
+    res: Res,
+    extra_fragment: &Option<String>,
+) -> Result<(Res, Option<String>), ErrorKind> {
     use rustc::ty::DefIdTree;
 
+    if extra_fragment.is_some() {
+        return Err(ErrorKind::AnchorFailure("variants cannot be followed by anchors"));
+    }
     let parent = if let Some(parent) = cx.tcx.parent(res.def_id()) {
         parent
     } else {
-        return Err(())
+        return Err(ErrorKind::ResolutionFailure)
     };
     let parent_def = Res::Def(DefKind::Enum, parent);
     let variant = cx.tcx.expect_variant_res(res);
diff --git a/src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr b/src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr
index 1a120dcb186..b432bfbf20f 100644
--- a/src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr
+++ b/src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr
@@ -1,4 +1,4 @@
-error: `[v2]` cannot be resolved, ignoring it...
+error: `[v2]` cannot be resolved, ignoring it.
   --> $DIR/deny-intra-link-resolution-failure.rs:3:6
    |
 LL | /// [v2]
diff --git a/src/test/rustdoc-ui/intra-doc-alias-ice.stderr b/src/test/rustdoc-ui/intra-doc-alias-ice.stderr
index cebb14cba7c..d4d3e5fea3e 100644
--- a/src/test/rustdoc-ui/intra-doc-alias-ice.stderr
+++ b/src/test/rustdoc-ui/intra-doc-alias-ice.stderr
@@ -1,4 +1,4 @@
-error: `[TypeAlias::hoge]` cannot be resolved, ignoring it...
+error: `[TypeAlias::hoge]` cannot be resolved, ignoring it.
   --> $DIR/intra-doc-alias-ice.rs:5:30
    |
 LL | /// [broken cross-reference](TypeAlias::hoge)
diff --git a/src/test/rustdoc-ui/intra-link-span-ice-55723.rs b/src/test/rustdoc-ui/intra-link-span-ice-55723.rs
index c7a13bbf606..44997c90f59 100644
--- a/src/test/rustdoc-ui/intra-link-span-ice-55723.rs
+++ b/src/test/rustdoc-ui/intra-link-span-ice-55723.rs
@@ -7,7 +7,7 @@
 /// ## For example:
 ///
 /// (arr[i])
-//~^ ERROR `[i]` cannot be resolved, ignoring it...
+//~^ ERROR `[i]` cannot be resolved, ignoring it.
 pub fn test_ice() {
     unimplemented!();
 }
diff --git a/src/test/rustdoc-ui/intra-link-span-ice-55723.stderr b/src/test/rustdoc-ui/intra-link-span-ice-55723.stderr
index 79702a1a546..edd5b8b92f2 100644
--- a/src/test/rustdoc-ui/intra-link-span-ice-55723.stderr
+++ b/src/test/rustdoc-ui/intra-link-span-ice-55723.stderr
@@ -1,4 +1,4 @@
-error: `[i]` cannot be resolved, ignoring it...
+error: `[i]` cannot be resolved, ignoring it.
   --> $DIR/intra-link-span-ice-55723.rs:9:10
    |
 LL | /// (arr[i])
diff --git a/src/test/rustdoc-ui/intra-links-anchors.rs b/src/test/rustdoc-ui/intra-links-anchors.rs
new file mode 100644
index 00000000000..7f8a8dd3c45
--- /dev/null
+++ b/src/test/rustdoc-ui/intra-links-anchors.rs
@@ -0,0 +1,45 @@
+#![deny(intra_doc_link_resolution_failure)]
+
+// A few tests on anchors.
+
+/// Hello people.
+///
+/// You can anchors? Here's one!
+///
+/// # hola
+///
+/// Isn't it amazing?
+pub struct Foo {
+    pub f: u8,
+}
+
+pub enum Enum {
+    A,
+    B,
+}
+
+/// Have you heard about stuff?
+///
+/// Like [Foo#hola].
+///
+/// Or maybe [Foo::f#hola].
+//~^ ERROR `[Foo::f#hola]` has an issue with the link anchor.
+pub fn foo() {}
+
+/// Empty.
+///
+/// Another anchor error: [hello#people#!].
+//~^ ERROR `[hello#people#!]` has an issue with the link anchor.
+pub fn bar() {}
+
+/// Empty?
+///
+/// Damn enum's variants: [Enum::A#whatever].
+//~^ ERROR `[Enum::A#whatever]` has an issue with the link anchor.
+pub fn enum_link() {}
+
+/// Primitives?
+///
+/// [u32#hello]
+//~^ ERROR `[u32#hello]` has an issue with the link anchor.
+pub fn x() {}
diff --git a/src/test/rustdoc-ui/intra-links-anchors.stderr b/src/test/rustdoc-ui/intra-links-anchors.stderr
new file mode 100644
index 00000000000..5fead8e4c35
--- /dev/null
+++ b/src/test/rustdoc-ui/intra-links-anchors.stderr
@@ -0,0 +1,32 @@
+error: `[Foo::f#hola]` has an issue with the link anchor.
+  --> $DIR/intra-links-anchors.rs:25:15
+   |
+LL | /// Or maybe [Foo::f#hola].
+   |               ^^^^^^^^^^^ struct fields cannot be followed by anchors
+   |
+note: lint level defined here
+  --> $DIR/intra-links-anchors.rs:1:9
+   |
+LL | #![deny(intra_doc_link_resolution_failure)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: `[hello#people#!]` has an issue with the link anchor.
+  --> $DIR/intra-links-anchors.rs:31:28
+   |
+LL | /// Another anchor error: [hello#people#!].
+   |                            ^^^^^^^^^^^^^^ only one `#` is allowed in a link
+
+error: `[Enum::A#whatever]` has an issue with the link anchor.
+  --> $DIR/intra-links-anchors.rs:37:28
+   |
+LL | /// Damn enum's variants: [Enum::A#whatever].
+   |                            ^^^^^^^^^^^^^^^^ variants cannot be followed by anchors
+
+error: `[u32#hello]` has an issue with the link anchor.
+  --> $DIR/intra-links-anchors.rs:43:6
+   |
+LL | /// [u32#hello]
+   |      ^^^^^^^^^ primitive types cannot be followed by anchors
+
+error: aborting due to 4 previous errors
+
diff --git a/src/test/rustdoc-ui/intra-links-warning-crlf.stderr b/src/test/rustdoc-ui/intra-links-warning-crlf.stderr
index b4e11c29ed5..e4dd13cfa01 100644
--- a/src/test/rustdoc-ui/intra-links-warning-crlf.stderr
+++ b/src/test/rustdoc-ui/intra-links-warning-crlf.stderr
@@ -1,4 +1,4 @@
-warning: `[error]` cannot be resolved, ignoring it...
+warning: `[error]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning-crlf.rs:7:6
    |
 LL | /// [error]
@@ -7,7 +7,7 @@ LL | /// [error]
    = note: `#[warn(intra_doc_link_resolution_failure)]` on by default
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[error1]` cannot be resolved, ignoring it...
+warning: `[error1]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning-crlf.rs:12:11
    |
 LL | /// docs [error1]
@@ -15,7 +15,7 @@ LL | /// docs [error1]
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[error2]` cannot be resolved, ignoring it...
+warning: `[error2]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning-crlf.rs:15:11
    |
 LL | /// docs [error2]
@@ -23,7 +23,7 @@ LL | /// docs [error2]
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[error]` cannot be resolved, ignoring it...
+warning: `[error]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning-crlf.rs:23:20
    |
 LL |  * It also has an [error].
diff --git a/src/test/rustdoc-ui/intra-links-warning.stderr b/src/test/rustdoc-ui/intra-links-warning.stderr
index 27cc3aeb081..5f1c9cfbc36 100644
--- a/src/test/rustdoc-ui/intra-links-warning.stderr
+++ b/src/test/rustdoc-ui/intra-links-warning.stderr
@@ -1,4 +1,4 @@
-warning: `[Foo::baz]` cannot be resolved, ignoring it...
+warning: `[Foo::baz]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:3:23
    |
 LL |        //! Test with [Foo::baz], [Bar::foo], ...
@@ -7,7 +7,7 @@ LL |        //! Test with [Foo::baz], [Bar::foo], ...
    = note: `#[warn(intra_doc_link_resolution_failure)]` on by default
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[Bar::foo]` cannot be resolved, ignoring it...
+warning: `[Bar::foo]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:3:35
    |
 LL |        //! Test with [Foo::baz], [Bar::foo], ...
@@ -15,7 +15,7 @@ LL |        //! Test with [Foo::baz], [Bar::foo], ...
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[Uniooon::X]` cannot be resolved, ignoring it...
+warning: `[Uniooon::X]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:6:13
    |
 LL |      //! , [Uniooon::X] and [Qux::Z].
@@ -23,7 +23,7 @@ LL |      //! , [Uniooon::X] and [Qux::Z].
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[Qux::Z]` cannot be resolved, ignoring it...
+warning: `[Qux::Z]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:6:30
    |
 LL |      //! , [Uniooon::X] and [Qux::Z].
@@ -31,7 +31,7 @@ LL |      //! , [Uniooon::X] and [Qux::Z].
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[Uniooon::X]` cannot be resolved, ignoring it...
+warning: `[Uniooon::X]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:10:14
    |
 LL |       //! , [Uniooon::X] and [Qux::Z].
@@ -39,7 +39,7 @@ LL |       //! , [Uniooon::X] and [Qux::Z].
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[Qux::Z]` cannot be resolved, ignoring it...
+warning: `[Qux::Z]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:10:31
    |
 LL |       //! , [Uniooon::X] and [Qux::Z].
@@ -47,7 +47,7 @@ LL |       //! , [Uniooon::X] and [Qux::Z].
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[Qux:Y]` cannot be resolved, ignoring it...
+warning: `[Qux:Y]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:14:13
    |
 LL |        /// [Qux:Y]
@@ -55,7 +55,7 @@ LL |        /// [Qux:Y]
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[error]` cannot be resolved, ignoring it...
+warning: `[error]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:58:30
    |
 LL |  * time to introduce a link [error]*/
@@ -63,7 +63,7 @@ LL |  * time to introduce a link [error]*/
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[error]` cannot be resolved, ignoring it...
+warning: `[error]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:64:30
    |
 LL |  * time to introduce a link [error]
@@ -71,7 +71,7 @@ LL |  * time to introduce a link [error]
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[error]` cannot be resolved, ignoring it...
+warning: `[error]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:68:1
    |
 LL | #[doc = "single line [error]"]
@@ -83,7 +83,7 @@ LL | #[doc = "single line [error]"]
                         ^^^^^
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[error]` cannot be resolved, ignoring it...
+warning: `[error]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:71:1
    |
 LL | #[doc = "single line with \"escaping\" [error]"]
@@ -95,7 +95,7 @@ LL | #[doc = "single line with \"escaping\" [error]"]
                                         ^^^^^
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[error]` cannot be resolved, ignoring it...
+warning: `[error]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:74:1
    |
 LL | / /// Item docs.
@@ -109,7 +109,7 @@ LL | | /// [error]
             ^^^^^
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[error1]` cannot be resolved, ignoring it...
+warning: `[error1]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:80:11
    |
 LL | /// docs [error1]
@@ -117,7 +117,7 @@ LL | /// docs [error1]
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[error2]` cannot be resolved, ignoring it...
+warning: `[error2]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:82:11
    |
 LL | /// docs [error2]
@@ -125,7 +125,7 @@ LL | /// docs [error2]
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[BarA]` cannot be resolved, ignoring it...
+warning: `[BarA]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:21:10
    |
 LL | /// bar [BarA] bar
@@ -133,7 +133,7 @@ LL | /// bar [BarA] bar
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[BarB]` cannot be resolved, ignoring it...
+warning: `[BarB]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:27:9
    |
 LL |  * bar [BarB] bar
@@ -141,7 +141,7 @@ LL |  * bar [BarB] bar
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[BarC]` cannot be resolved, ignoring it...
+warning: `[BarC]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:34:6
    |
 LL | bar [BarC] bar
@@ -149,7 +149,7 @@ LL | bar [BarC] bar
    |
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[BarD]` cannot be resolved, ignoring it...
+warning: `[BarD]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:45:1
    |
 LL | #[doc = "Foo\nbar [BarD] bar\nbaz"]
@@ -161,7 +161,7 @@ LL | #[doc = "Foo\nbar [BarD] bar\nbaz"]
                 ^^^^
    = help: to escape `[` and `]` characters, just add '\' before them like `\[` or `\]`
 
-warning: `[BarF]` cannot be resolved, ignoring it...
+warning: `[BarF]` cannot be resolved, ignoring it.
   --> $DIR/intra-links-warning.rs:50:9
    |
 LL |         #[doc = $f]
diff --git a/src/test/rustdoc-ui/lint-group.stderr b/src/test/rustdoc-ui/lint-group.stderr
index 63274ae2be4..dca98cf58df 100644
--- a/src/test/rustdoc-ui/lint-group.stderr
+++ b/src/test/rustdoc-ui/lint-group.stderr
@@ -15,7 +15,7 @@ LL | #![deny(rustdoc)]
    |         ^^^^^^^
    = note: `#[deny(private_doc_tests)]` implied by `#[deny(rustdoc)]`
 
-error: `[error]` cannot be resolved, ignoring it...
+error: `[error]` cannot be resolved, ignoring it.
   --> $DIR/lint-group.rs:9:29
    |
 LL | /// what up, let's make an [error]
diff --git a/src/test/rustdoc/intra-links-anchors.rs b/src/test/rustdoc/intra-links-anchors.rs
new file mode 100644
index 00000000000..f554e16b4f4
--- /dev/null
+++ b/src/test/rustdoc/intra-links-anchors.rs
@@ -0,0 +1,12 @@
+/// I want...
+///
+/// # Anchor!
+pub struct Something;
+
+// @has intra_links_anchors/struct.SomeOtherType.html
+// @has - '//a/@href' '../intra_links_anchors/struct.Something.html#Anchor!'
+
+/// I want...
+///
+/// To link to [Something#Anchor!]
+pub struct SomeOtherType;