@@ -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 , 
@@ -549,6 +576,7 @@ impl Documentation {
549576impl_lint_pass ! ( Documentation  => [ 
550577    DOC_LINK_WITH_QUOTES , 
551578    DOC_MARKDOWN , 
579+     DOC_REFDEF_LIST_ITEM , 
552580    MISSING_SAFETY_DOC , 
553581    MISSING_ERRORS_DOC , 
554582    MISSING_PANICS_DOC , 
@@ -836,11 +864,37 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
836864                    in_heading = true ; 
837865                } 
838866                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 ) ) ; 
867+                     let  indent = if  let  Some ( ( next_event,  next_range) )  = events. peek ( )  { 
868+                         let  next_start = match  next_event { 
869+                             End ( TagEnd :: Item )  => next_range. end , 
870+                             _ => next_range. start , 
871+                         } ; 
872+                         if  let  Some ( refdefrange)  = looks_like_refdef ( doc,  range. start ..next_start)  &&
873+                             let  Some ( refdefspan)  = fragments. span ( cx,  refdefrange. clone ( ) ) 
874+                         { 
875+                             span_lint_and_then ( 
876+                                 cx, 
877+                                 DOC_REFDEF_LIST_ITEM , 
878+                                 refdefspan, 
879+                                 "link reference defined in list item" , 
880+                                 |diag| { 
881+                                     diag. span_suggestion_short ( 
882+                                         refdefspan. shrink_to_hi ( ) , 
883+                                         "for an intra-doc link, add `[]` between the label and the colon" , 
884+                                         "[]" , 
885+                                         Applicability :: MaybeIncorrect , 
886+                                     ) ; 
887+                                     diag. help ( "link definitions are not shown in rendered documentation" ) ; 
888+                                 } 
889+                             ) ; 
890+                             refdefrange. start  - range. start 
891+                         }  else  { 
892+                             next_range. start  - range. start 
893+                         } 
841894                    }  else  { 
842-                         containers. push ( Container :: List ( 0 ) ) ; 
843-                     } 
895+                         0 
896+                     } ; 
897+                     containers. push ( Container :: List ( indent) ) ; 
844898                } 
845899                ticks_unbalanced = false ; 
846900                paragraph_range = range; 
@@ -1012,3 +1066,25 @@ impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
10121066        self . cx . tcx . hir ( ) 
10131067    } 
10141068} 
1069+ 
1070+ #[ allow( clippy:: range_plus_one) ]   // inclusive ranges aren't the same type 
1071+ fn  looks_like_refdef ( doc :  & str ,  range :  Range < usize > )  -> Option < Range < usize > >  { 
1072+     let  offset = range. start ; 
1073+     let  mut  iterator = doc. as_bytes ( ) [ range] . iter ( ) . copied ( ) . enumerate ( ) ; 
1074+     let  mut  start = None ; 
1075+     while  let  Some ( ( i,  byte) )  = iterator. next ( )  { 
1076+         if  byte == b'\\'  { 
1077+             iterator. next ( ) ; 
1078+             continue ; 
1079+         } 
1080+         if  byte == b'['  { 
1081+             start = Some ( i + offset) ; 
1082+         } 
1083+         if  let  Some ( start)  = start
1084+             && byte == b']' 
1085+         { 
1086+             return  Some ( start..i + offset + 1 ) ; 
1087+         } 
1088+     } 
1089+     None 
1090+ } 
0 commit comments