about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs12
-rw-r--r--src/librustdoc/core.rs2
-rw-r--r--src/librustdoc/passes/automatic_links.rs93
-rw-r--r--src/librustdoc/passes/mod.rs5
5 files changed, 114 insertions, 1 deletions
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 7297a6de420..0da3ece167e 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -67,7 +67,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,
 };
@@ -313,6 +313,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,
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index a1b7c13e4c0..3fc9c096696 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -1891,6 +1891,17 @@ declare_lint! {
 }
 
 declare_lint! {
+    /// The `automatic_links` lint detects when a URL/email address could be
+    /// written using only brackets. This is a `rustdoc` only lint, see the
+    /// documentation in the [rustdoc book].
+    ///
+    /// [rustdoc book]: ../../../rustdoc/lints.html#automatic_links
+    pub AUTOMATIC_LINKS,
+    Allow,
+    "detects URLs/email adresses that could be written using only brackets"
+}
+
+declare_lint! {
     /// The `where_clauses_object_safety` lint detects for [object safety] of
     /// [where clauses].
     ///
@@ -2795,6 +2806,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,
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 45a84c4fb30..f834be84d4c 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -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(),
diff --git a/src/librustdoc/passes/automatic_links.rs b/src/librustdoc/passes/automatic_links.rs
new file mode 100644
index 00000000000..79542241326
--- /dev/null
+++ b/src/librustdoc/passes/automatic_links.rs
@@ -0,0 +1,93 @@
+use super::{span_of_attrs, Pass};
+use crate::clean::*;
+use crate::core::DocContext;
+use crate::fold::DocFolder;
+use crate::html::markdown::opts;
+use pulldown_cmark::{Event, Parser, Tag};
+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/email addresses that could be written using brackets",
+};
+
+struct AutomaticLinksLinter<'a, 'tcx> {
+    cx: &'a DocContext<'tcx>,
+}
+
+impl<'a, 'tcx> AutomaticLinksLinter<'a, 'tcx> {
+    fn new(cx: &'a DocContext<'tcx>) -> Self {
+        AutomaticLinksLinter { cx }
+    }
+}
+
+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 cx = &self.cx;
+
+            let p = Parser::new_ext(&dox, opts()).into_offset_iter();
+
+            let mut title = String::new();
+            let mut in_link = false;
+
+            for (event, range) in p {
+                match event {
+                    Event::Start(Tag::Link(..)) => in_link = true,
+                    Event::End(Tag::Link(_, url, _)) => {
+                        in_link = false;
+                        if url.as_ref() != title {
+                            continue;
+                        }
+                        let sp = match super::source_span_for_markdown_range(
+                            cx,
+                            &dox,
+                            &range,
+                            &item.attrs,
+                        ) {
+                            Some(sp) => sp,
+                            None => 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("Unneeded long form for URL")
+                                    .help(&format!("Try with `<{}>` instead", url))
+                                    .emit()
+                            },
+                        );
+                        title.clear();
+                    }
+                    Event::Text(s) if in_link => {
+                        title.push_str(&s);
+                    }
+                    _ => {}
+                }
+            }
+        }
+
+        self.fold_item_recur(item)
+    }
+}
diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs
index 2591650e3f9..48dc52c9550 100644
--- a/src/librustdoc/passes/mod.rs
+++ b/src/librustdoc/passes/mod.rs
@@ -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.