Skip to main content

solidity_language_server/
build.rs

1use crate::utils::byte_offset_to_position;
2use serde_json::Value;
3use std::path::Path;
4use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Range};
5
6pub fn ignored_error_code_warning(value: &serde_json::Value) -> bool {
7    let error_code = value
8        .get("errorCode")
9        .and_then(|v| v.as_str())
10        .unwrap_or_default();
11
12    error_code == "5574" || error_code == "3860"
13}
14
15pub fn build_output_to_diagnostics(
16    forge_output: &serde_json::Value,
17    path: impl AsRef<Path>,
18    content: &str,
19) -> Vec<Diagnostic> {
20    let Some(errors) = forge_output.get("errors").and_then(|v| v.as_array()) else {
21        return Vec::new();
22    };
23    let path = path.as_ref();
24    errors
25        .iter()
26        .filter_map(|err| parse_diagnostic(err, path, content))
27        .collect()
28}
29
30/// Check whether the source path from forge's error output refers to the same
31/// file the editor has open.
32///
33/// Forge reports error paths relative to its working directory (wherever the
34/// LSP process runs from), e.g. `example/Shop.sol` or just `Shop.sol`.  The
35/// editor provides the full absolute path.  We simply check whether the
36/// absolute path ends with the relative path forge reported.
37fn source_location_matches(source_path: &str, path: &Path) -> bool {
38    let source_path = Path::new(source_path);
39    if source_path.is_absolute() {
40        source_path == path
41    } else {
42        path.ends_with(source_path)
43    }
44}
45
46fn parse_diagnostic(err: &Value, path: &Path, content: &str) -> Option<Diagnostic> {
47    if ignored_error_code_warning(err) {
48        return None;
49    }
50    let source_file = err
51        .get("sourceLocation")
52        .and_then(|loc| loc.get("file"))
53        .and_then(|f| f.as_str())?;
54
55    if !source_location_matches(source_file, path) {
56        return None;
57    }
58
59    let start_offset = err
60        .get("sourceLocation")
61        .and_then(|loc| loc.get("start"))
62        .and_then(|s| s.as_u64())
63        .unwrap_or(0) as usize;
64
65    let end_offset = err
66        .get("sourceLocation")
67        .and_then(|loc| loc.get("end"))
68        .and_then(|s| s.as_u64())
69        .map(|v| v as usize)
70        .unwrap_or(start_offset);
71
72    let start = byte_offset_to_position(content, start_offset);
73    let end = byte_offset_to_position(content, end_offset);
74
75    let range = Range { start, end };
76
77    let message = err
78        .get("message")
79        .and_then(|m| m.as_str())
80        .unwrap_or("Unknown error");
81
82    let severity = match err.get("severity").and_then(|s| s.as_str()) {
83        Some("error") => Some(DiagnosticSeverity::ERROR),
84        Some("warning") => Some(DiagnosticSeverity::WARNING),
85        Some("note") => Some(DiagnosticSeverity::INFORMATION),
86        Some("help") => Some(DiagnosticSeverity::HINT),
87        _ => Some(DiagnosticSeverity::INFORMATION),
88    };
89
90    let code = err
91        .get("errorCode")
92        .and_then(|c| c.as_str())
93        .map(|s| NumberOrString::String(s.to_string()));
94
95    Some(Diagnostic {
96        range,
97        severity,
98        code,
99        code_description: None,
100        source: Some("forge-build".to_string()),
101        message: format!("[forge build] {message}"),
102        related_information: None,
103        tags: None,
104        data: None,
105    })
106}