Skip to content

Commit 47fc515

Browse files
committed
Auto merge of #158493 - JonathanBrouwer:rollup-dmiIIUL, r=JonathanBrouwer
Rollup of 4 pull requests Successful merges: - #137858 (Add new `unused_footnote_definition` rustdoc lint) - #158233 (Allow the unstable attribute on foreign type) - #158470 (Upgrade `jsonsocck` and `jsondoclint` to edition 2024.) - #158488 (Upgrade `rustdoc-json-types` to 2024 edition.)
2 parents 13f1859 + 179fcc2 commit 47fc515

14 files changed

Lines changed: 316 additions & 47 deletions

File tree

compiler/rustc_attr_parsing/src/attributes/stability.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[
4040
Allow(Target::Static),
4141
Allow(Target::ForeignFn),
4242
Allow(Target::ForeignStatic),
43+
Allow(Target::ForeignTy),
4344
Allow(Target::ExternCrate),
4445
]);
4546

src/librustdoc/lint.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,20 @@ declare_rustdoc_lint! {
196196
"detects redundant explicit links in doc comments"
197197
}
198198

199+
declare_rustdoc_lint! {
200+
/// This lint checks for uses of footnote references without definition.
201+
BROKEN_FOOTNOTE,
202+
Warn,
203+
"detects footnote references with no associated definition"
204+
}
205+
206+
declare_rustdoc_lint! {
207+
/// This lint checks if all footnote definitions are used.
208+
UNUSED_FOOTNOTE_DEFINITION,
209+
Warn,
210+
"detects unused footnote definitions"
211+
}
212+
199213
pub(crate) static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
200214
vec![
201215
BROKEN_INTRA_DOC_LINKS,
@@ -209,6 +223,8 @@ pub(crate) static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
209223
MISSING_CRATE_LEVEL_DOCS,
210224
UNESCAPED_BACKTICKS,
211225
REDUNDANT_EXPLICIT_LINKS,
226+
BROKEN_FOOTNOTE,
227+
UNUSED_FOOTNOTE_DEFINITION,
212228
]
213229
});
214230

src/librustdoc/passes/lint.rs

Lines changed: 2 additions & 0 deletions
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;
@@ -41,6 +42,7 @@ impl DocVisitor<'_> for Linter<'_, '_> {
4142
if may_have_link {
4243
bare_urls::visit_item(self.cx, item, hir_id, &dox);
4344
redundant_explicit_links::visit_item(self.cx, item, hir_id);
45+
footnotes::visit_item(self.cx, item, hir_id, &dox);
4446
}
4547
if may_have_code {
4648
check_code_block_syntax::visit_item(self.cx, item, &dox);
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use std::ops::Range;
2+
3+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
4+
use rustc_errors::DiagDecorator;
5+
use rustc_hir::HirId;
6+
use rustc_lint_defs::Applicability;
7+
use rustc_resolve::rustdoc::pulldown_cmark::{Event, Options, Parser, Tag};
8+
use rustc_resolve::rustdoc::source_span_for_markdown_range;
9+
10+
use crate::clean::Item;
11+
use crate::core::DocContext;
12+
13+
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
14+
let tcx = cx.tcx;
15+
16+
let mut missing_footnote_references = FxHashSet::default();
17+
let mut footnote_references = FxHashSet::default();
18+
let mut footnote_definitions = FxHashMap::default();
19+
20+
let options = Options::ENABLE_FOOTNOTES;
21+
let mut parser = Parser::new_ext(dox, options).into_offset_iter().peekable();
22+
while let Some((event, span)) = parser.next() {
23+
match event {
24+
Event::Text(text)
25+
if &*text == "["
26+
&& (span.start == 0 || dox.as_bytes().get(span.start - 1) != Some(&b'\\'))
27+
&& let Some(len) = scan_footnote_ref(&dox[span.start..]) =>
28+
{
29+
missing_footnote_references
30+
.insert(Range { start: span.start, end: span.start + len });
31+
}
32+
Event::FootnoteReference(label) => {
33+
footnote_references.insert(label);
34+
}
35+
Event::Start(Tag::FootnoteDefinition(label)) => {
36+
footnote_definitions.insert(label, span.start + 1);
37+
}
38+
_ => {}
39+
}
40+
}
41+
42+
#[allow(rustc::potential_query_instability)]
43+
for (footnote, span) in footnote_definitions {
44+
if !footnote_references.contains(&footnote) {
45+
let (span, _) = source_span_for_markdown_range(
46+
tcx,
47+
dox,
48+
&(span..span + 1),
49+
&item.attrs.doc_strings,
50+
)
51+
.unwrap_or_else(|| (item.attr_span(tcx), false));
52+
53+
tcx.emit_node_span_lint(
54+
crate::lint::UNUSED_FOOTNOTE_DEFINITION,
55+
hir_id,
56+
span,
57+
DiagDecorator(|lint| {
58+
lint.primary_message("unused footnote definition");
59+
}),
60+
);
61+
}
62+
}
63+
64+
#[allow(rustc::potential_query_instability)]
65+
for span in missing_footnote_references {
66+
let ref_span = source_span_for_markdown_range(tcx, dox, &span, &item.attrs.doc_strings)
67+
.map(|(span, _)| span)
68+
.unwrap_or_else(|| item.attr_span(tcx));
69+
70+
tcx.emit_node_span_lint(
71+
crate::lint::BROKEN_FOOTNOTE,
72+
hir_id,
73+
ref_span,
74+
DiagDecorator(|lint| {
75+
lint.primary_message("no footnote definition matching this footnote");
76+
lint.span_suggestion(
77+
ref_span.shrink_to_lo(),
78+
"if it should not be a footnote, escape it",
79+
"\\",
80+
Applicability::MaybeIncorrect,
81+
);
82+
}),
83+
);
84+
}
85+
}
86+
87+
fn scan_footnote_ref(dox: &str) -> Option<usize> {
88+
let dox = dox.as_bytes();
89+
let mut i = 0;
90+
if dox.get(i) != Some(&b'[') {
91+
return None;
92+
}
93+
i += 1;
94+
if dox.get(i) != Some(&b'^') {
95+
return None;
96+
}
97+
i += 1;
98+
while let Some(&c) = dox.get(i) {
99+
if c == b']' {
100+
i += 1;
101+
return Some(i);
102+
}
103+
if c == b'\r' || c == b'\n' || c == b'[' {
104+
// Can't nest things like this.
105+
break;
106+
}
107+
if c == b'\\' {
108+
i += 1;
109+
}
110+
if dox.get(i) == Some(&b'\r') || dox.get(i) == Some(&b'\n') {
111+
// Can't have line breaks in footnote refs
112+
break;
113+
}
114+
i += 1;
115+
}
116+
None
117+
}

