@@ -3,23 +3,55 @@ use crate::clean::*;
33use crate :: core:: DocContext ;
44use crate :: fold:: DocFolder ;
55use 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 ;
710use rustc_feature:: UnstableFeatures ;
811use rustc_session:: lint;
912
1013pub const CHECK_AUTOMATIC_LINKS : Pass = Pass {
1114 name : "check-automatic-links" ,
1215 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" ,
1417} ;
1518
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+
1622struct AutomaticLinksLinter < ' a , ' tcx > {
1723 cx : & ' a DocContext < ' tcx > ,
24+ regex : Regex ,
1825}
1926
2027impl < ' a , ' tcx > AutomaticLinksLinter < ' a , ' tcx > {
2128 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+ }
2355 }
2456}
2557
@@ -44,45 +76,48 @@ impl<'a, 'tcx> DocFolder for AutomaticLinksLinter<'a, 'tcx> {
4476 } ;
4577 let dox = item. attrs . collapsed_doc_value ( ) . unwrap_or_default ( ) ;
4678 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+ } ;
4894
4995 let p = Parser :: new_ext ( & dox, opts ( ) ) . into_offset_iter ( ) ;
5096
5197 let mut title = String :: new ( ) ;
5298 let mut in_link = false ;
99+ let mut ignore = false ;
53100
54101 for ( event, range) in p {
55102 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+ }
57107 Event :: End ( Tag :: Link ( _, url, _) ) => {
58108 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 ) ;
61111 }
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- ) ;
81112 title. clear ( ) ;
113+ ignore = false ;
82114 }
83115 Event :: Text ( s) if in_link => {
84- title. push_str ( & s) ;
116+ if !ignore {
117+ title. push_str ( & s) ;
118+ }
85119 }
120+ Event :: Text ( s) => self . find_raw_urls ( & s, range, & report_diag) ,
86121 _ => { }
87122 }
88123 }
0 commit comments