Skip to content

Commit fbd39d3

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 8298da7 commit fbd39d3

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
@@ -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_refdef_list_item`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_refdef_list_item
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

Lines changed: 1 addition & 0 deletions
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_REFDEF_LIST_ITEM_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

Lines changed: 81 additions & 5 deletions
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.
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+
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_REFDEF_LIST_ITEM,
578606
MISSING_SAFETY_DOC,
579607
MISSING_ERRORS_DOC,
580608
MISSING_PANICS_DOC,
@@ -864,11 +892,37 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
864892
in_heading = true;
865893
}
866894
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+
}
869922
} else {
870-
containers.push(Container::List(0));
871-
}
923+
0
924+
};
925+
containers.push(Container::List(indent));
872926
}
873927
ticks_unbalanced = false;
874928
paragraph_range = range;
@@ -1040,3 +1094,25 @@ impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
10401094
self.cx.tcx.hir()
10411095
}
10421096
}
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+
}
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)