ricecoder_lsp/semantic/
mod.rs

1//! Semantic Analysis Engine
2//!
3//! This module provides semantic analysis capabilities for multiple programming languages.
4//! It includes language detection, symbol extraction, and semantic information gathering.
5
6use 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/// Error type for semantic analysis
26#[derive(Debug, thiserror::Error)]
27pub enum SemanticError {
28    /// Parse error
29    #[error("Parse error: {0}")]
30    ParseError(String),
31
32    /// Analysis error
33    #[error("Analysis error: {0}")]
34    AnalysisError(String),
35
36    /// Unsupported language
37    #[error("Unsupported language: {0:?}")]
38    UnsupportedLanguage(Language),
39
40    /// IO error
41    #[error("IO error: {0}")]
42    IoError(#[from] std::io::Error),
43}
44
45/// Result type for semantic analysis
46pub type SemanticResult<T> = Result<T, SemanticError>;
47
48/// Trait for semantic analysis of code
49pub trait SemanticAnalyzer: Send + Sync {
50    /// Analyze code and extract semantic information
51    fn analyze(&self, code: &str) -> SemanticResult<SemanticInfo>;
52
53    /// Extract symbols from code
54    fn extract_symbols(&self, code: &str) -> SemanticResult<Vec<Symbol>>;
55
56    /// Get hover information at a specific position
57    fn get_hover_info(&self, code: &str, position: Position) -> SemanticResult<Option<String>>;
58
59    /// Get supported language
60    fn language(&self) -> Language;
61}
62
63/// Language detection utilities
64pub struct LanguageDetector;
65
66impl LanguageDetector {
67    /// Detect language from file extension
68    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    /// Detect language from file content (shebang or imports)
76    pub fn from_content(content: &str) -> Language {
77        // Check for shebang
78        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        // Check for language-specific imports/patterns
89        // Check Rust first (most specific)
90        if content.contains("use ") && content.contains("fn ") {
91            return Language::Rust;
92        }
93
94        // Check Python (look for def keyword which is Python-specific)
95        if content.contains("def ") {
96            return Language::Python;
97        }
98
99        // Check TypeScript/JavaScript (look for export which is more specific)
100        if content.contains("export ") {
101            return Language::TypeScript;
102        }
103
104        // Check for import statements (but only if no def keyword)
105        if content.contains("import ") {
106            return Language::TypeScript;
107        }
108
109        Language::Unknown
110    }
111
112    /// Detect language from both extension and content
113    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
122/// Factory for creating appropriate semantic analyzer
123pub struct SemanticAnalyzerFactory;
124
125impl SemanticAnalyzerFactory {
126    /// Create a semantic analyzer for the given language
127    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    /// Create a semantic analyzer from file path and content
137    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}