|
12 | 12 |
|
13 | 13 | use std::ops::Range; |
14 | 14 |
|
15 | | -use rustc_data_structures::fx::FxHashSet; |
| 15 | +use pulldown_cmark::{Event, Options, Parser, Tag}; |
| 16 | +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
16 | 17 | use rustc_hir::HirId; |
17 | 18 | use rustc_lint_defs::Applicability; |
18 | 19 | use rustc_resolve::rustdoc::source_span_for_markdown_range; |
19 | 20 |
|
20 | 21 | use crate::clean::Item; |
21 | 22 | use crate::core::DocContext; |
22 | 23 |
|
23 | | -use pulldown_cmark::{Event, Options, Parser}; |
24 | | - |
25 | 24 | pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) { |
26 | 25 | let tcx = cx.tcx; |
27 | 26 |
|
28 | 27 | let mut missing_footnote_references = FxHashSet::default(); |
| 28 | + let mut footnote_references = FxHashSet::default(); |
| 29 | + let mut footnote_definitions = FxHashMap::default(); |
29 | 30 |
|
30 | 31 | let options = Options::ENABLE_FOOTNOTES; |
31 | 32 | let mut parser = Parser::new_ext(dox, options).into_offset_iter().peekable(); |
32 | 33 | while let Some((event, span)) = parser.next() { |
33 | 34 | match event { |
34 | | - Event::Text(text) 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 == "]" => |
| 35 | + Event::Text(text) |
| 36 | + if &*text == "[" |
| 37 | + && let Some((Event::Text(text), _)) = parser.peek() |
| 38 | + && text.trim_start().starts_with('^') |
| 39 | + && parser.next().is_some() |
| 40 | + && let Some((Event::Text(text), end_span)) = parser.peek() |
| 41 | + && &**text == "]" => |
40 | 42 | { |
41 | 43 | missing_footnote_references.insert(Range { start: span.start, end: end_span.end }); |
42 | 44 | } |
| 45 | + Event::FootnoteReference(label) => { |
| 46 | + footnote_references.insert(label); |
| 47 | + } |
| 48 | + Event::Start(Tag::FootnoteDefinition(label)) => { |
| 49 | + footnote_definitions.insert(label, span.start + 1); |
| 50 | + } |
43 | 51 | _ => {} |
44 | 52 | } |
45 | 53 | } |
46 | 54 |
|
| 55 | + #[allow(rustc::potential_query_instability)] |
| 56 | + for (footnote, span) in footnote_definitions { |
| 57 | + if !footnote_references.contains(&footnote) { |
| 58 | + let span = source_span_for_markdown_range( |
| 59 | + tcx, |
| 60 | + dox, |
| 61 | + &(span..span + 1), |
| 62 | + &item.attrs.doc_strings, |
| 63 | + ) |
| 64 | + .unwrap_or_else(|| item.attr_span(tcx)); |
| 65 | + |
| 66 | + tcx.node_span_lint(crate::lint::UNUSED_FOOTNOTE_DEFINITION, hir_id, span, |lint| { |
| 67 | + lint.primary_message("unused footnote definition"); |
| 68 | + }); |
| 69 | + } |
| 70 | + } |
| 71 | + |
47 | 72 | #[allow(rustc::potential_query_instability)] |
48 | 73 | for span in missing_footnote_references { |
49 | 74 | let (ref_span, precise) = |
|
0 commit comments