use super::{AnalysisConfig, DeadCodeAnalysis, RustCallGraph, RustCallGraphBuilder};
use crate::analysis::effects::{lift_pure, query_config};
use crate::effects::AnalysisEffect;
use crate::env::RealEnv;
use crate::errors::AnalysisError;
use im::HashSet;
use stillwater::Effect;
use syn::File;
use crate::priority::call_graph::FunctionId;
fn build_call_graph_pure(
file_path: &std::path::Path,
ast: &File,
config: &AnalysisConfig,
) -> anyhow::Result<RustCallGraph> {
let mut builder = RustCallGraphBuilder::with_config(config.clone());
builder.analyze_basic_calls(file_path, ast)?;
builder.analyze_trait_dispatch(file_path, ast)?;
builder.analyze_function_pointers(file_path, ast)?;
builder.analyze_framework_patterns(file_path, ast)?;
Ok(builder.build())
}
pub fn analyze_dead_code_effect(graph: RustCallGraph) -> AnalysisEffect<Vec<DeadCodeAnalysis>> {
let analysis = graph.analyze_dead_code();
lift_pure(analysis)
}
pub fn get_live_functions_effect(graph: RustCallGraph) -> AnalysisEffect<HashSet<FunctionId>> {
let live = graph.get_live_functions();
lift_pure(live)
}
pub fn get_dead_code_effect(graph: RustCallGraph) -> AnalysisEffect<HashSet<FunctionId>> {
let dead = graph.get_potential_dead_code();
lift_pure(dead)
}
pub fn get_analysis_config_effect(
) -> impl Effect<Output = AnalysisConfig, Error = AnalysisError, Env = RealEnv> {
query_config(get_analysis_config_from_debtmap_config)
}
fn get_analysis_config_from_debtmap_config(
config: &crate::config::DebtmapConfig,
) -> AnalysisConfig {
let analysis = config.analysis.as_ref();
AnalysisConfig {
enable_trait_analysis: analysis
.and_then(|a| a.enable_trait_analysis)
.unwrap_or(true),
enable_function_pointer_tracking: analysis
.and_then(|a| a.enable_function_pointer_tracking)
.unwrap_or(true),
enable_framework_patterns: analysis
.and_then(|a| a.enable_framework_patterns)
.unwrap_or(true),
enable_cross_module_analysis: analysis
.and_then(|a| a.enable_cross_module_analysis)
.unwrap_or(true),
max_analysis_depth: analysis.and_then(|a| a.max_analysis_depth).unwrap_or(10),
}
}
pub fn build_call_graph_result(
file_path: &std::path::Path,
ast: &File,
config: &crate::config::DebtmapConfig,
) -> anyhow::Result<RustCallGraph> {
let analysis_config = get_analysis_config_from_debtmap_config(config);
build_call_graph_pure(file_path, ast, &analysis_config)
}
pub fn analyze_dead_code_result(graph: &RustCallGraph) -> Vec<DeadCodeAnalysis> {
graph.analyze_dead_code()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::DebtmapConfig;
use crate::env::RealEnv;
use quote::quote;
use syn::parse2;
fn create_test_ast() -> File {
let tokens = quote! {
fn main() {
helper();
}
fn helper() {
println!("Hello");
}
};
parse2(tokens).unwrap()
}
#[tokio::test]
async fn test_analyze_dead_code_effect() {
let env = RealEnv::default();
let graph = RustCallGraph::new();
let effect = analyze_dead_code_effect(graph);
let result = effect.run(&env).await.unwrap();
assert!(result.is_empty());
}
#[tokio::test]
async fn test_get_analysis_config_effect() {
let env = RealEnv::default();
let effect = get_analysis_config_effect();
let config = effect.run(&env).await.unwrap();
assert!(config.enable_trait_analysis);
assert!(config.enable_framework_patterns);
}
#[test]
fn test_backwards_compat_build_call_graph() {
use std::path::Path;
let ast = create_test_ast();
let path = Path::new("test.rs");
let config = DebtmapConfig::default();
let result = build_call_graph_result(path, &ast, &config);
assert!(result.is_ok());
}
}