From 29803670304bfad1c46502dfb71af6dd12953b5d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 12 Oct 2020 18:28:57 +0200 Subject: [PATCH] Add new lint for automatic_links improvements --- compiler/rustc_lint/src/lib.rs | 3 +- compiler/rustc_lint_defs/src/builtin.rs | 12 +++ src/librustdoc/core.rs | 2 + src/librustdoc/passes/automatic_links.rs | 93 ++++++++++++++++++++++++ src/librustdoc/passes/mod.rs | 5 ++ 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/librustdoc/passes/automatic_links.rs 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 @@ -1890,6 +1890,17 @@ declare_lint! { "detects invalid HTML tags in doc comments" } +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 { + 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.