cargo-mate 1.7.6

Rust development companion that enhances cargo with intelligent workflows, state management, performance optimization, and comprehensive project monitoring.
Documentation
use super::{Tool, Result, ToolError, common_options, parse_output_format, OutputFormat};
use clap::{Arg, ArgMatches, Command};
use colored::*;
use std::collections::HashMap;
use std::path::Path;
use std::fs;
use regex::Regex;
use walkdir::WalkDir;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone)]
pub struct CodeAnalyzer;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionMetrics {
    pub name: String,
    pub file: String,
    pub lines: usize,
    pub complexity: f64,
    pub parameters: usize,
    pub is_public: bool,
    pub is_async: bool,
    pub return_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeAnalysis {
    pub total_functions: usize,
    pub public_functions: usize,
    pub async_functions: usize,
    pub average_complexity: f64,
    pub total_lines: usize,
    pub largest_function: Option<FunctionMetrics>,
    pub functions: Vec<FunctionMetrics>,
}
impl CodeAnalyzer {
    pub fn new() -> Self {
        Self
    }
    fn parse_rust_functions(&self, file_path: &str) -> Result<Vec<FunctionMetrics>> {
        let content = fs::read_to_string(file_path)
            .map_err(|e| {
                ToolError::InvalidArguments(
                    format!("Failed to read {}: {}", file_path, e),
                )
            })?;
        let mut functions = Vec::new();
        let function_regex = Regex::new(
                r"(?s)(?:pub\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+(\w+)\s*\(([^)]*)\)\s*(?:->\s*([^;{]*?))?\s*\{([^}]*)\}",
            )
            .unwrap();
        for captures in function_regex.captures_iter(&content) {
            let function_name = captures[1].to_string();
            let parameters = captures.get(2).map_or("", |m| m.as_str());
            let return_type = captures.get(3).map_or("()", |m| m.as_str().trim());
            let function_body = captures.get(4).map_or("", |m| m.as_str());
            let metrics = FunctionMetrics {
                name: function_name.clone(),
                file: file_path.to_string(),
                lines: function_body.lines().count(),
                complexity: self.calculate_complexity(function_body),
                parameters: self.count_parameters(parameters),
                is_public: function_name.starts_with("pub")
                    || content.contains(&format!("pub fn {}", function_name)),
                is_async: content.contains("async fn")
                    && content.contains(&function_name),
                return_type: return_type.to_string(),
            };
            functions.push(metrics);
        }
        Ok(functions)
    }
    fn calculate_complexity(&self, code: &str) -> f64 {
        let lines = code.lines().count() as f64;
        let branches = code.matches("if").count() as f64
            + code.matches("match").count() as f64;
        let loops = code.matches("for").count() + code.matches("while").count()
            + code.matches("loop").count();
        lines * 0.1 + branches * 0.5 + loops as f64 * 0.8
    }
    fn count_parameters(&self, params: &str) -> usize {
        if params.trim().is_empty() {
            return 0;
        }
        params.split(',').filter(|p| !p.trim().is_empty()).count()
    }
    fn analyze_codebase(&self, path: &str) -> Result<CodeAnalysis> {
        let mut all_functions = Vec::new();
        if Path::new(path).is_file() {
            if path.ends_with(".rs") {
                all_functions.extend(self.parse_rust_functions(path)?);
            }
        } else {
            for entry in WalkDir::new(path)
                .into_iter()
                .filter_map(|e| e.ok())
                .filter(|e| e.path().extension().map_or(false, |ext| ext == "rs"))
            {
                all_functions
                    .extend(self.parse_rust_functions(&entry.path().to_string_lossy())?);
            }
        }
        let total_functions = all_functions.len();
        let public_functions = all_functions.iter().filter(|f| f.is_public).count();
        let async_functions = all_functions.iter().filter(|f| f.is_async).count();
        let total_lines = all_functions.iter().map(|f| f.lines).sum::<usize>();
        let average_complexity = if total_functions > 0 {
            all_functions.iter().map(|f| f.complexity).sum::<f64>()
                / total_functions as f64
        } else {
            0.0
        };
        let largest_function = all_functions.iter().max_by_key(|f| f.lines).cloned();
        Ok(CodeAnalysis {
            total_functions,
            public_functions,
            async_functions,
            average_complexity,
            total_lines,
            largest_function,
            functions: all_functions,
        })
    }
    fn display_analysis(&self, analysis: &CodeAnalysis, verbose: bool) {
        println!(
            "\n📊 {} - Code Analysis Report", "CargoMate CodeAnalyzer".bold().blue()
        );
        println!("{}", "".repeat(50).blue());
        println!("\n📈 Summary:");
        println!("  • Total Functions: {}", analysis.total_functions);
        println!("  • Public API: {}", analysis.public_functions);
        println!("  • Async Functions: {}", analysis.async_functions);
        println!("  • Total Lines: {}", analysis.total_lines);
        println!("  • Average Complexity: {:.2}", analysis.average_complexity);
        if let Some(ref largest) = analysis.largest_function {
            println!(
                "  • Largest Function: {} ({} lines)", largest.name, largest.lines
            );
        }
        if verbose && !analysis.functions.is_empty() {
            println!("\n🔍 Function Details:");
            for function in &analysis.functions {
                let complexity_color = if function.complexity > 10.0 {
                    format!("{:.2}", function.complexity).red()
                } else if function.complexity > 5.0 {
                    format!("{:.2}", function.complexity).yellow()
                } else {
                    format!("{:.2}", function.complexity).green()
                };
                println!(
                    "{}::{} ({}, {} lines, complexity: {})", function.file
                    .split('/').last().unwrap_or(& function.file), function.name.cyan(),
                    if function.is_public { "public".green() } else { "private".dimmed()
                    }, function.lines, complexity_color
                );
            }
        }
        println!("\n✅ Analysis complete!");
    }
}
impl Tool for CodeAnalyzer {
    fn name(&self) -> &'static str {
        "code-analyzer"
    }
    fn description(&self) -> &'static str {
        "Analyze Rust code metrics and complexity"
    }
    fn command(&self) -> Command {
        Command::new(self.name())
            .about(self.description())
            .long_about(
                "Analyzes Rust code to provide metrics on function complexity, API surface, and code quality indicators.",
            )
            .args(
                &[
                    Arg::new("path")
                        .long("path")
                        .short('p')
                        .help("Path to Rust project to analyze")
                        .default_value("."),
                    Arg::new("output-format")
                        .long("output-format")
                        .short('f')
                        .help("Output format (human, json, table)")
                        .default_value("human"),
                ],
            )
            .args(&common_options())
    }
    fn execute(&self, matches: &ArgMatches) -> Result<()> {
        let path = matches.get_one::<String>("path").unwrap();
        let output_format = matches.get_one::<String>("output-format").unwrap();
        let verbose = matches.get_flag("verbose");
        println!(
            "📊 {} - Analyzing Code Metrics", "CargoMate CodeAnalyzer".bold().blue()
        );
        if !Path::new(path).exists() {
            return Err(
                ToolError::InvalidArguments(format!("Path {} does not exist", path)),
            );
        }
        let analysis = self.analyze_codebase(path)?;
        if analysis.total_functions == 0 {
            println!("{}", "No Rust functions found to analyze".yellow());
            return Ok(());
        }
        match output_format.as_str() {
            "json" => {
                let json = serde_json::to_string_pretty(&analysis)?;
                println!("{}", json);
            }
            "table" => {
                println!(
                    "{:<30} {:<10} {:<12} {:<8} {:<8}", "Function", "Lines",
                    "Complexity", "Params", "Public"
                );
                println!("{}", "".repeat(70));
                for function in &analysis.functions {
                    println!(
                        "{:<30} {:<10} {:<12.2} {:<8} {:<8}", format!("{}::{}", function
                        .file.split('/').last().unwrap_or(""), function.name), function
                        .lines, function.complexity, function.parameters, if function
                        .is_public { "Yes" } else { "No" }
                    );
                }
            }
            _ => {
                self.display_analysis(&analysis, verbose);
            }
        }
        Ok(())
    }
}
impl Default for CodeAnalyzer {
    fn default() -> Self {
        Self::new()
    }
}