solidity_language_server/
build.rs1use crate::utils::{byte_offset_to_position, find_project_root};
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 let project_root = find_project_root(path);
25 errors
26 .iter()
27 .filter_map(|err| parse_diagnostic(err, path, project_root.as_deref(), content))
28 .collect()
29}
30
31fn source_location_matches(source_path: &str, path: &Path, project_root: Option<&Path>) -> bool {
32 let source_path = Path::new(source_path);
33 if source_path.is_absolute() {
36 source_path == path
37 } else if let Some(root) = project_root {
38 path.strip_prefix(root)
40 .map(|rel| rel == source_path)
41 .unwrap_or(false)
42 } else {
43 source_path.file_name() == path.file_name()
45 }
46}
47
48fn parse_diagnostic(
49 err: &Value,
50 path: &Path,
51 project_root: Option<&Path>,
52 content: &str,
53) -> Option<Diagnostic> {
54 if ignored_error_code_warning(err) {
55 return None;
56 }
57 let source_file = err
58 .get("sourceLocation")
59 .and_then(|loc| loc.get("file"))
60 .and_then(|f| f.as_str())?;
61
62 if !source_location_matches(source_file, path, project_root) {
63 return None;
64 }
65
66 let start_offset = err
67 .get("sourceLocation")
68 .and_then(|loc| loc.get("start"))
69 .and_then(|s| s.as_u64())
70 .unwrap_or(0) as usize;
71
72 let end_offset = err
73 .get("sourceLocation")
74 .and_then(|loc| loc.get("end"))
75 .and_then(|s| s.as_u64())
76 .map(|v| v as usize)
77 .unwrap_or(start_offset);
78
79 let (start_line, start_col) = byte_offset_to_position(content, start_offset);
80 let (end_line, end_col) = byte_offset_to_position(content, end_offset);
81
82 let range = Range {
83 start: Position {
84 line: start_line,
85 character: start_col,
86 },
87 end: Position {
88 line: end_line,
89 character: end_col,
90 },
91 };
92
93 let message = err
94 .get("message")
95 .and_then(|m| m.as_str())
96 .unwrap_or("Unknown error");
97
98 let severity = match err.get("severity").and_then(|s| s.as_str()) {
99 Some("error") => Some(DiagnosticSeverity::ERROR),
100 Some("warning") => Some(DiagnosticSeverity::WARNING),
101 Some("note") => Some(DiagnosticSeverity::INFORMATION),
102 Some("help") => Some(DiagnosticSeverity::HINT),
103 _ => Some(DiagnosticSeverity::INFORMATION),
104 };
105
106 let code = err
107 .get("errorCode")
108 .and_then(|c| c.as_str())
109 .map(|s| NumberOrString::String(s.to_string()));
110
111 Some(Diagnostic {
112 range,
113 severity,
114 code,
115 code_description: None,
116 source: Some("forge-build".to_string()),
117 message: format!("[forge build] {message}"),
118 related_information: None,
119 tags: None,
120 data: None,
121 })
122}