Skip to content

Commit 75cd27c

Browse files
committed
Simplify broken link detection without state machine usage
1 parent 5e84e85 commit 75cd27c

File tree

3 files changed

+58
-92
lines changed

3 files changed

+58
-92
lines changed

clippy_lints/src/doc/broken_link.rs

Lines changed: 34 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -23,79 +23,53 @@ enum BrokenLinkReason {
2323
MultipleLines,
2424
}
2525

26-
#[derive(Debug)]
27-
enum State {
28-
ProcessingLinkText,
29-
ProcessedLinkText,
30-
ProcessingLinkUrl(UrlState),
31-
}
32-
33-
#[derive(Debug)]
34-
enum UrlState {
35-
Empty,
36-
FilledEntireSingleLine,
37-
FilledBrokenMultipleLines,
38-
}
39-
4026
fn warn_if_broken_link(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &String, fragments: &Vec<DocFragment>) {
4127
if let Some(span) = source_span_for_markdown_range(cx.tcx, doc, &bl.span, fragments) {
42-
// `PullDownBrokenLink` provided by pulldown_cmark always
43-
// start with `[` which makes pulldown_cmark consider this a link tag.
44-
let mut state = State::ProcessingLinkText;
28+
let mut len = 0;
4529

46-
// Whether it was detected a line break within the link tag url part.
47-
let mut reading_link_url_new_line = false;
30+
// grab raw link data
31+
let (_, raw_link) = doc.split_at(bl.span.start);
4832

49-
// Skip the first char because we already know it is a `[` char.
50-
for (abs_pos, c) in doc.char_indices().skip(bl.span.start + 1) {
51-
match &state {
52-
State::ProcessingLinkText => {
53-
if c == ']' {
54-
state = State::ProcessedLinkText;
55-
}
56-
},
57-
State::ProcessedLinkText => {
58-
if c == '(' {
59-
state = State::ProcessingLinkUrl(UrlState::Empty);
60-
} else {
61-
// not a real link, just skip it without reporting a broken link for it.
62-
break;
63-
}
64-
},
65-
State::ProcessingLinkUrl(url_state) => {
66-
if c == '\n' {
67-
reading_link_url_new_line = true;
68-
continue;
69-
}
33+
// strip off link text part
34+
let raw_link = match raw_link.split_once(']') {
35+
None => return,
36+
Some((prefix, suffix)) => {
37+
len += prefix.len() + 1;
38+
suffix
39+
},
40+
};
7041

71-
if c == ')' {
72-
// record full broken link tag
73-
if let UrlState::FilledBrokenMultipleLines = url_state {
74-
let offset = abs_pos - bl.span.start;
75-
report_broken_link(cx, span, offset, BrokenLinkReason::MultipleLines);
76-
}
77-
break;
78-
}
42+
let raw_link = match raw_link.split_once('(') {
43+
None => return,
44+
Some((prefix, suffix)) => {
45+
if !prefix.is_empty() {
46+
// there is text between ']' and '(' chars, so it is not a valid link
47+
return;
48+
}
49+
len += prefix.len() + 1;
50+
suffix
51+
},
52+
};
7953

80-
if !c.is_whitespace() {
81-
if reading_link_url_new_line {
82-
// It was reading a link url which was entirely in a single line, but a new char
83-
// was found in this new line which turned the url into a broken state.
84-
state = State::ProcessingLinkUrl(UrlState::FilledBrokenMultipleLines);
85-
continue;
86-
}
54+
for c in raw_link.chars() {
55+
if c == ')' {
56+
// it is a valid link
57+
return;
58+
}
8759

88-
state = State::ProcessingLinkUrl(UrlState::FilledEntireSingleLine);
89-
}
90-
},
91-
};
60+
if c == '\n' {
61+
// detected break line within the url part
62+
report_broken_link(cx, span, len, BrokenLinkReason::MultipleLines);
63+
break;
64+
}
65+
len += 1;
9266
}
9367
}
9468
}
9569

9670
fn report_broken_link(cx: &LateContext<'_>, frag_span: Span, offset: usize, reason: BrokenLinkReason) {
9771
let start = frag_span.lo();
98-
let end = start + BytePos::from_usize(offset + 5);
72+
let end = start + BytePos::from_usize(offset);
9973

10074
let span = Span::new(start, end, frag_span.ctxt(), frag_span.parent());
10175

tests/ui/doc_broken_link.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ fn main() {}
44

55
pub struct FakeType {}
66

7+
/// This might be considered a link false positive
8+
/// and should be ignored by this lint rule:
9+
/// Example of referencing some code with brackets [FakeType].
10+
pub fn doc_ignore_link_false_positive_1() {}
11+
12+
/// This might be considered a link false positive
13+
/// and should be ignored by this lint rule:
14+
/// [`FakeType`]. Continue text after brackets,
15+
/// then (something in
16+
/// parenthesis).
17+
pub fn doc_ignore_link_false_positive_2() {}
18+
719
/// Test valid link, whole link single line.
820
/// [doc valid link](https://test.fake/doc_valid_link)
921
pub fn doc_valid_link() {}
@@ -58,15 +70,3 @@ pub fn doc_invalid_link_broken_url_host_part() {}
5870
//~^^ ERROR: possible broken doc link: broken across multiple lines
5971
/// line of comment.
6072
pub fn doc_multiple_invalid_link_broken_url() {}
61-
62-
/// This might be considered a link false positive
63-
/// and should be ignored by this lint rule:
64-
/// Example of referencing some code with brackets [FakeType].
65-
pub fn doc_ignore_link_false_positive_1() {}
66-
67-
/// This might be considered a link false positive
68-
/// and should be ignored by this lint rule:
69-
/// [`FakeType`]. Continue text after brackets,
70-
/// then (something in
71-
/// parenthesis).
72-
pub fn doc_ignore_link_false_positive_2() {}

tests/ui/doc_broken_link.stderr

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,29 @@
11
error: possible broken doc link: broken across multiple lines
2-
--> tests/ui/doc_broken_link.rs:41:5
2+
--> tests/ui/doc_broken_link.rs:53:5
33
|
4-
LL | /// [doc invalid link broken url scheme part part](https://
5-
| _____^
6-
LL | | /// test.fake/doc_invalid_link_broken_url_scheme_part)
7-
| |______________________________________________________^
4+
LL | /// [doc invalid link broken url scheme part part](https://
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
86
|
97
= note: `-D clippy::doc-broken-link` implied by `-D warnings`
108
= help: to override `-D warnings` add `#[allow(clippy::doc_broken_link)]`
119

1210
error: possible broken doc link: broken across multiple lines
13-
--> tests/ui/doc_broken_link.rs:47:5
11+
--> tests/ui/doc_broken_link.rs:59:5
1412
|
15-
LL | /// [doc invalid link broken url host part](https://test
16-
| _____^
17-
LL | | /// .fake/doc_invalid_link_broken_url_host_part)
18-
| |________________________________________________^
13+
LL | /// [doc invalid link broken url host part](https://test
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1915

2016
error: possible broken doc link: broken across multiple lines
21-
--> tests/ui/doc_broken_link.rs:53:16
17+
--> tests/ui/doc_broken_link.rs:65:16
2218
|
23-
LL | /// There is a [fist link - invalid](https://test
24-
| ________________^
25-
LL | | /// .fake) then it continues
26-
| |__________^
19+
LL | /// There is a [fist link - invalid](https://test
20+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2721

2822
error: possible broken doc link: broken across multiple lines
29-
--> tests/ui/doc_broken_link.rs:56:80
23+
--> tests/ui/doc_broken_link.rs:68:80
3024
|
31-
LL | /// with a [second link - valid](https://test.fake/doc_valid_link) and another [third link - invalid](https://test
32-
| ________________________________________________________________________________^
33-
LL | | /// .fake). It ends with another
34-
| |__________^
25+
LL | /// with a [second link - valid](https://test.fake/doc_valid_link) and another [third link - invalid](https://test
26+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3527

3628
error: aborting due to 4 previous errors
3729

0 commit comments

Comments
 (0)