Skip to content

Commit 2980367

Browse files
Add new lint for automatic_links improvements
1 parent 8c20701 commit 2980367

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
@@ -67,7 +67,7 @@ use rustc_hir::def_id::LocalDefId;
6767
use rustc_middle::ty::query::Providers;
6868
use rustc_middle::ty::TyCtxt;
6969
use rustc_session::lint::builtin::{
70-
BARE_TRAIT_OBJECTS, BROKEN_INTRA_DOC_LINKS, ELIDED_LIFETIMES_IN_PATHS,
70+
AUTOMATIC_LINKS, BARE_TRAIT_OBJECTS, BROKEN_INTRA_DOC_LINKS, ELIDED_LIFETIMES_IN_PATHS,
7171
EXPLICIT_OUTLIVES_REQUIREMENTS, INVALID_CODEBLOCK_ATTRIBUTES, INVALID_HTML_TAGS,
7272
MISSING_DOC_CODE_EXAMPLES, PRIVATE_DOC_TESTS,
7373
};
@@ -313,6 +313,7 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
313313

314314
add_lint_group!(
315315
"rustdoc",
316+
AUTOMATIC_LINKS,
316317
BROKEN_INTRA_DOC_LINKS,
317318
PRIVATE_INTRA_DOC_LINKS,
318319
INVALID_CODEBLOCK_ATTRIBUTES,

compiler/rustc_lint_defs/src/builtin.rs

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

1893+
declare_lint! {
1894+
/// The `automatic_links` lint detects when a URL/email address could be
1895+
/// written using only brackets. This is a `rustdoc` only lint, see the
1896+
/// documentation in the [rustdoc book].
1897+
///
1898+
/// [rustdoc book]: ../../../rustdoc/lints.html#automatic_links
1899+
pub AUTOMATIC_LINKS,
1900+
Allow,
1901+
"detects URLs/email adresses that could be written using only brackets"
1902+
}
1903+
18931904
declare_lint! {
18941905
/// The `where_clauses_object_safety` lint detects for [object safety] of
18951906
/// [where clauses].
@@ -2795,6 +2806,7 @@ declare_lint_pass! {
27952806
MISSING_DOC_CODE_EXAMPLES,
27962807
INVALID_HTML_TAGS,
27972808
PRIVATE_DOC_TESTS,
2809+
AUTOMATIC_LINKS,
27982810
WHERE_CLAUSES_OBJECT_SAFETY,
27992811
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
28002812
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
@@ -11,6 +11,9 @@ use crate::core::DocContext;
1111
mod stripper;
1212
pub use stripper::*;
1313

14+
mod automatic_links;
15+
pub use self::automatic_links::CHECK_AUTOMATIC_LINKS;
16+
1417
mod collapse_docs;
1518
pub use self::collapse_docs::COLLAPSE_DOCS;
1619

@@ -90,6 +93,7 @@ pub const PASSES: &[Pass] = &[
9093
COLLECT_TRAIT_IMPLS,
9194
CALCULATE_DOC_COVERAGE,
9295
CHECK_INVALID_HTML_TAGS,
96+
CHECK_AUTOMATIC_LINKS,
9397
];
9498

9599
/// The list of passes run by default.
@@ -105,6 +109,7 @@ pub const DEFAULT_PASSES: &[ConditionalPass] = &[
105109
ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX),
106110
ConditionalPass::always(CHECK_INVALID_HTML_TAGS),
107111
ConditionalPass::always(PROPAGATE_DOC_CFG),
112+
ConditionalPass::always(CHECK_AUTOMATIC_LINKS),
108113
];
109114

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

0 commit comments

Comments
 (0)