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: diag.label().into_owned(),
38 related_information: None, tags: None,
40 data: None,
41 })
42}
43
44fn span_to_range(
45 source_map: &SourceMap,
46 span: solar::interface::Span,
47) -> tower_lsp::lsp_types::Range {
48 let start_loc = source_map.lookup_char_pos(span.lo());
49 let end_loc = source_map.lookup_char_pos(span.hi());
50 tower_lsp::lsp_types::Range {
51 start: Position {
52 line: start_loc.data.line as u32 - 1,
53 character: start_loc.data.col.0 as u32 - 1,
54 },
55 end: Position {
56 line: end_loc.data.line as u32 - 1,
57 character: end_loc.data.col.0 as u32 - 1,
58 },
59 }
60}
61
62fn severity(
63 level: solar::interface::diagnostics::Level,
64) -> tower_lsp::lsp_types::DiagnosticSeverity {
65 use solar::interface::diagnostics::Level::*;
66 match level {
67 Error | Fatal | Bug => tower_lsp::lsp_types::DiagnosticSeverity::ERROR,
68 Warning => tower_lsp::lsp_types::DiagnosticSeverity::WARNING,
69 Note | OnceNote => tower_lsp::lsp_types::DiagnosticSeverity::INFORMATION,
70 Help | OnceHelp => tower_lsp::lsp_types::DiagnosticSeverity::HINT,
71 _ => tower_lsp::lsp_types::DiagnosticSeverity::INFORMATION,
72 }
73}
74
75#[async_trait]
76impl Runner for SolarRunner {
77 async fn build(&self, _file: &str) -> Result<serde_json::Value, RunnerError> {
78 Ok(serde_json::Value::Object(serde_json::Map::new()))
81 }
82
83 async fn lint(
84 &self,
85 _file: &str,
86 _lint_settings: &LintSettings,
87 ) -> Result<serde_json::Value, RunnerError> {
88 Ok(serde_json::Value::Array(Vec::new()))
91 }
92
93 async fn format(&self, file: &str) -> Result<String, RunnerError> {
94 tokio::fs::read_to_string(file)
96 .await
97 .map_err(|_| RunnerError::ReadError)
98 }
99
100 async fn ast(&self, file: &str) -> Result<serde_json::Value, RunnerError> {
101 let file = file.to_string();
104
105 task::spawn_blocking(move || {
106 let paths = [Path::new(&file)];
107 let sess = Session::builder()
108 .with_buffer_emitter(solar::interface::ColorChoice::Auto)
109 .build();
110 let mut compiler = Compiler::new(sess);
111
112 let _ = compiler.enter_mut(|compiler| -> solar::interface::Result<_> {
113 let mut parsing_context = compiler.parse();
114 parsing_context.load_files(paths)?;
115 parsing_context.parse();
116 Ok(())
117 });
118
119 Ok(serde_json::Value::Object(serde_json::Map::new()))
122 })
123 .await
124 .map_err(|_| RunnerError::CommandError(Error::other("Task panicked")))?
125 }
126
127 async fn get_build_diagnostics(&self, file: &Url) -> Result<Vec<Diagnostic>, RunnerError> {
128 let path = file.to_file_path().map_err(|_| RunnerError::InvalidUrl)?;
129 let path_str = path.to_str().ok_or(RunnerError::InvalidUrl)?.to_string();
130
131 let content = tokio::fs::read_to_string(&path)
133 .await
134 .map_err(|_| RunnerError::ReadError)?;
135
136 task::spawn_blocking(move || {
137 let (emitter, diag_buffer) = InMemoryEmitter::new();
138 let sess = Session::builder()
139 .dcx(DiagCtxt::new(Box::new(emitter)))
140 .build();
141 let mut compiler = Compiler::new(sess);
142
143 let _ = compiler.enter_mut(|compiler| -> solar::interface::Result<_> {
144 let mut parsing_context = compiler.parse();
145 parsing_context.add_files(vec![
147 compiler
148 .sess()
149 .source_map()
150 .new_source_file(path_str.clone(), content)
151 .unwrap(),
152 ]);
153 parsing_context.parse();
154 Ok(())
155 });
156
157 let _ = compiler.enter_mut(|compiler| -> solar::interface::Result<_> {
158 let _ = compiler.lower_asts();
159 let _ = compiler.analysis();
160 Ok(())
161 });
162
163 let mut diagnostics = Vec::new();
164 println!("Diag buffer has {} items", diag_buffer.read().len());
165 for diag in diag_buffer.read().iter() {
166 if let Some(lsp_diag) =
168 solar_diag_to_lsp(diag, &path_str, compiler.sess().source_map())
169 {
170 diagnostics.push(lsp_diag);
171 }
172 }
173
174 Ok(diagnostics)
175 })
176 .await
177 .map_err(|_| RunnerError::CommandError(Error::other("Task panicked")))?
178 }
179
180 async fn get_lint_diagnostics(
181 &self,
182 _file: &Url,
183 _lint_settings: &LintSettings,
184 ) -> Result<Vec<Diagnostic>, RunnerError> {
185 let diagnostics = Vec::new();
186 Ok(diagnostics)
189 }
190}