solidity_language_server/
solar_runner.rs1use crate::config::LintSettings;
2use crate::runner::{Runner, RunnerError};
3use solar::{
4 interface::{
5 Session, SourceMap,
6 diagnostics::{Diag, DiagCtxt, InMemoryEmitter},
7 },
8 sema::Compiler,
9};
10use std::{io::Error, path::Path};
11use tokio::task;
12use tower_lsp::async_trait;
13use tower_lsp::lsp_types::{Diagnostic, Position, Url};
14
15pub struct SolarRunner;
16
17fn solar_diag_to_lsp(
18 diag: &Diag,
19 target_file: &str,
20 source_map: &SourceMap,
21) -> Option<tower_lsp::lsp_types::Diagnostic> {
22 use tower_lsp::lsp_types::NumberOrString;
23
24 let primary_span = diag.span.primary_span()?;
25 let _uri = Url::from_file_path(target_file).ok()?;
26 let range = span_to_range(source_map, primary_span);
27
28 Some(tower_lsp::lsp_types::Diagnostic {
29 range,
30 severity: Some(severity(diag.level())),
31 code: diag
32 .code
33 .as_ref()
34 .map(|id| NumberOrString::String(id.as_string())),
35 code_description: None,
36 source: Some("solar".into()),
37 message: {
40 let label = diag.label().into_owned();
41 if !label.is_empty() {
42 label
43 } else {
44 "Compiler error".to_string()
45 }
46 },
47 related_information: None, tags: None,
49 data: None,
50 })
51}
52
53fn span_to_range(
54 source_map: &SourceMap,
55 span: solar::interface::Span,
56) -> tower_lsp::lsp_types::Range {
57 let start_loc = source_map.lookup_char_pos(span.lo());
58 let end_loc = source_map.lookup_char_pos(span.hi());
59 tower_lsp::lsp_types::Range {
60 start: Position {
61 line: start_loc.data.line as u32 - 1,
62 character: start_loc.data.col.0 as u32 - 1,
63 },
64 end: Position {
65 line: end_loc.data.line as u32 - 1,
66 character: end_loc.data.col.0 as u32 - 1,
67 },
68 }
69}
70
71fn severity(
72 level: solar::interface::diagnostics::Level,
73) -> tower_lsp::lsp_types::DiagnosticSeverity {
74 use solar::interface::diagnostics::Level::*;
75 match level {
76 Error | Fatal | Bug => tower_lsp::lsp_types::DiagnosticSeverity::ERROR,
77 Warning => tower_lsp::lsp_types::DiagnosticSeverity::WARNING,
78 Note | OnceNote => tower_lsp::lsp_types::DiagnosticSeverity::INFORMATION,
79 Help | OnceHelp => tower_lsp::lsp_types::DiagnosticSeverity::HINT,
80 _ => tower_lsp::lsp_types::DiagnosticSeverity::INFORMATION,
81 }
82}
83
84#[async_trait]
85impl Runner for SolarRunner {
86 async fn build(&self, _file: &str) -> Result<serde_json::Value, RunnerError> {
87 Ok(serde_json::Value::Object(serde_json::Map::new()))
90 }
91
92 async fn lint(
93 &self,
94 _file: &str,
95 _lint_settings: &LintSettings,
96 ) -> Result<serde_json::Value, RunnerError> {
97 Ok(serde_json::Value::Array(Vec::new()))
100 }
101
102 async fn format(&self, file: &str) -> Result<String, RunnerError> {
103 tokio::fs::read_to_string(file)
105 .await
106 .map_err(|_| RunnerError::ReadError)
107 }
108
109 async fn ast(&self, file: &str) -> Result<serde_json::Value, RunnerError> {
110 let file = file.to_string();
113
114 task::spawn_blocking(move || {
115 let paths = [Path::new(&file)];
116 let sess = Session::builder()
117 .with_buffer_emitter(solar::interface::ColorChoice::Auto)
118 .build();
119 let mut compiler = Compiler::new(sess);
120
121 let _ = compiler.enter_mut(|compiler| -> solar::interface::Result<_> {
122 let mut parsing_context = compiler.parse();
123 parsing_context.load_files(paths)?;
124 parsing_context.parse();
125 Ok(())
126 });
127
128 Ok(serde_json::Value::Object(serde_json::Map::new()))
131 })
132 .await
133 .map_err(|_| RunnerError::CommandError(Error::other("Task panicked")))?
134 }
135
136 async fn get_build_diagnostics(&self, file: &Url) -> Result<Vec<Diagnostic>, RunnerError> {
137 let path = file.to_file_path().map_err(|_| RunnerError::InvalidUrl)?;
138 let path_str = path.to_str().ok_or(RunnerError::InvalidUrl)?.to_string();
139
140 let content = tokio::fs::read_to_string(&path)
142 .await
143 .map_err(|_| RunnerError::ReadError)?;
144
145 task::spawn_blocking(move || {
146 let (emitter, diag_buffer) = InMemoryEmitter::new();
147 let sess = Session::builder()
148 .dcx(DiagCtxt::new(Box::new(emitter)))
149 .build();
150 let mut compiler = Compiler::new(sess);
151
152 let _ = compiler.enter_mut(|compiler| -> solar::interface::Result<_> {
153 let mut parsing_context = compiler.parse();
154 parsing_context.add_files(vec![
156 compiler
157 .sess()
158 .source_map()
159 .new_source_file(path_str.clone(), content)
160 .unwrap(),
161 ]);
162 parsing_context.parse();
163 Ok(())
164 });
165
166 let _ = compiler.enter_mut(|compiler| -> solar::interface::Result<_> {
167 let _ = compiler.lower_asts();
168 let _ = compiler.analysis();
169 Ok(())
170 });
171
172 let mut diagnostics = Vec::new();
173 for diag in diag_buffer.read().iter() {
174 if let Some(lsp_diag) =
176 solar_diag_to_lsp(diag, &path_str, compiler.sess().source_map())
177 {
178 diagnostics.push(lsp_diag);
179 }
180 }
181
182 Ok(diagnostics)
183 })
184 .await
185 .map_err(|_| RunnerError::CommandError(Error::other("Task panicked")))?
186 }
187
188 async fn get_lint_diagnostics(
189 &self,
190 _file: &Url,
191 _lint_settings: &LintSettings,
192 ) -> Result<Vec<Diagnostic>, RunnerError> {
193 let diagnostics = Vec::new();
194 Ok(diagnostics)
197 }
198}