Skip to content

Commit 9a71233

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 9a71233

File tree

6 files changed

+352
-5
lines changed

6 files changed

+352
-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: 80 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,
@@ -836,11 +863,37 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
836863
in_heading = true;
837864
}
838865
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));
866+
let indent = if let Some((next_event, next_range)) = events.peek() {
867+
let next_start = match next_event {
868+
End(TagEnd::Item) => next_range.end,
869+
_ => next_range.start,
870+
};
871+
if let Some(refdefrange) = looks_like_refdef(doc, range.start..next_start) &&
872+
let Some(refdefspan) = fragments.span(cx, refdefrange.clone())
873+
{
874+
span_lint_and_then(
875+
cx,
876+
DOC_REFDEF_LIST_ITEM,
877+
refdefspan,
878+
"link reference defined in list item",
879+
|diag| {
880+
diag.span_suggestion_short(
881+
refdefspan.shrink_to_hi(),
882+
"for an intra-doc link, add `[]` between the label and the colon",
883+
"[]",
884+
Applicability::MaybeIncorrect,
885+
);
886+
diag.help("link definitions are not shown in rendered documentation");
887+
}
888+
);
889+
refdefrange.start - range.start
890+
} else {
891+
next_range.start - range.start
892+
}
841893
} else {
842-
containers.push(Container::List(0));
843-
}
894+
0
895+
};
896+
containers.push(Container::List(indent));
844897
}
845898
ticks_unbalanced = false;
846899
paragraph_range = range;
@@ -1012,3 +1065,25 @@ impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
10121065
self.cx.tcx.hir()
10131066
}
10141067
}
1068+
1069+
#[allow(clippy::range_plus_one)] // inclusive ranges aren't the same type
1070+
fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> {
1071+
let offset = range.start;
1072+
let mut iterator = doc.as_bytes()[range].iter().copied().enumerate();
1073+
let mut start = None;
1074+
while let Some((i, byte)) = iterator.next() {
1075+
if byte == b'\\' {
1076+
iterator.next();
1077+
continue;
1078+
}
1079+
if byte == b'[' {
1080+
start = Some(i + offset);
1081+
}
1082+
if let Some(start) = start
1083+
&& byte == b']'
1084+
{
1085+
return Some(start..i + offset + 1);
1086+
}
1087+
}
1088+
None
1089+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// https://github.com/rust-lang/rust/issues/133150
2+
#![warn(clippy::doc_refdef_list_item)]
3+
/// - [link][]: def
4+
//~^ ERROR: link reference defined in list item
5+
///
6+
/// - [link][]: def (title)
7+
//~^ ERROR: link reference defined in list item
8+
///
9+
/// - [link][]: def "title"
10+
//~^ ERROR: link reference defined in list item
11+
///
12+
/// - [link]: not def
13+
///
14+
/// - [link][]: notdef
15+
///
16+
/// - [link]\: notdef
17+
pub struct Empty;
18+
19+
/// - [link][]: def
20+
//~^ ERROR: link reference defined in list item
21+
/// - [link][]: def (title)
22+
//~^ ERROR: link reference defined in list item
23+
/// - [link][]: def "title"
24+
//~^ ERROR: link reference defined in list item
25+
/// - [link]: not def
26+
/// - [link][]: notdef
27+
/// - [link]\: notdef
28+
pub struct EmptyTight;
29+
30+
/// - [link][]: def
31+
//~^ ERROR: link reference defined in list item
32+
/// inner text
33+
///
34+
/// - [link][]: def (title)
35+
//~^ ERROR: link reference defined in list item
36+
/// inner text
37+
///
38+
/// - [link][]: def "title"
39+
//~^ ERROR: link reference defined in list item
40+
/// inner text
41+
///
42+
/// - [link]: not def inner text
43+
///
44+
/// - [link][]: notdef inner text
45+
///
46+
/// - [link]\: notdef inner text
47+
pub struct NotEmpty;
48+
49+
/// - [link][]: def
50+
//~^ ERROR: link reference defined in list item
51+
/// inner text
52+
/// - [link][]: def (title)
53+
//~^ ERROR: link reference defined in list item
54+
/// inner text
55+
/// - [link][]: def "title"
56+
//~^ ERROR: link reference defined in list item
57+
/// inner text
58+
/// - [link]: not def inner text
59+
/// - [link][]: notdef inner text
60+
/// - [link]\: notdef inner text
61+
pub struct NotEmptyTight;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// https://github.com/rust-lang/rust/issues/133150
2+
#![warn(clippy::doc_refdef_list_item)]
3+
/// - [link]: def
4+
//~^ ERROR: link reference defined in list item
5+
///
6+
/// - [link]: def (title)
7+
//~^ ERROR: link reference defined in list item
8+
///
9+
/// - [link]: def "title"
10+
//~^ ERROR: link reference defined in list item
11+
///
12+
/// - [link]: not def
13+
///
14+
/// - [link][]: notdef
15+
///
16+
/// - [link]\: notdef
17+
pub struct Empty;
18+
19+
/// - [link]: def
20+
//~^ ERROR: link reference defined in list item
21+
/// - [link]: def (title)
22+
//~^ ERROR: link reference defined in list item
23+
/// - [link]: def "title"
24+
//~^ ERROR: link reference defined in list item
25+
/// - [link]: not def
26+
/// - [link][]: notdef
27+
/// - [link]\: notdef
28+
pub struct EmptyTight;
29+
30+
/// - [link]: def
31+
//~^ ERROR: link reference defined in list item
32+
/// inner text
33+
///
34+
/// - [link]: def (title)
35+
//~^ ERROR: link reference defined in list item
36+
/// inner text
37+
///
38+
/// - [link]: def "title"
39+
//~^ ERROR: link reference defined in list item
40+
/// inner text
41+
///
42+
/// - [link]: not def inner text
43+
///
44+
/// - [link][]: notdef inner text
45+
///
46+
/// - [link]\: notdef inner text
47+
pub struct NotEmpty;
48+
49+
/// - [link]: def
50+
//~^ ERROR: link reference defined in list item
51+
/// inner text
52+
/// - [link]: def (title)
53+
//~^ ERROR: link reference defined in list item
54+
/// inner text
55+
/// - [link]: def "title"
56+
//~^ ERROR: link reference defined in list item
57+
/// inner text
58+
/// - [link]: not def inner text
59+
/// - [link][]: notdef inner text
60+
/// - [link]\: notdef inner text
61+
pub struct NotEmptyTight;

0 commit comments

Comments
 (0)