src/rustdoc-json-types/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "rustdoc-json-types"
33
version = "0.1.0"
4-
edition = "2021"
4+
edition = "2024"
55

66
[lib]
77
path = "lib.rs"

src/tools/jsondocck/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "jsondocck"
33
version = "0.1.0"
4-
edition = "2021"
4+
edition = "2024"
55

66
[dependencies]
77
jsonpath-rust = "1.0.0"

src/tools/jsondoclint/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "jsondoclint"
33
version = "0.1.0"
4-
edition = "2021"
4+
edition = "2024"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#![deny(rustdoc::broken_footnote)]
2+
3+
//! Footnote referenced [^1]. And [^2]. And [^bla].
4+
//!
5+
//! [^1]: footnote defined
6+
//~^^^ ERROR: no footnote definition matching this footnote
7+
//~| ERROR: no footnote definition matching this footnote
8+
9+
// Should not lint.
10+
//! foo[^1]
11+
//!
12+
//! ```
13+
//!
14+
//! [^1]: bar
15+
//!
16+
//! ```
17+
18+
// Edge cases from https://pulldown-cmark.github.io/pulldown-cmark/specs/footnotes.html
19+
/// The following are not footnote references:
20+
///
21+
/// \[^a]
22+
///
23+
/// [\^b]
24+
///
25+
/// [^c\]
26+
///
27+
/// [^d
28+
/// e]
29+
///
30+
/// [^f\
31+
/// g]
32+
pub struct NotReferences;
33+
34+
/// The following are not footnote references:
35+
///
36+
/// [^a b]
37+
//~^ ERROR: no footnote definition matching this footnote
38+
///
39+
/// [^1\.2]
40+
//~^ ERROR: no footnote definition matching this footnote
41+
///
42+
/// [^*]
43+
//~^ ERROR: no footnote definition matching this footnote
44+
pub struct EdgeCases;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
error: no footnote definition matching this footnote
2+
--> $DIR/broken-footnote.rs:3:45
3+
|
4+
LL | //! Footnote referenced [^1]. And [^2]. And [^bla].
5+
| -^^^^^
6+
| |
7+
| help: if it should not be a footnote, escape it: `\`
8+
|
9+
note: the lint level is defined here
10+
--> $DIR/broken-footnote.rs:1:9
11+
|
12+
LL | #![deny(rustdoc::broken_footnote)]
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^
14+
15+
error: no footnote definition matching this footnote
16+
--> $DIR/broken-footnote.rs:3:35
17+
|
18+
LL | //! Footnote referenced [^1]. And [^2]. And [^bla].
19+
| -^^^
20+
| |
21+
| help: if it should not be a footnote, escape it: `\`
22+
23+
error: no footnote definition matching this footnote
24+
--> $DIR/broken-footnote.rs:39:5
25+
|
26+
LL | /// [^1\.2]
27+
| -^^^^^^
28+
| |
29+
| help: if it should not be a footnote, escape it: `\`
30+
31+
error: no footnote definition matching this footnote
32+
--> $DIR/broken-footnote.rs:42:5
33+
|
34+
LL | /// [^*]
35+
| -^^^
36+
| |
37+
| help: if it should not be a footnote, escape it: `\`
38+
39+
error: no footnote definition matching this footnote
40+
--> $DIR/broken-footnote.rs:36:5
41+
|
42+
LL | /// [^a b]
43+
| -^^^^^
44+
| |
45+
| help: if it should not be a footnote, escape it: `\`
46+
47+
error: aborting due to 5 previous errors
48+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// This test ensures that the `rustdoc::unused_footnote` lint is working as expected.
2+
3+
#![deny(rustdoc::unused_footnote_definition)]
4+
5+
//! Footnote referenced. [^2]
6+
//!
7+
//! [^1]: footnote defined
8+
//! [^2]: footnote defined
9+
//~^^ ERROR: unused_footnote_definition

0 commit comments

Comments
 (0)