about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-02-20 08:38:28 +0000
committerbors <bors@rust-lang.org>2022-02-20 08:38:28 +0000
commit6d7aa4763fe7f737d6add4261b9e050b36701089 (patch)
tree3f48ea10f5e8a10c06d0d4e93bea05ac7fe0a5e9
parenta6fe969541d14ad8ba286c47416e6d3f58a1c9a4 (diff)
parent1a6d41cdb5219763eeaf3604e5805a854e5ba931 (diff)
downloadrust-6d7aa4763fe7f737d6add4261b9e050b36701089.tar.gz
rust-6d7aa4763fe7f737d6add4261b9e050b36701089.zip
Auto merge of #93605 - notriddle:notriddle/rustdoc-html-tags-resolve, r=GuillaumeGomez
rustdoc: resolve intra-doc links when checking HTML

Similar to #86451

CC #67799

Given this test case:

```rust
#![warn(rustdoc::invalid_html_tags)]
#![warn(rustdoc::broken_intra_doc_links)]

pub struct ExistentStruct<T>(T);

/// This [test][ExistentStruct<i32>] thing!
pub struct NoError;
```

This pull request silences the following, spurious warning:

```text
warning: unclosed HTML tag `i32`
 --> test.rs:6:31
  |
6 | /// This [test][ExistentStruct<i32>] thing!
  |                               ^^^^^
  |
note: the lint level is defined here
 --> test.rs:1:9
  |
1 | #![warn(rustdoc::invalid_html_tags)]
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^
help: try marking as source code
  |
6 | /// This [test][`ExistentStruct<i32>`] thing!
  |                 +                   +

warning: 1 warning emitted
```
-rw-r--r--src/librustdoc/passes/html_tags.rs28
-rw-r--r--src/test/rustdoc-ui/intra-doc/html-as-generics-intra-doc.rs25
-rw-r--r--src/test/rustdoc-ui/intra-doc/html-as-generics-intra-doc.stderr69
3 files changed, 120 insertions, 2 deletions
diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs
index 9caadef3dec..f047f6bb169 100644
--- a/src/librustdoc/passes/html_tags.rs
+++ b/src/librustdoc/passes/html_tags.rs
@@ -5,7 +5,7 @@ use crate::core::DocContext;
 use crate::html::markdown::main_body_opts;
 use crate::visit::DocVisitor;
 
-use pulldown_cmark::{Event, Parser, Tag};
+use pulldown_cmark::{BrokenLink, Event, LinkType, Parser, Tag};
 
 use std::iter::Peekable;
 use std::ops::Range;
