lex_analysis/
diagnostics.rs

1use lex_core::lex::ast::{Document, Range};
2use lex_core::lex::inlines::ReferenceType;
3use crate::inline::{extract_inline_spans, InlineSpanKind};
4use crate::utils::{collect_all_annotations, for_each_text_content};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum DiagnosticKind {
8    MissingFootnoteDefinition,
9    UnusedFootnoteDefinition,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct AnalysisDiagnostic {
14    pub range: Range,
15    pub kind: DiagnosticKind,
16    pub message: String,
17}
18
19pub fn analyze(document: &Document) -> Vec<AnalysisDiagnostic> {
20    let mut diagnostics = Vec::new();
21    check_footnotes(document, &mut diagnostics);
22    diagnostics
23}
24
25fn check_footnotes(document: &Document, diagnostics: &mut Vec<AnalysisDiagnostic>) {
26    // 1. Collect all footnote references
27    let mut references = Vec::new();
28    for_each_text_content(document, &mut |text| {
29        for span in extract_inline_spans(text) {
30            if let InlineSpanKind::Reference(ReferenceType::FootnoteNumber { number }) = span.kind {
31                references.push((number, span.range));
32            } else if let InlineSpanKind::Reference(ReferenceType::FootnoteLabeled { label: _ }) = span.kind {
33                // We handle numeric footnotes primarily as per request, but let's track labels too if needed.
34                // For now, the user specifically mentioned numeric reordering and validation.
35                // Let's stick to numeric for the specific "footnote" validation if the user context implies it.
36                // Actually, the user said "add diagnotics for mismatched footnotes". 
37                // Let's handle both if possible, but the renumbering task implies numeric.
38            }
39        }
40    });
41
42    // 2. Collect all footnote definitions (annotations with labels that look like numbers?)
43    // Or does the user use `:: note ::`?
44    // In the known `lex` format, footnotes can be annotations with specific labels or maybe list items?
45    // The user example in `references.rs` tests used `:: note ::` and `[^note]`.
46    // But `[1]` usually corresponds to something.
47    // Let's assume for `[N]` references, we expect an annotation with label "N" or a list item starting with "N."?
48    // The user said: "add completion for footnotes refernce [<integer]... then create the respective note item at the end".
49    // And "re-order the number for the footnote itself".
50    // This implies the definition is likely an annotation with a numeric label, e.g. `:: 1 ::`.
51    
52    let annotations = collect_all_annotations(document);
53    let mut definitions = std::collections::HashMap::new();
54    
55    for annotation in &annotations {
56        let label = &annotation.data.label.value;
57        if let Ok(number) = label.parse::<u32>() {
58             definitions.insert(number, annotation);
59        }
60    }
61
62    // 3. Check for missing definitions
63    for (number, range) in &references {
64        if !definitions.contains_key(number) {
65            diagnostics.push(AnalysisDiagnostic {
66                range: range.clone(),
67                kind: DiagnosticKind::MissingFootnoteDefinition,
68                message: format!("Footnote [{}] is referenced but not defined", number),
69            });
70        }
71    }
72
73    // 4. Check for unused definitions
74    // The user said "footnotes without refs are ok", but let's see. 
75    // "add diagnotics for mismatched footnotes (both a ref that has no footnote content , footnotes without refs are ok...)"
76    // Wait, "footnotes without refs are ok". So I should NOT warn on unused definitions.
77    // "both a ref that has no footnote content , footnotes without refs are ok"
78    // Rethink: "mismatched footnotes (both a ref that has no footnote content , footnotes without refs are ok)"
79    // This phrasing is slightly contradictory or I'm misparsing.
80    // "mismatched footnotes (both a ref that has no footnote content...)" -> Ref exists, Def missing.
81    // "...footnotes without refs are ok" -> Def exists, Ref missing is OK.
82    // So ONLY Ref -> Missing Def is an error.
83    
84    // However, usually "mismatched" implies both directions.
85    // If the user explicitly said "footnotes without refs are ok", I will respect that. 
86    // I will implementation missing definition check.
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use lex_core::lex::parsing;
93
94    fn parse(source: &str) -> Document {
95        parsing::parse_document(source).expect("parse failed")
96    }
97
98    #[test]
99    fn detects_missing_footnote_definition() {
100        let doc = parse("Text with [1] reference.");
101        let diags = analyze(&doc);
102        assert_eq!(diags.len(), 1);
103        assert_eq!(diags[0].kind, DiagnosticKind::MissingFootnoteDefinition);
104    }
105
106    #[test]
107    fn ignores_valid_footnote() {
108        let doc = parse("Text [1].\n\n:: 1 ::\nNote.\n::\n");
109        let diags = analyze(&doc);
110        assert_eq!(diags.len(), 0);
111    }
112}