Skip to content

Commit 44feca7

Browse files
committed
doc_nested_refdefs: new lint for suspicious refdef 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 or quote. ```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 8298da7 commit 44feca7

9 files changed

+812
-5
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5445,6 +5445,7 @@ Released 2018-09-13
54455445
[`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
54465446
[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes
54475447
[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
5448+
[`doc_nested_refdefs`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_nested_refdefs
54485449
[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
54495450
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
54505451
[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
139139
crate::doc::DOC_LAZY_CONTINUATION_INFO,
140140
crate::doc::DOC_LINK_WITH_QUOTES_INFO,
141141
crate::doc::DOC_MARKDOWN_INFO,
142+
crate::doc::DOC_NESTED_REFDEFS_INFO,
142143
crate::doc::EMPTY_DOCS_INFO,
143144
crate::doc::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
144145
crate::doc::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,

clippy_lints/src/doc/mod.rs

+106-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod too_long_first_doc_paragraph;
55

66
use clippy_config::Conf;
77
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};
99
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
1010
use clippy_utils::ty::is_type_diagnostic_item;
1111
use clippy_utils::visitors::Visitable;
@@ -18,6 +18,7 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It
1818
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd};
1919
use rustc_ast::ast::Attribute;
2020
use rustc_data_structures::fx::FxHashSet;
21+
use rustc_errors::Applicability;
2122
use rustc_hir::intravisit::{self, Visitor};
2223
use rustc_hir::{AnonConst, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
2324
use 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 or quote.
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 or quote.
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_NESTED_REFDEFS,
584+
suspicious,
585+
"link reference defined in list item or quote"
586+
}
587+
561588
pub struct Documentation {
562589
valid_idents: FxHashSet<String>,
563590
check_private_items: bool,
@@ -575,6 +602,7 @@ impl Documentation {
575602
impl_lint_pass!(Documentation => [
576603
DOC_LINK_WITH_QUOTES,
577604
DOC_MARKDOWN,
605+
DOC_NESTED_REFDEFS,
578606
MISSING_SAFETY_DOC,
579607
MISSING_ERRORS_DOC,
580608
MISSING_PANICS_DOC,
@@ -826,6 +854,31 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
826854
Start(BlockQuote(_)) => {
827855
blockquote_level += 1;
828856
containers.push(Container::Blockquote);
857+
if let Some((next_event, next_range)) = events.peek() {
858+
let next_start = match next_event {
859+
End(TagEnd::BlockQuote) => next_range.end,
860+
_ => next_range.start,
861+
};
862+
if let Some(refdefrange) = looks_like_refdef(doc, range.start..next_start) &&
863+
let Some(refdefspan) = fragments.span(cx, refdefrange.clone())
864+
{
865+
span_lint_and_then(
866+
cx,
867+
DOC_NESTED_REFDEFS,
868+
refdefspan,
869+
"link reference defined in quote",
870+
|diag| {
871+
diag.span_suggestion_short(
872+
refdefspan.shrink_to_hi(),
873+
"for an intra-doc link, add `[]` between the label and the colon",
874+
"[]",
875+
Applicability::MaybeIncorrect,
876+
);
877+
diag.help("link definitions are not shown in rendered documentation");
878+
}
879+
);
880+
}
881+
}
829882
},
830883
End(TagEnd::BlockQuote) => {
831884
blockquote_level -= 1;
@@ -864,11 +917,37 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
864917
in_heading = true;
865918
}
866919
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));
920+
let indent = if let Some((next_event, next_range)) = events.peek() {
921+
let next_start = match next_event {
922+
End(TagEnd::Item) => next_range.end,
923+
_ => next_range.start,
924+
};
925+
if let Some(refdefrange) = looks_like_refdef(doc, range.start..next_start) &&
926+
let Some(refdefspan) = fragments.span(cx, refdefrange.clone())
927+
{
928+
span_lint_and_then(
929+
cx,
930+
DOC_NESTED_REFDEFS,
931+
refdefspan,
932+
"link reference defined in list item",
933+
|diag| {
934+
diag.span_suggestion_short(
935+
refdefspan.shrink_to_hi(),
936+
"for an intra-doc link, add `[]` between the label and the colon",
937+
"[]",
938+
Applicability::MaybeIncorrect,
939+
);
940+
diag.help("link definitions are not shown in rendered documentation");
941+
}
942+
);
943+
refdefrange.start - range.start
944+
} else {
945+
next_range.start - range.start
946+
}
869947
} else {
870-
containers.push(Container::List(0));
871-
}
948+
0
949+
};
950+
containers.push(Container::List(indent));
872951
}
873952
ticks_unbalanced = false;
874953
paragraph_range = range;
@@ -1040,3 +1119,25 @@ impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
10401119
self.cx.tcx.hir()
10411120
}
10421121
}
1122+
1123+
#[allow(clippy::range_plus_one)] // inclusive ranges aren't the same type
1124+
fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> {
1125+
let offset = range.start;
1126+
let mut iterator = doc.as_bytes()[range].iter().copied().enumerate();
1127+
let mut start = None;
1128+
while let Some((i, byte)) = iterator.next() {
1129+
if byte == b'\\' {
1130+
iterator.next();
1131+
continue;
1132+
}
1133+
if byte == b'[' {
1134+
start = Some(i + offset);
1135+
}
1136+
if let Some(start) = start
1137+
&& byte == b']'
1138+
{
1139+
return Some(start..i + offset + 1);
1140+
}
1141+
}
1142+
None
1143+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// https://github.com/rust-lang/rust/issues/133150
2+
#![warn(clippy::doc_nested_refdefs)]
3+
#[rustfmt::skip]
4+
/// > [link][]: def
5+
//~^ ERROR: link reference defined in quote
6+
///
7+
/// > [link][]: def (title)
8+
//~^ ERROR: link reference defined in quote
9+
///
10+
/// > [link][]: def "title"
11+
//~^ ERROR: link reference defined in quote
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 quote
23+
/// > inner text
24+
///
25+
/// > [link][]: def (title)
26+
//~^ ERROR: link reference defined in quote
27+
/// > inner text
28+
///
29+
/// > [link][]: def "title"
30+
//~^ ERROR: link reference defined in quote
31+
/// > inner text
32+
///
33+
/// > [link]: not def
34+
/// > inner text
35+
///
36+
/// > [link][]: notdef
37+
/// > inner text
38+
///
39+
/// > [link]\: notdef
40+
/// > inner text
41+
pub struct NotEmpty;
42+
43+
#[rustfmt::skip]
44+
/// > [link][]: def
45+
//~^ ERROR: link reference defined in quote
46+
/// >
47+
/// > inner text
48+
///
49+
/// > [link][]: def (title)
50+
//~^ ERROR: link reference defined in quote
51+
/// >
52+
/// > inner text
53+
///
54+
/// > [link][]: def "title"
55+
//~^ ERROR: link reference defined in quote
56+
/// >
57+
/// > inner text
58+
///
59+
/// > [link]: not def
60+
/// >
61+
/// > inner text
62+
///
63+
/// > [link][]: notdef
64+
/// >
65+
/// > inner text
66+
///
67+
/// > [link]\: notdef
68+
/// >
69+
/// > inner text
70+
pub struct NotEmptyLoose;
71+
72+
#[rustfmt::skip]
73+
/// > first lines
74+
/// > [link]: def
75+
///
76+
/// > first lines
77+
/// > [link]: def (title)
78+
///
79+
/// > firs lines
80+
/// > [link]: def "title"
81+
///
82+
/// > firs lines
83+
/// > [link]: not def
84+
///
85+
/// > first lines
86+
/// > [link][]: notdef
87+
///
88+
/// > first lines
89+
/// > [link]\: notdef
90+
pub struct NotAtStartTight;
91+
92+
#[rustfmt::skip]
93+
/// > first lines
94+
/// >
95+
/// > [link]: def
96+
///
97+
/// > first lines
98+
/// >
99+
/// > [link]: def (title)
100+
///
101+
/// > firs lines
102+
/// >
103+
/// > [link]: def "title"
104+
///
105+
/// > firs lines
106+
/// >
107+
/// > [link]: not def
108+
///
109+
/// > first lines
110+
/// >
111+
/// > [link][]: notdef
112+
///
113+
/// > first lines
114+
/// >
115+
/// > [link]\: notdef
116+
pub struct NotAtStartLoose;
117+
118+
#[rustfmt::skip]
119+
/// > - [link][]: def
120+
//~^ ERROR: link reference defined in list item
121+
/// >
122+
/// > - [link][]: def (title)
123+
//~^ ERROR: link reference defined in list item
124+
/// >
125+
/// > - [link][]: def "title"
126+
//~^ ERROR: link reference defined in list item
127+
/// >
128+
/// > - [link]: not def
129+
/// >
130+
/// > - [link][]: notdef
131+
/// >
132+
/// > - [link]\: notdef
133+
pub struct ListNested;

0 commit comments

Comments
 (0)