Auto merge of #77856 - GuillaumeGomez:automatic-links-lint, r=jyn514,ollie27
Add non_autolinks lint Part of #77501. r? `@jyn514`
This commit is contained in:
commit
f92b931045
@ -4297,6 +4297,7 @@ dependencies = [
|
||||
"itertools 0.9.0",
|
||||
"minifier",
|
||||
"pulldown-cmark 0.8.0",
|
||||
"regex",
|
||||
"rustc-rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Handling of enum discriminants
|
||||
//!
|
||||
//! Adapted from https://github.com/rust-lang/rust/blob/d760df5aea483aae041c9a241e7acacf48f75035/src/librustc_codegen_ssa/mir/place.rs
|
||||
//! Adapted from <https://github.com/rust-lang/rust/blob/d760df5aea483aae041c9a241e7acacf48f75035/src/librustc_codegen_ssa/mir/place.rs>
|
||||
|
||||
use rustc_target::abi::{Int, TagEncoding, Variants};
|
||||
|
||||
|
@ -69,7 +69,7 @@
|
||||
use rustc_session::lint::builtin::{
|
||||
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,
|
||||
MISSING_DOC_CODE_EXAMPLES, NON_AUTOLINKS, PRIVATE_DOC_TESTS,
|
||||
};
|
||||
use rustc_span::symbol::{Ident, Symbol};
|
||||
use rustc_span::Span;
|
||||
@ -313,6 +313,7 @@ macro_rules! register_passes {
|
||||
|
||||
add_lint_group!(
|
||||
"rustdoc",
|
||||
NON_AUTOLINKS,
|
||||
BROKEN_INTRA_DOC_LINKS,
|
||||
PRIVATE_INTRA_DOC_LINKS,
|
||||
INVALID_CODEBLOCK_ATTRIBUTES,
|
||||
|
@ -1890,6 +1890,17 @@
|
||||
"detects invalid HTML tags in doc comments"
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// The `non_autolinks` 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#non_autolinks
|
||||
pub NON_AUTOLINKS,
|
||||
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].
|
||||
@ -2795,6 +2806,7 @@
|
||||
MISSING_DOC_CODE_EXAMPLES,
|
||||
INVALID_HTML_TAGS,
|
||||
PRIVATE_DOC_TESTS,
|
||||
NON_AUTOLINKS,
|
||||
WHERE_CLAUSES_OBJECT_SAFETY,
|
||||
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
|
||||
MACRO_USE_EXTERN_CRATE,
|
||||
|
@ -99,13 +99,13 @@ pub struct Lint {
|
||||
/// The name is written with underscores, e.g., "unused_imports".
|
||||
/// On the command line, underscores become dashes.
|
||||
///
|
||||
/// See https://rustc-dev-guide.rust-lang.org/diagnostics.html#lint-naming
|
||||
/// See <https://rustc-dev-guide.rust-lang.org/diagnostics.html#lint-naming>
|
||||
/// for naming guidelines.
|
||||
pub name: &'static str,
|
||||
|
||||
/// Default level for the lint.
|
||||
///
|
||||
/// See https://rustc-dev-guide.rust-lang.org/diagnostics.html#diagnostic-levels
|
||||
/// See <https://rustc-dev-guide.rust-lang.org/diagnostics.html#diagnostic-levels>
|
||||
/// for guidelines on choosing a default level.
|
||||
pub default_level: Level,
|
||||
|
||||
@ -330,8 +330,8 @@ pub fn buffer_lint_with_diagnostic(
|
||||
|
||||
/// Declares a static item of type `&'static Lint`.
|
||||
///
|
||||
/// See https://rustc-dev-guide.rust-lang.org/diagnostics.html for documentation
|
||||
/// and guidelines on writing lints.
|
||||
/// See <https://rustc-dev-guide.rust-lang.org/diagnostics.html> for
|
||||
/// documentation and guidelines on writing lints.
|
||||
///
|
||||
/// The macro call should start with a doc comment explaining the lint
|
||||
/// which will be embedded in the rustc user documentation book. It should
|
||||
|
@ -17,9 +17,9 @@ pub struct ExpressionOperandId {
|
||||
impl ExpressionOperandId {
|
||||
/// An expression operand for a "zero counter", as described in the following references:
|
||||
///
|
||||
/// * https://github.com/rust-lang/llvm-project/blob/llvmorg-8.0.0/llvm/docs/CoverageMappingFormat.rst#counter
|
||||
/// * https://github.com/rust-lang/llvm-project/blob/llvmorg-8.0.0/llvm/docs/CoverageMappingFormat.rst#tag
|
||||
/// * https://github.com/rust-lang/llvm-project/blob/llvmorg-8.0.0/llvm/docs/CoverageMappingFormat.rst#counter-expressions
|
||||
/// * <https://github.com/rust-lang/llvm-project/blob/llvmorg-8.0.0/llvm/docs/CoverageMappingFormat.rst#counter>
|
||||
/// * <https://github.com/rust-lang/llvm-project/blob/llvmorg-8.0.0/llvm/docs/CoverageMappingFormat.rst#tag>
|
||||
/// * <https://github.com/rust-lang/llvm-project/blob/llvmorg-8.0.0/llvm/docs/CoverageMappingFormat.rst#counter-expressions>
|
||||
///
|
||||
/// This operand can be used to count two or more separate code regions with a single counter,
|
||||
/// if they run sequentially with no branches, by injecting the `Counter` in a `BasicBlock` for
|
||||
|
@ -228,7 +228,7 @@ pub struct CodegenUnit<'tcx> {
|
||||
|
||||
/// Specifies the linkage type for a `MonoItem`.
|
||||
///
|
||||
/// See https://llvm.org/docs/LangRef.html#linkage-types for more details about these variants.
|
||||
/// See <https://llvm.org/docs/LangRef.html#linkage-types> for more details about these variants.
|
||||
#[derive(Copy, Clone, PartialEq, Debug, TyEncodable, TyDecodable, HashStable)]
|
||||
pub enum Linkage {
|
||||
External,
|
||||
|
@ -368,7 +368,7 @@ pub struct TypeckResults<'tcx> {
|
||||
/// leads to a `vec![&&Option<i32>, &Option<i32>]`. Empty vectors are not stored.
|
||||
///
|
||||
/// See:
|
||||
/// https://github.com/rust-lang/rfcs/blob/master/text/2005-match-ergonomics.md#definitions
|
||||
/// <https://github.com/rust-lang/rfcs/blob/master/text/2005-match-ergonomics.md#definitions>
|
||||
pat_adjustments: ItemLocalMap<Vec<Ty<'tcx>>>,
|
||||
|
||||
/// Borrows
|
||||
|
@ -1364,7 +1364,7 @@ fn check_universal_regions(
|
||||
/// terms that the "longer free region" `'a` outlived the "shorter free region" `'b`.
|
||||
///
|
||||
/// More details can be found in this blog post by Niko:
|
||||
/// http://smallcultfollowing.com/babysteps/blog/2019/01/17/polonius-and-region-errors/
|
||||
/// <http://smallcultfollowing.com/babysteps/blog/2019/01/17/polonius-and-region-errors/>
|
||||
///
|
||||
/// In the canonical example
|
||||
///
|
||||
|
@ -8,7 +8,7 @@
|
||||
//! inside a single block to shuffle a value around unnecessarily.
|
||||
//!
|
||||
//! LLVM by itself is not good enough at eliminating these redundant copies (eg. see
|
||||
//! https://github.com/rust-lang/rust/issues/32966), so this leaves some performance on the table
|
||||
//! <https://github.com/rust-lang/rust/issues/32966>), so this leaves some performance on the table
|
||||
//! that we can regain by implementing an optimization for removing these assign statements in rustc
|
||||
//! itself. When this optimization runs fast enough, it can also speed up the constant evaluation
|
||||
//! and code generation phases of rustc due to the reduced number of statements and locals.
|
||||
|
@ -8,7 +8,7 @@
|
||||
//! (b) each pattern is necessary (usefulness)
|
||||
//!
|
||||
//! The algorithm implemented here is a modified version of the one described in:
|
||||
//! http://moscova.inria.fr/~maranget/papers/warn/index.html
|
||||
//! <http://moscova.inria.fr/~maranget/papers/warn/index.html>
|
||||
//! However, to save future implementors from reading the original paper, we
|
||||
//! summarise the algorithm here to hopefully save time and be a little clearer
|
||||
//! (without being so rigorous).
|
||||
@ -2040,7 +2040,7 @@ fn report_patterns<'p>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Algorithm from http://moscova.inria.fr/~maranget/papers/warn/index.html.
|
||||
/// Algorithm from <http://moscova.inria.fr/~maranget/papers/warn/index.html>.
|
||||
/// The algorithm from the paper has been modified to correctly handle empty
|
||||
/// types. The changes are:
|
||||
/// (0) We don't exit early if the pattern matrix has zero rows. We just
|
||||
|
@ -511,7 +511,7 @@ fn report_unterminated_raw_string(
|
||||
}
|
||||
|
||||
/// Note: It was decided to not add a test case, because it would be to big.
|
||||
/// https://github.com/rust-lang/rust/pull/50296#issuecomment-392135180
|
||||
/// <https://github.com/rust-lang/rust/pull/50296#issuecomment-392135180>
|
||||
fn report_too_many_hashes(&self, start: BytePos, found: usize) -> ! {
|
||||
self.fatal_span_(
|
||||
start,
|
||||
|
@ -38,7 +38,7 @@
|
||||
/// Implemented to visit all `DefId`s in a type.
|
||||
/// Visiting `DefId`s is useful because visibilities and reachabilities are attached to them.
|
||||
/// The idea is to visit "all components of a type", as documented in
|
||||
/// https://github.com/rust-lang/rfcs/blob/master/text/2145-type-privacy.md#how-to-determine-visibility-of-a-type.
|
||||
/// <https://github.com/rust-lang/rfcs/blob/master/text/2145-type-privacy.md#how-to-determine-visibility-of-a-type>.
|
||||
/// The default type visitor (`TypeVisitor`) does most of the job, but it has some shortcomings.
|
||||
/// First, it doesn't have overridable `fn visit_trait_ref`, so we have to catch trait `DefId`s
|
||||
/// manually. Second, it doesn't visit some type components like signatures of fn types, or traits
|
||||
|
@ -3,7 +3,7 @@
|
||||
//!
|
||||
//! Table of CRT objects for popular toolchains.
|
||||
//! The `crtx` ones are generally distributed with libc and the `begin/end` ones with gcc.
|
||||
//! See https://dev.gentoo.org/~vapier/crt.txt for some more details.
|
||||
//! See <https://dev.gentoo.org/~vapier/crt.txt> for some more details.
|
||||
//!
|
||||
//! | Pre-link CRT objects | glibc | musl | bionic | mingw | wasi |
|
||||
//! |----------------------|------------------------|------------------------|------------------|-------------------|------|
|
||||
|
@ -950,7 +950,7 @@ pub struct TargetOptions {
|
||||
/// The MergeFunctions pass is generally useful, but some targets may need
|
||||
/// to opt out. The default is "aliases".
|
||||
///
|
||||
/// Workaround for: https://github.com/rust-lang/rust/issues/57356
|
||||
/// Workaround for: <https://github.com/rust-lang/rust/issues/57356>
|
||||
pub merge_functions: MergeFunctions,
|
||||
|
||||
/// Use platform dependent mcount function
|
||||
|
@ -8,7 +8,7 @@
|
||||
//! (e.g. trying to create a TCP stream or something like that).
|
||||
//!
|
||||
//! This target is more or less managed by the Rust and WebAssembly Working
|
||||
//! Group nowadays at https://github.com/rustwasm.
|
||||
//! Group nowadays at <https://github.com/rustwasm>.
|
||||
|
||||
use super::wasm32_base;
|
||||
use super::{LinkerFlavor, LldFlavor, Target};
|
||||
|
@ -7,7 +7,7 @@
|
||||
//! intended to empower WebAssembly binaries with native capabilities such as
|
||||
//! filesystem access, network access, etc.
|
||||
//!
|
||||
//! You can see more about the proposal at https://wasi.dev
|
||||
//! You can see more about the proposal at <https://wasi.dev>.
|
||||
//!
|
||||
//! The Rust target definition here is interesting in a few ways. We want to
|
||||
//! serve two use cases here with this target:
|
||||
|
@ -621,7 +621,7 @@ fn object_ty_for_trait<'tcx>(
|
||||
///
|
||||
/// In practice, we cannot use `dyn Trait` explicitly in the obligation because it would result
|
||||
/// in a new check that `Trait` is object safe, creating a cycle (until object_safe_for_dispatch
|
||||
/// is stabilized, see tracking issue https://github.com/rust-lang/rust/issues/43561).
|
||||
/// is stabilized, see tracking issue <https://github.com/rust-lang/rust/issues/43561>).
|
||||
/// Instead, we fudge a little by introducing a new type parameter `U` such that
|
||||
/// `Self: Unsize<U>` and `U: Trait + ?Sized`, and use `U` in place of `dyn Trait`.
|
||||
/// Written as a chalk-style query:
|
||||
|
@ -32,7 +32,7 @@
|
||||
/// This type is needed because:
|
||||
///
|
||||
/// a) Generators cannot implement `for<'a, 'b> Generator<&'a mut Context<'b>>`, so we need to pass
|
||||
/// a raw pointer (see https://github.com/rust-lang/rust/issues/68923).
|
||||
/// a raw pointer (see <https://github.com/rust-lang/rust/issues/68923>).
|
||||
/// b) Raw pointers and `NonNull` aren't `Send` or `Sync`, so that would make every single future
|
||||
/// non-Send/Sync as well, and we don't want that.
|
||||
///
|
||||
|
@ -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.
|
||||
//!
|
||||
|
@ -287,6 +287,7 @@
|
||||
unused_imports,
|
||||
unsafe_op_in_unsafe_fn
|
||||
)]
|
||||
#[cfg_attr(not(bootstrap), allow(non_autolinks))]
|
||||
// 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)]
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -1,9 +1,9 @@
|
||||
//! Parsing of GCC-style Language-Specific Data Area (LSDA)
|
||||
//! For details see:
|
||||
//! http://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/ehframechpt.html
|
||||
//! http://mentorembedded.github.io/cxx-abi/exceptions.pdf
|
||||
//! http://www.airs.com/blog/archives/460
|
||||
//! http://www.airs.com/blog/archives/464
|
||||
//! * <http://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/ehframechpt.html>
|
||||
//! * <http://mentorembedded.github.io/cxx-abi/exceptions.pdf>
|
||||
//! * <http://www.airs.com/blog/archives/460>
|
||||
//! * <http://www.airs.com/blog/archives/464>
|
||||
//!
|
||||
//! A reference implementation may be found in the GCC source tree
|
||||
//! (`<root>/libgcc/unwind-c.c` as of this writing).
|
||||
|
@ -4,9 +4,9 @@
|
||||
//! "Exception Handling in LLVM" (llvm.org/docs/ExceptionHandling.html) and
|
||||
//! documents linked from it.
|
||||
//! These are also good reads:
|
||||
//! https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html
|
||||
//! http://monoinfinito.wordpress.com/series/exception-handling-in-c/
|
||||
//! http://www.airs.com/blog/index.php?s=exception+frames
|
||||
//! * <https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html>
|
||||
//! * <http://monoinfinito.wordpress.com/series/exception-handling-in-c/>
|
||||
//! * <http://www.airs.com/blog/index.php?s=exception+frames>
|
||||
//!
|
||||
//! ## A brief summary
|
||||
//!
|
||||
|
@ -152,7 +152,7 @@ impl Step for ToolStateCheck {
|
||||
/// error if there are any.
|
||||
///
|
||||
/// This also handles publishing the results to the `history` directory of
|
||||
/// the toolstate repo https://github.com/rust-lang-nursery/rust-toolstate
|
||||
/// the toolstate repo <https://github.com/rust-lang-nursery/rust-toolstate>
|
||||
/// if the env var `TOOLSTATE_PUBLISH` is set. Note that there is a
|
||||
/// *separate* step of updating the `latest.json` file and creating GitHub
|
||||
/// issues and comments in `src/ci/publish_toolstate.sh`, which is only
|
||||
@ -162,7 +162,7 @@ impl Step for ToolStateCheck {
|
||||
/// The rules for failure are:
|
||||
/// * If the PR modifies a tool, the status must be test-pass.
|
||||
/// NOTE: There is intent to change this, see
|
||||
/// https://github.com/rust-lang/rust/issues/65000.
|
||||
/// <https://github.com/rust-lang/rust/issues/65000>.
|
||||
/// * All "stable" tools must be test-pass on the stable or beta branches.
|
||||
/// * During beta promotion week, a PR is not allowed to "regress" a
|
||||
/// stable tool. That is, the status is not allowed to get worse
|
||||
|
@ -32,7 +32,7 @@ macro_rules! t {
|
||||
|
||||
/// Reads an environment variable and adds it to dependencies.
|
||||
/// Supposed to be used for all variables except those set for build scripts by cargo
|
||||
/// https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
|
||||
/// <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts>
|
||||
pub fn tracked_env_var_os<K: AsRef<OsStr> + Display>(key: K) -> Option<OsString> {
|
||||
println!("cargo:rerun-if-env-changed={}", key);
|
||||
env::var_os(key)
|
||||
|
@ -285,3 +285,41 @@ warning: unclosed HTML tag `h1`
|
||||
|
||||
warning: 2 warnings emitted
|
||||
```
|
||||
|
||||
## non_autolinks
|
||||
|
||||
This lint is **nightly-only** and **warns by default**. It detects links which
|
||||
could use the "automatic" link syntax. For example:
|
||||
|
||||
```rust
|
||||
/// http://example.org
|
||||
/// [http://example.com](http://example.com)
|
||||
/// [http://example.net]
|
||||
///
|
||||
/// [http://example.com]: http://example.com
|
||||
pub fn foo() {}
|
||||
```
|
||||
|
||||
Which will give:
|
||||
|
||||
```text
|
||||
warning: this URL is not a hyperlink
|
||||
--> foo.rs:1:5
|
||||
|
|
||||
1 | /// http://example.org
|
||||
| ^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.org>`
|
||||
|
|
||||
= note: `#[warn(non_autolinks)]` on by default
|
||||
|
||||
warning: unneeded long form for URL
|
||||
--> foo.rs:2:5
|
||||
|
|
||||
2 | /// [http://example.com](http://example.com)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.com>`
|
||||
|
||||
warning: this URL is not a hyperlink
|
||||
--> foo.rs:3:6
|
||||
|
|
||||
3 | /// [http://example.net]
|
||||
| ^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://example.net>`
|
||||
```
|
||||
|
@ -16,6 +16,7 @@ serde_json = "1.0"
|
||||
smallvec = "1.0"
|
||||
tempfile = "3"
|
||||
itertools = "0.9"
|
||||
regex = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
expect-test = "1.0"
|
||||
|
@ -330,6 +330,7 @@ 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 non_autolinks = rustc_lint::builtin::NON_AUTOLINKS.name;
|
||||
let unknown_lints = rustc_lint::builtin::UNKNOWN_LINTS.name;
|
||||
|
||||
// In addition to those specific lints, we also need to allow those given through
|
||||
@ -344,6 +345,7 @@ pub fn run_core(
|
||||
invalid_html_tags.to_owned(),
|
||||
renamed_and_removed_lints.to_owned(),
|
||||
unknown_lints.to_owned(),
|
||||
non_autolinks.to_owned(),
|
||||
];
|
||||
|
||||
let (lint_opts, lint_caps) = init_lints(lints_to_show, lint_opts, |lint| {
|
||||
@ -663,7 +665,7 @@ fn report_deprecated_attr(name: &str, diag: &rustc_errors::Handler) {
|
||||
(krate, ctxt.renderinfo.into_inner(), ctxt.render_options)
|
||||
}
|
||||
|
||||
/// Due to https://github.com/rust-lang/rust/pull/73566,
|
||||
/// Due to <https://github.com/rust-lang/rust/pull/73566>,
|
||||
/// the name resolution pass may find errors that are never emitted.
|
||||
/// If typeck is called after this happens, then we'll get an ICE:
|
||||
/// 'Res::Error found but not reported'. To avoid this, emit the errors now.
|
||||
|
@ -11,6 +11,9 @@
|
||||
mod stripper;
|
||||
pub use stripper::*;
|
||||
|
||||
mod non_autolinks;
|
||||
pub use self::non_autolinks::CHECK_NON_AUTOLINKS;
|
||||
|
||||
mod collapse_docs;
|
||||
pub use self::collapse_docs::COLLAPSE_DOCS;
|
||||
|
||||
@ -90,6 +93,7 @@ pub enum Condition {
|
||||
COLLECT_TRAIT_IMPLS,
|
||||
CALCULATE_DOC_COVERAGE,
|
||||
CHECK_INVALID_HTML_TAGS,
|
||||
CHECK_NON_AUTOLINKS,
|
||||
];
|
||||
|
||||
/// The list of passes run by default.
|
||||
@ -105,6 +109,7 @@ pub enum Condition {
|
||||
ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX),
|
||||
ConditionalPass::always(CHECK_INVALID_HTML_TAGS),
|
||||
ConditionalPass::always(PROPAGATE_DOC_CFG),
|
||||
ConditionalPass::always(CHECK_NON_AUTOLINKS),
|
||||
];
|
||||
|
||||
/// The list of default passes run when `--doc-coverage` is passed to rustdoc.
|
||||
|
139
src/librustdoc/passes/non_autolinks.rs
Normal file
139
src/librustdoc/passes/non_autolinks.rs
Normal file
@ -0,0 +1,139 @@
|
||||
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_NON_AUTOLINKS: Pass = Pass {
|
||||
name: "check-non-autolinks",
|
||||
run: check_non_autolinks,
|
||||
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,63}", // root domain
|
||||
r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)" // optional query or url fragments
|
||||
);
|
||||
|
||||
struct NonAutolinksLinter<'a, 'tcx> {
|
||||
cx: &'a DocContext<'tcx>,
|
||||
regex: Regex,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> NonAutolinksLinter<'a, 'tcx> {
|
||||
fn new(cx: &'a DocContext<'tcx>) -> Self {
|
||||
Self { 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_non_autolinks(krate: Crate, cx: &DocContext<'_>) -> Crate {
|
||||
if !UnstableFeatures::from_environment().is_nightly_build() {
|
||||
krate
|
||||
} else {
|
||||
let mut coll = NonAutolinksLinter::new(cx);
|
||||
|
||||
coll.fold_crate(krate)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DocFolder for NonAutolinksLinter<'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::NON_AUTOLINKS, hir_id, sp, |lint| {
|
||||
lint.build(msg)
|
||||
.span_suggestion(
|
||||
sp,
|
||||
"use an automatic link instead",
|
||||
format!("<{}>", url),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
.emit()
|
||||
});
|
||||
};
|
||||
|
||||
let mut p = Parser::new_ext(&dox, opts()).into_offset_iter();
|
||||
|
||||
while let Some((event, range)) = p.next() {
|
||||
match event {
|
||||
Event::Start(Tag::Link(kind, _, _)) => {
|
||||
let ignore = matches!(kind, LinkType::Autolink | LinkType::Email);
|
||||
let mut title = String::new();
|
||||
|
||||
while let Some((event, range)) = p.next() {
|
||||
match event {
|
||||
Event::End(Tag::Link(_, url, _)) => {
|
||||
// NOTE: links cannot be nested, so we don't need to
|
||||
// check `kind`
|
||||
if url.as_ref() == title && !ignore && self.regex.is_match(&url)
|
||||
{
|
||||
report_diag(
|
||||
self.cx,
|
||||
"unneeded long form for URL",
|
||||
&url,
|
||||
range,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Event::Text(s) if !ignore => title.push_str(&s),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Text(s) => self.find_raw_urls(&s, range, &report_diag),
|
||||
Event::Start(Tag::CodeBlock(_)) => {
|
||||
// We don't want to check the text inside the code blocks.
|
||||
while let Some((event, _)) = p.next() {
|
||||
match event {
|
||||
Event::End(Tag::CodeBlock(_)) => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.fold_item_recur(item)
|
||||
}
|
||||
}
|
66
src/test/rustdoc-ui/url-improvements.rs
Normal file
66
src/test/rustdoc-ui/url-improvements.rs
Normal file
@ -0,0 +1,66 @@
|
||||
#![deny(non_autolinks)]
|
||||
|
||||
/// [http://aa.com](http://aa.com)
|
||||
//~^ ERROR unneeded long form for URL
|
||||
/// [http://bb.com]
|
||||
//~^ ERROR unneeded long form for URL
|
||||
///
|
||||
/// [http://bb.com]: http://bb.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
|
||||
///
|
||||
/// ```
|
||||
/// This link should not be linted: http://example.com
|
||||
/// ```
|
||||
///
|
||||
/// [should_not.lint](should_not.lint)
|
||||
pub fn everything_is_fine_here() {}
|
||||
|
||||
#[allow(non_autolinks)]
|
||||
pub mod foo {
|
||||
/// https://somewhere.com/a?hello=12&bye=11#xyz
|
||||
pub fn bar() {}
|
||||
}
|
122
src/test/rustdoc-ui/url-improvements.stderr
Normal file
122
src/test/rustdoc-ui/url-improvements.stderr
Normal file
@ -0,0 +1,122 @@
|
||||
error: unneeded long form for URL
|
||||
--> $DIR/url-improvements.rs:3:5
|
||||
|
|
||||
LL | /// [http://aa.com](http://aa.com)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://aa.com>`
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> $DIR/url-improvements.rs:1:9
|
||||
|
|
||||
LL | #![deny(non_autolinks)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: unneeded long form for URL
|
||||
--> $DIR/url-improvements.rs:5:5
|
||||
|
|
||||
LL | /// [http://bb.com]
|
||||
| ^^^^^^^^^^^^^^^ help: use an automatic link instead: `<http://bb.com>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.rs:13:5
|
||||
|
|
||||
LL | /// https://somewhere.com
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.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/url-improvements.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/url-improvements.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/url-improvements.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/url-improvements.rs:23:5
|
||||
|
|
||||
LL | /// https://somewhere.com?
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://somewhere.com?>`
|
||||
|
||||
error: this URL is not a hyperlink
|
||||
--> $DIR/url-improvements.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/url-improvements.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/url-improvements.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/url-improvements.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/url-improvements.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/url-improvements.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/url-improvements.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/url-improvements.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/url-improvements.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/url-improvements.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/url-improvements.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
|
||||
|
Loading…
Reference in New Issue
Block a user