ricecoder_lsp/semantic/
generic_analyzer.rs

1//! Generic semantic analyzer that works with pluggable providers
2//!
3//! This module provides a language-agnostic semantic analyzer that delegates
4//! to language-specific providers registered in the provider registry.
5
6use crate::providers::{ProviderResult, SemanticAnalyzerProvider, SemanticAnalyzerRegistry};
7use crate::semantic::{fallback_analyzer::FallbackAnalyzer, SemanticAnalyzer};
8use crate::types::{Position, SemanticInfo, Symbol};
9
10/// Generic semantic analyzer that delegates to pluggable providers
11pub struct GenericSemanticAnalyzer {
12    registry: SemanticAnalyzerRegistry,
13    fallback: FallbackAnalyzer,
14}
15
16impl GenericSemanticAnalyzer {
17    /// Create a new generic semantic analyzer
18    pub fn new() -> Self {
19        Self {
20            registry: SemanticAnalyzerRegistry::new(),
21            fallback: FallbackAnalyzer::new(),
22        }
23    }
24
25    /// Register a semantic analyzer provider
26    pub fn register_provider(&mut self, provider: Box<dyn SemanticAnalyzerProvider>) {
27        self.registry.register(provider);
28    }
29
30    /// Get the provider registry
31    pub fn registry(&self) -> &SemanticAnalyzerRegistry {
32        &self.registry
33    }
34
35    /// Get a mutable reference to the provider registry
36    pub fn registry_mut(&mut self) -> &mut SemanticAnalyzerRegistry {
37        &mut self.registry
38    }
39
40    /// Analyze code using the appropriate provider or fallback
41    pub fn analyze(&self, code: &str, language: &str) -> ProviderResult<SemanticInfo> {
42        if let Some(provider) = self.registry.get(language) {
43            provider.analyze(code)
44        } else {
45            // Gracefully degrade to fallback analysis
46            tracing::debug!(
47                "No provider found for language '{}', using fallback",
48                language
49            );
50            self.fallback
51                .analyze(code)
52                .map_err(|e| crate::providers::ProviderError::Error(e.to_string()))
53        }
54    }
55
56    /// Extract symbols using the appropriate provider or fallback
57    pub fn extract_symbols(&self, code: &str, language: &str) -> ProviderResult<Vec<Symbol>> {
58        if let Some(provider) = self.registry.get(language) {
59            provider.extract_symbols(code)
60        } else {
61            // Gracefully degrade to fallback analysis
62            tracing::debug!(
63                "No provider found for language '{}', using fallback",
64                language
65            );
66            self.fallback
67                .extract_symbols(code)
68                .map_err(|e| crate::providers::ProviderError::Error(e.to_string()))
69        }
70    }
71
72    /// Get hover information using the appropriate provider or fallback
73    pub fn get_hover_info(
74        &self,
75        code: &str,
76        language: &str,
77        position: Position,
78    ) -> ProviderResult<Option<String>> {
79        if let Some(provider) = self.registry.get(language) {
80            provider.get_hover_info(code, position)
81        } else {
82            // Gracefully degrade to fallback analysis
83            tracing::debug!(
84                "No provider found for language '{}', using fallback",
85                language
86            );
87            self.fallback
88                .get_hover_info(code, position)
89                .map_err(|e| crate::providers::ProviderError::Error(e.to_string()))
90        }
91    }
92
93    /// Check if a provider is registered for a language
94    pub fn has_provider(&self, language: &str) -> bool {
95        self.registry.has_provider(language)
96    }
97
98    /// List all registered languages
99    pub fn languages(&self) -> Vec<&str> {
100        self.registry.languages()
101    }
102}
103
104impl Default for GenericSemanticAnalyzer {
105    fn default() -> Self {
106        Self::new()
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    struct MockProvider;
115
116    impl SemanticAnalyzerProvider for MockProvider {
117        fn language(&self) -> &str {
118            "mock"
119        }
120
121        fn analyze(&self, _code: &str) -> ProviderResult<SemanticInfo> {
122            Ok(SemanticInfo {
123                symbols: vec![],
124                imports: vec![],
125                definitions: vec![],
126                references: vec![],
127            })
128        }
129
130        fn extract_symbols(&self, _code: &str) -> ProviderResult<Vec<Symbol>> {
131            Ok(vec![])
132        }
133
134        fn get_hover_info(
135            &self,
136            _code: &str,
137            _position: Position,
138        ) -> ProviderResult<Option<String>> {
139            Ok(Some("mock info".to_string()))
140        }
141    }
142
143    #[test]
144    fn test_generic_analyzer_with_provider() {
145        let mut analyzer = GenericSemanticAnalyzer::new();
146        analyzer.register_provider(Box::new(MockProvider));
147
148        assert!(analyzer.has_provider("mock"));
149        assert!(analyzer.analyze("test", "mock").is_ok());
150    }
151
152    #[test]
153    fn test_generic_analyzer_fallback() {
154        let analyzer = GenericSemanticAnalyzer::new();
155
156        // Should use fallback for unknown language
157        assert!(!analyzer.has_provider("unknown"));
158        assert!(analyzer.analyze("test", "unknown").is_ok());
159    }
160
161    #[test]
162    fn test_generic_analyzer_languages() {
163        let mut analyzer = GenericSemanticAnalyzer::new();
164        analyzer.register_provider(Box::new(MockProvider));
165
166        let languages = analyzer.languages();
167        assert!(languages.contains(&"mock"));
168    }
169}