3 use crate::core::DocContext;
4 use crate::fold::DocFolder;
5 use crate::html::markdown::opts;
7 use pulldown_cmark::{Event, Parser, Tag};
9 use rustc_errors::Applicability;
10 use std::lazy::SyncLazy;
13 crate const CHECK_BARE_URLS: Pass = Pass {
14 name: "check-bare-urls",
16 description: "detects URLs that are not hyperlinks",
19 static URL_REGEX: SyncLazy<Regex> = SyncLazy::new(|| {
21 r"https?://", // url scheme
22 r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", // one or more subdomains
23 r"[a-zA-Z]{2,63}", // root domain
24 r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)" // optional query or url fragments
26 .expect("failed to build regex")
29 struct BareUrlsLinter<'a, 'tcx> {
30 cx: &'a mut DocContext<'tcx>,
33 impl<'a, 'tcx> BareUrlsLinter<'a, 'tcx> {
38 f: &impl Fn(&DocContext<'_>, &str, &str, Range<usize>),
40 trace!("looking for raw urls in {}", text);
41 // For now, we only check "full" URLs (meaning, starting with "http://" or "https://").
42 for match_ in URL_REGEX.find_iter(&text) {
43 let url = match_.as_str();
44 let url_range = match_.range();
47 "this URL is not a hyperlink",
49 Range { start: range.start + url_range.start, end: range.start + url_range.end },
55 crate fn check_bare_urls(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
56 BareUrlsLinter { cx }.fold_crate(krate)
59 impl<'a, 'tcx> DocFolder for BareUrlsLinter<'a, 'tcx> {
60 fn fold_item(&mut self, item: Item) -> Option<Item> {
61 let hir_id = match DocContext::as_local_hir_id(self.cx.tcx, item.def_id) {
62 Some(hir_id) => hir_id,
64 // If non-local, no need to check anything.
65 return Some(self.fold_item_recur(item));
68 let dox = item.attrs.collapsed_doc_value().unwrap_or_default();
70 let report_diag = |cx: &DocContext<'_>, msg: &str, url: &str, range: Range<usize>| {
71 let sp = super::source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs)
72 .unwrap_or_else(|| item.attr_span(cx.tcx));
73 cx.tcx.struct_span_lint_hir(crate::lint::BARE_URLS, hir_id, sp, |lint| {
75 .note("bare URLs are not automatically turned into clickable links")
78 "use an automatic link instead",
80 Applicability::MachineApplicable,
86 let mut p = Parser::new_ext(&dox, opts()).into_offset_iter();
88 while let Some((event, range)) = p.next() {
90 Event::Text(s) => self.find_raw_urls(&s, range, &report_diag),
91 // We don't want to check the text inside code blocks or links.
92 Event::Start(tag @ (Tag::CodeBlock(_) | Tag::Link(..))) => {
93 while let Some((event, _)) = p.next() {
96 if mem::discriminant(&end) == mem::discriminant(&tag) =>
109 Some(self.fold_item_recur(item))