#![cfg_attr(coverage_nightly, coverage(off))]
use super::{
AnalyzeComplexityContract, AnalyzeDeadCodeContract, AnalyzeLintHotspotContract,
AnalyzeSatdContract, AnalyzeTdgContract, ContractValidation,
};
use crate::cli::{commands::AnalyzeCommands, Commands};
use anyhow::Result;
use std::sync::Arc;
pub struct ContractCliHandler {
service: Arc<crate::contracts::service::ContractService>,
}
impl ContractCliHandler {
pub fn new() -> Result<Self> {
Ok(Self {
service: Arc::new(crate::contracts::service::ContractService::new()?),
})
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn handle_command(&self, cmd: Commands) -> Result<()> {
if let Commands::Analyze(analyze_cmd) = cmd {
self.handle_analyze_command(analyze_cmd).await
} else {
println!("🚧 Executing command in standard mode");
Ok(())
}
}
async fn handle_analyze_command(&self, cmd: AnalyzeCommands) -> Result<()> {
let warnings = super::adapter::ContractAdapter::deprecation_warnings(&cmd);
for warning in warnings {
eprintln!("{warning}");
}
let result = match cmd {
AnalyzeCommands::Complexity { .. } => self.handle_complexity_analysis(&cmd).await?,
AnalyzeCommands::Satd { .. } => self.handle_satd_analysis(&cmd).await?,
AnalyzeCommands::DeadCode { .. } => self.handle_dead_code_analysis(&cmd).await?,
AnalyzeCommands::Tdg { .. } => self.handle_tdg_analysis(&cmd).await?,
AnalyzeCommands::LintHotspot { .. } => self.handle_lint_hotspot_analysis(&cmd).await?,
_ => {
println!("📊 Running analysis in standard mode");
serde_json::Value::Null
}
};
self.output_result(result, &cmd)?;
Ok(())
}
async fn handle_complexity_analysis(&self, cmd: &AnalyzeCommands) -> Result<serde_json::Value> {
let contract = super::adapter::ContractAdapter::from_cli(cmd)?;
if let Some(complexity_contract) = contract
.as_any()
.downcast_ref::<AnalyzeComplexityContract>()
{
self.service
.analyze_complexity(complexity_contract.clone())
.await
} else {
Err(anyhow::anyhow!(
"Invalid contract type for complexity analysis"
))
}
}
async fn handle_satd_analysis(&self, cmd: &AnalyzeCommands) -> Result<serde_json::Value> {
let contract = super::adapter::ContractAdapter::from_cli(cmd)?;
if let Some(satd_contract) = contract.as_any().downcast_ref::<AnalyzeSatdContract>() {
self.service.analyze_satd(satd_contract.clone()).await
} else {
Err(anyhow::anyhow!("Invalid contract type for SATD analysis"))
}
}
async fn handle_dead_code_analysis(&self, cmd: &AnalyzeCommands) -> Result<serde_json::Value> {
let contract = super::adapter::ContractAdapter::from_cli(cmd)?;
if let Some(dead_code_contract) =
contract.as_any().downcast_ref::<AnalyzeDeadCodeContract>()
{
self.service
.analyze_dead_code(dead_code_contract.clone())
.await
} else {
Err(anyhow::anyhow!(
"Invalid contract type for dead code analysis"
))
}
}
async fn handle_tdg_analysis(&self, cmd: &AnalyzeCommands) -> Result<serde_json::Value> {
let contract = super::adapter::ContractAdapter::from_cli(cmd)?;
if let Some(tdg_contract) = contract.as_any().downcast_ref::<AnalyzeTdgContract>() {
self.service.analyze_tdg(tdg_contract.clone()).await
} else {
Err(anyhow::anyhow!("Invalid contract type for TDG analysis"))
}
}
async fn handle_lint_hotspot_analysis(
&self,
cmd: &AnalyzeCommands,
) -> Result<serde_json::Value> {
let contract = super::adapter::ContractAdapter::from_cli(cmd)?;
if let Some(lint_contract) = contract
.as_any()
.downcast_ref::<AnalyzeLintHotspotContract>()
{
self.service
.analyze_lint_hotspot(lint_contract.clone())
.await
} else {
Err(anyhow::anyhow!(
"Invalid contract type for lint hotspot analysis"
))
}
}
fn output_result(&self, result: serde_json::Value, cmd: &AnalyzeCommands) -> Result<()> {
let output_path = match cmd {
AnalyzeCommands::Complexity { output, .. }
| AnalyzeCommands::Satd { output, .. }
| AnalyzeCommands::DeadCode { output, .. }
| AnalyzeCommands::Tdg { output, .. }
| AnalyzeCommands::LintHotspot { output, .. } => output,
_ => &None,
};
let output_str = match result {
serde_json::Value::String(s) => s,
other => serde_json::to_string_pretty(&other)?,
};
if let Some(path) = output_path {
std::fs::write(path, output_str)?;
println!("Results written to: {}", path.display());
} else {
println!("{output_str}");
}
Ok(())
}
}
trait AsAny {
fn as_any(&self) -> &dyn std::any::Any;
}
impl AsAny for Box<dyn ContractValidation> {
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use tempfile::TempDir;
include!("cli_impl_tests_core.rs");
include!("cli_impl_tests_integration.rs");
}