sway_lsp/capabilities/
diagnostic.rs

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use std::collections::HashMap;
use std::path::PathBuf;

use lsp_types::{Diagnostic, DiagnosticSeverity, DiagnosticTag, Position, Range};
use serde::{Deserialize, Serialize};
use sway_error::warning::CompileWarning;
use sway_error::{error::CompileError, warning::Warning};
use sway_types::{LineCol, LineColRange, SourceEngine, Spanned};

pub(crate) type DiagnosticMap = HashMap<PathBuf, Diagnostics>;

#[derive(Debug, Default, Clone)]
pub struct Diagnostics {
    pub warnings: Vec<Diagnostic>,
    pub errors: Vec<Diagnostic>,
}

fn get_error_diagnostic(error: &CompileError) -> Diagnostic {
    let data = serde_json::to_value(DiagnosticData::try_from(error.clone()).ok()).ok();

    Diagnostic {
        range: get_range(error.span().line_col()),
        severity: Some(DiagnosticSeverity::ERROR),
        message: format!("{error}"),
        data,
        ..Default::default()
    }
}

fn get_warning_diagnostic(warning: &CompileWarning) -> Diagnostic {
    Diagnostic {
        range: get_range(warning.span().line_col()),
        severity: Some(DiagnosticSeverity::WARNING),
        message: warning.to_friendly_warning_string(),
        tags: get_warning_diagnostic_tags(&warning.warning_content),
        ..Default::default()
    }
}

pub fn get_diagnostics(
    warnings: &[CompileWarning],
    errors: &[CompileError],
    source_engine: &SourceEngine,
) -> DiagnosticMap {
    let mut diagnostics = DiagnosticMap::new();
    for warning in warnings {
        let diagnostic = get_warning_diagnostic(warning);
        if let Some(source_id) = warning.span().source_id() {
            let path = source_engine.get_path(source_id);
            diagnostics
                .entry(path)
                .or_default()
                .warnings
                .push(diagnostic);
        }
    }
    for error in errors {
        let diagnostic = get_error_diagnostic(error);
        if let Some(source_id) = error.span().source_id() {
            let path = source_engine.get_path(source_id);
            diagnostics.entry(path).or_default().errors.push(diagnostic);
        }
    }
    diagnostics
}

fn get_range(LineColRange { start, end }: LineColRange) -> Range {
    let pos = |lc: LineCol| Position::new(lc.line as u32 - 1, lc.col as u32 - 1);
    let start = pos(start);
    let end = pos(end);
    Range { start, end }
}

fn get_warning_diagnostic_tags(warning: &Warning) -> Option<Vec<DiagnosticTag>> {
    match warning {
        Warning::StructFieldNeverRead
        | Warning::DeadDeclaration
        | Warning::DeadEnumDeclaration
        | Warning::DeadEnumVariant { .. }
        | Warning::DeadFunctionDeclaration
        | Warning::DeadMethod
        | Warning::DeadStorageDeclaration
        | Warning::DeadStorageDeclarationForFunction { .. }
        | Warning::DeadStructDeclaration
        | Warning::DeadTrait
        | Warning::MatchExpressionUnreachableArm { .. }
        | Warning::UnreachableCode
        | Warning::UnusedReturnValue { .. } => Some(vec![DiagnosticTag::UNNECESSARY]),
        _ => None,
    }
}

/// Extra data to be sent with a diagnostic and provided in CodeAction context.
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct DiagnosticData {
    pub unknown_symbol_name: Option<String>,
}

impl TryFrom<CompileWarning> for DiagnosticData {
    type Error = anyhow::Error;

    fn try_from(_value: CompileWarning) -> Result<Self, Self::Error> {
        anyhow::bail!("Not implemented");
    }
}

impl TryFrom<CompileError> for DiagnosticData {
    type Error = anyhow::Error;

    fn try_from(value: CompileError) -> Result<Self, Self::Error> {
        match value {
            CompileError::SymbolNotFound { name, .. } => Ok(DiagnosticData {
                unknown_symbol_name: Some(name.to_string()),
            }),
            CompileError::TraitNotFound { name, .. } => Ok(DiagnosticData {
                unknown_symbol_name: Some(name),
            }),
            CompileError::UnknownVariable { var_name, .. } => Ok(DiagnosticData {
                unknown_symbol_name: Some(var_name.to_string()),
            }),
            _ => anyhow::bail!("Not implemented"),
        }
    }
}