-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
1356e76
99b16ae
98426f5
3d8e0d3
a5f4d5d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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", | ||
notriddle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|diag| { | ||
let mut applicability = Applicability::MachineApplicable; | ||
let snippet = snippet_with_applicability(cx, span, "..", &mut applicability); | ||
diag.span_suggestion_verbose(span, "try", format!("`{snippet}`"), applicability); | ||
notriddle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
); | ||
} | ||
} | ||
} | ||
|
||
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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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. | ||
/// | ||
notriddle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// ### 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, | ||
|
@@ -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 { | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
} | ||
} | ||
FootnoteReference(_) => {} | ||
|
notriddle marked this conversation as resolved.
Show resolved
Hide resolved
|
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 | ||
} |
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 | ||
notriddle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pub fn footnotes() { | ||
// test code goes here | ||
} |
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 | ||
|
There was a problem hiding this comment.
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]
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#14708 (comment)