doc_suspicious_footnotes: lint text that looks like a footnote (#14708)

changelog: [`doc_suspicious_footnotes`]: lint for text that looks like a
footnote reference but has no definition

This is an alternative to https://github.com/rust-lang/rust/pull/137803,
meant to address the concerns about false positives.

This lint only fires when the apparent footnote reference has a name
that's made from pure ASCII digits. This choice is justified by running
lintcheck on the top 200 crates, plus the clippy default set:

1. [I ran
lintcheck](https://gist.github.com/notriddle/59072476c9c1fd569fee421270dad665)
with a modded version of this lint that didn't check for digits only. It
produced a false positive warning on a line in mdbook that had a regex,
and no true positives at all.
2. [I also ran
lintcheck](https://gist.github.com/notriddle/74eb8c9e1939b9f5c5549bf1d4fa238a)
with a custom lint that fired on any valid footnote reference with a
non-ascii-digit name. `cargo` uses one in its job_queue module, and
that's all it found.

cc @GuillaumeGomez
This commit is contained in:
Alejandra González 2025-06-05 09:09:59 +00:00 committed by GitHub
commit c90c7c494d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 714 additions and 2 deletions

View file

@ -5736,6 +5736,7 @@ Released 2018-09-13
[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
[`doc_nested_refdefs`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_nested_refdefs
[`doc_overindented_list_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_overindented_list_items
[`doc_suspicious_footnotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_suspicious_footnotes
[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
[`double_ended_iterator_last`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_ended_iterator_last
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use

View file

@ -119,6 +119,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::doc::DOC_MARKDOWN_INFO,
crate::doc::DOC_NESTED_REFDEFS_INFO,
crate::doc::DOC_OVERINDENTED_LIST_ITEMS_INFO,
crate::doc::DOC_SUSPICIOUS_FOOTNOTES_INFO,
crate::doc::EMPTY_DOCS_INFO,
crate::doc::MISSING_ERRORS_DOC_INFO,
crate::doc::MISSING_PANICS_DOC_INFO,

View file

@ -0,0 +1,113 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::token::CommentKind;
use rustc_errors::Applicability;
use rustc_hir::{AttrStyle, Attribute};
use rustc_lint::{LateContext, LintContext};
use rustc_resolve::rustdoc::DocFragmentKind;
use std::ops::Range;
use super::{DOC_SUSPICIOUS_FOOTNOTES, Fragments};
pub fn check(cx: &LateContext<'_>, doc: &str, range: Range<usize>, fragments: &Fragments<'_>, attrs: &[Attribute]) {
for i in doc[range.clone()]
.bytes()
.enumerate()
.filter_map(|(i, c)| if c == b'[' { Some(i) } else { None })
{
let start = i + range.start;
if doc.as_bytes().get(start + 1) == Some(&b'^')
&& let Some(end) = all_numbers_upto_brace(doc, start + 2)
&& doc.as_bytes().get(end) != Some(&b':')
&& doc.as_bytes().get(start - 1) != Some(&b'\\')
&& let Some(this_fragment) = {
// the `doc` string contains all fragments concatenated together
// figure out which one this suspicious footnote comes from
let mut starting_position = 0;
let mut found_fragment = fragments.fragments.last();
for fragment in fragments.fragments {
if start >= starting_position && start < starting_position + fragment.doc.as_str().len() {
found_fragment = Some(fragment);
break;
}
starting_position += fragment.doc.as_str().len();
}
found_fragment
}
{
let span = fragments.span(cx, start..end).unwrap_or(this_fragment.span);
span_lint_and_then(
cx,
DOC_SUSPICIOUS_FOOTNOTES,
span,
"looks like a footnote ref, but has no matching footnote",
|diag| {
if this_fragment.kind == DocFragmentKind::SugaredDoc {
let (doc_attr, (_, doc_attr_comment_kind)) = attrs
.iter()
.filter(|attr| attr.span().overlaps(this_fragment.span))
.rev()
.find_map(|attr| Some((attr, attr.doc_str_and_comment_kind()?)))
.unwrap();
let (to_add, terminator) = match (doc_attr_comment_kind, doc_attr.style()) {
(CommentKind::Line, AttrStyle::Outer) => ("\n///\n/// ", ""),
(CommentKind::Line, AttrStyle::Inner) => ("\n//!\n//! ", ""),
(CommentKind::Block, AttrStyle::Outer) => ("\n/** ", " */"),
(CommentKind::Block, AttrStyle::Inner) => ("\n/*! ", " */"),
};
diag.span_suggestion_verbose(
doc_attr.span().shrink_to_hi(),
"add footnote definition",
format!(
"{to_add}{label}: <!-- description -->{terminator}",
label = &doc[start..end]
),
Applicability::HasPlaceholders,
);
} else {
let is_file_include = cx
.sess()
.source_map()
.span_to_snippet(this_fragment.span)
.as_ref()
.map(|vdoc| vdoc.trim())
== Ok(doc);
if is_file_include {
// if this is a file include, then there's no quote marks
diag.span_suggestion_verbose(
this_fragment.span.shrink_to_hi(),
"add footnote definition",
format!("\n\n{label}: <!-- description -->", label = &doc[start..end],),
Applicability::HasPlaceholders,
);
} else {
// otherwise, we wrap in a string
diag.span_suggestion_verbose(
this_fragment.span,
"add footnote definition",
format!(
"r#\"{doc}\n\n{label}: <!-- description -->\"#",
doc = this_fragment.doc,
label = &doc[start..end],
),
Applicability::HasPlaceholders,
);
}
}
},
);
}
}
}
fn all_numbers_upto_brace(text: &str, i: usize) -> Option<usize> {
for (j, c) in text.as_bytes()[i..].iter().copied().enumerate().take(64) {
if c == b']' && j != 0 {
return Some(i + j + 1);
}
if !c.is_ascii_digit() || j >= 64 {
break;
}
}
None
}

View file

@ -25,6 +25,7 @@ use std::ops::Range;
use url::Url;
mod doc_comment_double_space_linebreaks;
mod doc_suspicious_footnotes;
mod include_in_doc_without_cfg;
mod lazy_continuation;
mod link_with_quotes;
@ -607,6 +608,37 @@ declare_clippy_lint! {
"double-space used for doc comment linebreak instead of `\\`"
}
declare_clippy_lint! {
/// ### What it does
/// Detects syntax that looks like a footnote reference.
///
/// Rustdoc footnotes are compatible with GitHub-Flavored Markdown (GFM).
/// GFM does not parse a footnote reference unless its definition also
/// exists. This lint checks for footnote references with missing
/// definitions, unless it thinks you're writing a regex.
///
/// ### Why is this bad?
/// This probably means that a footnote was meant to exist,
/// but was not written.
///
/// ### Example
/// ```no_run
/// /// This is not a footnote[^1], because no definition exists.
/// fn my_fn() {}
/// ```
/// Use instead:
/// ```no_run
/// /// This is a footnote[^1].
/// ///
/// /// [^1]: defined here
/// fn my_fn() {}
/// ```
#[clippy::version = "1.88.0"]
pub DOC_SUSPICIOUS_FOOTNOTES,
suspicious,
"looks like a link or footnote ref, but with no definition"
}
pub struct Documentation {
valid_idents: FxHashSet<String>,
check_private_items: bool,
@ -638,7 +670,8 @@ impl_lint_pass!(Documentation => [
DOC_OVERINDENTED_LIST_ITEMS,
TOO_LONG_FIRST_DOC_PARAGRAPH,
DOC_INCLUDE_WITHOUT_CFG,
DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS
DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS,
DOC_SUSPICIOUS_FOOTNOTES,
]);
impl EarlyLintPass for Documentation {
@ -825,6 +858,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
doc: &doc,
fragments: &fragments,
},
attrs,
))
}
@ -905,6 +939,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
events: Events,
doc: &str,
fragments: Fragments<'_>,
attrs: &[Attribute],
) -> DocHeaders {
// true if a safety header was found
let mut headers = DocHeaders::default();
@ -1148,7 +1183,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
// Don't check the text associated with external URLs
continue;
}
text_to_check.push((text, range, code_level));
text_to_check.push((text, range.clone(), code_level));
doc_suspicious_footnotes::check(cx, doc, range, &fragments, attrs);
}
}
FootnoteReference(_) => {}

View file

@ -0,0 +1,186 @@
#![warn(clippy::doc_suspicious_footnotes)]
#![allow(clippy::needless_raw_string_hashes)]
//! This is not a footnote[^1].
//!
//! [^1]: <!-- description -->
//~^ doc_suspicious_footnotes
//!
//! This is not a footnote[^either], but it doesn't warn.
//!
//! This is not a footnote\[^1], but it also doesn't warn.
//!
//! This is not a footnote[^1\], but it also doesn't warn.
//!
//! This is not a `footnote[^1]`, but it also doesn't warn.
//!
//! This is a footnote[^2].
//!
//! [^2]: hello world
/// This is not a footnote[^1].
///
/// [^1]: <!-- description -->
//~^ doc_suspicious_footnotes
///
/// This is not a footnote[^either], but it doesn't warn.
///
/// This is not a footnote\[^1], but it also doesn't warn.
///
/// This is not a footnote[^1\], but it also doesn't warn.
///
/// This is not a `footnote[^1]`, but it also doesn't warn.
///
/// This is a footnote[^2].
///
/// [^2]: hello world
pub fn footnotes() {
// test code goes here
}
pub struct Foo;
#[rustfmt::skip]
impl Foo {
#[doc = r#"This is not a footnote[^1].
[^1]: <!-- description -->"#]
//~^ doc_suspicious_footnotes
#[doc = r#""#]
#[doc = r#"This is not a footnote[^either], but it doesn't warn."#]
#[doc = r#""#]
#[doc = r#"This is not a footnote\[^1], but it also doesn't warn."#]
#[doc = r#""#]
#[doc = r#"This is not a footnote[^1\], but it also doesn't warn."#]
#[doc = r#""#]
#[doc = r#"This is not a `footnote[^1]`, but it also doesn't warn."#]
#[doc = r#""#]
#[doc = r#"This is a footnote[^2]."#]
#[doc = r#""#]
#[doc = r#"[^2]: hello world"#]
pub fn footnotes() {
// test code goes here
}
#[doc = r#"This is not a footnote[^1].
This is not a footnote[^either], but it doesn't warn.
This is not a footnote\[^1], but it also doesn't warn.
This is not a footnote[^1\], but it also doesn't warn.
This is not a `footnote[^1]`, but it also doesn't warn.
This is a footnote[^2].
[^2]: hello world
[^1]: <!-- description -->"#]
//~^^^^^^^^^^^^^^ doc_suspicious_footnotes
pub fn footnotes2() {
// test code goes here
}
#[cfg_attr(
not(FALSE),
doc = r#"This is not a footnote[^1].
This is not a footnote[^either], but it doesn't warn.
[^1]: <!-- description -->"#
//~^ doc_suspicious_footnotes
)]
pub fn footnotes3() {
// test code goes here
}
#[doc = "My footnote [^foot\note]"]
pub fn footnote4() {
// test code goes here
}
#[doc = "Hihi"]pub fn footnote5() {
// test code goes here
}
}
#[doc = r#"This is not a footnote[^1].
[^1]: <!-- description -->"#]
//~^ doc_suspicious_footnotes
#[doc = r""]
#[doc = r"This is not a footnote[^either], but it doesn't warn."]
#[doc = r""]
#[doc = r"This is not a footnote\[^1], but it also doesn't warn."]
#[doc = r""]
#[doc = r"This is not a footnote[^1\], but it also doesn't warn."]
#[doc = r""]
#[doc = r"This is not a `footnote[^1]`, but it also doesn't warn."]
#[doc = r""]
#[doc = r"This is a footnote[^2]."]
#[doc = r""]
#[doc = r"[^2]: hello world"]
pub fn footnotes_attrs() {
// test code goes here
}
pub mod multiline {
/*!
* This is not a footnote[^1]. //~ doc_suspicious_footnotes
*
* This is not a footnote\[^1], but it doesn't warn.
*
* This is a footnote[^2].
*
* These give weird results, but correct ones, so it works.
*
* [^2]: hello world
*/
/*! [^1]: <!-- description --> */
/**
* This is not a footnote[^1]. //~ doc_suspicious_footnotes
*
* This is not a footnote\[^1], but it doesn't warn.
*
* This is a footnote[^2].
*
* These give weird results, but correct ones, so it works.
*
* [^2]: hello world
*/
/** [^1]: <!-- description --> */
pub fn foo() {}
}
/// This is not a footnote [^1]
///
/// [^1]: <!-- description -->
//~^ doc_suspicious_footnotes
///
/// This one is [^2]
///
/// [^2]: contents
#[doc = r#"This is not a footnote [^3]
[^3]: <!-- description -->"#]
//~^ doc_suspicious_footnotes
#[doc = ""]
#[doc = "This one is [^4]"]
#[doc = ""]
#[doc = "[^4]: contents"]
pub struct MultiFragmentFootnote;
#[doc(inline)]
/// This is not a footnote [^5]
///
/// [^5]: <!-- description -->
//~^ doc_suspicious_footnotes
///
/// This one is [^6]
///
/// [^6]: contents
#[doc = r#"This is not a footnote [^7]
[^7]: <!-- description -->"#]
//~^ doc_suspicious_footnotes
#[doc = ""]
#[doc = "This one is [^8]"]
#[doc = ""]
#[doc = "[^8]: contents"]
pub use MultiFragmentFootnote as OtherInlinedFootnote;

