summaryrefslogtreecommitdiff
path: root/support/texlab/crates/diagnostics/src/grammar/bib.rs
blob: e101e68f2aa79e5bc842c65b9aab0b6b6fcd3ed9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
use base_db::{Document, DocumentData, Workspace};
use rowan::{ast::AstNode, TextRange};
use syntax::bibtex::{self, HasDelims, HasEq, HasName, HasType, HasValue};

use crate::{
    types::{BibError, DiagnosticData},
    util::SimpleDiagnosticSource,
    Diagnostic, DiagnosticBuilder, DiagnosticSource,
};

#[derive(Default)]
pub struct BibSyntaxErrors(SimpleDiagnosticSource);

impl DiagnosticSource for BibSyntaxErrors {
    fn update(&mut self, _workspace: &Workspace, document: &Document) {
        let mut analyzer = Analyzer {
            document,
            diagnostics: Vec::new(),
        };

        analyzer.analyze_root();
        self.0
            .errors
            .insert(document.uri.clone(), analyzer.diagnostics);
    }

    fn publish<'db>(
        &'db mut self,
        workspace: &'db Workspace,
        builder: &mut DiagnosticBuilder<'db>,
    ) {
        self.0.publish(workspace, builder);
    }
}

struct Analyzer<'a> {
    document: &'a Document,
    diagnostics: Vec<Diagnostic>,
}

impl<'a> Analyzer<'a> {
    fn analyze_root(&mut self) {
        let DocumentData::Bib(data) = &self.document.data else { return };

        for node in bibtex::SyntaxNode::new_root(data.green.clone()).descendants() {
            if let Some(entry) = bibtex::Entry::cast(node.clone()) {
                self.analyze_entry(entry);
            } else if let Some(field) = bibtex::Field::cast(node.clone()) {
                self.analyze_field(field);
            }
        }
    }

    fn analyze_entry(&mut self, entry: bibtex::Entry) {
        if entry.left_delim_token().is_none() {
            let offset = entry.type_token().unwrap().text_range().end();
            self.diagnostics.push(Diagnostic {
                range: TextRange::empty(offset),
                data: DiagnosticData::Bib(BibError::ExpectingLCurly),
            });

            return;
        }

        if entry.name_token().is_none() {
            let offset = entry.left_delim_token().unwrap().text_range().end();
            self.diagnostics.push(Diagnostic {
                range: TextRange::empty(offset),
                data: DiagnosticData::Bib(BibError::ExpectingKey),
            });

            return;
        }

        if entry.right_delim_token().is_none() {
            let offset = entry.syntax().text_range().end();
            self.diagnostics.push(Diagnostic {
                range: TextRange::empty(offset),
                data: DiagnosticData::Bib(BibError::ExpectingRCurly),
            });
        }
    }

    fn analyze_field(&mut self, field: bibtex::Field) {
        if field.eq_token().is_none() {
            let offset = field.name_token().unwrap().text_range().end();
            self.diagnostics.push(Diagnostic {
                range: TextRange::empty(offset),
                data: DiagnosticData::Bib(BibError::ExpectingEq),
            });

            return;
        }

        if field.value().is_none() {
            let offset = field.eq_token().unwrap().text_range().end();
            self.diagnostics.push(Diagnostic {
                range: TextRange::empty(offset),
                data: DiagnosticData::Bib(BibError::ExpectingFieldValue),
            });
        }
    }
}