@@ -5,7 +5,7 @@ mod too_long_first_doc_paragraph;
55
66use  clippy_config:: Conf ; 
77use  clippy_utils:: attrs:: is_doc_hidden; 
8- use  clippy_utils:: diagnostics:: { span_lint,  span_lint_and_help} ; 
8+ use  clippy_utils:: diagnostics:: { span_lint,  span_lint_and_help,  span_lint_and_then } ; 
99use  clippy_utils:: macros:: { is_panic,  root_macro_call_first_node} ; 
1010use  clippy_utils:: ty:: is_type_diagnostic_item; 
1111use  clippy_utils:: visitors:: Visitable ; 
@@ -18,6 +18,7 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It
1818use  pulldown_cmark:: { BrokenLink ,  CodeBlockKind ,  CowStr ,  Options ,  TagEnd } ; 
1919use  rustc_ast:: ast:: Attribute ; 
2020use  rustc_data_structures:: fx:: FxHashSet ; 
21+ use  rustc_errors:: Applicability ; 
2122use  rustc_hir:: intravisit:: { self ,  Visitor } ; 
2223use  rustc_hir:: { AnonConst ,  Expr ,  ImplItemKind ,  ItemKind ,  Node ,  Safety ,  TraitItemKind } ; 
2324use  rustc_lint:: { LateContext ,  LateLintPass ,  LintContext } ; 
@@ -558,6 +559,32 @@ declare_clippy_lint! {
558559    "check if files included in documentation are behind `cfg(doc)`" 
559560} 
560561
562+ declare_clippy_lint !  { 
563+     /// ### What it does 
564+ /// Warns if a link reference definition appears at the start of a 
565+ /// list item. 
566+ /// 
567+ /// ### Why is this bad? 
568+ /// This is probably intended as an intra-doc link. If it is really 
569+ /// supposed to be a reference definition, it can be written outside 
570+ /// of the list item. 
571+ /// 
572+ /// ### Example 
573+ /// ```no_run 
574+ /// //! - [link]: description 
575+ /// ``` 
576+ /// Use instead: 
577+ /// ```no_run 
578+ /// //! - [link][]: description (for intra-doc link) 
579+ /// //! 
580+ /// //! [link]: destination (for link reference definition) 
581+ /// ``` 
582+ [ clippy:: version = "1.84.0" ] 
583+     pub  DOC_REFDEF_LIST_ITEM , 
584+     suspicious, 
585+     "link reference defined in list item" 
586+ } 
587+ 
561588pub  struct  Documentation  { 
562589    valid_idents :  FxHashSet < String > , 
563590    check_private_items :  bool , 
@@ -575,6 +602,7 @@ impl Documentation {
575602impl_lint_pass ! ( Documentation  => [ 
576603    DOC_LINK_WITH_QUOTES , 
577604    DOC_MARKDOWN , 
605+     DOC_REFDEF_LIST_ITEM , 
578606    MISSING_SAFETY_DOC , 
579607    MISSING_ERRORS_DOC , 
580608    MISSING_PANICS_DOC , 
@@ -864,11 +892,37 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
864892                    in_heading = true ; 
865893                } 
866894                if  let  Start ( Item )  = event { 
867-                     if  let  Some ( ( _next_event,  next_range) )  = events. peek ( )  { 
868-                         containers. push ( Container :: List ( next_range. start  - range. start ) ) ; 
895+                     let  indent = if  let  Some ( ( next_event,  next_range) )  = events. peek ( )  { 
896+                         let  next_start = match  next_event { 
897+                             End ( TagEnd :: Item )  => next_range. end , 
898+                             _ => next_range. start , 
899+                         } ; 
900+                         if  let  Some ( refdefrange)  = looks_like_refdef ( doc,  range. start ..next_start)  &&
901+                             let  Some ( refdefspan)  = fragments. span ( cx,  refdefrange. clone ( ) ) 
902+                         { 
903+                             span_lint_and_then ( 
904+                                 cx, 
905+                                 DOC_REFDEF_LIST_ITEM , 
906+                                 refdefspan, 
907+                                 "link reference defined in list item" , 
908+                                 |diag| { 
909+                                     diag. span_suggestion_short ( 
910+                                         refdefspan. shrink_to_hi ( ) , 
911+                                         "for an intra-doc link, add `[]` between the label and the colon" , 
912+                                         "[]" , 
913+                                         Applicability :: MaybeIncorrect , 
914+                                     ) ; 
915+                                     diag. help ( "link definitions are not shown in rendered documentation" ) ; 
916+                                 } 
917+                             ) ; 
918+                             refdefrange. start  - range. start 
919+                         }  else  { 
920+                             next_range. start  - range. start 
921+                         } 
869922                    }  else  { 
870-                         containers. push ( Container :: List ( 0 ) ) ; 
871-                     } 
923+                         0 
924+                     } ; 
925+                     containers. push ( Container :: List ( indent) ) ; 
872926                } 
873927                ticks_unbalanced = false ; 
874928                paragraph_range = range; 
@@ -1040,3 +1094,25 @@ impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
10401094        self . cx . tcx . hir ( ) 
10411095    } 
10421096} 
1097+ 
1098+ #[ allow( clippy:: range_plus_one) ]   // inclusive ranges aren't the same type 
1099+ fn  looks_like_refdef ( doc :  & str ,  range :  Range < usize > )  -> Option < Range < usize > >  { 
1100+     let  offset = range. start ; 
1101+     let  mut  iterator = doc. as_bytes ( ) [ range] . iter ( ) . copied ( ) . enumerate ( ) ; 
1102+     let  mut  start = None ; 
1103+     while  let  Some ( ( i,  byte) )  = iterator. next ( )  { 
1104+         if  byte == b'\\'  { 
1105+             iterator. next ( ) ; 
1106+             continue ; 
1107+         } 
1108+         if  byte == b'['  { 
1109+             start = Some ( i + offset) ; 
1110+         } 
1111+         if  let  Some ( start)  = start
1112+             && byte == b']' 
1113+         { 
1114+             return  Some ( start..i + offset + 1 ) ; 
1115+         } 
1116+     } 
1117+     None 
1118+ } 
0 commit comments