ricecoder_lsp/diagnostics/
generic_engine.rs

1//! Generic diagnostics engine that works with pluggable providers
2//!
3//! This module provides a language-agnostic diagnostics engine that delegates
4//! to language-specific providers registered in the provider registry.
5
6use crate::providers::{DiagnosticsProvider, DiagnosticsRegistry, ProviderResult};
7use crate::types::{Diagnostic, Range};
8
9/// Generic diagnostics engine that delegates to pluggable providers
10pub struct GenericDiagnosticsEngine {
11    registry: DiagnosticsRegistry,
12}
13
14impl GenericDiagnosticsEngine {
15    /// Create a new generic diagnostics engine
16    pub fn new() -> Self {
17        Self {
18            registry: DiagnosticsRegistry::new(),
19        }
20    }
21
22    /// Register a diagnostics provider
23    pub fn register_provider(&mut self, provider: Box<dyn DiagnosticsProvider>) {
24        self.registry.register(provider);
25    }
26
27    /// Get the provider registry
28    pub fn registry(&self) -> &DiagnosticsRegistry {
29        &self.registry
30    }
31
32    /// Get a mutable reference to the provider registry
33    pub fn registry_mut(&mut self) -> &mut DiagnosticsRegistry {
34        &mut self.registry
35    }
36
37    /// Generate diagnostics using the appropriate provider or fallback
38    pub fn generate_diagnostics(
39        &self,
40        code: &str,
41        language: &str,
42    ) -> ProviderResult<Vec<Diagnostic>> {
43        if let Some(provider) = self.registry.get(language) {
44            provider.generate_diagnostics(code)
45        } else {
46            // Gracefully degrade to empty diagnostics for unconfigured languages
47            tracing::debug!(
48                "No diagnostics provider found for language '{}', returning empty",
49                language
50            );
51            Ok(Vec::new())
52        }
53    }
54
55    /// Generate diagnostics for a specific range
56    pub fn generate_diagnostics_for_range(
57        &self,
58        code: &str,
59        language: &str,
60        range: Range,
61    ) -> ProviderResult<Vec<Diagnostic>> {
62        let all_diagnostics = self.generate_diagnostics(code, language)?;
63
64        // Filter diagnostics that fall within the specified range
65        let filtered = all_diagnostics
66            .into_iter()
67            .filter(|diag| {
68                diag.range.start.line >= range.start.line && diag.range.end.line <= range.end.line
69            })
70            .collect();
71
72        Ok(filtered)
73    }
74
75    /// Check if a provider is registered for a language
76    pub fn has_provider(&self, language: &str) -> bool {
77        self.registry.has_provider(language)
78    }
79
80    /// List all registered languages
81    pub fn languages(&self) -> Vec<&str> {
82        self.registry.languages()
83    }
84}
85
86impl Default for GenericDiagnosticsEngine {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    struct MockDiagnosticsProvider;
97
98    impl DiagnosticsProvider for MockDiagnosticsProvider {
99        fn language(&self) -> &str {
100            "mock"
101        }
102
103        fn generate_diagnostics(&self, _code: &str) -> ProviderResult<Vec<Diagnostic>> {
104            Ok(vec![])
105        }
106
107        fn config(&self) -> Option<&crate::config::LanguageConfig> {
108            None
109        }
110    }
111
112    #[test]
113    fn test_generic_diagnostics_engine_with_provider() {
114        let mut engine = GenericDiagnosticsEngine::new();
115        engine.register_provider(Box::new(MockDiagnosticsProvider));
116
117        assert!(engine.has_provider("mock"));
118        assert!(engine.generate_diagnostics("test", "mock").is_ok());
119    }
120
121    #[test]
122    fn test_generic_diagnostics_engine_fallback() {
123        let engine = GenericDiagnosticsEngine::new();
124
125        // Should gracefully degrade for unknown language
126        assert!(!engine.has_provider("unknown"));
127        let result = engine.generate_diagnostics("test", "unknown");
128        assert!(result.is_ok());
129        assert!(result.unwrap().is_empty());
130    }
131
132    #[test]
133    fn test_generic_diagnostics_engine_languages() {
134        let mut engine = GenericDiagnosticsEngine::new();
135        engine.register_provider(Box::new(MockDiagnosticsProvider));
136
137        let languages = engine.languages();
138        assert!(languages.contains(&"mock"));
139    }
140}