Skip to main content

solidity_language_server/
solar_runner.rs

1use 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        // label() can be empty for some diagnostic kinds; fall back to the
38        // rendered message so LSP clients that require non-empty messages don't crash.
39        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, // TODO: Implement
48        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        // For solar, build diagnostics are handled in get_build_diagnostics
88        // Return empty JSON for compatibility
89        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        // For solar, lint diagnostics are handled in get_lint_diagnostics
98        // Return empty array for compatibility
99        Ok(serde_json::Value::Array(Vec::new()))
100    }
101
102    async fn format(&self, file: &str) -> Result<String, RunnerError> {
103        // Solar does not have formatting, return the original content
104        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        // For solar, we can return the AST as JSON
111        // This is a simplified version; in practice, you might need to serialize the AST
112        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            // Get the AST - this is simplified, solar might have different API
129            // For now, return empty object
130            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        // Read the file content
141        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                // Add the file with content to source_map
155                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                // Convert solar diagnostic to LSP diagnostic
175                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        // TODO:
195
196        Ok(diagnostics)
197    }
198}