pub mod repomap;
pub mod storage;
pub mod types;
#[cfg(feature = "stack-graphs")]
pub mod stack_graphs;
use anyhow::Result;
pub use types::{
CallEdge, CallGraphNode, Definition, DefinitionResult, PrecisionLevel, Reference,
ReferenceKind, ReferenceResult, SymbolId, SymbolInfo, SymbolKind, Visibility,
};
use crate::indexer::FileInfo;
use std::collections::HashMap;
pub trait RelationsProvider: Send + Sync {
fn extract_definitions(&self, file_info: &FileInfo) -> Result<Vec<Definition>>;
fn extract_references(
&self,
file_info: &FileInfo,
symbol_index: &HashMap<String, Vec<Definition>>,
) -> Result<Vec<Reference>>;
fn supports_language(&self, language: &str) -> bool;
fn precision_level(&self, language: &str) -> PrecisionLevel;
}
pub struct HybridRelationsProvider {
#[cfg(feature = "stack-graphs")]
stack_graphs: Option<stack_graphs::StackGraphsProvider>,
repomap: repomap::RepoMapProvider,
}
impl HybridRelationsProvider {
pub fn new(_enable_stack_graphs: bool) -> Result<Self> {
#[cfg(feature = "stack-graphs")]
let stack_graphs = if _enable_stack_graphs {
match stack_graphs::StackGraphsProvider::new() {
Ok(sg) => Some(sg),
Err(e) => {
tracing::warn!("Failed to initialize stack-graphs: {}", e);
None
}
}
} else {
None
};
Ok(Self {
#[cfg(feature = "stack-graphs")]
stack_graphs,
repomap: repomap::RepoMapProvider::new(),
})
}
fn provider_for_language(&self, _language: &str) -> &dyn RelationsProvider {
#[cfg(feature = "stack-graphs")]
if let Some(ref sg) = self.stack_graphs {
if sg.supports_language(_language) {
return sg;
}
}
&self.repomap
}
#[cfg(feature = "stack-graphs")]
pub fn has_stack_graphs_for(&self, language: &str) -> bool {
self.stack_graphs
.as_ref()
.is_some_and(|sg| sg.supports_language(language))
}
#[cfg(not(feature = "stack-graphs"))]
pub fn has_stack_graphs_for(&self, _language: &str) -> bool {
false
}
}
impl RelationsProvider for HybridRelationsProvider {
fn extract_definitions(&self, file_info: &FileInfo) -> Result<Vec<Definition>> {
let language = file_info.language.as_deref().unwrap_or("Unknown");
self.provider_for_language(language)
.extract_definitions(file_info)
}
fn extract_references(
&self,
file_info: &FileInfo,
symbol_index: &HashMap<String, Vec<Definition>>,
) -> Result<Vec<Reference>> {
let language = file_info.language.as_deref().unwrap_or("Unknown");
self.provider_for_language(language)
.extract_references(file_info, symbol_index)
}
fn supports_language(&self, language: &str) -> bool {
self.repomap.supports_language(language)
}
fn precision_level(&self, language: &str) -> PrecisionLevel {
#[cfg(feature = "stack-graphs")]
if self.has_stack_graphs_for(language) {
return PrecisionLevel::High;
}
self.repomap.precision_level(language)
}
}
#[derive(Debug, Clone)]
pub struct RelationsConfig {
pub enabled: bool,
pub use_stack_graphs: bool,
pub max_call_depth: usize,
}
impl Default for RelationsConfig {
fn default() -> Self {
Self {
enabled: true,
use_stack_graphs: cfg!(feature = "stack-graphs"),
max_call_depth: 3,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hybrid_provider_creation() {
let provider = HybridRelationsProvider::new(false).unwrap();
assert!(provider.supports_language("Rust"));
assert!(provider.supports_language("Python"));
assert!(provider.supports_language("Unknown"));
}
#[test]
fn test_precision_level_without_stack_graphs() {
let provider = HybridRelationsProvider::new(false).unwrap();
assert_eq!(provider.precision_level("Rust"), PrecisionLevel::Medium);
assert_eq!(provider.precision_level("Python"), PrecisionLevel::Medium);
}
#[test]
fn test_relations_config_default() {
let config = RelationsConfig::default();
assert!(config.enabled);
assert_eq!(config.max_call_depth, 3);
}
#[test]
fn test_has_stack_graphs() {
let provider = HybridRelationsProvider::new(false).unwrap();
#[cfg(not(feature = "stack-graphs"))]
assert!(!provider.has_stack_graphs_for("Python"));
}
}