Deny broken intra-doc links in linkchecker

Since rustdoc isn't warning about these links, check for them manually.
This commit is contained in:
Joshua Nelson 2020-10-15 09:25:30 -04:00
parent dd7fc54ebd
commit 65835d1059
15 changed files with 90 additions and 18 deletions

View File

@ -1744,6 +1744,10 @@ dependencies = [
[[package]]
name = "linkchecker"
version = "0.1.0"
dependencies = [
"once_cell",
"regex",
]
[[package]]
name = "linked-hash-map"

View File

@ -9,4 +9,4 @@ llvm_asm!("nop" "nop");
Considering that this would be a long explanation, we instead recommend you
take a look at the [`llvm_asm`] chapter of the Unstable book:
[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html
[`llvm_asm`]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html

View File

@ -10,4 +10,4 @@ llvm_asm!("nop" : "r"(a));
Considering that this would be a long explanation, we instead recommend you
take a look at the [`llvm_asm`] chapter of the Unstable book:
[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html
[`llvm_asm`]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html

View File

@ -13,4 +13,4 @@ llvm_asm!("xor %eax, %eax"
Considering that this would be a long explanation, we instead recommend you
take a look at the [`llvm_asm`] chapter of the Unstable book:
[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html
[`llvm_asm`]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html

View File

@ -13,4 +13,4 @@ llvm_asm!("xor %eax, %eax"
Considering that this would be a long explanation, we instead recommend you
take a look at the [`llvm_asm`] chapter of the Unstable book:
[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html
[`llvm_asm`]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html

View File

@ -13,4 +13,4 @@ llvm_asm!("mov $$0x200, %eax"
Considering that this would be a long explanation, we instead recommend you
take a look at the [`llvm_asm`] chapter of the Unstable book:
[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html
[`llvm_asm`]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html

View File

@ -89,13 +89,11 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
pub unsafe trait AllocRef {
/// Attempts to allocate a block of memory.
///
/// On success, returns a [`NonNull<[u8]>`] meeting the size and alignment guarantees of `layout`.
/// On success, returns a [`NonNull<[u8]>`][NonNull] meeting the size and alignment guarantees of `layout`.
///
/// The returned block may have a larger size than specified by `layout.size()`, and may or may
/// not have its contents initialized.
///
/// [`NonNull<[u8]>`]: NonNull
///
/// # Errors
///
/// Returning `Err` indicates that either memory is exhausted or `layout` does not meet
@ -146,7 +144,7 @@ fn alloc_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
/// Attempts to extend the memory block.
///
/// Returns a new [`NonNull<[u8]>`] containing a pointer and the actual size of the allocated
/// Returns a new [`NonNull<[u8]>`][NonNull] containing a pointer and the actual size of the allocated
/// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish
/// this, the allocator may extend the allocation referenced by `ptr` to fit the new layout.
///
@ -158,8 +156,6 @@ fn alloc_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
/// If this method returns `Err`, then ownership of the memory block has not been transferred to
/// this allocator, and the contents of the memory block are unaltered.
///
/// [`NonNull<[u8]>`]: NonNull
///
/// # Safety
///
/// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
@ -271,7 +267,7 @@ unsafe fn grow_zeroed(
/// Attempts to shrink the memory block.
///
/// Returns a new [`NonNull<[u8]>`] containing a pointer and the actual size of the allocated
/// Returns a new [`NonNull<[u8]>`][NonNull] containing a pointer and the actual size of the allocated
/// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish
/// this, the allocator may shrink the allocation referenced by `ptr` to fit the new layout.
///
@ -283,8 +279,6 @@ unsafe fn grow_zeroed(
/// If this method returns `Err`, then ownership of the memory block has not been transferred to
/// this allocator, and the contents of the memory block are unaltered.
///
/// [`NonNull<[u8]>`]: NonNull
///
/// # Safety
///
/// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.

View File

@ -134,6 +134,7 @@ pub const fn identity<T>(x: T) -> T {
/// want to accept all references that can be converted to [`&str`] as an argument.
/// Since both [`String`] and [`&str`] implement `AsRef<str>` we can accept both as input argument.
///
/// [`&str`]: primitive@str
/// [`Option<T>`]: Option
/// [`Result<T, E>`]: Result
/// [`Borrow`]: crate::borrow::Borrow

View File

@ -122,6 +122,9 @@ pub trait DoubleEndedIterator: Iterator {
/// assert_eq!(iter.advance_back_by(0), Ok(()));
/// assert_eq!(iter.advance_back_by(100), Err(1)); // only `&3` was skipped
/// ```
///
/// [`Ok(())`]: Ok
/// [`Err(k)`]: Err
#[inline]
#[unstable(feature = "iter_advance_by", reason = "recently added", issue = "77404")]
fn advance_back_by(&mut self, n: usize) -> Result<(), usize> {

View File

@ -289,12 +289,12 @@ fn some<T>(_: Option<T>, x: T) -> Option<T> {
/// This method will eagerly skip `n` elements by calling [`next`] up to `n`
/// times until [`None`] is encountered.
///
/// `advance_by(n)` will return [`Ok(())`] if the iterator successfully advances by
/// `n` elements, or [`Err(k)`] if [`None`] is encountered, where `k` is the number
/// `advance_by(n)` will return [`Ok(())`][Ok] if the iterator successfully advances by
/// `n` elements, or [`Err(k)`][Err] if [`None`] is encountered, where `k` is the number
/// of elements the iterator is advanced by before running out of elements (i.e. the
/// length of the iterator). Note that `k` is always less than `n`.
///
/// Calling `advance_by(0)` does not consume any elements and always returns [`Ok(())`].
/// Calling `advance_by(0)` does not consume any elements and always returns [`Ok(())`][Ok].
///
/// [`next`]: Iterator::next
///

View File

@ -687,6 +687,7 @@ pub fn and_then<U, F: FnOnce(T) -> Option<U>>(self, f: F) -> Option<U> {
/// assert_eq!(Some(4).filter(is_even), Some(4));
/// ```
///
/// [`Some(t)`]: Some
#[inline]
#[stable(feature = "option_filter", since = "1.27.0")]
pub fn filter<P: FnOnce(&T) -> bool>(self, predicate: P) -> Self {

View File

@ -1383,7 +1383,8 @@ pub fn to_str(&self) -> Result<&str, str::Utf8Error> {
/// [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD] and return a
/// [`Cow`]`::`[`Owned`]`(`[`String`]`)` with the result.
///
/// [`str`]: prim@str
/// [`str`]: primitive@str
/// [`&str`]: primitive@str
/// [`Borrowed`]: Cow::Borrowed
/// [`Owned`]: Cow::Owned
/// [U+FFFD]: crate::char::REPLACEMENT_CHARACTER

View File

@ -10,6 +10,8 @@ Adds a free `default()` function to the `std::default` module. This function
just forwards to [`Default::default()`], but may remove repetition of the word
"default" from the call site.
[`Default::default()`]: https://doc.rust-lang.org/nightly/std/default/trait.Default.html#tymethod.default
Here is an example:
```rust

View File

@ -7,3 +7,7 @@ edition = "2018"
[[bin]]
name = "linkchecker"
path = "main.rs"
[dependencies]
regex = "1"
once_cell = "1"

View File

@ -21,6 +21,9 @@
use std::path::{Component, Path, PathBuf};
use std::rc::Rc;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::Redirect::*;
// Add linkcheck exceptions here
@ -50,6 +53,44 @@
("alloc/collections/btree_set/struct.BTreeSet.html", &["#insert-and-complex-keys"]),
];
#[rustfmt::skip]
const INTRA_DOC_LINK_EXCEPTIONS: &[(&str, &[&str])] = &[
// This will never have links that are not in other pages.
// To avoid repeating the exceptions twice, an empty list means all broken links are allowed.
("reference/print.html", &[]),
// All the reference 'links' are actually ENBF highlighted as code
("reference/comments.html", &[
"/</code> <code>!",
"*</code> <code>!",
]),
("reference/identifiers.html", &[
"a</code>-<code>z</code> <code>A</code>-<code>Z",
"a</code>-<code>z</code> <code>A</code>-<code>Z</code> <code>0</code>-<code>9</code> <code>_",
"a</code>-<code>z</code> <code>A</code>-<code>Z</code>] [<code>a</code>-<code>z</code> <code>A</code>-<code>Z</code> <code>0</code>-<code>9</code> <code>_",
]),
("reference/tokens.html", &[
"0</code>-<code>1",
"0</code>-<code>7",
"0</code>-<code>9",
"0</code>-<code>9",
"0</code>-<code>9</code> <code>a</code>-<code>f</code> <code>A</code>-<code>F",
]),
("reference/notation.html", &[
"b</code> <code>B",
"a</code>-<code>z",
]),
// This is being used in the sense of 'inclusive range', not a markdown link
("core/ops/struct.RangeInclusive.html", &["begin</code>, <code>end"]),
("std/ops/struct.RangeInclusive.html", &["begin</code>, <code>end"]),
("core/slice/trait.SliceIndex.html", &["begin</code>, <code>end"]),
("alloc/slice/trait.SliceIndex.html", &["begin</code>, <code>end"]),
("std/slice/trait.SliceIndex.html", &["begin</code>, <code>end"]),
];
static BROKEN_INTRA_DOC_LINK: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"\[<code>(.*)</code>\]"#).unwrap());
macro_rules! t {
($e:expr) => {
match $e {
@ -138,6 +179,14 @@ fn walk(cache: &mut Cache, root: &Path, dir: &Path, errors: &mut bool) {
}
}
fn is_intra_doc_exception(file: &Path, link: &str) -> bool {
if let Some(entry) = INTRA_DOC_LINK_EXCEPTIONS.iter().find(|&(f, _)| file.ends_with(f)) {
entry.1.is_empty() || entry.1.contains(&link)
} else {
false
}
}
fn is_exception(file: &Path, link: &str) -> bool {
if let Some(entry) = LINKCHECK_EXCEPTIONS.iter().find(|&(f, _)| file.ends_with(f)) {
entry.1.contains(&link)
@ -292,6 +341,19 @@ fn check(cache: &mut Cache, root: &Path, file: &Path, errors: &mut bool) -> Opti
}
}
});
// Search for intra-doc links that rustdoc didn't warn about
// FIXME(#77199, 77200) Rustdoc should just warn about these directly.
// NOTE: only looks at one line at a time; in practice this should find most links
for (i, line) in contents.lines().enumerate() {
for broken_link in BROKEN_INTRA_DOC_LINK.captures_iter(line) {
if !is_intra_doc_exception(file, &broken_link[1]) {
*errors = true;
print!("{}:{}: broken intra-doc link - ", pretty_file.display(), i + 1);
println!("{}", &broken_link[0]);
}
}
}
Some(pretty_file)
}