1
// Basic syntactic error detection helpers for the LSP API.
2

            
3
use crate::diagnostics::diagnostics_api::{Diagnostic, Position, Range, Severity};
4
use crate::errors::RecoverableParseError;
5
use crate::parse_essence_with_context;
6
use conjure_cp_core::context::Context;
7
use std::sync::{Arc, RwLock};
8

            
9
385
pub fn detect_errors(source: &str) -> Vec<Diagnostic> {
10
385
    let mut diagnostics = Vec::new();
11
385
    let mut errors = vec![];
12
385
    let context = Arc::new(RwLock::new(Context::default()));
13

            
14
385
    match parse_essence_with_context(source, context, &mut errors) {
15
385
        Ok(_model) => {
16
            // Convert all recoverable errors to diagnostics
17
396
            for error in errors {
18
396
                diagnostics.push(error_to_diagnostic(&error));
19
396
            }
20
        }
21
        Err(_fatal) => {
22
            // Fatal error means something went wrong internally (e.g., tree-sitter parser failure)
23
            // We can't provide meaningful diagnostics in this case, so just return empty
24
            // TODO: Figure out how LSP should handle fatal errors from the parser.
25
        }
26
    }
27

            
28
385
    diagnostics
29
385
}
30

            
31
396
pub fn error_to_diagnostic(err: &RecoverableParseError) -> Diagnostic {
32
396
    let (start, end) = range_to_position(&err.range);
33
396
    Diagnostic {
34
396
        range: Range { start, end },
35
396
        severity: Severity::Error,
36
396
        source: "semantic error detection",
37
396
        message: err.msg.clone(),
38
396
    }
39
396
}
40

            
41
396
fn range_to_position(range: &Option<tree_sitter::Range>) -> (Position, Position) {
42
396
    match range {
43
396
        Some(r) => (
44
396
            Position {
45
396
                line: r.start_point.row as u32,
46
396
                character: r.start_point.column as u32,
47
396
            },
48
396
            Position {
49
396
                line: r.end_point.row as u32,
50
396
                character: r.end_point.column as u32,
51
396
            },
52
396
        ),
53
        None => (
54
            Position {
55
                line: 0,
56
                character: 0,
57
            },
58
            Position {
59
                line: 0,
60
                character: 0,
61
            },
62
        ),
63
    }
64
396
}
65

            
66
/// Helper function for tests to compare the actual diagnostic with the expected one.
67
396
pub fn check_diagnostic(
68
396
    diag: &Diagnostic,
69
396
    line_start: u32,
70
396
    char_start: u32,
71
396
    line_end: u32,
72
396
    char_end: u32,
73
396
    msg: &str,
74
396
) {
75
    // Checking range
76
396
    assert_eq!(diag.range.start.line, line_start);
77
396
    assert_eq!(diag.range.start.character, char_start);
78
396
    assert_eq!(diag.range.end.line, line_end);
79
396
    assert_eq!(diag.range.end.character, char_end);
80

            
81
    // Check the message
82
396
    assert_eq!(diag.message, msg);
83
396
}