Skip to content

Rollup of 3 pull requests #78020

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
@@ -4233,6 +4233,7 @@ dependencies = [
"itertools 0.9.0",
"minifier",
"pulldown-cmark 0.8.0",
"regex",
"rustc-rayon",
"serde",
"serde_json",
3 changes: 2 additions & 1 deletion compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
@@ -63,7 +63,7 @@ use rustc_hir::def_id::LocalDefId;
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::TyCtxt;
use rustc_session::lint::builtin::{
BARE_TRAIT_OBJECTS, BROKEN_INTRA_DOC_LINKS, ELIDED_LIFETIMES_IN_PATHS,
AUTOMATIC_LINKS, BARE_TRAIT_OBJECTS, BROKEN_INTRA_DOC_LINKS, ELIDED_LIFETIMES_IN_PATHS,
EXPLICIT_OUTLIVES_REQUIREMENTS, INVALID_CODEBLOCK_ATTRIBUTES, INVALID_HTML_TAGS,
MISSING_DOC_CODE_EXAMPLES, PRIVATE_DOC_TESTS,
};
@@ -307,6 +307,7 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {

add_lint_group!(
"rustdoc",
AUTOMATIC_LINKS,
BROKEN_INTRA_DOC_LINKS,
PRIVATE_INTRA_DOC_LINKS,
INVALID_CODEBLOCK_ATTRIBUTES,
12 changes: 12 additions & 0 deletions compiler/rustc_session/src/lint/builtin.rs
Original file line number Diff line number Diff line change
@@ -1891,6 +1891,17 @@ declare_lint! {
"detects invalid HTML tags in doc comments"
}

declare_lint! {
/// The `automatic_links` lint detects when a URL could be written using
/// only angle brackets. This is a `rustdoc` only lint, see the
/// documentation in the [rustdoc book].
///
/// [rustdoc book]: ../../../rustdoc/lints.html#automatic_links
pub AUTOMATIC_LINKS,
Warn,
"detects URLs that could be written using only angle brackets"
}

declare_lint! {
/// The `where_clauses_object_safety` lint detects for [object safety] of
/// [where clauses].
@@ -2711,6 +2722,7 @@ declare_lint_pass! {
MISSING_DOC_CODE_EXAMPLES,
INVALID_HTML_TAGS,
PRIVATE_DOC_TESTS,
AUTOMATIC_LINKS,
WHERE_CLAUSES_OBJECT_SAFETY,
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
MACRO_USE_EXTERN_CRATE,
2 changes: 1 addition & 1 deletion library/core/src/intrinsics.rs
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
//! This includes changes in the stability of the constness.
//!
//! In order to make an intrinsic usable at compile-time, one needs to copy the implementation
//! from https://github.com/rust-lang/miri/blob/master/src/shims/intrinsics.rs to
//! from <https://github.com/rust-lang/miri/blob/master/src/shims/intrinsics.rs> to
//! `compiler/rustc_mir/src/interpret/intrinsics.rs` and add a
//! `#[rustc_const_unstable(feature = "foo", issue = "01234")]` to the intrinsic.
//!
1 change: 1 addition & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -284,6 +284,7 @@ pub mod primitive;
unused_imports,
unsafe_op_in_unsafe_fn
)]
#[cfg_attr(not(bootstrap), allow(automatic_links))]
// FIXME: This annotation should be moved into rust-lang/stdarch after clashing_extern_declarations is
// merged. It currently cannot because bootstrap fails as the lint hasn't been defined yet.
#[allow(clashing_extern_declarations)]
2 changes: 1 addition & 1 deletion library/core/src/num/dec2flt/mod.rs
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@
//!
//! Primarily, this module and its children implement the algorithms described in:
//! "How to Read Floating Point Numbers Accurately" by William D. Clinger,
//! available online: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.45.4152
//! available online: <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.45.4152>
//!
//! In addition, there are numerous helper functions that are used in the paper but not available
//! in Rust (or at least in core). Our version is additionally complicated by the need to handle
2 changes: 1 addition & 1 deletion library/core/src/slice/sort.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Slice sorting
//!
//! This module contains a sorting algorithm based on Orson Peters' pattern-defeating quicksort,
//! published at: https://github.com/orlp/pdqsort
//! published at: <https://github.com/orlp/pdqsort>
//!
//! Unstable sorting is compatible with libcore because it doesn't allocate memory, unlike our
//! stable sorting implementation.
37 changes: 37 additions & 0 deletions src/doc/rustdoc/src/lints.md
Original file line number Diff line number Diff line change
@@ -285,3 +285,40 @@ warning: unclosed HTML tag `h1`

warning: 2 warnings emitted
```

## automatic_links

This lint is **nightly-only** and **warns by default**. It detects links which
could use the "automatic" link syntax. For example:

```rust
/// http://hello.rs
/// [http://a.com](http://a.com)
/// [http://b.com]
///
/// [http://b.com]: http://b.com
pub fn foo() {}
```

Which will give:

```text
warning: this URL is not a hyperlink
--> foo.rs:3:5
|
3 | /// http://hello.rs
| ^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://hello.rs>`
|

warning: unneeded long form for URL
--> foo.rs:4:5
|
4 | /// [http://a.com](http://a.com)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://a.com>`

warning: unneeded long form for URL
--> foo.rs:5:5
|
5 | /// [http://b.com]
| ^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://b.com>`
```
1 change: 1 addition & 0 deletions src/librustdoc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ serde_json = "1.0"
smallvec = "1.0"
tempfile = "3"
itertools = "0.9"
regex = "1"

[dev-dependencies]
expect-test = "1.0"
11 changes: 8 additions & 3 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ use rustc_ast::{self as ast, AttrStyle};
use rustc_ast::{FloatTy, IntTy, UintTy};
use rustc_attr::{Stability, StabilityLevel};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_feature::UnstableFeatures;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE};
@@ -679,9 +680,13 @@ impl Attributes {
"../".repeat(depth)
}
Some(&(_, _, ExternalLocation::Remote(ref s))) => s.to_string(),
Some(&(_, _, ExternalLocation::Unknown)) | None => {
String::from("https://doc.rust-lang.org/nightly")
}
Some(&(_, _, ExternalLocation::Unknown)) | None => String::from(
if UnstableFeatures::from_environment().is_nightly_build() {
"https://doc.rust-lang.org/nightly"
} else {
"https://doc.rust-lang.org"
},
),
};
// This is a primitive so the url is done "by hand".
let tail = fragment.find('#').unwrap_or_else(|| fragment.len());
2 changes: 2 additions & 0 deletions src/librustdoc/core.rs
Original file line number Diff line number Diff line change
@@ -330,11 +330,13 @@ pub fn run_core(
let invalid_codeblock_attributes_name = rustc_lint::builtin::INVALID_CODEBLOCK_ATTRIBUTES.name;
let invalid_html_tags = rustc_lint::builtin::INVALID_HTML_TAGS.name;
let renamed_and_removed_lints = rustc_lint::builtin::RENAMED_AND_REMOVED_LINTS.name;
let automatic_links = rustc_lint::builtin::AUTOMATIC_LINKS.name;
let unknown_lints = rustc_lint::builtin::UNKNOWN_LINTS.name;

// In addition to those specific lints, we also need to allow those given through
// command line, otherwise they'll get ignored and we don't want that.
let lints_to_show = vec![
automatic_links.to_owned(),
intra_link_resolution_failure_name.to_owned(),
missing_docs.to_owned(),
missing_doc_example.to_owned(),
4 changes: 0 additions & 4 deletions src/librustdoc/html/static/main.js
Original file line number Diff line number Diff line change
@@ -2716,10 +2716,6 @@ function defocusSearchBar() {
};
}

window.onresize = function() {
hideSidebar();
};

if (main) {
onEachLazy(main.getElementsByClassName("loading-content"), function(e) {
e.remove();
127 changes: 127 additions & 0 deletions src/librustdoc/passes/automatic_links.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use super::{span_of_attrs, Pass};
use crate::clean::*;
use crate::core::DocContext;
use crate::fold::DocFolder;
use crate::html::markdown::opts;
use core::ops::Range;
use pulldown_cmark::{Event, LinkType, Parser, Tag};
use regex::Regex;
use rustc_errors::Applicability;
use rustc_feature::UnstableFeatures;
use rustc_session::lint;

pub const CHECK_AUTOMATIC_LINKS: Pass = Pass {
name: "check-automatic-links",
run: check_automatic_links,
description: "detects URLS that could be written using angle brackets",
};

const URL_REGEX: &str = concat!(
r"https?://", // url scheme
r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", // one or more subdomains
r"[a-zA-Z]{2,4}", // root domain
r"\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)" // optional query or url fragments
);

struct AutomaticLinksLinter<'a, 'tcx> {
cx: &'a DocContext<'tcx>,
regex: Regex,
}

impl<'a, 'tcx> AutomaticLinksLinter<'a, 'tcx> {
fn new(cx: &'a DocContext<'tcx>) -> Self {
AutomaticLinksLinter { cx, regex: Regex::new(URL_REGEX).expect("failed to build regex") }
}

fn find_raw_urls(
&self,
text: &str,
range: Range<usize>,
f: &impl Fn(&DocContext<'_>, &str, &str, Range<usize>),
) {
// For now, we only check "full" URLs (meaning, starting with "http://" or "https://").
for match_ in self.regex.find_iter(&text) {
let url = match_.as_str();
let url_range = match_.range();
f(
self.cx,
"this URL is not a hyperlink",
url,
Range { start: range.start + url_range.start, end: range.start + url_range.end },
);
}
}
}

pub fn check_automatic_links(krate: Crate, cx: &DocContext<'_>) -> Crate {
if !UnstableFeatures::from_environment().is_nightly_build() {
krate
} else {
let mut coll = AutomaticLinksLinter::new(cx);

coll.fold_crate(krate)
}
}

impl<'a, 'tcx> DocFolder for AutomaticLinksLinter<'a, 'tcx> {
fn fold_item(&mut self, item: Item) -> Option<Item> {
let hir_id = match self.cx.as_local_hir_id(item.def_id) {
Some(hir_id) => hir_id,
None => {
// If non-local, no need to check anything.
return self.fold_item_recur(item);
}
};
let dox = item.attrs.collapsed_doc_value().unwrap_or_default();
if !dox.is_empty() {
let report_diag = |cx: &DocContext<'_>, msg: &str, url: &str, range: Range<usize>| {
let sp = super::source_span_for_markdown_range(cx, &dox, &range, &item.attrs)
.or_else(|| span_of_attrs(&item.attrs))
.unwrap_or(item.source.span());
cx.tcx.struct_span_lint_hir(lint::builtin::AUTOMATIC_LINKS, hir_id, sp, |lint| {
lint.build(msg)
.span_suggestion(
sp,
"use an automatic link instead",
format!("<{}>", url),
Applicability::MachineApplicable,
)
.emit()
});
};

let p = Parser::new_ext(&dox, opts()).into_offset_iter();

let mut title = String::new();
let mut in_link = false;
let mut ignore = false;

for (event, range) in p {
match event {
Event::Start(Tag::Link(kind, _, _)) => {
in_link = true;
ignore = matches!(kind, LinkType::Autolink | LinkType::Email);
}
Event::End(Tag::Link(_, url, _)) => {
in_link = false;
// NOTE: links cannot be nested, so we don't need to check `kind`
if url.as_ref() == title && !ignore {
report_diag(self.cx, "unneeded long form for URL", &url, range);
}
title.clear();
ignore = false;
}
Event::Text(s) if in_link => {
if !ignore {
title.push_str(&s);
}
}
Event::Text(s) => self.find_raw_urls(&s, range, &report_diag),
_ => {}
}
}
}

self.fold_item_recur(item)
}
}
5 changes: 5 additions & 0 deletions src/librustdoc/passes/mod.rs
Original file line number Diff line number Diff line change
@@ -11,6 +11,9 @@ use crate::core::DocContext;
mod stripper;
pub use stripper::*;

mod automatic_links;
pub use self::automatic_links::CHECK_AUTOMATIC_LINKS;

mod collapse_docs;
pub use self::collapse_docs::COLLAPSE_DOCS;

@@ -90,6 +93,7 @@ pub const PASSES: &[Pass] = &[
COLLECT_TRAIT_IMPLS,
CALCULATE_DOC_COVERAGE,
CHECK_INVALID_HTML_TAGS,
CHECK_AUTOMATIC_LINKS,
];

/// The list of passes run by default.
@@ -105,6 +109,7 @@ pub const DEFAULT_PASSES: &[ConditionalPass] = &[
ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX),
ConditionalPass::always(CHECK_INVALID_HTML_TAGS),
ConditionalPass::always(PROPAGATE_DOC_CFG),
ConditionalPass::always(CHECK_AUTOMATIC_LINKS),
];

/// The list of default passes run when `--doc-coverage` is passed to rustdoc.
60 changes: 60 additions & 0 deletions src/test/rustdoc-ui/automatic-links.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#![deny(automatic_links)]

/// [http://a.com](http://a.com)
//~^ ERROR unneeded long form for URL
/// [http://b.com]
//~^ ERROR unneeded long form for URL
///
/// [http://b.com]: http://b.com
///
/// [http://c.com][http://c.com]
pub fn a() {}

/// https://somewhere.com
//~^ ERROR this URL is not a hyperlink
/// https://somewhere.com/a
//~^ ERROR this URL is not a hyperlink
/// https://www.somewhere.com
//~^ ERROR this URL is not a hyperlink
/// https://www.somewhere.com/a
//~^ ERROR this URL is not a hyperlink
/// https://subdomain.example.com
//~^ ERROR not a hyperlink
/// https://somewhere.com?
//~^ ERROR this URL is not a hyperlink
/// https://somewhere.com/a?
//~^ ERROR this URL is not a hyperlink
/// https://somewhere.com?hello=12
//~^ ERROR this URL is not a hyperlink
/// https://somewhere.com/a?hello=12
//~^ ERROR this URL is not a hyperlink
/// https://example.com?hello=12#xyz
//~^ ERROR this URL is not a hyperlink
/// https://example.com/a?hello=12#xyz
//~^ ERROR this URL is not a hyperlink
/// https://example.com#xyz
//~^ ERROR this URL is not a hyperlink
/// https://example.com/a#xyz
//~^ ERROR this URL is not a hyperlink
/// https://somewhere.com?hello=12&bye=11
//~^ ERROR this URL is not a hyperlink
/// https://somewhere.com/a?hello=12&bye=11
//~^ ERROR this URL is not a hyperlink
/// https://somewhere.com?hello=12&bye=11#xyz
//~^ ERROR this URL is not a hyperlink
/// hey! https://somewhere.com/a?hello=12&bye=11#xyz
//~^ ERROR this URL is not a hyperlink
pub fn c() {}

/// <https://somewhere.com>
/// [a](http://a.com)
/// [b]
///
/// [b]: http://b.com
pub fn everything_is_fine_here() {}

#[allow(automatic_links)]
pub mod foo {
/// https://somewhere.com/a?hello=12&bye=11#xyz
pub fn bar() {}
}
122 changes: 122 additions & 0 deletions src/test/rustdoc-ui/automatic-links.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
error: unneeded long form for URL
--> $DIR/automatic-links.rs:3:5
|
LL | /// [http://a.com](http://a.com)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://a.com>`
|
note: the lint level is defined here
--> $DIR/automatic-links.rs:1:9
|
LL | #![deny(automatic_links)]
| ^^^^^^^^^^^^^^^

error: unneeded long form for URL
--> $DIR/automatic-links.rs:5:5
|
LL | /// [http://b.com]
| ^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://b.com>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:13:5
|
LL | /// https://somewhere.com
| ^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:15:5
|
LL | /// https://somewhere.com/a
| ^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:17:5
|
LL | /// https://www.somewhere.com
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://www.somewhere.com>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:19:5
|
LL | /// https://www.somewhere.com/a
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://www.somewhere.com/a>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:21:5
|
LL | /// https://subdomain.example.com
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://subdomain.example.com>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:23:5
|
LL | /// https://somewhere.com?
| ^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:25:5
|
LL | /// https://somewhere.com/a?
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a?>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:27:5
|
LL | /// https://somewhere.com?hello=12
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?hello=12>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:29:5
|
LL | /// https://somewhere.com/a?hello=12
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a?hello=12>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:31:5
|
LL | /// https://example.com?hello=12#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com?hello=12#xyz>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:33:5
|
LL | /// https://example.com/a?hello=12#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com/a?hello=12#xyz>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:35:5
|
LL | /// https://example.com#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com#xyz>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:37:5
|
LL | /// https://example.com/a#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://example.com/a#xyz>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:39:5
|
LL | /// https://somewhere.com?hello=12&bye=11
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?hello=12&bye=11>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:41:5
|
LL | /// https://somewhere.com/a?hello=12&bye=11
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a?hello=12&bye=11>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:43:5
|
LL | /// https://somewhere.com?hello=12&bye=11#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?hello=12&bye=11#xyz>`

error: this URL is not a hyperlink
--> $DIR/automatic-links.rs:45:10
|
LL | /// hey! https://somewhere.com/a?hello=12&bye=11#xyz
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com/a?hello=12&bye=11#xyz>`

error: aborting due to 19 previous errors