Skip to main content

solidity_language_server/
solar_runner.rs

1use 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, Position, 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, // TODO: Implement
38        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        // For solar, build diagnostics are handled in get_build_diagnostics
78        // Return empty JSON for compatibility
79        Ok(serde_json::Value::Object(serde_json::Map::new()))
80    }
81
82    async fn lint(&self, _file: &str) -> Result<serde_json::Value, RunnerError> {
83        // For solar, lint diagnostics are handled in get_lint_diagnostics
84        // Return empty array for compatibility
85        Ok(serde_json::Value::Array(Vec::new()))
86    }
87
88    async fn format(&self, file: &str) -> Result<String, RunnerError> {
89        // Solar does not have formatting, return the original content
90        tokio::fs::read_to_string(file)
91            .await
92            .map_err(|_| RunnerError::ReadError)
93    }
94
95    async fn ast(&self, file: &str) -> Result<serde_json::Value, RunnerError> {
96        // For solar, we can return the AST as JSON
97        // This is a simplified version; in practice, you might need to serialize the AST
98        let file = file.to_string();
99
100        task::spawn_blocking(move || {
101            let paths = [Path::new(&file)];
102            let sess = Session::builder()
103                .with_buffer_emitter(solar::interface::ColorChoice::Auto)
104                .build();
105            let mut compiler = Compiler::new(sess);
106
107            let _ = compiler.enter_mut(|compiler| -> solar::interface::Result<_> {
108                let mut parsing_context = compiler.parse();
109                parsing_context.load_files(paths)?;
110                parsing_context.parse();
111                Ok(())
112            });
113
114            // Get the AST - this is simplified, solar might have different API
115            // For now, return empty object
116            Ok(serde_json::Value::Object(serde_json::Map::new()))
117        })
118        .await
119        .map_err(|_| RunnerError::CommandError(Error::other("Task panicked")))?
120    }
121
122    async fn get_build_diagnostics(&self, file: &Url) -> Result<Vec<Diagnostic>, RunnerError> {
123        let path = file.to_file_path().map_err(|_| RunnerError::InvalidUrl)?;
124        let path_str = path.to_str().ok_or(RunnerError::InvalidUrl)?.to_string();
125
126        // Read the file content
127        let content = tokio::fs::read_to_string(&path)
128            .await
129            .map_err(|_| RunnerError::ReadError)?;
130
131        task::spawn_blocking(move || {
132            let (emitter, diag_buffer) = InMemoryEmitter::new();
133            let sess = Session::builder()
134                .dcx(DiagCtxt::new(Box::new(emitter)))
135                .build();
136            let mut compiler = Compiler::new(sess);
137
138            let _ = compiler.enter_mut(|compiler| -> solar::interface::Result<_> {
139                let mut parsing_context = compiler.parse();
140                // Add the file with content to source_map
141                parsing_context.add_files(vec![
142                    compiler
143                        .sess()
144                        .source_map()
145                        .new_source_file(path_str.clone(), content)
146                        .unwrap(),
147                ]);
148                parsing_context.parse();
149                Ok(())
150            });
151
152            let _ = compiler.enter_mut(|compiler| -> solar::interface::Result<_> {
153                let _ = compiler.lower_asts();
154                let _ = compiler.analysis();
155                Ok(())
156            });
157
158            let mut diagnostics = Vec::new();
159            println!("Diag buffer has {} items", diag_buffer.read().len());
160            for diag in diag_buffer.read().iter() {
161                // Convert solar diagnostic to LSP diagnostic
162                if let Some(lsp_diag) =
163                    solar_diag_to_lsp(diag, &path_str, compiler.sess().source_map())
164                {
165                    diagnostics.push(lsp_diag);
166                }
167            }
168
169            Ok(diagnostics)
170        })
171        .await
172        .map_err(|_| RunnerError::CommandError(Error::other("Task panicked")))?
173    }
174
175    async fn get_lint_diagnostics(&self, _file: &Url) -> Result<Vec<Diagnostic>, RunnerError> {
176        let diagnostics = Vec::new();
177        // TODO:
178
179        Ok(diagnostics)
180    }
181}