|
1 |
| -use super::{DOC_BROKEN_LINK, Fragments}; |
2 | 1 | use clippy_utils::diagnostics::span_lint;
|
| 2 | +use rustc_ast::{AttrKind, AttrStyle, Attribute}; |
3 | 3 | use rustc_lint::LateContext;
|
4 |
| -use std::ops::Range; |
5 |
| - |
6 |
| -// Check broken links in code docs. |
7 |
| -pub fn check(cx: &LateContext<'_>, _trimmed_text: &str, range: Range<usize>, fragments: Fragments<'_>, link: &str) { |
8 |
| - if let Some(span) = fragments.span(cx, range) { |
9 |
| - // Broken links are replaced with "fake" value by `fake_broken_link_callback` at `doc/mod.rs`. |
10 |
| - if link == "fake" { |
11 |
| - span_lint(cx, DOC_BROKEN_LINK, span, "possible broken doc link"); |
| 4 | +use rustc_span::{BytePos, Span}; |
| 5 | + |
| 6 | +use super::DOC_BROKEN_LINK; |
| 7 | + |
| 8 | +pub fn check(cx: &LateContext<'_>, attrs: &[Attribute]) { |
| 9 | + let broken_links: Vec<_> = BrokenLinkLoader::collect_spans_broken_link(attrs); |
| 10 | + |
| 11 | + for span in broken_links { |
| 12 | + span_lint(cx, DOC_BROKEN_LINK, span, "possible broken doc link"); |
| 13 | + } |
| 14 | +} |
| 15 | + |
| 16 | +struct BrokenLinkLoader { |
| 17 | + spans_broken_link: Vec<Span>, |
| 18 | + active: bool, |
| 19 | + processing_title: bool, |
| 20 | + processing_link: bool, |
| 21 | + start_at: u32, |
| 22 | +} |
| 23 | + |
| 24 | +impl BrokenLinkLoader { |
| 25 | + fn collect_spans_broken_link(attrs: &[Attribute]) -> Vec<Span> { |
| 26 | + let mut loader = BrokenLinkLoader { |
| 27 | + spans_broken_link: vec![], |
| 28 | + active: false, |
| 29 | + processing_title: false, |
| 30 | + processing_link: false, |
| 31 | + start_at: 0_u32, |
| 32 | + }; |
| 33 | + loader.scan_attrs(attrs); |
| 34 | + loader.spans_broken_link |
| 35 | + } |
| 36 | + |
| 37 | + fn scan_attrs(&mut self, attrs: &[Attribute]) -> Vec<(Span, String)> { |
| 38 | + let broken_links: Vec<(Span, String)> = vec![]; |
| 39 | + |
| 40 | + for attr in attrs { |
| 41 | + if let AttrKind::DocComment(_com_kind, sym) = attr.kind |
| 42 | + && let AttrStyle::Outer = attr.style |
| 43 | + { |
| 44 | + self.scan_line(sym.as_str(), attr.span); |
| 45 | + } |
12 | 46 | }
|
| 47 | + |
| 48 | + broken_links |
| 49 | + } |
| 50 | + |
| 51 | + fn scan_line(&mut self, the_str: &str, attr_span: Span) { |
| 52 | + // Note that we specifically need the char _byte_ indices here, not the positional indexes |
| 53 | + // within the char array to deal with multi-byte characters properly. `char_indices` does |
| 54 | + // exactly that. It provides an iterator over tuples of the form `(byte position, char)`. |
| 55 | + let char_indices: Vec<_> = the_str.char_indices().collect(); |
| 56 | + |
| 57 | + let mut no_url_curr_line = true; |
| 58 | + |
| 59 | + for (pos, c) in char_indices { |
| 60 | + if !self.active { |
| 61 | + if c == '[' { |
| 62 | + self.processing_title = true; |
| 63 | + self.active = true; |
| 64 | + self.start_at = attr_span.lo().0 + u32::try_from(pos).unwrap(); |
| 65 | + } |
| 66 | + continue; |
| 67 | + } |
| 68 | + |
| 69 | + if self.processing_title { |
| 70 | + if c == ']' { |
| 71 | + self.processing_title = false; |
| 72 | + } |
| 73 | + continue; |
| 74 | + } |
| 75 | + |
| 76 | + if !self.processing_link { |
| 77 | + if c == '(' { |
| 78 | + self.processing_link = true; |
| 79 | + } else { |
| 80 | + // not a real link, start lookup over again |
| 81 | + self.reset_lookup(); |
| 82 | + no_url_curr_line = true; |
| 83 | + } |
| 84 | + continue; |
| 85 | + } |
| 86 | + |
| 87 | + if c == ')' { |
| 88 | + self.reset_lookup(); |
| 89 | + no_url_curr_line = true; |
| 90 | + } else if no_url_curr_line && c != ' ' { |
| 91 | + no_url_curr_line = false; |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + // If it got to the end of the line and it still processing link part, |
| 96 | + // it means this is a broken link. |
| 97 | + if self.active && self.processing_link && !no_url_curr_line { |
| 98 | + let pos_end_line = u32::try_from(the_str.len()).unwrap() - 1; |
| 99 | + |
| 100 | + // +3 skips the opening delimiter |
| 101 | + let start = BytePos(self.start_at + 3); |
| 102 | + let end = start + BytePos(pos_end_line); |
| 103 | + |
| 104 | + let com_span = Span::new(start, end, attr_span.ctxt(), attr_span.parent()); |
| 105 | + |
| 106 | + self.spans_broken_link.push(com_span); |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + fn reset_lookup(&mut self) { |
| 111 | + self.processing_link = false; |
| 112 | + self.active = false; |
| 113 | + self.start_at = 0; |
13 | 114 | }
|
14 | 115 | }
|
0 commit comments