Skip to content

Commit eb228e7

Browse files
committed
doc_refdef_list_item: new lint for suspicious list syntax
This is more likely to be intended as an intra-doc link than it is to be intended as a refdef. If a refdef is intended, it does not need to be nested within a list item. ```markdown - [`LONG_INTRA_DOC_LINK`]: this looks like an intra-doc link, but is actually a refdef. The first line will seem to disappear when rendered as HTML. ```
1 parent 10677c3 commit eb228e7

File tree

6 files changed

+373
-5
lines changed

6 files changed

+373
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5444,6 +5444,7 @@ Released 2018-09-13
54445444
[`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
54455445
[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes
54465446
[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
5447+
[`doc_refdef_list_item`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_refdef_list_item
54475448
[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
54485449
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
54495450
[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
138138
crate::doc::DOC_LAZY_CONTINUATION_INFO,
139139
crate::doc::DOC_LINK_WITH_QUOTES_INFO,
140140
crate::doc::DOC_MARKDOWN_INFO,
141+
crate::doc::DOC_REFDEF_LIST_ITEM_INFO,
141142
crate::doc::EMPTY_DOCS_INFO,
142143
crate::doc::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
143144
crate::doc::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,

clippy_lints/src/doc/mod.rs

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod too_long_first_doc_paragraph;
33

44
use clippy_config::Conf;
55
use 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};
77
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
88
use clippy_utils::ty::is_type_diagnostic_item;
99
use clippy_utils::visitors::Visitable;
@@ -16,6 +16,7 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It
1616
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd};
1717
use rustc_ast::ast::Attribute;
1818
use rustc_data_structures::fx::FxHashSet;
19+
use rustc_errors::Applicability;
1920
use rustc_hir::intravisit::{self, Visitor};
2021
use rustc_hir::{AnonConst, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
2122
use 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+
535562
pub struct Documentation {
536563
valid_idents: FxHashSet<String>,
537564
check_private_items: bool,
@@ -549,6 +576,7 @@ impl Documentation {
549576
impl_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+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// https://github.com/rust-lang/rust/issues/133150
2+
#![warn(clippy::doc_refdef_list_item)]
3+
#[rustfmt::skip]
4+
/// - [link][]: def
5+
//~^ ERROR: link reference defined in list item
6+
///
7+
/// - [link][]: def (title)
8+
//~^ ERROR: link reference defined in list item
9+
///
10+
/// - [link][]: def "title"
11+
//~^ ERROR: link reference defined in list item
12+
///
13+
/// - [link]: not def
14+
///
15+
/// - [link][]: notdef
16+
///
17+
/// - [link]\: notdef
18+
pub struct Empty;
19+
20+
#[rustfmt::skip]
21+
/// - [link][]: def
22+
//~^ ERROR: link reference defined in list item
23+
/// - [link][]: def (title)
24+
//~^ ERROR: link reference defined in list item
25+
/// - [link][]: def "title"
26+
//~^ ERROR: link reference defined in list item
27+
/// - [link]: not def
28+
/// - [link][]: notdef
29+
/// - [link]\: notdef
30+
pub struct EmptyTight;
31+
32+
#[rustfmt::skip]
33+
/// - [link][]: def
34+
//~^ ERROR: link reference defined in list item
35+
/// inner text
36+
///
37+
/// - [link][]: def (title)
38+
//~^ ERROR: link reference defined in list item
39+
/// inner text
40+
///
41+
/// - [link][]: def "title"
42+
//~^ ERROR: link reference defined in list item
43+
/// inner text
44+
///
45+
/// - [link]: not def
46+
/// inner text
47+
///
48+
/// - [link][]: notdef
49+
/// inner text
50+
///
51+
/// - [link]\: notdef
52+
/// inner text
53+
pub struct NotEmpty;
54+
55+
#[rustfmt::skip]
56+
/// - [link][]: def
57+
//~^ ERROR: link reference defined in list item
58+
/// inner text
59+
/// - [link][]: def (title)
60+
//~^ ERROR: link reference defined in list item
61+
/// inner text
62+
/// - [link][]: def "title"
63+
//~^ ERROR: link reference defined in list item
64+
/// inner text
65+
/// - [link]: not def
66+
/// inner text
67+
/// - [link][]: notdef
68+
/// inner text
69+
/// - [link]\: notdef
70+
/// inner text
71+
pub struct NotEmptyTight;

tests/ui/doc/doc_refdef_list_item.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// https://github.com/rust-lang/rust/issues/133150
2+
#![warn(clippy::doc_refdef_list_item)]
3+
#[rustfmt::skip]
4+
/// - [link]: def
5+
//~^ ERROR: link reference defined in list item
6+
///
7+
/// - [link]: def (title)
8+
//~^ ERROR: link reference defined in list item
9+
///
10+
/// - [link]: def "title"
11+
//~^ ERROR: link reference defined in list item
12+
///
13+
/// - [link]: not def
14+
///
15+
/// - [link][]: notdef
16+
///
17+
/// - [link]\: notdef
18+
pub struct Empty;
19+
20+
#[rustfmt::skip]
21+
/// - [link]: def
22+
//~^ ERROR: link reference defined in list item
23+
/// - [link]: def (title)
24+
//~^ ERROR: link reference defined in list item
25+
/// - [link]: def "title"
26+
//~^ ERROR: link reference defined in list item
27+
/// - [link]: not def
28+
/// - [link][]: notdef
29+
/// - [link]\: notdef
30+
pub struct EmptyTight;
31+
32+
#[rustfmt::skip]
33+
/// - [link]: def
34+
//~^ ERROR: link reference defined in list item
35+
/// inner text
36+
///
37+
/// - [link]: def (title)
38+
//~^ ERROR: link reference defined in list item
39+
/// inner text
40+
///
41+
/// - [link]: def "title"
42+
//~^ ERROR: link reference defined in list item
43+
/// inner text
44+
///
45+
/// - [link]: not def
46+
/// inner text
47+
///
48+
/// - [link][]: notdef
49+
/// inner text
50+
///
51+
/// - [link]\: notdef
52+
/// inner text
53+
pub struct NotEmpty;
54+
55+
#[rustfmt::skip]
56+
/// - [link]: def
57+
//~^ ERROR: link reference defined in list item
58+
/// inner text
59+
/// - [link]: def (title)
60+
//~^ ERROR: link reference defined in list item
61+
/// inner text
62+
/// - [link]: def "title"
63+
//~^ ERROR: link reference defined in list item
64+
/// inner text
65+
/// - [link]: not def
66+
/// inner text
67+
/// - [link][]: notdef
68+
/// inner text
69+
/// - [link]\: notdef
70+
/// inner text
71+
pub struct NotEmptyTight;

0 commit comments

Comments
 (0)