View file

@ -0,0 +1,162 @@
#![warn(clippy::doc_suspicious_footnotes)]
#![allow(clippy::needless_raw_string_hashes)]
//! This is not a footnote[^1].
//~^ doc_suspicious_footnotes
//!
//! This is not a footnote[^either], but it doesn't warn.
//!
//! This is not a footnote\[^1], but it also doesn't warn.
//!
//! This is not a footnote[^1\], but it also doesn't warn.
//!
//! This is not a `footnote[^1]`, but it also doesn't warn.
//!
//! This is a footnote[^2].
//!
//! [^2]: hello world
/// This is not a footnote[^1].
//~^ doc_suspicious_footnotes
///
/// This is not a footnote[^either], but it doesn't warn.
///
/// This is not a footnote\[^1], but it also doesn't warn.
///
/// This is not a footnote[^1\], but it also doesn't warn.
///
/// This is not a `footnote[^1]`, but it also doesn't warn.
///
/// This is a footnote[^2].
///
/// [^2]: hello world
pub fn footnotes() {
// test code goes here
}
pub struct Foo;
#[rustfmt::skip]
impl Foo {
#[doc = r#"This is not a footnote[^1]."#]
//~^ doc_suspicious_footnotes
#[doc = r#""#]
#[doc = r#"This is not a footnote[^either], but it doesn't warn."#]
#[doc = r#""#]
#[doc = r#"This is not a footnote\[^1], but it also doesn't warn."#]
#[doc = r#""#]
#[doc = r#"This is not a footnote[^1\], but it also doesn't warn."#]
#[doc = r#""#]
#[doc = r#"This is not a `footnote[^1]`, but it also doesn't warn."#]
#[doc = r#""#]
#[doc = r#"This is a footnote[^2]."#]
#[doc = r#""#]
#[doc = r#"[^2]: hello world"#]
pub fn footnotes() {
// test code goes here
}
#[doc = "This is not a footnote[^1].
This is not a footnote[^either], but it doesn't warn.
This is not a footnote\\[^1], but it also doesn't warn.
This is not a footnote[^1\\], but it also doesn't warn.
This is not a `footnote[^1]`, but it also doesn't warn.
This is a footnote[^2].
[^2]: hello world
"]
//~^^^^^^^^^^^^^^ doc_suspicious_footnotes
pub fn footnotes2() {
// test code goes here
}
#[cfg_attr(
not(FALSE),
doc = "This is not a footnote[^1].\n\nThis is not a footnote[^either], but it doesn't warn."
//~^ doc_suspicious_footnotes
)]
pub fn footnotes3() {
// test code goes here
}
#[doc = "My footnote [^foot\note]"]
pub fn footnote4() {
// test code goes here
}
#[doc = "Hihi"]pub fn footnote5() {
// test code goes here
}
}
#[doc = r"This is not a footnote[^1]."]
//~^ doc_suspicious_footnotes
#[doc = r""]
#[doc = r"This is not a footnote[^either], but it doesn't warn."]
#[doc = r""]
#[doc = r"This is not a footnote\[^1], but it also doesn't warn."]
#[doc = r""]
#[doc = r"This is not a footnote[^1\], but it also doesn't warn."]
#[doc = r""]
#[doc = r"This is not a `footnote[^1]`, but it also doesn't warn."]
#[doc = r""]
#[doc = r"This is a footnote[^2]."]
#[doc = r""]
#[doc = r"[^2]: hello world"]
pub fn footnotes_attrs() {
// test code goes here
}
pub mod multiline {
/*!
* This is not a footnote[^1]. //~ doc_suspicious_footnotes
*
* This is not a footnote\[^1], but it doesn't warn.
*
* This is a footnote[^2].
*
* These give weird results, but correct ones, so it works.
*
* [^2]: hello world
*/
/**
* This is not a footnote[^1]. //~ doc_suspicious_footnotes
*
* This is not a footnote\[^1], but it doesn't warn.
*
* This is a footnote[^2].
*
* These give weird results, but correct ones, so it works.
*
* [^2]: hello world
*/
pub fn foo() {}
}
/// This is not a footnote [^1]
//~^ doc_suspicious_footnotes
///
/// This one is [^2]
///
/// [^2]: contents
#[doc = "This is not a footnote [^3]"]
//~^ doc_suspicious_footnotes
#[doc = ""]
#[doc = "This one is [^4]"]
#[doc = ""]
#[doc = "[^4]: contents"]
pub struct MultiFragmentFootnote;
#[doc(inline)]
/// This is not a footnote [^5]
//~^ doc_suspicious_footnotes
///
/// This one is [^6]
///
/// [^6]: contents
#[doc = "This is not a footnote [^7]"]
//~^ doc_suspicious_footnotes
#[doc = ""]
#[doc = "This one is [^8]"]
#[doc = ""]
#[doc = "[^8]: contents"]
pub use MultiFragmentFootnote as OtherInlinedFootnote;

