Skip to content

Commit 7e4028f

Browse files
Add new lint for automatic_links improvements
1 parent 0e022fc commit 7e4028f

File tree

5 files changed

+114
-1
lines changed

5 files changed

+114
-1
lines changed

compiler/rustc_lint/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ use rustc_hir::def_id::LocalDefId;
6363
use rustc_middle::ty::query::Providers;
6464
use rustc_middle::ty::TyCtxt;
6565
use rustc_session::lint::builtin::{
66-
BARE_TRAIT_OBJECTS, BROKEN_INTRA_DOC_LINKS, ELIDED_LIFETIMES_IN_PATHS,
66+
AUTOMATIC_LINKS, BARE_TRAIT_OBJECTS, BROKEN_INTRA_DOC_LINKS, ELIDED_LIFETIMES_IN_PATHS,
6767
EXPLICIT_OUTLIVES_REQUIREMENTS, INVALID_CODEBLOCK_ATTRIBUTES, INVALID_HTML_TAGS,
6868
MISSING_DOC_CODE_EXAMPLES, PRIVATE_DOC_TESTS,
6969
};
@@ -307,6 +307,7 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
307307

308308
add_lint_group!(
309309
"rustdoc",
310+
AUTOMATIC_LINKS,
310311
BROKEN_INTRA_DOC_LINKS,
311312
PRIVATE_INTRA_DOC_LINKS,
312313
INVALID_CODEBLOCK_ATTRIBUTES,

compiler/rustc_session/src/lint/builtin.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,6 +1891,17 @@ declare_lint! {
18911891
"detects invalid HTML tags in doc comments"
18921892
}
18931893

1894+
declare_lint! {
1895+
/// The `automatic_links` lint detects when a URL/email address could be
1896+
/// written using only brackets. This is a `rustdoc` only lint, see the
1897+
/// documentation in the [rustdoc book].
1898+
///
1899+
/// [rustdoc book]: ../../../rustdoc/lints.html#automatic_links
1900+
pub AUTOMATIC_LINKS,
1901+
Allow,
1902+
"detects URLs/email adresses that could be written using only brackets"
1903+
}
1904+
18941905
declare_lint! {
18951906
/// The `where_clauses_object_safety` lint detects for [object safety] of
18961907
/// [where clauses].
@@ -2711,6 +2722,7 @@ declare_lint_pass! {
27112722
MISSING_DOC_CODE_EXAMPLES,
27122723
INVALID_HTML_TAGS,
27132724
PRIVATE_DOC_TESTS,
2725+
AUTOMATIC_LINKS,
27142726
WHERE_CLAUSES_OBJECT_SAFETY,
27152727
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
27162728
MACRO_USE_EXTERN_CRATE,

src/librustdoc/core.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,13 @@ pub fn run_core(
330330
let invalid_codeblock_attributes_name = rustc_lint::builtin::INVALID_CODEBLOCK_ATTRIBUTES.name;
331331
let invalid_html_tags = rustc_lint::builtin::INVALID_HTML_TAGS.name;
332332
let renamed_and_removed_lints = rustc_lint::builtin::RENAMED_AND_REMOVED_LINTS.name;
333+
let automatic_links = rustc_lint::builtin::AUTOMATIC_LINKS.name;
333334
let unknown_lints = rustc_lint::builtin::UNKNOWN_LINTS.name;
334335

335336
// In addition to those specific lints, we also need to allow those given through
336337
// command line, otherwise they'll get ignored and we don't want that.
337338
let lints_to_show = vec![
339+
automatic_links.to_owned(),
338340
intra_link_resolution_failure_name.to_owned(),
339341
missing_docs.to_owned(),
340342
missing_doc_example.to_owned(),
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use super::{span_of_attrs, Pass};
2+
use crate::clean::*;
3+
use crate::core::DocContext;
4+
use crate::fold::DocFolder;
5+
use crate::html::markdown::opts;
6+
use pulldown_cmark::{Event, Parser, Tag};
7+
use rustc_feature::UnstableFeatures;
8+
use rustc_session::lint;
9+
10+
pub const CHECK_AUTOMATIC_LINKS: Pass = Pass {
11+
name: "check-automatic-links",
12+
run: check_automatic_links,
13+
description: "detects URLS/email addresses that could be written using brackets",
14+
};
15+
16+
struct AutomaticLinksLinter<'a, 'tcx> {
17+
cx: &'a DocContext<'tcx>,
18+
}
19+
20+
impl<'a, 'tcx> AutomaticLinksLinter<'a, 'tcx> {
21+
fn new(cx: &'a DocContext<'tcx>) -> Self {
22+
AutomaticLinksLinter { cx }
23+
}
24+
}
25+
26+
pub fn check_automatic_links(krate: Crate, cx: &DocContext<'_>) -> Crate {
27+
if !UnstableFeatures::from_environment().is_nightly_build() {
28+
krate
29+
} else {
30+
let mut coll = AutomaticLinksLinter::new(cx);
31+
32+
coll.fold_crate(krate)
33+
}
34+
}
35+
36+
impl<'a, 'tcx> DocFolder for AutomaticLinksLinter<'a, 'tcx> {
37+
fn fold_item(&mut self, item: Item) -> Option<Item> {
38+
let hir_id = match self.cx.as_local_hir_id(item.def_id) {
39+
Some(hir_id) => hir_id,
40+
None => {
41+
// If non-local, no need to check anything.
42+
return self.fold_item_recur(item);
43+
}
44+
};
45+
let dox = item.attrs.collapsed_doc_value().unwrap_or_default();
46+
if !dox.is_empty() {
47+
let cx = &self.cx;
48+
49+
let p = Parser::new_ext(&dox, opts()).into_offset_iter();
50+
51+
let mut title = String::new();
52+
let mut in_link = false;
53+
54+
for (event, range) in p {
55+
match event {
56+
Event::Start(Tag::Link(..)) => in_link = true,
57+
Event::End(Tag::Link(_, url, _)) => {
58+
in_link = false;
59+
if url.as_ref() != title {
60+
continue;
61+
}
62+
let sp = match super::source_span_for_markdown_range(
63+
cx,
64+
&dox,
65+
&range,
66+
&item.attrs,
67+
) {
68+
Some(sp) => sp,
69+
None => span_of_attrs(&item.attrs).unwrap_or(item.source.span()),
70+
};
71+
cx.tcx.struct_span_lint_hir(
72+
lint::builtin::AUTOMATIC_LINKS,
73+
hir_id,
74+
sp,
75+
|lint| {
76+
lint.build("Unneeded long form for URL")
77+
.help(&format!("Try with `<{}>` instead", url))
78+
.emit()
79+
},
80+
);
81+
title.clear();
82+
}
83+
Event::Text(s) if in_link => {
84+
title.push_str(&s);
85+
}
86+
_ => {}
87+
}
88+
}
89+
}
90+
91+
self.fold_item_recur(item)
92+
}
93+
}

src/librustdoc/passes/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ use crate::clean::{self, DocFragmentKind, GetDefId, Item};
1212
use crate::core::DocContext;
1313
use crate::fold::{DocFolder, StripItem};
1414

15+
mod automatic_links;
16+
pub use self::automatic_links::CHECK_AUTOMATIC_LINKS;
17+
1518
mod collapse_docs;
1619
pub use self::collapse_docs::COLLAPSE_DOCS;
1720

@@ -91,6 +94,7 @@ pub const PASSES: &[Pass] = &[
9194
COLLECT_TRAIT_IMPLS,
9295
CALCULATE_DOC_COVERAGE,
9396
CHECK_INVALID_HTML_TAGS,
97+
CHECK_AUTOMATIC_LINKS,
9498
];
9599

96100
/// The list of passes run by default.
@@ -106,6 +110,7 @@ pub const DEFAULT_PASSES: &[ConditionalPass] = &[
106110
ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX),
107111
ConditionalPass::always(CHECK_INVALID_HTML_TAGS),
108112
ConditionalPass::always(PROPAGATE_DOC_CFG),
113+
ConditionalPass::always(CHECK_AUTOMATIC_LINKS),
109114
];
110115

111116
/// The list of default passes run when `--doc-coverage` is passed to rustdoc.

0 commit comments

Comments
 (0)