| use clippy_utils::diagnostics::span_lint; |
| use rustc_lint::LateContext; |
| use rustc_resolve::rustdoc::pulldown_cmark::BrokenLink as PullDownBrokenLink; |
| use rustc_resolve::rustdoc::{DocFragment, source_span_for_markdown_range}; |
| use rustc_span::{BytePos, Pos, Span}; |
| |
| use super::DOC_BROKEN_LINK; |
| |
| /// Scan and report broken link on documents. |
| /// It ignores false positives detected by `pulldown_cmark`, and only |
| /// warns users when the broken link is consider a URL. |
| // NOTE: We don't check these other cases because |
| // rustdoc itself will check and warn about it: |
| // - When a link url is broken across multiple lines in the URL path part |
| // - When a link tag is missing the close parenthesis character at the end. |
| // - When a link has whitespace within the url link. |
| pub fn check(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &str, fragments: &[DocFragment]) { |
| warn_if_broken_link(cx, bl, doc, fragments); |
| } |
| |
| fn warn_if_broken_link(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &str, fragments: &[DocFragment]) { |
| let mut len = 0; |
| |
| // grab raw link data |
| let (_, raw_link) = doc.split_at(bl.span.start); |
| |
| // strip off link text part |
| let raw_link = match raw_link.split_once(']') { |
| None => return, |
| Some((prefix, suffix)) => { |
| len += prefix.len() + 1; |
| suffix |
| }, |
| }; |
| |
| let raw_link = match raw_link.split_once('(') { |
| None => return, |
| Some((prefix, suffix)) => { |
| if !prefix.is_empty() { |
| // there is text between ']' and '(' chars, so it is not a valid link |
| return; |
| } |
| len += prefix.len() + 1; |
| suffix |
| }, |
| }; |
| |
| if raw_link.starts_with("(http") { |
| // reduce chances of false positive reports |
| // by limiting this checking only to http/https links. |
| return; |
| } |
| |
| for c in raw_link.chars() { |
| if c == ')' { |
| // it is a valid link |
| return; |
| } |
| |
| if c == '\n' |
| && let Some((span, _)) = source_span_for_markdown_range(cx.tcx, doc, &bl.span, fragments) |
| { |
| report_broken_link(cx, span, len); |
| break; |
| } |
| |
| len += 1; |
| } |
| } |
| |
| fn report_broken_link(cx: &LateContext<'_>, frag_span: Span, offset: usize) { |
| let start = frag_span.lo(); |
| let end = start + BytePos::from_usize(offset); |
| |
| let span = Span::new(start, end, frag_span.ctxt(), frag_span.parent()); |
| |
| span_lint( |
| cx, |
| DOC_BROKEN_LINK, |
| span, |
| "possible broken doc link: broken across multiple lines", |
| ); |
| } |