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, Range};
5
6const DEFAULT_IGNORED_CODES: &[&str] = &["5574", "3860"];
9
10pub fn ignored_error_code_warning(value: &serde_json::Value, extra_codes: &[u64]) -> bool {
15 let error_code = value
16 .get("errorCode")
17 .and_then(|v| v.as_str())
18 .unwrap_or_default();
19
20 if DEFAULT_IGNORED_CODES.contains(&error_code) {
21 return true;
22 }
23
24 if let Ok(code_num) = error_code.parse::<u64>() {
26 if extra_codes.contains(&code_num) {
27 return true;
28 }
29 }
30
31 false
32}
33
34pub fn build_output_to_diagnostics(
35 forge_output: &serde_json::Value,
36 path: impl AsRef<Path>,
37 content: &str,
38 ignored_error_codes: &[u64],
39) -> Vec<Diagnostic> {
40 let Some(errors) = forge_output.get("errors").and_then(|v| v.as_array()) else {
41 return Vec::new();
42 };
43 let path = path.as_ref();
44 errors
45 .iter()
46 .filter_map(|err| parse_diagnostic(err, path, content, ignored_error_codes))
47 .collect()
48}
49
50fn source_location_matches(source_path: &str, path: &Path) -> bool {
58 let source_path = Path::new(source_path);
59 if source_path.is_absolute() {
60 source_path == path
61 } else {
62 path.ends_with(source_path)
63 }
64}
65
66fn parse_diagnostic(
67 err: &Value,
68 path: &Path,
69 content: &str,
70 ignored_error_codes: &[u64],
71) -> Option<Diagnostic> {
72 if ignored_error_code_warning(err, ignored_error_codes) {
73 return None;
74 }
75 let source_file = err
76 .get("sourceLocation")
77 .and_then(|loc| loc.get("file"))
78 .and_then(|f| f.as_str())?;
79
80 if !source_location_matches(source_file, path) {
81 return None;
82 }
83
84 let start_offset = err
85 .get("sourceLocation")
86 .and_then(|loc| loc.get("start"))
87 .and_then(|s| s.as_u64())
88 .unwrap_or(0) as usize;
89
90 let end_offset = err
91 .get("sourceLocation")
92 .and_then(|loc| loc.get("end"))
93 .and_then(|s| s.as_u64())
94 .map(|v| v as usize)
95 .unwrap_or(start_offset);
96
97 let start = byte_offset_to_position(content, start_offset);
98 let end = byte_offset_to_position(content, end_offset);
99
100 let range = Range { start, end };
101
102 let message = err
103 .get("message")
104 .and_then(|m| m.as_str())
105 .unwrap_or("Unknown error");
106
107 let severity = match err.get("severity").and_then(|s| s.as_str()) {
108 Some("error") => Some(DiagnosticSeverity::ERROR),
109 Some("warning") => Some(DiagnosticSeverity::WARNING),
110 Some("note") => Some(DiagnosticSeverity::INFORMATION),
111 Some("help") => Some(DiagnosticSeverity::HINT),
112 _ => Some(DiagnosticSeverity::INFORMATION),
113 };
114
115 let code = err
116 .get("errorCode")
117 .and_then(|c| c.as_str())
118 .map(|s| NumberOrString::String(s.to_string()));
119
120 Some(Diagnostic {
121 range,
122 severity,
123 code,
124 code_description: None,
125 source: Some("forge-build".to_string()),
126 message: message.to_string(),
127 related_information: None,
128 tags: None,
129 data: None,
130 })
131}