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        message: diag.label().into_owned(),
38        related_information: None, // TODO: Implement
39        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        // For solar, build diagnostics are handled in get_build_diagnostics
79        // Return empty JSON for compatibility
80        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        // For solar, lint diagnostics are handled in get_lint_diagnostics
89        // Return empty array for compatibility
90        Ok(serde_json::Value::Array(Vec::new()))
91    }
92
93    async fn format(&self, file: &str) -> Result<String, RunnerError> {
94        // Solar does not have formatting, return the original content
95        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        // For solar, we can return the AST as JSON
102        // This is a simplified version; in practice, you might need to serialize the AST
103        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            // Get the AST - this is simplified, solar might have different API
120            // For now, return empty object
121            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        // Read the file content
132        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                // Add the file with content to source_map
146                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                // Convert solar diagnostic to LSP diagnostic
167                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        // TODO:
187
188        Ok(diagnostics)
189    }
190}