#![cfg_attr(coverage_nightly, coverage(off))]
use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::path::Path;
pub mod big_o;
pub mod complexity;
pub mod dead_code;
pub mod defect;
pub mod integration_tests;
pub mod satd;
#[async_trait]
pub trait Analyzer {
type Input;
type Output;
type Config;
async fn analyze(&self, input: Self::Input, config: Self::Config) -> Result<Self::Output>;
fn name(&self) -> &'static str;
fn version(&self) -> &'static str {
env!("CARGO_PKG_VERSION")
}
}
#[async_trait]
pub trait ProjectAnalyzer: Analyzer<Input = ProjectInput, Config = ProjectConfig> {
async fn analyze_project(&self, project_path: &Path) -> Result<Self::Output> {
let input = ProjectInput {
project_path: project_path.to_path_buf(),
};
let config = ProjectConfig::default();
self.analyze(input, config).await
}
}
#[async_trait]
pub trait FileAnalyzer: Analyzer<Input = FileInput, Config = FileConfig> {
async fn analyze_file(&self, file_path: &Path, content: Option<&str>) -> Result<Self::Output> {
let input = FileInput {
file_path: file_path.to_path_buf(),
content: content.map(String::from),
};
let config = FileConfig::default();
self.analyze(input, config).await
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectInput {
pub project_path: std::path::PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectConfig {
pub include_tests: bool,
pub max_depth: Option<usize>,
pub parallel: bool,
}
impl Default for ProjectConfig {
fn default() -> Self {
Self {
include_tests: true,
max_depth: None,
parallel: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileInput {
pub file_path: std::path::PathBuf,
pub content: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FileConfig {
pub language: Option<String>,
pub strict_mode: bool,
}
pub struct AnalyzerRegistry {
analyzers: std::collections::HashMap<String, Box<dyn AnalyzerInfo>>,
}
pub trait AnalyzerInfo {
fn name(&self) -> &str;
fn version(&self) -> &str;
fn description(&self) -> &str;
}
impl AnalyzerRegistry {
#[must_use]
pub fn new() -> Self {
Self {
analyzers: std::collections::HashMap::new(),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn register<T>(&mut self, analyzer: T)
where
T: AnalyzerInfo + 'static,
{
self.analyzers
.insert(analyzer.name().to_string(), Box::new(analyzer));
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_info(&self, name: &str) -> Option<&dyn AnalyzerInfo> {
self.analyzers.get(name).map(std::convert::AsRef::as_ref)
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn list_analyzers(&self) -> Vec<&str> {
self.analyzers
.keys()
.map(std::string::String::as_str)
.collect()
}
}
impl Default for AnalyzerRegistry {
fn default() -> Self {
Self::new()
}
}
pub trait AnalysisPatterns {
fn filter_by_confidence<T>(&self, results: Vec<T>, threshold: f64) -> Vec<T>
where
T: HasConfidence;
fn sort_by_priority<T>(&self, results: &mut [T])
where
T: HasPriority;
}
pub trait HasConfidence {
fn confidence(&self) -> f64;
}
pub trait HasPriority {
fn priority(&self) -> i32;
}
pub struct DefaultAnalysisPatterns;
impl AnalysisPatterns for DefaultAnalysisPatterns {
fn filter_by_confidence<T>(&self, results: Vec<T>, threshold: f64) -> Vec<T>
where
T: HasConfidence,
{
results
.into_iter()
.filter(|item| item.confidence() >= threshold)
.collect()
}
fn sort_by_priority<T>(&self, results: &mut [T])
where
T: HasPriority,
{
results.sort_by_key(|b| std::cmp::Reverse(b.priority()));
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyzer_registry() {
let registry = AnalyzerRegistry::new();
assert_eq!(registry.list_analyzers().len(), 0);
assert!(registry.get_info("nonexistent").is_none());
}
#[test]
fn test_project_config_default() {
let config = ProjectConfig::default();
assert!(config.include_tests);
assert!(config.parallel);
assert!(config.max_depth.is_none());
}
#[test]
fn test_file_config_default() {
let config = FileConfig::default();
assert!(!config.strict_mode);
assert!(config.language.is_none());
}
#[test]
fn test_unified_analyzer_framework() {
use crate::services::analyzer::{
complexity::ComplexityAnalyzer, dead_code::DeadCodeAnalyzer, satd::SATDAnalyzer,
};
let mut registry = AnalyzerRegistry::new();
registry.register(DeadCodeAnalyzer::new());
registry.register(ComplexityAnalyzer::new());
registry.register(SATDAnalyzer::new());
assert_eq!(registry.list_analyzers().len(), 3);
assert!(registry.list_analyzers().contains(&"dead_code"));
assert!(registry.list_analyzers().contains(&"complexity"));
assert!(registry.list_analyzers().contains(&"satd"));
let dead_code_info = registry.get_info("dead_code").unwrap();
assert_eq!(dead_code_info.name(), "dead_code");
assert!(dead_code_info.description().contains("unreachable"));
let complexity_info = registry.get_info("complexity").unwrap();
assert_eq!(complexity_info.name(), "complexity");
assert!(complexity_info.description().contains("complexity"));
let satd_info = registry.get_info("satd").unwrap();
assert_eq!(satd_info.name(), "satd");
assert!(satd_info.description().contains("Technical Debt"));
}
#[test]
fn test_analyzer_registry_operations() {
use crate::services::analyzer::{
complexity::ComplexityAnalyzer, dead_code::DeadCodeAnalyzer, satd::SATDAnalyzer,
};
let mut registry = AnalyzerRegistry::new();
registry.register(ComplexityAnalyzer::new());
registry.register(DeadCodeAnalyzer::new());
registry.register(SATDAnalyzer::new());
let analyzers = registry.list_analyzers();
assert_eq!(analyzers.len(), 3);
assert!(analyzers.contains(&"complexity"));
assert!(analyzers.contains(&"dead_code"));
assert!(analyzers.contains(&"satd"));
let complexity_info = registry.get_info("complexity");
assert!(complexity_info.is_some());
if let Some(info) = complexity_info {
assert_eq!(info.name(), "complexity");
assert!(!info.description().is_empty());
}
assert!(registry.get_info("nonexistent").is_none());
}
#[test]
fn test_project_input_creation() {
use std::path::PathBuf;
let path = PathBuf::from("/test/project");
let input = ProjectInput {
project_path: path.clone(),
};
assert_eq!(input.project_path, path);
}
#[test]
fn test_analyzer_consistency() {
use crate::services::analyzer::{
complexity::ComplexityAnalyzer, dead_code::DeadCodeAnalyzer, satd::SATDAnalyzer,
};
let dead_code = DeadCodeAnalyzer::new();
let complexity = ComplexityAnalyzer::new();
let satd = SATDAnalyzer::new();
assert_eq!(Analyzer::name(&dead_code), "dead_code");
assert_eq!(Analyzer::name(&complexity), "complexity");
assert_eq!(Analyzer::name(&satd), "satd");
assert_eq!(Analyzer::version(&dead_code), env!("CARGO_PKG_VERSION"));
assert_eq!(Analyzer::version(&complexity), env!("CARGO_PKG_VERSION"));
assert_eq!(Analyzer::version(&satd), env!("CARGO_PKG_VERSION"));
assert!(!AnalyzerInfo::description(&dead_code).is_empty());
assert!(!AnalyzerInfo::description(&complexity).is_empty());
assert!(!AnalyzerInfo::description(&satd).is_empty());
}
}
#[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);
}
}
}