@@ -17,7 +17,10 @@ use crate::core::DocContext;
1717use  crate :: html:: markdown:: main_body_opts; 
1818
1919pub ( super )  fn  visit_item ( cx :  & DocContext < ' _ > ,  item :  & Item ,  hir_id :  HirId ,  dox :  & str )  { 
20-     let  report_diag = |cx :  & DocContext < ' _ > ,  msg :  & ' static  str ,  range :  Range < usize > | { 
20+     let  report_diag = |cx :  & DocContext < ' _ > , 
21+                        msg :  & ' static  str , 
22+                        range :  Range < usize > , 
23+                        without_brackets :  Option < & str > | { 
2124        let  maybe_sp = source_span_for_markdown_range ( cx. tcx ,  dox,  & range,  & item. attrs . doc_strings ) 
2225            . map ( |( sp,  _) | sp) ; 
2326        let  sp = maybe_sp. unwrap_or_else ( || item. attr_span ( cx. tcx ) ) ; 
@@ -27,14 +30,22 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &
2730            // The fallback of using the attribute span is suitable for 
2831            // highlighting where the error is, but not for placing the < and > 
2932            if  let  Some ( sp)  = maybe_sp { 
30-                 lint. multipart_suggestion ( 
31-                     "use an automatic link instead" , 
32-                     vec ! [ 
33-                         ( sp. shrink_to_lo( ) ,  "<" . to_string( ) ) , 
34-                         ( sp. shrink_to_hi( ) ,  ">" . to_string( ) ) , 
35-                     ] , 
36-                     Applicability :: MachineApplicable , 
37-                 ) ; 
33+                 if  let  Some ( without_brackets)  = without_brackets { 
34+                     lint. multipart_suggestion ( 
35+                         "use an automatic link instead" , 
36+                         vec ! [ ( sp,  format!( "<{without_brackets}>" ) ) ] , 
37+                         Applicability :: MachineApplicable , 
38+                     ) ; 
39+                 }  else  { 
40+                     lint. multipart_suggestion ( 
41+                         "use an automatic link instead" , 
42+                         vec ! [ 
43+                             ( sp. shrink_to_lo( ) ,  "<" . to_string( ) ) , 
44+                             ( sp. shrink_to_hi( ) ,  ">" . to_string( ) ) , 
45+                         ] , 
46+                         Applicability :: MachineApplicable , 
47+                     ) ; 
48+                 } 
3849            } 
3950        } ) ; 
4051    } ; 
@@ -43,7 +54,7 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &
4354
4455    while  let  Some ( ( event,  range) )  = p. next ( )  { 
4556        match  event { 
46-             Event :: Text ( s)  => find_raw_urls ( cx,  & s,  range,  & report_diag) , 
57+             Event :: Text ( s)  => find_raw_urls ( cx,  dox ,   & s,  range,  & report_diag) , 
4758            // We don't want to check the text inside code blocks or links. 
4859            Event :: Start ( tag @ ( Tag :: CodeBlock ( _)  | Tag :: Link  {  .. } ) )  => { 
4960                for  ( event,  _)  in  p. by_ref ( )  { 
@@ -67,25 +78,35 @@ static URL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
6778        r"https?://" ,                           // url scheme 
6879        r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+" ,  // one or more subdomains 
6980        r"[a-zA-Z]{2,63}" ,                      // root domain 
70-         r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)"        // optional query or url fragments 
81+         r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)" ,      // optional query or url fragments 
7182    ) ) 
7283    . expect ( "failed to build regex" ) 
7384} ) ; 
7485
7586fn  find_raw_urls ( 
7687    cx :  & DocContext < ' _ > , 
88+     dox :  & str , 
7789    text :  & str , 
7890    range :  Range < usize > , 
79-     f :  & impl  Fn ( & DocContext < ' _ > ,  & ' static  str ,  Range < usize > ) , 
91+     f :  & impl  Fn ( & DocContext < ' _ > ,  & ' static  str ,  Range < usize > ,   Option < & str > ) , 
8092)  { 
8193    trace ! ( "looking for raw urls in {text}" ) ; 
8294    // For now, we only check "full" URLs (meaning, starting with "http://" or "https://"). 
8395    for  match_ in  URL_REGEX . find_iter ( text)  { 
84-         let  url_range = match_. range ( ) ; 
85-         f ( 
86-             cx, 
87-             "this URL is not a hyperlink" , 
88-             Range  {  start :  range. start  + url_range. start ,  end :  range. start  + url_range. end  } , 
89-         ) ; 
96+         let  mut  url_range = match_. range ( ) ; 
97+         url_range. start  += range. start ; 
98+         url_range. end  += range. start ; 
99+         let  mut  without_brackets = None ; 
100+         // If the link is contained inside `[]`, then we need to replace the brackets and 
101+         // not just add `<>`. 
102+         if  dox[ ..url_range. start ] . ends_with ( '[' ) 
103+             && url_range. end  <= dox. len ( ) 
104+             && dox[ url_range. end ..] . starts_with ( ']' ) 
105+         { 
106+             url_range. start  -= 1 ; 
107+             url_range. end  += 1 ; 
108+             without_brackets = Some ( match_. as_str ( ) ) ; 
109+         } 
110+         f ( cx,  "this URL is not a hyperlink" ,  url_range,  without_brackets) ; 
90111    } 
91112} 
0 commit comments