|
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