solidity_language_server/
build.rs1use crate::utils::byte_offset_to_position;
2use serde_json::Value;
3use std::path::Path;
4use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Position, 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
30fn 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_line, start_col) = byte_offset_to_position(content, start_offset);
73 let (end_line, end_col) = byte_offset_to_position(content, end_offset);
74
75 let range = Range {
76 start: Position {
77 line: start_line,
78 character: start_col,
79 },
80 end: Position {
81 line: end_line,
82 character: end_col,
83 },
84 };
85
86 let message = err
87 .get("message")
88 .and_then(|m| m.as_str())
89 .unwrap_or("Unknown error");
90
91 let severity = match err.get("severity").and_then(|s| s.as_str()) {
92 Some("error") => Some(DiagnosticSeverity::ERROR),
93 Some("warning") => Some(DiagnosticSeverity::WARNING),
94 Some("note") => Some(DiagnosticSeverity::INFORMATION),
95 Some("help") => Some(DiagnosticSeverity::HINT),
96 _ => Some(DiagnosticSeverity::INFORMATION),
97 };
98
99 let code = err
100 .get("errorCode")
101 .and_then(|c| c.as_str())
102 .map(|s| NumberOrString::String(s.to_string()));
103
104 Some(Diagnostic {
105 range,
106 severity,
107 code,
108 code_description: None,
109 source: Some("forge-build".to_string()),
110 message: format!("[forge build] {message}"),
111 related_information: None,
112 tags: None,
113 data: None,
114 })
115}