View file

@ -0,0 +1,179 @@
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:3:27
|
LL | //! This is not a footnote[^1].
| ^^^^
|
= note: `-D clippy::doc-suspicious-footnotes` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::doc_suspicious_footnotes)]`
help: add footnote definition
|
LL ~ //! This is not a footnote[^1].
LL + //!
LL + //! [^1]: <!-- description -->
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:18:27
|
LL | /// This is not a footnote[^1].
| ^^^^
|
help: add footnote definition
|
LL ~ /// This is not a footnote[^1].
LL + ///
LL + /// [^1]: <!-- description -->
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:39:13
|
LL | #[doc = r#"This is not a footnote[^1]."#]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: add footnote definition
|
LL ~ #[doc = r#"This is not a footnote[^1].
LL +
LL ~ [^1]: <!-- description -->"#]
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:56:13
|
LL | #[doc = "This is not a footnote[^1].
| _____________^
LL | |
LL | | This is not a footnote[^either], but it doesn't warn.
... |
LL | | [^2]: hello world
LL | | "]
| |_____^
|
help: add footnote definition
|
LL ~ #[doc = r#"This is not a footnote[^1].
LL +
LL + This is not a footnote[^either], but it doesn't warn.
LL +
LL + This is not a footnote\[^1], but it also doesn't warn.
LL +
LL + This is not a footnote[^1\], but it also doesn't warn.
LL +
LL + This is not a `footnote[^1]`, but it also doesn't warn.
LL +
LL + This is a footnote[^2].
LL +
LL + [^2]: hello world
LL +
LL +
LL ~ [^1]: <!-- description -->"#]
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:76:38
|
LL | doc = "This is not a footnote[^1].\n\nThis is not a footnote[^either], but it doesn't warn."
| ^^^^
|
help: add footnote definition
|
LL ~ doc = r#"This is not a footnote[^1].
LL +
LL + This is not a footnote[^either], but it doesn't warn.
LL +
LL + [^1]: <!-- description -->"#
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:91:9
|
LL | #[doc = r"This is not a footnote[^1]."]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: add footnote definition
|
LL ~ #[doc = r#"This is not a footnote[^1].
LL +
LL ~ [^1]: <!-- description -->"#]
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:111:30
|
LL | * This is not a footnote[^1].
| ^^^^
|
help: add footnote definition
|
LL ~ */
LL + /*! [^1]: <!-- description --> */
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:122:30
|
LL | * This is not a footnote[^1].
| ^^^^
|
help: add footnote definition
|
LL ~ */
LL + /** [^1]: <!-- description --> */
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:135:28
|
LL | /// This is not a footnote [^1]
| ^^^^
|
help: add footnote definition
|
LL ~ /// This is not a footnote [^1]
LL + ///
LL + /// [^1]: <!-- description -->
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:141:33
|
LL | #[doc = "This is not a footnote [^3]"]
| ^^^^
|
help: add footnote definition
|
LL ~ #[doc = r#"This is not a footnote [^3]
LL +
LL ~ [^3]: <!-- description -->"#]
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:150:28
|
LL | /// This is not a footnote [^5]
| ^^^^
|
help: add footnote definition
|
LL ~ /// This is not a footnote [^5]
LL + ///
LL + /// [^5]: <!-- description -->
|
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes.rs:156:33
|
LL | #[doc = "This is not a footnote [^7]"]
| ^^^^
|
help: add footnote definition
|
LL ~ #[doc = r#"This is not a footnote [^7]
LL +
LL ~ [^7]: <!-- description -->"#]
|
error: aborting due to 12 previous errors

View file

@ -0,0 +1,4 @@
//@ error-in-other-file: footnote
//@ no-rustfix
#![warn(clippy::doc_suspicious_footnotes)]
#![doc=include_str!("doc_suspicious_footnotes_include.txt")]

View file

@ -0,0 +1,17 @@
error: looks like a footnote ref, but has no matching footnote
--> tests/ui/doc_suspicious_footnotes_include.txt:1:23
|
LL | This is not a footnote[^1].
| ^^^^
|
= note: `-D clippy::doc-suspicious-footnotes` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::doc_suspicious_footnotes)]`
help: add footnote definition
|
LL ~ [^2]: hello world
LL +
LL + [^1]: <!-- description -->
|
error: aborting due to 1 previous error

View file

@ -0,0 +1,13 @@
This is not a footnote[^1]. //~ doc_suspicious_footnotes
This is not a footnote[^either], but it doesn't warn.
This is not a footnote\[^1], but it also doesn't warn.
This is not a footnote[^1\], but it also doesn't warn.
This is not a `footnote[^1]`, but it also doesn't warn.
This is a footnote[^2].
[^2]: hello world