Skip to content

Commit bbfa2f2

Browse files
committed
Include reason why a link is considered broken
1 parent ed3271d commit bbfa2f2

File tree

3 files changed

+64
-28
lines changed

3 files changed

+64
-28
lines changed

clippy_lints/src/doc/broken_link.rs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,40 @@ use rustc_span::{BytePos, Span};
66
use super::DOC_BROKEN_LINK;
77

88
pub fn check(cx: &LateContext<'_>, attrs: &[Attribute]) {
9-
for span in BrokenLinkLoader::collect_spans_broken_link(attrs) {
10-
span_lint(cx, DOC_BROKEN_LINK, span, "possible broken doc link");
9+
for broken_link in BrokenLinkLoader::collect_spans_broken_link(attrs) {
10+
let reason_msg = match broken_link.reason {
11+
BrokenLinkReason::MultipleLines => "broken across multiple lines",
12+
BrokenLinkReason::MissingCloseParenthesis => "missing close parenthesis",
13+
BrokenLinkReason::WhiteSpace => "whitespace within url",
14+
};
15+
16+
span_lint(
17+
cx,
18+
DOC_BROKEN_LINK,
19+
broken_link.span,
20+
format!("possible broken doc link: {reason_msg}"),
21+
);
1122
}
1223
}
1324

25+
/// The reason why a link is considered broken.
26+
enum BrokenLinkReason {
27+
MultipleLines,
28+
MissingCloseParenthesis,
29+
WhiteSpace,
30+
}
31+
32+
/// Broken link data.
33+
struct BrokenLink {
34+
reason: BrokenLinkReason,
35+
span: Span,
36+
}
37+
1438
/// Scan AST attributes looking up in doc comments for broken links
1539
/// which rustdoc won't be able to properly create link tags later.
16-
#[derive(Debug)]
1740
struct BrokenLinkLoader {
1841
/// List of spans for detected broken links.
19-
spans_broken_link: Vec<Span>,
42+
broken_links: Vec<BrokenLink>,
2043

2144
/// Mark it has detected a link and it is processing whether
2245
/// or not it is broken.
@@ -51,9 +74,9 @@ struct BrokenLinkLoader {
5174

5275
impl BrokenLinkLoader {
5376
/// Return spans of broken links.
54-
fn collect_spans_broken_link(attrs: &[Attribute]) -> Vec<Span> {
77+
fn collect_spans_broken_link(attrs: &[Attribute]) -> Vec<BrokenLink> {
5578
let mut loader = BrokenLinkLoader {
56-
spans_broken_link: vec![],
79+
broken_links: vec![],
5780
active: false,
5881
active_pos_start: 0,
5982
active_span: None,
@@ -65,7 +88,7 @@ impl BrokenLinkLoader {
6588
start: 0_u32,
6689
};
6790
loader.scan_attrs(attrs);
68-
loader.spans_broken_link
91+
loader.broken_links
6992
}
7093

7194
fn scan_attrs(&mut self, attrs: &[Attribute]) {
@@ -79,15 +102,15 @@ impl BrokenLinkLoader {
79102
if idx > 0 && self.active && self.processing_link_url {
80103
let prev_attr = &attrs[idx - 1];
81104
let prev_end_line = prev_attr.span.hi().0;
82-
self.record_broken_link(prev_end_line);
105+
self.record_broken_link(prev_end_line, BrokenLinkReason::MissingCloseParenthesis);
83106
}
84107
self.reset_lookup();
85108
}
86109
}
87110

88111
if self.active && self.processing_link_url {
89112
let last_end_line = attrs.last().unwrap().span.hi().0;
90-
self.record_broken_link(last_end_line);
113+
self.record_broken_link(last_end_line, BrokenLinkReason::MissingCloseParenthesis);
91114
self.reset_lookup();
92115
}
93116
}
@@ -139,7 +162,7 @@ impl BrokenLinkLoader {
139162
if self.url_multiple_lines {
140163
// +3 skips the opening delimiter and +1 to include the closing parethesis
141164
let pos_end = attr_span.lo().0 + u32::try_from(pos).unwrap() + 4;
142-
self.record_broken_link(pos_end);
165+
self.record_broken_link(pos_end, BrokenLinkReason::MultipleLines);
143166
self.reset_lookup();
144167
}
145168
self.reset_lookup();
@@ -148,7 +171,7 @@ impl BrokenLinkLoader {
148171

149172
if self.reading_link_url && c.is_whitespace() {
150173
let pos_end = u32::try_from(pos).unwrap();
151-
self.record_broken_link(pos_end);
174+
self.record_broken_link(pos_end, BrokenLinkReason::WhiteSpace);
152175
self.reset_lookup();
153176
continue;
154177
}
@@ -173,14 +196,14 @@ impl BrokenLinkLoader {
173196
self.url_multiple_lines = false;
174197
}
175198

176-
fn record_broken_link(&mut self, pos_end: u32) {
199+
fn record_broken_link(&mut self, pos_end: u32, reason: BrokenLinkReason) {
177200
if let Some(attr_span) = self.active_span {
178201
let start = BytePos(self.active_pos_start);
179202
let end = BytePos(pos_end);
180203

181-
let com_span = Span::new(start, end, attr_span.ctxt(), attr_span.parent());
204+
let span = Span::new(start, end, attr_span.ctxt(), attr_span.parent());
182205

183-
self.spans_broken_link.push(com_span);
206+
self.broken_links.push(BrokenLink { reason, span });
184207
}
185208
}
186209
}

tests/ui/doc_broken_link.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,32 @@ pub fn doc_valid_link_broken_url_tag_three_lines() {}
4040
/// Test invalid link, url part broken across multiple lines.
4141
/// [doc invalid link broken url scheme part part](https://
4242
/// test.fake/doc_invalid_link_broken_url_scheme_part)
43-
//~^^ ERROR: possible broken doc link
43+
//~^^ ERROR: possible broken doc link: broken across multiple lines
4444
pub fn doc_invalid_link_broken_url_scheme_part() {}
4545

4646
/// Test invalid link, url part broken across multiple lines.
4747
/// [doc invalid link broken url host part](https://test
4848
/// .fake/doc_invalid_link_broken_url_host_part)
49-
//~^^ ERROR: possible broken doc link
49+
//~^^ ERROR: possible broken doc link: broken across multiple lines
5050
pub fn doc_invalid_link_broken_url_host_part() {}
5151

52-
/// Test invalid link, url part broken across multiple lines.
53-
/// [doc invalid link broken url host part](
54-
/// https://test.fake/doc_invalid_link_missing_close_parenthesis
55-
//~^^ ERROR: possible broken doc link
56-
pub fn doc_invalid_link_missing_close_parenthesis() {}
57-
5852
// NOTE: We don't test doc links where the url is broken accross
5953
// multiple lines in the path part because that is something
6054
// rustdoc itself will check and warn about it.
6155
pub fn doc_invalid_link_broken_url_path_part() {}
6256

57+
/// Test invalid link, url missing close parenthesis.
58+
/// [doc invalid link broken url missing close parenthesis](
59+
/// https://test.fake/doc_invalid_link_missing_close_parenthesis
60+
//~^^ ERROR: possible broken doc link: missing close parenthesis
61+
pub fn doc_invalid_link_missing_close_parenthesis() {}
62+
63+
/// Test invalid link, url whitespace within url.
64+
/// [doc invalid link broken url whitespace within url](
65+
/// https://test.fake/doc_invalid_link_url whitespace_within_url)
66+
//~^^ ERROR: possible broken doc link: whitespace within url
67+
pub fn doc_invalid_link_url_whitespace_within_url() {}
68+
6369
/// This might be considered a link false positive
6470
/// and should be ignored by this lint rule:
6571
/// Example of referencing some code with brackets [FakeType].

tests/ui/doc_broken_link.stderr

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: possible broken doc link
1+
error: possible broken doc link: broken across multiple lines
22
--> tests/ui/doc_broken_link.rs:41:5
33
|
44
LL | /// [doc invalid link broken url scheme part part](https://
@@ -9,21 +9,28 @@ LL | | /// test.fake/doc_invalid_link_broken_url_scheme_part)
99
= note: `-D clippy::doc-broken-link` implied by `-D warnings`
1010
= help: to override `-D warnings` add `#[allow(clippy::doc_broken_link)]`
1111

12-
error: possible broken doc link
12+
error: possible broken doc link: broken across multiple lines
1313
--> tests/ui/doc_broken_link.rs:47:5
1414
|
1515
LL | /// [doc invalid link broken url host part](https://test
1616
| _____^
1717
LL | | /// .fake/doc_invalid_link_broken_url_host_part)
1818
| |________________________________________________^
1919

20-
error: possible broken doc link
21-
--> tests/ui/doc_broken_link.rs:53:5
20+
error: possible broken doc link: missing close parenthesis
21+
--> tests/ui/doc_broken_link.rs:58:5
2222
|
23-
LL | /// [doc invalid link broken url host part](
23+
LL | /// [doc invalid link broken url missing close parenthesis](
2424
| _____^
2525
LL | | /// https://test.fake/doc_invalid_link_missing_close_parenthesis
2626
| |________________________________________________________________^
2727

28-
error: aborting due to 3 previous errors
28+
error: possible broken doc link: whitespace within url
29+
--> /Users/maxnunes/Development/forks/rust-clippy/tests/clippy.toml:1:40
30+
|
31+
LL | # default config for tests, overrides clippy.toml at the project root
32+
| ________________________________________^
33+
... |
34+
35+
error: aborting due to 4 previous errors
2936

0 commit comments

Comments
 (0)