Skip to content

doc_suspicious_footnotes: lint text that looks like a footnote #14708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5636,6 +5636,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
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,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,
Expand Down
48 changes: 48 additions & 0 deletions clippy_lints/src/doc/doc_suspicious_footnotes.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all_numbers_upto_brace should probably also support letters, as those are also as an option for rustdoc footnotes ([^myfootnote]).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_lint::LateContext;

use std::ops::Range;

use super::{DOC_SUSPICIOUS_FOOTNOTES, Fragments};

pub fn check(cx: &LateContext<'_>, doc: &str, range: Range<usize>, fragments: &Fragments<'_>) {
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(span) = fragments.span(cx, start..end)
{
span_lint_and_then(
cx,
DOC_SUSPICIOUS_FOOTNOTES,
span,
"looks like a footnote ref, but no matching footnote",
|diag| {
let mut applicability = Applicability::MachineApplicable;
let snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
diag.span_suggestion_verbose(span, "try", format!("`{snippet}`"), applicability);
},
);
}
}
}

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
}
35 changes: 33 additions & 2 deletions clippy_lints/src/doc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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;
Expand Down Expand Up @@ -608,6 +609,34 @@ 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,
/// because it matches the regexp `\[\^[0-9]+\]`,
/// but has no referent.
///
/// ### Why is this bad?
/// This probably means that a definition 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,
Expand Down Expand Up @@ -639,7 +668,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 {
Expand Down Expand Up @@ -1147,7 +1177,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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure that you've already tried this, but is there a reason that you didn't use FootnoteReference from ķEvent? You would not have to do span magic and manually parse footnotes and it seems that this function doesn't actually use text

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FootnoteReference event is only emitted if the code is actually a footnote. This lint fires for text that looks like a footnote reference but isn't.

}
}
FootnoteReference(_) => {}
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/doc_suspicious_footnotes.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![warn(clippy::doc_suspicious_footnotes)]

/// 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
}
19 changes: 19 additions & 0 deletions tests/ui/doc_suspicious_footnotes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![warn(clippy::doc_suspicious_footnotes)]

/// 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
}
16 changes: 16 additions & 0 deletions tests/ui/doc_suspicious_footnotes.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
error: looks like a footnote ref, but 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: try
|
LL - /// This is not a footnote[^1].
LL + /// This is not a footnote`[^1]`.
|

error: aborting due to 1 previous error