solidity_language_server/
build.rs1use crate::utils::byte_offset_to_position;
2use serde_json::Value;
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Range};
6
7const DEFAULT_IGNORED_CODES: &[&str] = &["5574", "3860"];
10
11pub fn ignored_error_code_warning(value: &serde_json::Value, extra_codes: &[u64]) -> bool {
16 let error_code = value
17 .get("errorCode")
18 .and_then(|v| v.as_str())
19 .unwrap_or_default();
20
21 if DEFAULT_IGNORED_CODES.contains(&error_code) {
22 return true;
23 }
24
25 if let Ok(code_num) = error_code.parse::<u64>()
27 && extra_codes.contains(&code_num)
28 {
29 return true;
30 }
31
32 false
33}
34
35pub fn build_output_to_diagnostics(
36 solc_output: &serde_json::Value,
37 path: impl AsRef<Path>,
38 content: &str,
39 ignored_error_codes: &[u64],
40) -> Vec<Diagnostic> {
41 let Some(errors) = solc_output.get("errors").and_then(|v| v.as_array()) else {
42 return Vec::new();
43 };
44 let path = path.as_ref();
45 errors
46 .iter()
47 .filter_map(|err| parse_diagnostic(err, path, content, ignored_error_codes))
48 .collect()
49}
50
51fn source_location_matches(source_path: &str, path: &Path) -> bool {
59 let source_path = Path::new(source_path);
60 if source_path.is_absolute() {
61 source_path == path
62 } else {
63 path.ends_with(source_path)
64 }
65}
66
67fn parse_diagnostic(
68 err: &Value,
69 path: &Path,
70 content: &str,
71 ignored_error_codes: &[u64],
72) -> Option<Diagnostic> {
73 if ignored_error_code_warning(err, ignored_error_codes) {
74 return None;
75 }
76 let source_file = err
77 .get("sourceLocation")
78 .and_then(|loc| loc.get("file"))
79 .and_then(|f| f.as_str())?;
80
81 if !source_location_matches(source_file, path) {
82 return None;
83 }
84
85 let start_offset = err
86 .get("sourceLocation")
87 .and_then(|loc| loc.get("start"))
88 .and_then(|s| s.as_u64())
89 .unwrap_or(0) as usize;
90
91 let end_offset = err
92 .get("sourceLocation")
93 .and_then(|loc| loc.get("end"))
94 .and_then(|s| s.as_u64())
95 .map(|v| v as usize)
96 .unwrap_or(start_offset);
97
98 let start = byte_offset_to_position(content, start_offset);
99 let end = byte_offset_to_position(content, end_offset);
100
101 let range = Range { start, end };
102
103 let message = err
104 .get("message")
105 .and_then(|m| m.as_str())
106 .unwrap_or("Unknown error");
107
108 let severity = match err.get("severity").and_then(|s| s.as_str()) {
109 Some("error") => Some(DiagnosticSeverity::ERROR),
110 Some("warning") => Some(DiagnosticSeverity::WARNING),
111 Some("note") => Some(DiagnosticSeverity::INFORMATION),
112 Some("help") => Some(DiagnosticSeverity::HINT),
113 _ => Some(DiagnosticSeverity::INFORMATION),
114 };
115
116 let code = err
117 .get("errorCode")
118 .and_then(|c| c.as_str())
119 .map(|s| NumberOrString::String(s.to_string()));
120
121 Some(Diagnostic {
122 range,
123 severity,
124 code,
125 code_description: None,
126 source: Some("solc".to_string()),
127 message: message.to_string(),
128 related_information: None,
129 tags: None,
130 data: None,
131 })
132}
133
134pub fn cross_file_error_diagnostics(
144 solc_output: &Value,
145 current_file: &Path,
146 project_root: &Path,
147 ignored_error_codes: &[u64],
148) -> HashMap<PathBuf, Vec<Diagnostic>> {
149 let Some(errors) = solc_output.get("errors").and_then(|v| v.as_array()) else {
150 return HashMap::new();
151 };
152
153 let mut result: HashMap<PathBuf, Vec<Diagnostic>> = HashMap::new();
154
155 for err in errors {
156 if ignored_error_code_warning(err, ignored_error_codes) {
157 continue;
158 }
159 if err.get("severity").and_then(|s| s.as_str()) != Some("error") {
160 continue;
161 }
162 let Some(source_file) = err
163 .get("sourceLocation")
164 .and_then(|loc| loc.get("file"))
165 .and_then(|f| f.as_str())
166 else {
167 continue;
168 };
169 if source_location_matches(source_file, current_file) {
170 continue;
171 }
172
173 let source_path = Path::new(source_file);
174 let abs_path = if source_path.is_absolute() {
175 source_path.to_path_buf()
176 } else {
177 project_root.join(source_path)
178 };
179 let Ok(content) = std::fs::read_to_string(&abs_path) else {
180 continue;
181 };
182
183 let start_offset = err
184 .get("sourceLocation")
185 .and_then(|loc| loc.get("start"))
186 .and_then(|s| s.as_u64())
187 .unwrap_or(0) as usize;
188 let end_offset = err
189 .get("sourceLocation")
190 .and_then(|loc| loc.get("end"))
191 .and_then(|s| s.as_u64())
192 .map(|v| v as usize)
193 .unwrap_or(start_offset);
194
195 let start = byte_offset_to_position(&content, start_offset);
196 let end = byte_offset_to_position(&content, end_offset);
197
198 let code = err
199 .get("errorCode")
200 .and_then(|c| c.as_str())
201 .map(|s| NumberOrString::String(s.to_string()));
202
203 let message = err
204 .get("message")
205 .and_then(|m| m.as_str())
206 .unwrap_or("Unknown error");
207
208 result.entry(abs_path).or_default().push(Diagnostic {
209 range: Range { start, end },
210 severity: Some(DiagnosticSeverity::ERROR),
211 code,
212 code_description: None,
213 source: Some("solc".to_string()),
214 message: message.to_string(),
215 related_information: None,
216 tags: None,
217 data: None,
218 });
219 }
220
221 result
222}