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