use crate::core::traits::{Analyzer, ConfigProvider, Formatter, PriorityCalculator, Scorer};
use anyhow::Result;
use std::sync::Arc;
pub struct AppContainer {
pub rust_analyzer: Arc<dyn Analyzer<Input = String, Output = crate::core::types::ModuleInfo>>,
pub python_analyzer:
Option<Arc<dyn Analyzer<Input = String, Output = crate::core::types::ModuleInfo>>>,
pub js_analyzer:
Option<Arc<dyn Analyzer<Input = String, Output = crate::core::types::ModuleInfo>>>,
pub ts_analyzer:
Option<Arc<dyn Analyzer<Input = String, Output = crate::core::types::ModuleInfo>>>,
pub debt_scorer: Arc<dyn Scorer<Item = crate::core::types::DebtItem>>,
pub config: Arc<dyn ConfigProvider>,
pub priority_calculator: Arc<dyn PriorityCalculator<Item = crate::core::types::DebtItem>>,
pub json_formatter: Arc<dyn Formatter<Report = crate::core::types::AnalysisResult>>,
pub markdown_formatter: Arc<dyn Formatter<Report = crate::core::types::AnalysisResult>>,
pub terminal_formatter: Arc<dyn Formatter<Report = crate::core::types::AnalysisResult>>,
}
pub struct AppContainerBuilder {
rust_analyzer:
Option<Arc<dyn Analyzer<Input = String, Output = crate::core::types::ModuleInfo>>>,
python_analyzer:
Option<Arc<dyn Analyzer<Input = String, Output = crate::core::types::ModuleInfo>>>,
js_analyzer: Option<Arc<dyn Analyzer<Input = String, Output = crate::core::types::ModuleInfo>>>,
ts_analyzer: Option<Arc<dyn Analyzer<Input = String, Output = crate::core::types::ModuleInfo>>>,
debt_scorer: Option<Arc<dyn Scorer<Item = crate::core::types::DebtItem>>>,
config: Option<Arc<dyn ConfigProvider>>,
priority_calculator: Option<Arc<dyn PriorityCalculator<Item = crate::core::types::DebtItem>>>,
json_formatter: Option<Arc<dyn Formatter<Report = crate::core::types::AnalysisResult>>>,
markdown_formatter: Option<Arc<dyn Formatter<Report = crate::core::types::AnalysisResult>>>,
terminal_formatter: Option<Arc<dyn Formatter<Report = crate::core::types::AnalysisResult>>>,
}
impl Default for AppContainerBuilder {
fn default() -> Self {
Self::new()
}
}
impl AppContainerBuilder {
pub fn new() -> Self {
Self {
rust_analyzer: None,
python_analyzer: None,
js_analyzer: None,
ts_analyzer: None,
debt_scorer: None,
config: None,
priority_calculator: None,
json_formatter: None,
markdown_formatter: None,
terminal_formatter: None,
}
}
pub fn with_rust_analyzer(
mut self,
analyzer: impl Analyzer<Input = String, Output = crate::core::types::ModuleInfo> + 'static,
) -> Self {
self.rust_analyzer = Some(Arc::new(analyzer));
self
}
pub fn with_python_analyzer(
mut self,
analyzer: impl Analyzer<Input = String, Output = crate::core::types::ModuleInfo> + 'static,
) -> Self {
self.python_analyzer = Some(Arc::new(analyzer));
self
}
pub fn with_js_analyzer(
mut self,
analyzer: impl Analyzer<Input = String, Output = crate::core::types::ModuleInfo> + 'static,
) -> Self {
self.js_analyzer = Some(Arc::new(analyzer));
self
}
pub fn with_ts_analyzer(
mut self,
analyzer: impl Analyzer<Input = String, Output = crate::core::types::ModuleInfo> + 'static,
) -> Self {
self.ts_analyzer = Some(Arc::new(analyzer));
self
}
pub fn with_debt_scorer(
mut self,
scorer: impl Scorer<Item = crate::core::types::DebtItem> + 'static,
) -> Self {
self.debt_scorer = Some(Arc::new(scorer));
self
}
pub fn with_config(mut self, config: impl ConfigProvider + 'static) -> Self {
self.config = Some(Arc::new(config));
self
}
pub fn with_priority_calculator(
mut self,
calculator: impl PriorityCalculator<Item = crate::core::types::DebtItem> + 'static,
) -> Self {
self.priority_calculator = Some(Arc::new(calculator));
self
}
pub fn with_json_formatter(
mut self,
formatter: impl Formatter<Report = crate::core::types::AnalysisResult> + 'static,
) -> Self {
self.json_formatter = Some(Arc::new(formatter));
self
}
pub fn with_markdown_formatter(
mut self,
formatter: impl Formatter<Report = crate::core::types::AnalysisResult> + 'static,
) -> Self {
self.markdown_formatter = Some(Arc::new(formatter));
self
}
pub fn with_terminal_formatter(
mut self,
formatter: impl Formatter<Report = crate::core::types::AnalysisResult> + 'static,
) -> Self {
self.terminal_formatter = Some(Arc::new(formatter));
self
}
pub fn build(self) -> Result<AppContainer, String> {
Ok(AppContainer {
rust_analyzer: self.rust_analyzer.ok_or("Rust analyzer is required")?,
python_analyzer: self.python_analyzer,
js_analyzer: self.js_analyzer,
ts_analyzer: self.ts_analyzer,
debt_scorer: self.debt_scorer.ok_or("Debt scorer is required")?,
config: self.config.ok_or("Config provider is required")?,
priority_calculator: self
.priority_calculator
.ok_or("Priority calculator is required")?,
json_formatter: self.json_formatter.ok_or("JSON formatter is required")?,
markdown_formatter: self
.markdown_formatter
.ok_or("Markdown formatter is required")?,
terminal_formatter: self
.terminal_formatter
.ok_or("Terminal formatter is required")?,
})
}
}
pub trait Factory<T> {
fn create(&self) -> T;
}
pub struct AnalyzerFactory;
impl AnalyzerFactory {
pub fn create_analyzer(
&self,
language: crate::core::types::Language,
) -> Box<dyn Analyzer<Input = String, Output = crate::core::types::ModuleInfo>> {
match language {
crate::core::types::Language::Rust => Box::new(RustAnalyzerAdapter::new()),
crate::core::types::Language::Python => {
panic!("Python analysis is not currently supported. Debtmap is focusing exclusively on Rust analysis.")
}
}
}
}
pub struct RustAnalyzerAdapter {
inner: crate::analyzers::rust::RustAnalyzer,
}
impl Default for RustAnalyzerAdapter {
fn default() -> Self {
Self::new()
}
}
impl RustAnalyzerAdapter {
pub fn new() -> Self {
Self {
inner: crate::analyzers::rust::RustAnalyzer::new(),
}
}
}
impl Analyzer for RustAnalyzerAdapter {
type Input = String;
type Output = crate::core::types::ModuleInfo;
fn analyze(&self, input: Self::Input) -> anyhow::Result<Self::Output> {
use crate::analyzers::Analyzer as AnalyzerImpl;
let path = std::path::PathBuf::from("temp.rs");
let ast = self.inner.parse(&input, path.clone())?;
let file_metrics = self.inner.analyze(&ast);
Ok(crate::core::types::ModuleInfo {
name: path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("module")
.to_string(),
language: crate::core::types::Language::Rust,
path: path.clone(),
functions: file_metrics
.complexity
.functions
.into_iter()
.map(|f| crate::core::types::FunctionInfo {
name: f.name,
location: crate::core::types::SourceLocation {
file: path.clone(),
line: f.line,
column: 0,
end_line: Some(f.line + f.length),
end_column: Some(0),
},
parameters: vec![],
return_type: None,
is_public: true,
is_async: false,
is_generic: false,
doc_comment: None,
})
.collect(),
exports: vec![],
imports: file_metrics
.dependencies
.iter()
.map(|d| d.name.clone())
.collect(),
})
}
fn name(&self) -> &str {
"RustAnalyzer"
}
}
pub struct ServiceLocator {
services: std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync>>,
}
impl Default for ServiceLocator {
fn default() -> Self {
Self::new()
}
}
impl ServiceLocator {
pub fn new() -> Self {
Self {
services: std::collections::HashMap::new(),
}
}
pub fn register<T: 'static + Send + Sync>(&mut self, service: T) {
let type_id = std::any::TypeId::of::<T>();
self.services.insert(type_id, Box::new(service));
}
pub fn resolve<T: 'static>(&self) -> Option<&T> {
let type_id = std::any::TypeId::of::<T>();
self.services
.get(&type_id)
.and_then(|service| service.downcast_ref::<T>())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::traits::PriorityFactor;
use crate::core::types::{DebtCategory, DebtItem, Language, ModuleInfo};
struct MockAnalyzer {
language: Language,
}
impl Analyzer for MockAnalyzer {
type Input = String;
type Output = ModuleInfo;
fn analyze(&self, _input: Self::Input) -> anyhow::Result<Self::Output> {
Ok(ModuleInfo {
name: "test_module".to_string(),
language: self.language,
path: std::path::PathBuf::from("test.rs"),
functions: vec![],
exports: vec![],
imports: vec![],
})
}
fn name(&self) -> &str {
"MockAnalyzer"
}
}
struct MockScorer;
impl Scorer for MockScorer {
type Item = DebtItem;
fn score(&self, item: &Self::Item) -> f64 {
match item.category {
DebtCategory::Complexity => 5.0,
DebtCategory::Testing => 3.0,
_ => 1.0,
}
}
fn methodology(&self) -> &str {
"Mock scoring based on debt type"
}
}
struct MockConfigProvider;
impl ConfigProvider for MockConfigProvider {
fn get(&self, key: &str) -> Option<String> {
match key {
"complexity_threshold" => Some("10".to_string()),
"max_file_size" => Some("1000000".to_string()),
_ => None,
}
}
fn set(&mut self, _key: String, _value: String) {
}
fn load_from_file(&self, _path: &std::path::Path) -> anyhow::Result<()> {
Ok(())
}
}
struct MockPriorityCalculator;
impl PriorityCalculator for MockPriorityCalculator {
type Item = DebtItem;
fn calculate_priority(&self, item: &Self::Item) -> f64 {
match item.category {
DebtCategory::Complexity => 0.8,
DebtCategory::Testing => 0.5,
_ => 0.2,
}
}
fn get_factors(&self, _item: &Self::Item) -> Vec<PriorityFactor> {
vec![PriorityFactor {
name: "debt_type".to_string(),
weight: 1.0,
value: 0.5,
description: "Mock factor".to_string(),
}]
}
}
struct MockFormatter;
impl Formatter for MockFormatter {
type Report = crate::core::types::AnalysisResult;
fn format(&self, _report: &Self::Report) -> anyhow::Result<String> {
Ok("Mock formatted report".to_string())
}
fn format_name(&self) -> &str {
"mock"
}
}
#[test]
fn test_app_container_builder() {
let builder = AppContainerBuilder::new()
.with_rust_analyzer(MockAnalyzer {
language: Language::Rust,
})
.with_python_analyzer(MockAnalyzer {
language: Language::Python,
})
.with_debt_scorer(MockScorer)
.with_config(MockConfigProvider)
.with_priority_calculator(MockPriorityCalculator)
.with_json_formatter(MockFormatter)
.with_markdown_formatter(MockFormatter)
.with_terminal_formatter(MockFormatter);
let container = builder.build();
assert!(container.is_ok());
}
#[test]
fn test_builder_missing_analyzer() {
let builder = AppContainerBuilder::new()
.with_python_analyzer(MockAnalyzer {
language: Language::Python,
})
.with_debt_scorer(MockScorer)
.with_config(MockConfigProvider)
.with_priority_calculator(MockPriorityCalculator)
.with_json_formatter(MockFormatter)
.with_markdown_formatter(MockFormatter)
.with_terminal_formatter(MockFormatter);
let container = builder.build();
assert!(container.is_err());
if let Err(msg) = container {
assert!(msg.contains("Rust analyzer is required"));
}
}
#[test]
fn test_service_locator() {
let mut locator = ServiceLocator::new();
locator.register(MockScorer);
let scorer = locator.resolve::<MockScorer>();
assert!(scorer.is_some());
let missing = locator.resolve::<MockConfigProvider>();
assert!(missing.is_none());
}
#[test]
fn test_analyzer_factory() {
let factory = AnalyzerFactory;
let rust_analyzer = factory.create_analyzer(Language::Rust);
assert_eq!(rust_analyzer.name(), "RustAnalyzer");
}
#[test]
#[should_panic(expected = "Python analysis is not currently supported")]
fn test_analyzer_factory_python_panics() {
let factory = AnalyzerFactory;
let _python_analyzer = factory.create_analyzer(Language::Python);
}
}