use std::cell::RefCell;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
static FILES_PROCESSED: AtomicUsize = AtomicUsize::new(0);
static FILES_TOTAL: AtomicUsize = AtomicUsize::new(0);
thread_local! {
pub(crate) static CURRENT_CONTEXT: RefCell<AnalysisContext> = const { RefCell::new(AnalysisContext::new()) };
}
#[derive(Debug, Clone, Default)]
pub struct AnalysisContext {
pub phase: Option<AnalysisPhase>,
pub current_file: Option<PathBuf>,
pub current_function: Option<String>,
}
impl AnalysisContext {
#[must_use]
pub const fn new() -> Self {
Self {
phase: None,
current_file: None,
current_function: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnalysisPhase {
FileDiscovery,
Parsing,
CallGraphBuilding,
PurityAnalysis,
CoverageLoading,
DebtScoring,
Prioritization,
OutputGeneration,
}
impl std::fmt::Display for AnalysisPhase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::FileDiscovery => write!(f, "file_discovery"),
Self::Parsing => write!(f, "parsing"),
Self::CallGraphBuilding => write!(f, "call_graph_building"),
Self::PurityAnalysis => write!(f, "purity_analysis"),
Self::CoverageLoading => write!(f, "coverage_loading"),
Self::DebtScoring => write!(f, "debt_scoring"),
Self::Prioritization => write!(f, "prioritization"),
Self::OutputGeneration => write!(f, "output_generation"),
}
}
}
pub struct ContextGuard {
previous: AnalysisContext,
}
impl Drop for ContextGuard {
fn drop(&mut self) {
CURRENT_CONTEXT.with(|ctx| {
*ctx.borrow_mut() = self.previous.clone();
});
}
}
#[must_use]
pub fn set_phase(phase: AnalysisPhase) -> ContextGuard {
CURRENT_CONTEXT.with(|ctx| {
let previous = ctx.borrow().clone();
ctx.borrow_mut().phase = Some(phase);
ContextGuard { previous }
})
}
pub fn set_phase_persistent(phase: AnalysisPhase) {
CURRENT_CONTEXT.with(|ctx| {
ctx.borrow_mut().phase = Some(phase);
});
}
#[must_use]
pub fn set_current_file(path: impl Into<PathBuf>) -> ContextGuard {
CURRENT_CONTEXT.with(|ctx| {
let previous = ctx.borrow().clone();
ctx.borrow_mut().current_file = Some(path.into());
ContextGuard { previous }
})
}
#[must_use]
pub fn set_current_function(name: impl Into<String>) -> ContextGuard {
CURRENT_CONTEXT.with(|ctx| {
let previous = ctx.borrow().clone();
ctx.borrow_mut().current_function = Some(name.into());
ContextGuard { previous }
})
}
pub fn set_progress(processed: usize, total: usize) {
FILES_PROCESSED.store(processed, Ordering::Relaxed);
FILES_TOTAL.store(total, Ordering::Relaxed);
}
pub fn increment_processed() {
FILES_PROCESSED.fetch_add(1, Ordering::Relaxed);
}
#[must_use]
pub fn get_current_context() -> AnalysisContext {
CURRENT_CONTEXT.with(|ctx| ctx.borrow().clone())
}
#[must_use]
pub fn get_progress() -> (usize, usize) {
(
FILES_PROCESSED.load(Ordering::Relaxed),
FILES_TOTAL.load(Ordering::Relaxed),
)
}
pub fn reset_progress() {
FILES_PROCESSED.store(0, Ordering::Relaxed);
FILES_TOTAL.store(0, Ordering::Relaxed);
}
pub fn reset_context() {
CURRENT_CONTEXT.with(|ctx| {
*ctx.borrow_mut() = AnalysisContext::new();
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_guard_restores_previous() {
reset_context();
let _phase1 = set_phase(AnalysisPhase::Parsing);
assert_eq!(
get_current_context().phase,
Some(AnalysisPhase::Parsing),
"Phase should be Parsing"
);
{
let _phase2 = set_phase(AnalysisPhase::DebtScoring);
assert_eq!(
get_current_context().phase,
Some(AnalysisPhase::DebtScoring),
"Phase should be DebtScoring"
);
}
assert_eq!(
get_current_context().phase,
Some(AnalysisPhase::Parsing),
"Phase should be restored to Parsing"
);
}
#[test]
fn test_nested_context_guards() {
reset_context();
let _phase = set_phase(AnalysisPhase::Parsing);
let _file = set_current_file("/path/to/test.rs");
let _func = set_current_function("test_function");
let ctx = get_current_context();
assert_eq!(ctx.phase, Some(AnalysisPhase::Parsing));
assert_eq!(ctx.current_file, Some(PathBuf::from("/path/to/test.rs")));
assert_eq!(ctx.current_function, Some("test_function".to_string()));
}
#[test]
fn test_progress_tracking() {
reset_progress();
set_progress(50, 100);
let (processed, total) = get_progress();
assert_eq!(processed, 50);
assert_eq!(total, 100);
}
#[test]
fn test_increment_processed() {
reset_progress();
set_progress(0, 100);
increment_processed();
increment_processed();
increment_processed();
let (processed, total) = get_progress();
assert_eq!(processed, 3);
assert_eq!(total, 100);
}
#[test]
fn test_analysis_phase_display() {
assert_eq!(
format!("{}", AnalysisPhase::FileDiscovery),
"file_discovery"
);
assert_eq!(format!("{}", AnalysisPhase::Parsing), "parsing");
assert_eq!(
format!("{}", AnalysisPhase::CallGraphBuilding),
"call_graph_building"
);
assert_eq!(
format!("{}", AnalysisPhase::PurityAnalysis),
"purity_analysis"
);
assert_eq!(
format!("{}", AnalysisPhase::CoverageLoading),
"coverage_loading"
);
assert_eq!(format!("{}", AnalysisPhase::DebtScoring), "debt_scoring");
assert_eq!(
format!("{}", AnalysisPhase::Prioritization),
"prioritization"
);
assert_eq!(
format!("{}", AnalysisPhase::OutputGeneration),
"output_generation"
);
}
#[test]
fn test_empty_context_by_default() {
reset_context();
let ctx = get_current_context();
assert!(ctx.phase.is_none());
assert!(ctx.current_file.is_none());
assert!(ctx.current_function.is_none());
}
}