@@ -198,6 +198,29 @@ declare_clippy_lint! {
198
198
"presence of `fn main() {` in code examples"
199
199
}
200
200
201
+ declare_clippy_lint ! {
202
+ /// ### What it does
203
+ /// Detects the syntax `['foo']` in documentation comments (notice quotes instead of backticks)
204
+ /// outside of code blocks
205
+ /// ### Why is this bad?
206
+ /// It is likely a typo when defining an intra-doc link
207
+ ///
208
+ /// ### Example
209
+ /// ```rust
210
+ /// /// See also: ['foo']
211
+ /// fn bar() {}
212
+ /// ```
213
+ /// Use instead:
214
+ /// ```rust
215
+ /// /// See also: [`foo`]
216
+ /// fn bar() {}
217
+ /// ```
218
+ #[ clippy:: version = "1.63.0" ]
219
+ pub DOC_LINK_WITH_QUOTES ,
220
+ pedantic,
221
+ "possible typo for an intra-doc link"
222
+ }
223
+
201
224
#[ expect( clippy:: module_name_repetitions) ]
202
225
#[ derive( Clone ) ]
203
226
pub struct DocMarkdown {
@@ -214,9 +237,14 @@ impl DocMarkdown {
214
237
}
215
238
}
216
239
217
- impl_lint_pass ! ( DocMarkdown =>
218
- [ DOC_MARKDOWN , MISSING_SAFETY_DOC , MISSING_ERRORS_DOC , MISSING_PANICS_DOC , NEEDLESS_DOCTEST_MAIN ]
219
- ) ;
240
+ impl_lint_pass ! ( DocMarkdown => [
241
+ DOC_LINK_WITH_QUOTES ,
242
+ DOC_MARKDOWN ,
243
+ MISSING_SAFETY_DOC ,
244
+ MISSING_ERRORS_DOC ,
245
+ MISSING_PANICS_DOC ,
246
+ NEEDLESS_DOCTEST_MAIN
247
+ ] ) ;
220
248
221
249
impl < ' tcx > LateLintPass < ' tcx > for DocMarkdown {
222
250
fn check_crate ( & mut self , cx : & LateContext < ' tcx > ) {
@@ -432,7 +460,7 @@ pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span:
432
460
( no_stars, sizes)
433
461
}
434
462
435
- #[ derive( Copy , Clone ) ]
463
+ #[ derive( Copy , Clone , Default ) ]
436
464
struct DocHeaders {
437
465
safety : bool ,
438
466
errors : bool ,
@@ -476,11 +504,7 @@ fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs
476
504
}
477
505
478
506
if doc. is_empty ( ) {
479
- return DocHeaders {
480
- safety : false ,
481
- errors : false ,
482
- panics : false ,
483
- } ;
507
+ return DocHeaders :: default ( ) ;
484
508
}
485
509
486
510
let mut cb = fake_broken_link_callback;
@@ -521,11 +545,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
521
545
use pulldown_cmark:: Tag :: { CodeBlock , Heading , Item , Link , Paragraph } ;
522
546
use pulldown_cmark:: { CodeBlockKind , CowStr } ;
523
547
524
- let mut headers = DocHeaders {
525
- safety : false ,
526
- errors : false ,
527
- panics : false ,
528
- } ;
548
+ let mut headers = DocHeaders :: default ( ) ;
529
549
let mut in_code = false ;
530
550
let mut in_link = None ;
531
551
let mut in_heading = false ;
@@ -612,6 +632,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
612
632
check_code ( cx, & text, edition, span) ;
613
633
}
614
634
} else {
635
+ check_link_quotes ( cx, in_link. is_some ( ) , trimmed_text, span, & range, begin, text. len ( ) ) ;
615
636
// Adjust for the beginning of the current `Event`
616
637
let span = span. with_lo ( span. lo ( ) + BytePos :: from_usize ( range. start - begin) ) ;
617
638
text_to_check. push ( ( text, span) ) ;
@@ -622,6 +643,27 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
622
643
headers
623
644
}
624
645
646
+ fn check_link_quotes (
647
+ cx : & LateContext < ' _ > ,
648
+ in_link : bool ,
649
+ trimmed_text : & str ,
650
+ span : Span ,
651
+ range : & Range < usize > ,
652
+ begin : usize ,
653
+ text_len : usize ,
654
+ ) {
655
+ if in_link && trimmed_text. starts_with ( '\'' ) && trimmed_text. ends_with ( '\'' ) {
656
+ // fix the span to only point at the text within the link
657
+ let lo = span. lo ( ) + BytePos :: from_usize ( range. start - begin) ;
658
+ span_lint (
659
+ cx,
660
+ DOC_LINK_WITH_QUOTES ,
661
+ span. with_lo ( lo) . with_hi ( lo + BytePos :: from_usize ( text_len) ) ,
662
+ "possible intra-doc link using quotes instead of backticks" ,
663
+ ) ;
664
+ }
665
+ }
666
+
625
667
fn get_current_span ( spans : & [ ( usize , Span ) ] , idx : usize ) -> ( usize , Span ) {
626
668
let index = match spans. binary_search_by ( |c| c. 0 . cmp ( & idx) ) {
627
669
Ok ( o) => o,
0 commit comments