@@ -5,7 +5,7 @@ mod too_long_first_doc_paragraph;
5
5
6
6
use clippy_config:: Conf ;
7
7
use 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 } ;
9
9
use clippy_utils:: macros:: { is_panic, root_macro_call_first_node} ;
10
10
use clippy_utils:: ty:: is_type_diagnostic_item;
11
11
use clippy_utils:: visitors:: Visitable ;
@@ -18,6 +18,7 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It
18
18
use pulldown_cmark:: { BrokenLink , CodeBlockKind , CowStr , Options , TagEnd } ;
19
19
use rustc_ast:: ast:: Attribute ;
20
20
use rustc_data_structures:: fx:: FxHashSet ;
21
+ use rustc_errors:: Applicability ;
21
22
use rustc_hir:: intravisit:: { self , Visitor } ;
22
23
use rustc_hir:: { AnonConst , Expr , ImplItemKind , ItemKind , Node , Safety , TraitItemKind } ;
23
24
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
@@ -558,6 +559,32 @@ declare_clippy_lint! {
558
559
"check if files included in documentation are behind `cfg(doc)`"
559
560
}
560
561
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
+
561
588
pub struct Documentation {
562
589
valid_idents : FxHashSet < String > ,
563
590
check_private_items : bool ,
@@ -575,6 +602,7 @@ impl Documentation {
575
602
impl_lint_pass ! ( Documentation => [
576
603
DOC_LINK_WITH_QUOTES ,
577
604
DOC_MARKDOWN ,
605
+ DOC_REFDEF_LIST_ITEM ,
578
606
MISSING_SAFETY_DOC ,
579
607
MISSING_ERRORS_DOC ,
580
608
MISSING_PANICS_DOC ,
@@ -864,11 +892,37 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
864
892
in_heading = true ;
865
893
}
866
894
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
+ }
869
922
} else {
870
- containers. push ( Container :: List ( 0 ) ) ;
871
- }
923
+ 0
924
+ } ;
925
+ containers. push ( Container :: List ( indent) ) ;
872
926
}
873
927
ticks_unbalanced = false ;
874
928
paragraph_range = range;
@@ -1040,3 +1094,25 @@ impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
1040
1094
self . cx . tcx . hir ( )
1041
1095
}
1042
1096
}
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