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
Show file tree
Hide file tree
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
Expand Up @@ -4233,6 +4233,7 @@ dependencies = [
"itertools 0.9.0",
"minifier",
"pulldown-cmark 0.8.0",
"regex",
"rustc-rayon",
"serde",
"serde_json",
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 12 additions & 0 deletions compiler/rustc_session/src/lint/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//!
Expand Down
1 change: 1 addition & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/num/dec2flt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
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.
Expand Down
37 changes: 37 additions & 0 deletions src/doc/rustdoc/src/lints.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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
Expand Up @@ -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};
Expand Down Expand Up @@ -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());
Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
4 changes: 0 additions & 4 deletions src/librustdoc/html/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2716,10 +2716,6 @@ function defocusSearchBar() {
};
}

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

if (main) {
onEachLazy(main.getElementsByClassName("loading-content"), function(e) {
e.remove();
Expand Down
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
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down
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() {}
}
Loading