Skip to content

Commit 273dd4d

Browse files
Add new rustdoc broken_footnote lint
1 parent 2b285cd commit 273dd4d

File tree

3 files changed

+76
-0
lines changed

3 files changed

+76
-0
lines changed

src/librustdoc/lint.rs

+8
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@ declare_rustdoc_lint! {
204204
"detects markdown that is interpreted differently in different parser"
205205
}
206206

207+
declare_rustdoc_lint! {
208+
/// This lint checks for uses of footnote references without definition.
209+
BROKEN_FOOTNOTE,
210+
Warn,
211+
"footnote reference with no associated definition"
212+
}
213+
207214
pub(crate) static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
208215
vec![
209216
BROKEN_INTRA_DOC_LINKS,
@@ -218,6 +225,7 @@ pub(crate) static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
218225
UNESCAPED_BACKTICKS,
219226
REDUNDANT_EXPLICIT_LINKS,
220227
UNPORTABLE_MARKDOWN,
228+
BROKEN_FOOTNOTE,
221229
]
222230
});
223231

src/librustdoc/passes/lint.rs

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
44
mod bare_urls;
55
mod check_code_block_syntax;
6+
mod footnotes;
67
mod html_tags;
78
mod redundant_explicit_links;
89
mod unescaped_backticks;
@@ -42,6 +43,7 @@ impl DocVisitor<'_> for Linter<'_, '_> {
4243
if may_have_link {
4344
bare_urls::visit_item(self.cx, item, hir_id, &dox);
4445
redundant_explicit_links::visit_item(self.cx, item, hir_id);
46+
footnotes::visit_item(self.cx, item, hir_id, &dox);
4547
}
4648
if may_have_code {
4749
check_code_block_syntax::visit_item(self.cx, item, &dox);
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//! Detects specific markdown syntax that's different between pulldown-cmark
2+
//! 0.9 and 0.11.
3+
//!
4+
//! This is a mitigation for old parser bugs that affected some
5+
//! real crates' docs. The old parser claimed to comply with CommonMark,
6+
//! but it did not. These warnings will eventually be removed,
7+
//! though some of them may become Clippy lints.
8+
//!
9+
//! <https://github.com/rust-lang/rust/pull/121659#issuecomment-1992752820>
10+
//!
11+
//! <https://rustc-dev-guide.rust-lang.org/bug-fix-procedure.html#add-the-lint-to-the-list-of-removed-lists>
12+
13+
use std::ops::Range;
14+
15+
use pulldown_cmark::{Event, Options, Parser};
16+
use rustc_data_structures::fx::FxHashSet;
17+
use rustc_hir::HirId;
18+
use rustc_lint_defs::Applicability;
19+
use rustc_resolve::rustdoc::source_span_for_markdown_range;
20+
21+
use crate::clean::Item;
22+
use crate::core::DocContext;
23+
24+
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
25+
let tcx = cx.tcx;
26+
27+
let mut missing_footnote_references = FxHashSet::default();
28+
29+
let options = Options::ENABLE_FOOTNOTES;
30+
let mut parser = Parser::new_ext(dox, options).into_offset_iter().peekable();
31+
while let Some((event, span)) = parser.next() {
32+
match event {
33+
Event::Text(text)
34+
if &*text == "["
35+
&& let Some((Event::Text(text), _)) = parser.peek()
36+
&& text.trim_start().starts_with('^')
37+
&& parser.next().is_some()
38+
&& let Some((Event::Text(text), end_span)) = parser.peek()
39+
&& &**text == "]" =>
40+
{
41+
missing_footnote_references.insert(Range { start: span.start, end: end_span.end });
42+
}
43+
_ => {}
44+
}
45+
}
46+
47+
#[allow(rustc::potential_query_instability)]
48+
for span in missing_footnote_references {
49+
let (ref_span, precise) =
50+
source_span_for_markdown_range(tcx, dox, &span, &item.attrs.doc_strings)
51+
.map(|span| (span, true))
52+
.unwrap_or_else(|| (item.attr_span(tcx), false));
53+
54+
if precise {
55+
tcx.node_span_lint(crate::lint::BROKEN_FOOTNOTE, hir_id, ref_span, |lint| {
56+
lint.primary_message("no footnote definition matching this footnote");
57+
lint.span_suggestion(
58+
ref_span.shrink_to_lo(),
59+
"if it should not be a footnote, escape it",
60+
"\\",
61+
Applicability::MaybeIncorrect,
62+
);
63+
});
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)