use anyhow::Result;
use chrono::Utc;
use debtmap::analyzers::{analyze_file, get_analyzer, get_analyzer_with_context};
use debtmap::core::{
AnalysisResults, ComplexityReport, ComplexitySummary, DebtItem, DebtType, DependencyReport,
FileMetrics, Language, Priority, TechnicalDebtReport,
};
use debtmap::debt::{
patterns::{find_code_smells_with_suppression, find_todos_and_fixmes_with_suppression},
suppression::parse_suppression_comments,
};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::time::Duration;
pub fn analyze_code_snippet(code: &str, language: Language) -> Result<FileMetrics> {
let analyzer = get_analyzer(language);
let path = PathBuf::from(match language {
Language::Rust => "test.rs",
Language::Python => "test.py",
_ => "test.txt",
});
analyze_file(code.to_string(), path, &*analyzer)
}
pub fn analyze_file_directly(file_path: &Path) -> Result<AnalysisResults> {
let content = std::fs::read_to_string(file_path)?;
let language = detect_language(file_path);
let context_aware = std::env::var("DEBTMAP_CONTEXT_AWARE")
.map(|v| v == "true")
.unwrap_or(false);
let analyzer = get_analyzer_with_context(language, context_aware);
let metrics = analyze_file(content.clone(), file_path.to_path_buf(), &*analyzer)?;
let suppression_comments = parse_suppression_comments(&content, language, file_path);
let todos =
find_todos_and_fixmes_with_suppression(&content, file_path, Some(&suppression_comments));
let smells =
find_code_smells_with_suppression(&content, file_path, Some(&suppression_comments));
let mut all_debt_items = Vec::new();
for (i, mut item) in todos.into_iter().chain(smells.into_iter()).enumerate() {
item.id = format!("debt_{}", i);
all_debt_items.push(item);
}
let mut by_type: HashMap<DebtType, Vec<DebtItem>> = HashMap::new();
for item in &all_debt_items {
by_type
.entry(item.debt_type.clone())
.or_default()
.push(item.clone());
}
let priorities: Vec<Priority> = all_debt_items.iter().map(|item| item.priority).collect();
let complexity_report = ComplexityReport {
metrics: vec![], summary: ComplexitySummary {
total_functions: 0, average_complexity: metrics.complexity.cyclomatic_complexity as f64,
max_complexity: metrics.complexity.cyclomatic_complexity,
high_complexity_count: if metrics.complexity.cyclomatic_complexity > 10 {
1
} else {
0
},
},
};
Ok(AnalysisResults {
project_path: file_path.parent().unwrap_or(Path::new(".")).to_path_buf(),
timestamp: Utc::now(),
complexity: complexity_report,
technical_debt: TechnicalDebtReport {
items: all_debt_items,
by_type,
priorities,
duplications: vec![],
},
dependencies: DependencyReport {
modules: vec![],
circular: vec![],
},
duplications: vec![],
file_contexts: HashMap::new(),
})
}
pub fn run_with_timeout<F, T>(f: F, timeout: Duration) -> Result<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let result = f();
let _ = tx.send(result);
});
match rx.recv_timeout(timeout) {
Ok(result) => Ok(result),
Err(_) => Err(anyhow::anyhow!("Operation timed out after {:?}", timeout)),
}
}
pub fn perform_unified_test_analysis(
results: &AnalysisResults,
_coverage_file: Option<&Path>,
) -> Result<Vec<(String, f64, DebtItem)>> {
let mut priorities = Vec::new();
for item in &results.technical_debt.items {
let score = match item.priority {
Priority::Critical => 10.0,
Priority::High => 7.0,
Priority::Medium => 4.0,
Priority::Low => 1.0,
};
priorities.push((format!("item_{}", priorities.len()), score, item.clone()));
}
priorities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
Ok(priorities)
}
fn detect_language(path: &Path) -> Language {
match path.extension().and_then(|s| s.to_str()) {
Some("rs") => Language::Rust,
Some("py") => Language::Python,
Some("js") => Language::Rust,
Some("ts") => Language::Rust,
_ => Language::Rust, }
}