3 use crate::core
::DocContext
;
4 use crate::html
::markdown
::main_body_opts
;
5 use crate::visit
::DocVisitor
;
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 }
.visit_crate(&krate
);
60 impl<'a
, 'tcx
> DocVisitor
for BareUrlsLinter
<'a
, 'tcx
> {
61 fn visit_item(&mut self, item
: &Item
) {
62 let hir_id
= match DocContext
::as_local_hir_id(self.cx
.tcx
, item
.def_id
) {
63 Some(hir_id
) => hir_id
,
65 // If non-local, no need to check anything.
69 let dox
= item
.attrs
.collapsed_doc_value().unwrap_or_default();
71 let report_diag
= |cx
: &DocContext
<'_
>, msg
: &str, url
: &str, range
: Range
<usize>| {
72 let sp
= super::source_span_for_markdown_range(cx
.tcx
, &dox
, &range
, &item
.attrs
)
73 .unwrap_or_else(|| item
.attr_span(cx
.tcx
));
74 cx
.tcx
.struct_span_lint_hir(crate::lint
::BARE_URLS
, hir_id
, sp
, |lint
| {
76 .note("bare URLs are not automatically turned into clickable links")
79 "use an automatic link instead",
81 Applicability
::MachineApplicable
,
87 let mut p
= Parser
::new_ext(&dox
, main_body_opts()).into_offset_iter();
89 while let Some((event
, range
)) = p
.next() {
91 Event
::Text(s
) => self.find_raw_urls(&s
, range
, &report_diag
),
92 // We don't want to check the text inside code blocks or links.
93 Event
::Start(tag @
(Tag
::CodeBlock(_
) | Tag
::Link(..))) => {
94 while let Some((event
, _
)) = p
.next() {
97 if mem
::discriminant(&end
) == mem
::discriminant(&tag
) =>
110 self.visit_item_recur(item
)