@@ -3,23 +3,55 @@ use crate::clean::*;
3
3
use crate :: core:: DocContext ;
4
4
use crate :: fold:: DocFolder ;
5
5
use crate :: html:: markdown:: opts;
6
- use pulldown_cmark:: { Event , Parser , Tag } ;
6
+ use core:: ops:: Range ;
7
+ use pulldown_cmark:: { Event , LinkType , Parser , Tag } ;
8
+ use regex:: Regex ;
9
+ use rustc_errors:: Applicability ;
7
10
use rustc_feature:: UnstableFeatures ;
8
11
use rustc_session:: lint;
9
12
10
13
pub const CHECK_AUTOMATIC_LINKS : Pass = Pass {
11
14
name : "check-automatic-links" ,
12
15
run : check_automatic_links,
13
- description : "detects URLS/email addresses that could be written using brackets" ,
16
+ description : "detects URLS/email addresses that could be written using angle brackets" ,
14
17
} ;
15
18
19
+ const URL_REGEX : & str =
20
+ r"https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)" ;
21
+
16
22
struct AutomaticLinksLinter < ' a , ' tcx > {
17
23
cx : & ' a DocContext < ' tcx > ,
24
+ regex : Regex ,
18
25
}
19
26
20
27
impl < ' a , ' tcx > AutomaticLinksLinter < ' a , ' tcx > {
21
28
fn new ( cx : & ' a DocContext < ' tcx > ) -> Self {
22
- AutomaticLinksLinter { cx }
29
+ AutomaticLinksLinter { cx, regex : Regex :: new ( URL_REGEX ) . expect ( "failed to build regex" ) }
30
+ }
31
+
32
+ fn find_raw_urls (
33
+ & self ,
34
+ text : & str ,
35
+ range : Range < usize > ,
36
+ f : & impl Fn ( & DocContext < ' _ > , & str , & str , Range < usize > ) ,
37
+ ) {
38
+ for ( pos, c) in text. char_indices ( ) {
39
+ // For now, we only check "full" URLs.
40
+ if c == 'h' {
41
+ let text = & text[ pos..] ;
42
+ if text. starts_with ( "http://" ) || text. starts_with ( "https://" ) {
43
+ if let Some ( m) = self . regex . find ( text) {
44
+ let url = & text[ ..m. end ( ) ] ;
45
+ f (
46
+ self . cx ,
47
+ "won't be a link as is" ,
48
+ url,
49
+ Range { start : range. start + pos, end : range. start + pos + m. end ( ) } ,
50
+ )
51
+ }
52
+ }
53
+ }
54
+ }
23
55
}
24
56
}
25
57
@@ -44,45 +76,48 @@ impl<'a, 'tcx> DocFolder for AutomaticLinksLinter<'a, 'tcx> {
44
76
} ;
45
77
let dox = item. attrs . collapsed_doc_value ( ) . unwrap_or_default ( ) ;
46
78
if !dox. is_empty ( ) {
47
- let cx = & self . cx ;
79
+ let report_diag = |cx : & DocContext < ' _ > , msg : & str , url : & str , range : Range < usize > | {
80
+ let sp = super :: source_span_for_markdown_range ( cx, & dox, & range, & item. attrs )
81
+ . or_else ( || span_of_attrs ( & item. attrs ) )
82
+ . unwrap_or ( item. source . span ( ) ) ;
83
+ cx. tcx . struct_span_lint_hir ( lint:: builtin:: AUTOMATIC_LINKS , hir_id, sp, |lint| {
84
+ lint. build ( msg)
85
+ . span_suggestion (
86
+ sp,
87
+ "use an automatic link instead" ,
88
+ format ! ( "<{}>" , url) ,
89
+ Applicability :: MachineApplicable ,
90
+ )
91
+ . emit ( )
92
+ } ) ;
93
+ } ;
48
94
49
95
let p = Parser :: new_ext ( & dox, opts ( ) ) . into_offset_iter ( ) ;
50
96
51
97
let mut title = String :: new ( ) ;
52
98
let mut in_link = false ;
99
+ let mut ignore = false ;
53
100
54
101
for ( event, range) in p {
55
102
match event {
56
- Event :: Start ( Tag :: Link ( ..) ) => in_link = true ,
103
+ Event :: Start ( Tag :: Link ( kind, _, _) ) => {
104
+ in_link = true ;
105
+ ignore = matches ! ( kind, LinkType :: Autolink | LinkType :: Email ) ;
106
+ }
57
107
Event :: End ( Tag :: Link ( _, url, _) ) => {
58
108
in_link = false ;
59
- if url. as_ref ( ) != title {
60
- continue ;
109
+ if url. as_ref ( ) == title && !ignore {
110
+ report_diag ( self . cx , "unneeded long form for URL" , & url , range ) ;
61
111
}
62
- let sp = match super :: source_span_for_markdown_range (
63
- cx,
64
- & dox,
65
- & range,
66
- & item. attrs ,
67
- ) {
68
- Some ( sp) => sp,
69
- None => span_of_attrs ( & item. attrs ) . unwrap_or ( item. source . span ( ) ) ,
70
- } ;
71
- cx. tcx . struct_span_lint_hir (
72
- lint:: builtin:: AUTOMATIC_LINKS ,
73
- hir_id,
74
- sp,
75
- |lint| {
76
- lint. build ( "Unneeded long form for URL" )
77
- . help ( & format ! ( "Try with `<{}>` instead" , url) )
78
- . emit ( )
79
- } ,
80
- ) ;
81
112
title. clear ( ) ;
113
+ ignore = false ;
82
114
}
83
115
Event :: Text ( s) if in_link => {
84
- title. push_str ( & s) ;
116
+ if !ignore {
117
+ title. push_str ( & s) ;
118
+ }
85
119
}
120
+ Event :: Text ( s) => self . find_raw_urls ( & s, range, & report_diag) ,
86
121
_ => { }
87
122
}
88
123
}
0 commit comments