use super::{Analyzer, AnalyzerInfo, ProjectAnalyzer, ProjectConfig, ProjectInput};
use crate::services::satd_detector::{SATDAnalysisResult, SATDDetector};
use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::path::Path;
pub struct SATDAnalyzer {
inner: SATDDetector,
}
impl SATDAnalyzer {
#[must_use]
pub fn new() -> Self {
Self {
inner: SATDDetector::new(),
}
}
#[must_use]
pub fn new_with_strict_mode(strict: bool) -> Self {
if strict {
Self {
inner: SATDDetector::new_strict(),
}
} else {
Self::new()
}
}
}
impl Default for SATDAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SATDConfig {
pub base: ProjectConfig,
pub strict_mode: bool,
pub critical_only: bool,
pub include_context: bool,
}
impl Default for SATDConfig {
fn default() -> Self {
Self {
base: ProjectConfig::default(),
strict_mode: false,
critical_only: false,
include_context: true,
}
}
}
pub type SATDOutput = SATDAnalysisResult;
#[async_trait]
impl Analyzer for SATDAnalyzer {
type Input = ProjectInput;
type Output = SATDOutput;
type Config = ProjectConfig;
async fn analyze(&self, input: Self::Input, config: Self::Config) -> Result<Self::Output> {
self.inner
.analyze_project(&input.project_path, config.include_tests)
.await
.map_err(|e| anyhow::anyhow!("SATD analysis failed: {e}"))
}
fn name(&self) -> &'static str {
"satd"
}
}
#[async_trait]
impl ProjectAnalyzer for SATDAnalyzer {
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
}
}
impl AnalyzerInfo for SATDAnalyzer {
fn name(&self) -> &'static str {
"satd"
}
fn version(&self) -> &'static str {
env!("CARGO_PKG_VERSION")
}
fn description(&self) -> &'static str {
"Analyzes code for Self-Admitted Technical Debt (SATD) in comments and documentation"
}
}
pub struct SATDAnalyzerFactory;
impl SATDAnalyzerFactory {
#[must_use]
pub fn create() -> SATDAnalyzer {
SATDAnalyzer::new()
}
#[must_use]
pub fn create_strict() -> SATDAnalyzer {
SATDAnalyzer::new_with_strict_mode(true)
}
#[must_use]
pub fn create_critical_only() -> SATDAnalyzer {
SATDAnalyzer::new_with_strict_mode(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[tokio::test]
async fn test_satd_analyzer_creation() {
let analyzer = SATDAnalyzer::new();
assert_eq!(Analyzer::name(&analyzer), "satd");
assert_eq!(Analyzer::version(&analyzer), env!("CARGO_PKG_VERSION"));
}
#[tokio::test]
async fn test_satd_config_default() {
let config = SATDConfig::default();
assert!(!config.strict_mode);
assert!(!config.critical_only);
assert!(config.include_context);
}
#[tokio::test]
async fn test_analyzer_info() {
let analyzer = SATDAnalyzer::new();
assert_eq!(Analyzer::name(&analyzer), "satd");
assert!(AnalyzerInfo::description(&analyzer).contains("Technical Debt"));
}
#[tokio::test]
async fn test_factory_creation() {
let analyzer = SATDAnalyzerFactory::create();
assert_eq!(Analyzer::name(&analyzer), "satd");
let strict_analyzer = SATDAnalyzerFactory::create_strict();
assert_eq!(Analyzer::name(&strict_analyzer), "satd");
let critical_analyzer = SATDAnalyzerFactory::create_critical_only();
assert_eq!(Analyzer::name(&critical_analyzer), "satd");
}
#[tokio::test]
async fn test_project_analysis() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
r#"
fn good_function() -> i32 {
42
}
fn bad_function() -> i32 {
// TODO: This needs to be fixed eventually
// FIXME: Memory leak possible here
// HACK: Quick workaround for now
0
}
"#,
)
.unwrap();
let analyzer = SATDAnalyzer::new();
let result = analyzer.analyze_project(temp_dir.path()).await.unwrap();
assert!(result.items.len() > 0);
assert_eq!(result.total_files_analyzed, 1);
assert_eq!(result.files_with_debt, 1);
let debt_texts: Vec<&str> = result.items.iter().map(|item| item.text.as_str()).collect();
assert!(debt_texts.iter().any(|text| text.contains("TODO")));
}
#[tokio::test]
async fn test_strict_mode_analyzer() {
let analyzer = SATDAnalyzer::new_with_strict_mode(true);
assert_eq!(Analyzer::name(&analyzer), "satd");
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(
&test_file,
"// NOTE: This might be considered debt in strict mode",
)
.unwrap();
let result = analyzer.analyze_project(temp_dir.path()).await.unwrap();
assert_eq!(result.total_files_analyzed, 1);
}
}
#[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);
}
}
}