@@ -249,7 +249,31 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> {
             let mut is_in_comment = None;
             let mut in_code_block = false;
 
-            let p = Parser::new_ext(&dox, main_body_opts()).into_offset_iter();
+            let link_names = item.link_names(&self.cx.cache);
+
+            let mut replacer = |broken_link: BrokenLink<'_>| {
+                if let Some(link) =
+                    link_names.iter().find(|link| *link.original_text == *broken_link.reference)
+                {
+                    Some((link.href.as_str().into(), link.new_text.as_str().into()))
+                } else if matches!(
+                    &broken_link.link_type,
+                    LinkType::Reference | LinkType::ReferenceUnknown
+                ) {
+                    // If the link is shaped [like][this], suppress any broken HTML in the [this] part.
+                    // The `broken_intra_doc_links` will report typos in there anyway.
+                    Some((
+                        broken_link.reference.to_string().into(),
+                        broken_link.reference.to_string().into(),
+                    ))
+                } else {
+                    None
+                }
+            };
+
+            let p =
+                Parser::new_with_broken_link_callback(&dox, main_body_opts(), Some(&mut replacer))
+                    .into_offset_iter();
 
             for (event, range) in p {
                 match event {
diff --git a/src/test/rustdoc-ui/intra-doc/html-as-generics-intra-doc.rs b/src/test/rustdoc-ui/intra-doc/html-as-generics-intra-doc.rs
new file mode 100644
index 00000000000..b5470c859fd
--- /dev/null
+++ b/src/test/rustdoc-ui/intra-doc/html-as-generics-intra-doc.rs
@@ -0,0 +1,25 @@
+#![deny(rustdoc::invalid_html_tags)]
+#![deny(rustdoc::broken_intra_doc_links)]
+
+pub struct ExistentStruct<T>(T);
+
+/// This [test][ExistentStruct<i32>] thing!
+pub struct NoError;
+
+/// This [ExistentStruct<i32>] thing!
+//~^ ERROR unclosed HTML tag `i32`
+pub struct PartialErrorOnlyHtml;
+
+/// This [test][NonExistentStruct<i32>] thing!
+//~^ ERROR unresolved link
+pub struct PartialErrorOnlyResolve;
+
+/// This [NonExistentStruct2<i32>] thing!
+//~^ ERROR unclosed HTML tag `i32`
+//~| ERROR unresolved link
+pub struct YesError;
+
+/// This [NonExistentStruct3<i32>][] thing!
+//~^ ERROR unclosed HTML tag `i32`
+//~| ERROR unresolved link
+pub struct YesErrorCollapsed;
diff --git a/src/test/rustdoc-ui/intra-doc/html-as-generics-intra-doc.stderr b/src/test/rustdoc-ui/intra-doc/html-as-generics-intra-doc.stderr
new file mode 100644
index 00000000000..00fe229da40
--- /dev/null
+++ b/src/test/rustdoc-ui/intra-doc/html-as-generics-intra-doc.stderr
@@ -0,0 +1,69 @@
+error: unresolved link to `NonExistentStruct`
+  --> $DIR/html-as-generics-intra-doc.rs:13:17
+   |
+LL | /// This [test][NonExistentStruct<i32>] thing!
+   |                 ^^^^^^^^^^^^^^^^^^^^^^ no item named `NonExistentStruct` in scope
+   |
+note: the lint level is defined here
+  --> $DIR/html-as-generics-intra-doc.rs:2:9
+   |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
+
+error: unresolved link to `NonExistentStruct2`
+  --> $DIR/html-as-generics-intra-doc.rs:17:11
+   |
+LL | /// This [NonExistentStruct2<i32>] thing!
+   |           ^^^^^^^^^^^^^^^^^^^^^^^ no item named `NonExistentStruct2` in scope
+   |
+   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
+
+error: unresolved link to `NonExistentStruct3`
+  --> $DIR/html-as-generics-intra-doc.rs:22:11
+   |
+LL | /// This [NonExistentStruct3<i32>][] thing!
+   |           ^^^^^^^^^^^^^^^^^^^^^^^ no item named `NonExistentStruct3` in scope
+   |
+   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
+
+error: unclosed HTML tag `i32`
+  --> $DIR/html-as-generics-intra-doc.rs:9:25
+   |
+LL | /// This [ExistentStruct<i32>] thing!
+   |                         ^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/html-as-generics-intra-doc.rs:1:9
+   |
+LL | #![deny(rustdoc::invalid_html_tags)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: try marking as source code
+   |
+LL | /// This [`ExistentStruct<i32>`] thing!
+   |           +                   +
+
+error: unclosed HTML tag `i32`
+  --> $DIR/html-as-generics-intra-doc.rs:17:29
+   |
+LL | /// This [NonExistentStruct2<i32>] thing!
+   |                             ^^^^^
+   |
+help: try marking as source code
+   |
+LL | /// This [`NonExistentStruct2<i32>`] thing!
+   |           +                       +
+
+error: unclosed HTML tag `i32`
+  --> $DIR/html-as-generics-intra-doc.rs:22:29
+   |
+LL | /// This [NonExistentStruct3<i32>][] thing!
+   |                             ^^^^^
+   |
+help: try marking as source code
+   |
+LL | /// This [`NonExistentStruct3<i32>`][] thing!
+   |           +                       +
+
+error: aborting due to 6 previous errors
+