]> git.proxmox.com Git - rustc.git/blame - src/librustdoc/passes/non_autolinks.rs
New upstream version 1.52.0~beta.3+dfsg1
[rustc.git] / src / librustdoc / passes / non_autolinks.rs
CommitLineData
29967ef6
XL
1use super::{span_of_attrs, Pass};
2use crate::clean::*;
3use crate::core::DocContext;
4use crate::fold::DocFolder;
5use crate::html::markdown::opts;
6use core::ops::Range;
7use pulldown_cmark::{Event, LinkType, Parser, Tag};
8use regex::Regex;
9use rustc_errors::Applicability;
29967ef6 10
fc512014 11crate const CHECK_NON_AUTOLINKS: Pass = Pass {
29967ef6
XL
12 name: "check-non-autolinks",
13 run: check_non_autolinks,
5869c6ff 14 description: "detects URLs that could be linkified",
29967ef6
XL
15};
16
17const URL_REGEX: &str = concat!(
18 r"https?://", // url scheme
19 r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", // one or more subdomains
20 r"[a-zA-Z]{2,63}", // root domain
21 r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)" // optional query or url fragments
22);
23
24struct NonAutolinksLinter<'a, 'tcx> {
6a06907d 25 cx: &'a mut DocContext<'tcx>,
29967ef6
XL
26 regex: Regex,
27}
28
29impl<'a, 'tcx> NonAutolinksLinter<'a, 'tcx> {
29967ef6
XL
30 fn find_raw_urls(
31 &self,
32 text: &str,
33 range: Range<usize>,
34 f: &impl Fn(&DocContext<'_>, &str, &str, Range<usize>),
35 ) {
36 // For now, we only check "full" URLs (meaning, starting with "http://" or "https://").
37 for match_ in self.regex.find_iter(&text) {
38 let url = match_.as_str();
39 let url_range = match_.range();
40 f(
41 self.cx,
42 "this URL is not a hyperlink",
43 url,
44 Range { start: range.start + url_range.start, end: range.start + url_range.end },
45 );
46 }
47 }
48}
49
6a06907d 50crate fn check_non_autolinks(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
fc512014 51 if !cx.tcx.sess.is_nightly_build() {
29967ef6
XL
52 krate
53 } else {
6a06907d
XL
54 let mut coll =
55 NonAutolinksLinter { cx, regex: Regex::new(URL_REGEX).expect("failed to build regex") };
29967ef6
XL
56
57 coll.fold_crate(krate)
58 }
59}
60
61impl<'a, 'tcx> DocFolder for NonAutolinksLinter<'a, 'tcx> {
62 fn fold_item(&mut self, item: Item) -> Option<Item> {
6a06907d 63 let hir_id = match DocContext::as_local_hir_id(self.cx.tcx, item.def_id) {
29967ef6
XL
64 Some(hir_id) => hir_id,
65 None => {
66 // If non-local, no need to check anything.
fc512014 67 return Some(self.fold_item_recur(item));
29967ef6
XL
68 }
69 };
70 let dox = item.attrs.collapsed_doc_value().unwrap_or_default();
71 if !dox.is_empty() {
72 let report_diag = |cx: &DocContext<'_>, msg: &str, url: &str, range: Range<usize>| {
6a06907d 73 let sp = super::source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs)
29967ef6
XL
74 .or_else(|| span_of_attrs(&item.attrs))
75 .unwrap_or(item.source.span());
6a06907d 76 cx.tcx.struct_span_lint_hir(crate::lint::NON_AUTOLINKS, hir_id, sp, |lint| {
29967ef6
XL
77 lint.build(msg)
78 .span_suggestion(
79 sp,
80 "use an automatic link instead",
81 format!("<{}>", url),
82 Applicability::MachineApplicable,
83 )
84 .emit()
85 });
86 };
87
88 let mut p = Parser::new_ext(&dox, opts()).into_offset_iter();
89
90 while let Some((event, range)) = p.next() {
91 match event {
92 Event::Start(Tag::Link(kind, _, _)) => {
93 let ignore = matches!(kind, LinkType::Autolink | LinkType::Email);
94 let mut title = String::new();
95
96 while let Some((event, range)) = p.next() {
97 match event {
98 Event::End(Tag::Link(_, url, _)) => {
99 // NOTE: links cannot be nested, so we don't need to
100 // check `kind`
101 if url.as_ref() == title && !ignore && self.regex.is_match(&url)
102 {
103 report_diag(
104 self.cx,
105 "unneeded long form for URL",
106 &url,
107 range,
108 );
109 }
110 break;
111 }
112 Event::Text(s) if !ignore => title.push_str(&s),
113 _ => {}
114 }
115 }
116 }
117 Event::Text(s) => self.find_raw_urls(&s, range, &report_diag),
118 Event::Start(Tag::CodeBlock(_)) => {
119 // We don't want to check the text inside the code blocks.
120 while let Some((event, _)) = p.next() {
121 match event {
122 Event::End(Tag::CodeBlock(_)) => break,
123 _ => {}
124 }
125 }
126 }
127 _ => {}
128 }
129 }
130 }
131
fc512014 132 Some(self.fold_item_recur(item))
29967ef6
XL
133 }
134}