Skip to content

Commit 23abd20

Browse files
authored
Merge pull request #2681 from krishanjmistry/issue-2649-footnotes
Warn on and ignore duplicate footnote definitions
2 parents 9b12c51 + 7e9be8d commit 23abd20

File tree

4 files changed

+90
-44
lines changed

4 files changed

+90
-44
lines changed

src/utils/mod.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,10 @@ pub fn render_markdown_with_path(
235235
// `count` is the number of references to this footnote (used for multiple
236236
// linkbacks, and checking for unused footnotes).
237237
let mut footnote_numbers = HashMap::new();
238-
// This is a list of (name, Vec<Event>)
238+
// This is a map of name -> Vec<Event>
239239
// `name` is the name of the footnote.
240240
// The events list is the list of events needed to build the footnote definition.
241-
let mut footnote_defs = Vec::new();
241+
let mut footnote_defs = HashMap::new();
242242

243243
// The following are used when currently processing a footnote definition.
244244
//
@@ -268,7 +268,16 @@ pub fn render_markdown_with_path(
268268
Event::End(TagEnd::FootnoteDefinition) => {
269269
let def_events = std::mem::take(&mut in_footnote);
270270
let name = std::mem::take(&mut in_footnote_name);
271-
footnote_defs.push((name, def_events));
271+
272+
if footnote_defs.contains_key(&name) {
273+
log::warn!(
274+
"footnote `{name}` in {} defined multiple times - \
275+
not updating to new definition",
276+
path.map_or_else(|| Cow::from("<unknown>"), |p| p.to_string_lossy())
277+
);
278+
} else {
279+
footnote_defs.insert(name, def_events);
280+
}
272281
None
273282
}
274283
Event::FootnoteReference(name) => {
@@ -304,7 +313,12 @@ pub fn render_markdown_with_path(
304313
html::push_html(&mut body, events);
305314

306315
if !footnote_defs.is_empty() {
307-
add_footnote_defs(&mut body, path, footnote_defs, &footnote_numbers);
316+
add_footnote_defs(
317+
&mut body,
318+
path,
319+
footnote_defs.into_iter().collect(),
320+
&footnote_numbers,
321+
);
308322
}
309323

310324
body

tests/testsuite/markdown.rs

+16-40
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Tests for special markdown rendering.
22
33
use crate::prelude::*;
4+
use snapbox::file;
45

56
// Checks custom header id and classes.
67
#[test]
@@ -17,46 +18,21 @@ fn custom_header_attributes() {
1718
#[test]
1819
fn footnotes() {
1920
BookTest::from_dir("markdown/footnotes")
20-
.check_main_file("book/footnotes.html", str![[r##"
21-
<h1 id="footnote-tests"><a class="header" href="#footnote-tests">Footnote tests</a></h1>
22-
<p>Footnote example<sup class="footnote-reference" id="fr-1-1"><a href="#footnote-1">1</a></sup>, or with a word<sup class="footnote-reference" id="fr-word-1"><a href="#footnote-word">2</a></sup>.</p>
23-
<p>There are multiple references to word<sup class="footnote-reference" id="fr-word-2"><a href="#footnote-word">2</a></sup>.</p>
24-
<p>Footnote without a paragraph<sup class="footnote-reference" id="fr-para-1"><a href="#footnote-para">3</a></sup></p>
25-
<p>Footnote with multiple paragraphs<sup class="footnote-reference" id="fr-multiple-1"><a href="#footnote-multiple">4</a></sup></p>
26-
<p>Footnote name with wacky characters<sup class="footnote-reference" id="fr-&quot;wacky&quot;-1"><a href="#footnote-&quot;wacky&quot;">5</a></sup></p>
27-
<p>Testing when referring to something earlier.<sup class="footnote-reference" id="fr-define-before-use-1"><a href="#footnote-define-before-use">6</a></sup></p>
28-
<hr>
29-
<ol class="footnote-definition"><li id="footnote-1">
30-
<p>This is a footnote. <a href="#fr-1-1">↩</a> <a href="#fr-1-2">↩2</a></p>
31-
</li>
32-
<li id="footnote-word">
33-
<p>A longer footnote.
34-
With multiple lines. <a href="other.html">Link to other</a>.
35-
With a reference inside.<sup class="footnote-reference" id="fr-1-2"><a href="#footnote-1">1</a></sup> <a href="#fr-word-1">↩</a> <a href="#fr-word-2">↩2</a></p>
36-
</li>
37-
<li id="footnote-para">
38-
<ol>
39-
<li>Item one
40-
<ol>
41-
<li>Sub-item</li>
42-
</ol>
43-
</li>
44-
<li>Item two</li>
45-
</ol>
46-
<a href="#fr-para-1">↩</a></li>
47-
<li id="footnote-multiple">
48-
<p>One</p>
49-
<p>Two</p>
50-
<p>Three <a href="#fr-multiple-1">↩</a></p>
51-
</li>
52-
<li id="footnote-&quot;wacky&quot;">
53-
<p>Testing footnote id with special characters. <a href="#fr-&quot;wacky&quot;-1">↩</a></p>
54-
</li>
55-
<li id="footnote-define-before-use">
56-
<p>This is defined before it is referred to. <a href="#fr-define-before-use-1">↩</a></p>
57-
</li>
58-
</ol>
59-
"##]]);
21+
.run("build", |cmd| {
22+
cmd.expect_stderr(str![[r#"
23+
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
24+
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
25+
[TIMESTAMP] [WARN] (mdbook::utils): footnote `multiple-definitions` in <unknown> defined multiple times - not updating to new definition
26+
[TIMESTAMP] [WARN] (mdbook::utils): footnote `unused` in `<unknown>` is defined but not referenced
27+
[TIMESTAMP] [WARN] (mdbook::utils): footnote `multiple-definitions` in footnotes.md defined multiple times - not updating to new definition
28+
[TIMESTAMP] [WARN] (mdbook::utils): footnote `unused` in `footnotes.md` is defined but not referenced
29+
30+
"#]]);
31+
})
32+
.check_main_file(
33+
"book/footnotes.html",
34+
file!["markdown/footnotes/expected/footnotes.html"],
35+
);
6036
}
6137

6238
// Basic table test.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<h1 id="footnote-tests"><a class="header" href="#footnote-tests">Footnote tests</a></h1>
2+
<p>Footnote example<sup class="footnote-reference" id="fr-1-1"><a href="#footnote-1">1</a></sup>, or with a word<sup class="footnote-reference" id="fr-word-1"><a href="#footnote-word">2</a></sup>.</p>
3+
<p>There are multiple references to word<sup class="footnote-reference" id="fr-word-2"><a href="#footnote-word">2</a></sup>.</p>
4+
<p>Footnote without a paragraph<sup class="footnote-reference" id="fr-para-1"><a href="#footnote-para">3</a></sup></p>
5+
<p>Footnote with multiple paragraphs<sup class="footnote-reference" id="fr-multiple-1"><a href="#footnote-multiple">4</a></sup></p>
6+
<p>Footnote name with wacky characters<sup class="footnote-reference" id="fr-&quot;wacky&quot;-1"><a href="#footnote-&quot;wacky&quot;">5</a></sup></p>
7+
<p>Testing when referring to something earlier.<sup class="footnote-reference" id="fr-define-before-use-1"><a href="#footnote-define-before-use">6</a></sup></p>
8+
<p>Footnote that is defined multiple times.<sup class="footnote-reference" id="fr-multiple-definitions-1"><a href="#footnote-multiple-definitions">7</a></sup></p>
9+
<p>And another<sup class="footnote-reference" id="fr-in-between-1"><a href="#footnote-in-between">8</a></sup> that references the duplicate again.<sup class="footnote-reference" id="fr-multiple-definitions-2"><a href="#footnote-multiple-definitions">7</a></sup></p>
10+
<hr>
11+
<ol class="footnote-definition"><li id="footnote-1">
12+
<p>This is a footnote. <a href="#fr-1-1"></a> <a href="#fr-1-2">↩2</a></p>
13+
</li>
14+
<li id="footnote-word">
15+
<p>A longer footnote.
16+
With multiple lines. <a href="other.html">Link to other</a>.
17+
With a reference inside.<sup class="footnote-reference" id="fr-1-2"><a href="#footnote-1">1</a></sup> <a href="#fr-word-1"></a> <a href="#fr-word-2">↩2</a></p>
18+
</li>
19+
<li id="footnote-para">
20+
<ol>
21+
<li>Item one
22+
<ol>
23+
<li>Sub-item</li>
24+
</ol>
25+
</li>
26+
<li>Item two</li>
27+
</ol>
28+
<a href="#fr-para-1"></a></li>
29+
<li id="footnote-multiple">
30+
<p>One</p>
31+
<p>Two</p>
32+
<p>Three <a href="#fr-multiple-1"></a></p>
33+
</li>
34+
<li id="footnote-&quot;wacky&quot;">
35+
<p>Testing footnote id with special characters. <a href="#fr-&quot;wacky&quot;-1"></a></p>
36+
</li>
37+
<li id="footnote-define-before-use">
38+
<p>This is defined before it is referred to. <a href="#fr-define-before-use-1"></a></p>
39+
</li>
40+
<li id="footnote-multiple-definitions">
41+
<p>This is the first definition of the footnote with tag multiple-definitions <a href="#fr-multiple-definitions-1"></a> <a href="#fr-multiple-definitions-2">↩2</a></p>
42+
</li>
43+
<li id="footnote-in-between">
44+
<p>Footnote between duplicates. <a href="#fr-in-between-1"></a></p>
45+
</li>
46+
</ol>

tests/testsuite/markdown/footnotes/src/footnotes.md

+10
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,13 @@ Footnote name with wacky characters[^"wacky"]
3535
[^"wacky"]: Testing footnote id with special characters.
3636

3737
Testing when referring to something earlier.[^define-before-use]
38+
39+
Footnote that is defined multiple times.[^multiple-definitions]
40+
41+
[^multiple-definitions]: This is the first definition of the footnote with tag multiple-definitions
42+
43+
And another[^in-between] that references the duplicate again.[^multiple-definitions]
44+
45+
[^in-between]: Footnote between duplicates.
46+
47+
[^multiple-definitions]: This is the second definition of the footnote with tag multiple-definitions

0 commit comments

Comments
 (0)