ricecoder_lsp/semantic/
mod.rs1use crate::types::{Language, Position, SemanticInfo, Symbol};
7use std::path::Path;
8
9pub mod adapters;
10pub mod fallback_analyzer;
11pub mod generic_analyzer;
12pub mod python_analyzer;
13pub mod rust_analyzer;
14pub mod typescript_analyzer;
15
16pub use adapters::{
17 FallbackAnalyzerAdapter, PythonAnalyzerAdapter, RustAnalyzerAdapter, TypeScriptAnalyzerAdapter,
18};
19pub use fallback_analyzer::FallbackAnalyzer;
20pub use generic_analyzer::GenericSemanticAnalyzer;
21pub use python_analyzer::PythonAnalyzer;
22pub use rust_analyzer::RustAnalyzer;
23pub use typescript_analyzer::TypeScriptAnalyzer;
24
25#[derive(Debug, thiserror::Error)]
27pub enum SemanticError {
28 #[error("Parse error: {0}")]
30 ParseError(String),
31
32 #[error("Analysis error: {0}")]
34 AnalysisError(String),
35
36 #[error("Unsupported language: {0:?}")]
38 UnsupportedLanguage(Language),
39
40 #[error("IO error: {0}")]
42 IoError(#[from] std::io::Error),
43}
44
45pub type SemanticResult<T> = Result<T, SemanticError>;
47
48pub trait SemanticAnalyzer: Send + Sync {
50 fn analyze(&self, code: &str) -> SemanticResult<SemanticInfo>;
52
53 fn extract_symbols(&self, code: &str) -> SemanticResult<Vec<Symbol>>;
55
56 fn get_hover_info(&self, code: &str, position: Position) -> SemanticResult<Option<String>>;
58
59 fn language(&self) -> Language;
61}
62
63pub struct LanguageDetector;
65
66impl LanguageDetector {
67 pub fn from_extension(path: &Path) -> Language {
69 path.extension()
70 .and_then(|ext| ext.to_str())
71 .map(Language::from_extension)
72 .unwrap_or(Language::Unknown)
73 }
74
75 pub fn from_content(content: &str) -> Language {
77 if let Some(first_line) = content.lines().next() {
79 if first_line.starts_with("#!") {
80 if first_line.contains("python") {
81 return Language::Python;
82 } else if first_line.contains("node") || first_line.contains("ts-node") {
83 return Language::TypeScript;
84 }
85 }
86 }
87
88 if content.contains("use ") && content.contains("fn ") {
91 return Language::Rust;
92 }
93
94 if content.contains("def ") {
96 return Language::Python;
97 }
98
99 if content.contains("export ") {
101 return Language::TypeScript;
102 }
103
104 if content.contains("import ") {
106 return Language::TypeScript;
107 }
108
109 Language::Unknown
110 }
111
112 pub fn detect(path: &Path, content: &str) -> Language {
114 let from_ext = Self::from_extension(path);
115 if from_ext != Language::Unknown {
116 return from_ext;
117 }
118 Self::from_content(content)
119 }
120}
121
122pub struct SemanticAnalyzerFactory;
124
125impl SemanticAnalyzerFactory {
126 pub fn create(language: Language) -> Box<dyn SemanticAnalyzer> {
128 match language {
129 Language::Rust => Box::new(RustAnalyzer::new()),
130 Language::TypeScript => Box::new(TypeScriptAnalyzer::new()),
131 Language::Python => Box::new(PythonAnalyzer::new()),
132 Language::Unknown => Box::new(FallbackAnalyzer::new()),
133 }
134 }
135
136 pub fn from_file(path: &Path, content: &str) -> Box<dyn SemanticAnalyzer> {
138 let language = LanguageDetector::detect(path, content);
139 Self::create(language)
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_language_detection_from_extension() {
149 assert_eq!(
150 LanguageDetector::from_extension(Path::new("test.rs")),
151 Language::Rust
152 );
153 assert_eq!(
154 LanguageDetector::from_extension(Path::new("test.ts")),
155 Language::TypeScript
156 );
157 assert_eq!(
158 LanguageDetector::from_extension(Path::new("test.py")),
159 Language::Python
160 );
161 assert_eq!(
162 LanguageDetector::from_extension(Path::new("test.unknown")),
163 Language::Unknown
164 );
165 }
166
167 #[test]
168 fn test_language_detection_from_content_shebang() {
169 let python_shebang = "#!/usr/bin/env python\nprint('hello')";
170 assert_eq!(
171 LanguageDetector::from_content(python_shebang),
172 Language::Python
173 );
174
175 let node_shebang = "#!/usr/bin/env node\nconsole.log('hello')";
176 assert_eq!(
177 LanguageDetector::from_content(node_shebang),
178 Language::TypeScript
179 );
180 }
181
182 #[test]
183 fn test_language_detection_from_content_patterns() {
184 let rust_code = "use std::io;\nfn main() {}";
185 assert_eq!(LanguageDetector::from_content(rust_code), Language::Rust);
186
187 let ts_code = "import { foo } from 'bar';\nexport const x = 1;";
188 assert_eq!(
189 LanguageDetector::from_content(ts_code),
190 Language::TypeScript
191 );
192
193 let py_code = "import os\ndef hello():\n pass";
194 assert_eq!(LanguageDetector::from_content(py_code), Language::Python);
195 }
196
197 #[test]
198 fn test_language_detection_combined() {
199 let path = Path::new("test.rs");
200 let content = "fn main() {}";
201 assert_eq!(LanguageDetector::detect(path, content), Language::Rust);
202 }
203
204 #[test]
205 fn test_semantic_analyzer_factory() {
206 let analyzer = SemanticAnalyzerFactory::create(Language::Rust);
207 assert_eq!(analyzer.language(), Language::Rust);
208
209 let analyzer = SemanticAnalyzerFactory::create(Language::TypeScript);
210 assert_eq!(analyzer.language(), Language::TypeScript);
211
212 let analyzer = SemanticAnalyzerFactory::create(Language::Python);
213 assert_eq!(analyzer.language(), Language::Python);
214
215 let analyzer = SemanticAnalyzerFactory::create(Language::Unknown);
216 assert_eq!(analyzer.language(), Language::Unknown);
217 }
218}