@@ -3,7 +3,7 @@ mod too_long_first_doc_paragraph;
33
44use  clippy_config:: Conf ; 
55use  clippy_utils:: attrs:: is_doc_hidden; 
6- use  clippy_utils:: diagnostics:: { span_lint,  span_lint_and_help} ; 
6+ use  clippy_utils:: diagnostics:: { span_lint,  span_lint_and_help,  span_lint_and_then } ; 
77use  clippy_utils:: macros:: { is_panic,  root_macro_call_first_node} ; 
88use  clippy_utils:: ty:: is_type_diagnostic_item; 
99use  clippy_utils:: visitors:: Visitable ; 
@@ -16,6 +16,7 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It
1616use  pulldown_cmark:: { BrokenLink ,  CodeBlockKind ,  CowStr ,  Options ,  TagEnd } ; 
1717use  rustc_ast:: ast:: Attribute ; 
1818use  rustc_data_structures:: fx:: FxHashSet ; 
19+ use  rustc_errors:: Applicability ; 
1920use  rustc_hir:: intravisit:: { self ,  Visitor } ; 
2021use  rustc_hir:: { AnonConst ,  Expr ,  ImplItemKind ,  ItemKind ,  Node ,  Safety ,  TraitItemKind } ; 
2122use  rustc_lint:: { LateContext ,  LateLintPass ,  LintContext } ; 
@@ -532,6 +533,32 @@ declare_clippy_lint! {
532533    "empty line after doc comments" 
533534} 
534535
536+ declare_clippy_lint !  { 
537+     /// ### What it does 
538+ /// Warns if a link reference definition appears at the start of a 
539+ /// list item. 
540+ /// 
541+ /// ### Why is this bad? 
542+ /// This is probably intended as an intra-doc link. If it is really 
543+ /// supposed to be a reference definition, it can be written outside 
544+ /// of the list item. 
545+ /// 
546+ /// ### Example 
547+ /// ```no_run 
548+ /// //! - [link]: description 
549+ /// ``` 
550+ /// Use instead: 
551+ /// ```no_run 
552+ /// //! - [link][]: description (for intra-doc link) 
553+ /// //! 
554+ /// //! [link]: destination (for link reference definition) 
555+ /// ``` 
556+ [ clippy:: version = "1.84.0" ] 
557+     pub  DOC_REFDEF_LIST_ITEM , 
558+     suspicious, 
559+     "link reference defined in list item" 
560+ } 
561+ 
535562pub  struct  Documentation  { 
536563    valid_idents :  FxHashSet < String > , 
537564    check_private_items :  bool , 
@@ -836,11 +863,37 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
836863                    in_heading = true ; 
837864                } 
838865                if  let  Start ( Item )  = event { 
839-                     if  let  Some ( ( _next_event,  next_range) )  = events. peek ( )  { 
840-                         containers. push ( Container :: List ( next_range. start  - range. start ) ) ; 
866+                     let  indent = if  let  Some ( ( next_event,  next_range) )  = events. peek ( )  { 
867+                         let  next_start = match  next_event { 
868+                             End ( TagEnd :: Item )  => next_range. end , 
869+                             _ => next_range. start , 
870+                         } ; 
871+                         if  let  Some ( refdefrange)  = looks_like_refdef ( doc,  range. start ..next_start)  &&
872+                             let  Some ( refdefspan)  = fragments. span ( cx,  refdefrange. clone ( ) ) 
873+                         { 
874+                             span_lint_and_then ( 
875+                                 cx, 
876+                                 DOC_REFDEF_LIST_ITEM , 
877+                                 refdefspan, 
878+                                 "link reference defined in list item" , 
879+                                 |diag| { 
880+                                     diag. span_suggestion_short ( 
881+                                         refdefspan. shrink_to_hi ( ) , 
882+                                         "for an intra-doc link, add `[]` between the label and the colon" , 
883+                                         "[]" , 
884+                                         Applicability :: MaybeIncorrect , 
885+                                     ) ; 
886+                                     diag. help ( "link definitions are not shown in rendered documentation" ) ; 
887+                                 } 
888+                             ) ; 
889+                             refdefrange. start  - range. start 
890+                         }  else  { 
891+                             next_range. start  - range. start 
892+                         } 
841893                    }  else  { 
842-                         containers. push ( Container :: List ( 0 ) ) ; 
843-                     } 
894+                         0 
895+                     } ; 
896+                     containers. push ( Container :: List ( indent) ) ; 
844897                } 
845898                ticks_unbalanced = false ; 
846899                paragraph_range = range; 
@@ -1012,3 +1065,25 @@ impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
10121065        self . cx . tcx . hir ( ) 
10131066    } 
10141067} 
1068+ 
1069+ #[ allow( clippy:: range_plus_one) ]   // inclusive ranges aren't the same type 
1070+ fn  looks_like_refdef ( doc :  & str ,  range :  Range < usize > )  -> Option < Range < usize > >  { 
1071+     let  offset = range. start ; 
1072+     let  mut  iterator = doc. as_bytes ( ) [ range] . iter ( ) . copied ( ) . enumerate ( ) ; 
1073+     let  mut  start = None ; 
1074+     while  let  Some ( ( i,  byte) )  = iterator. next ( )  { 
1075+         if  byte == b'\\'  { 
1076+             iterator. next ( ) ; 
1077+             continue ; 
1078+         } 
1079+         if  byte == b'['  { 
1080+             start = Some ( i + offset) ; 
1081+         } 
1082+         if  let  Some ( start)  = start
1083+             && byte == b']' 
1084+         { 
1085+             return  Some ( start..i + offset + 1 ) ; 
1086+         } 
1087+     } 
1088+     None 
1089+ } 
0 commit comments