use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tree_sitter::{Parser, Query, QueryCursor, Tree, Node};
use crate::integration::HostIntegration;
use crate::tools::{Tool, ToolResponse, ToolError, Permission, validation};
pub struct CodeAnalysisTool {
parsers: HashMap<String, Parser>,
queries: HashMap<String, Query>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplexityAnalysis {
pub cyclomatic_complexity: u32,
pub cognitive_complexity: u32,
pub lines_of_code: usize,
pub functions: usize,
pub classes: usize,
pub conditionals: usize,
pub loops: usize,
pub nested_depth: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityMetrics {
pub maintainability_index: f64,
pub code_duplication: f64,
pub test_coverage_estimate: f64,
pub documentation_ratio: f64,
pub naming_consistency: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityAnalysis {
pub vulnerabilities: Vec<SecurityIssue>,
pub risk_score: u32,
pub recommendations: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityIssue {
pub severity: String,
pub category: String,
pub description: String,
pub line: u32,
pub column: u32,
pub suggestion: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DependencyAnalysis {
pub imports: Vec<String>,
pub external_dependencies: Vec<String>,
pub internal_dependencies: Vec<String>,
pub circular_dependencies: Vec<Vec<String>>,
pub unused_imports: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeAnalysisResult {
pub language: String,
pub file_path: String,
pub complexity: ComplexityAnalysis,
pub quality: QualityMetrics,
pub security: SecurityAnalysis,
pub dependencies: DependencyAnalysis,
pub suggestions: Vec<String>,
}
impl CodeAnalysisTool {
pub fn new() -> Result<Self, ToolError> {
let mut parsers = HashMap::new();
let mut queries = HashMap::new();
Self::init_rust_parser(&mut parsers, &mut queries)?;
Self::init_python_parser(&mut parsers, &mut queries)?;
Self::init_javascript_parser(&mut parsers, &mut queries)?;
Self::init_typescript_parser(&mut parsers, &mut queries)?;
Self::init_go_parser(&mut parsers, &mut queries)?;
Self::init_java_parser(&mut parsers, &mut queries)?;
Self::init_c_parser(&mut parsers, &mut queries)?;
Self::init_cpp_parser(&mut parsers, &mut queries)?;
Ok(Self { parsers, queries })
}
fn init_rust_parser(
parsers: &mut HashMap<String, Parser>,
queries: &mut HashMap<String, Query>,
) -> Result<(), ToolError> {
let mut parser = Parser::new();
parser.set_language(tree_sitter_rust::language())
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to set Rust language: {}", e)))?;
parsers.insert("rust".to_string(), parser);
let complexity_query = Query::new(
tree_sitter_rust::language(),
r#"
(if_expression) @conditional
(while_expression) @loop
(for_expression) @loop
(loop_expression) @loop
(match_expression) @conditional
(function_item) @function
(impl_item) @class
(struct_item) @class
(enum_item) @class
"#
).map_err(|e| ToolError::ExecutionFailed(format!("Failed to create Rust query: {}", e)))?;
queries.insert("rust_complexity".to_string(), complexity_query);
let security_query = Query::new(
tree_sitter_rust::language(),
r#"
(call_expression
function: (identifier) @func_name
(#match? @func_name "^(unsafe|transmute|from_raw|as_ptr)$")
) @unsafe_call
(macro_invocation
macro: (identifier) @macro_name
(#match? @macro_name "^(println!|print!|dbg!)$")
) @debug_macro
"#
).map_err(|e| ToolError::ExecutionFailed(format!("Failed to create Rust security query: {}", e)))?;
queries.insert("rust_security".to_string(), security_query);
Ok(())
}
fn init_python_parser(
parsers: &mut HashMap<String, Parser>,
queries: &mut HashMap<String, Query>,
) -> Result<(), ToolError> {
let mut parser = Parser::new();
parser.set_language(tree_sitter_python::language())
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to set Python language: {}", e)))?;
parsers.insert("python".to_string(), parser);
let complexity_query = Query::new(
tree_sitter_python::language(),
r#"
(if_statement) @conditional
(while_statement) @loop
(for_statement) @loop
(function_definition) @function
(class_definition) @class
"#
).map_err(|e| ToolError::ExecutionFailed(format!("Failed to create Python query: {}", e)))?;
queries.insert("python_complexity".to_string(), complexity_query);
Ok(())
}
fn init_javascript_parser(
parsers: &mut HashMap<String, Parser>,
queries: &mut HashMap<String, Query>,
) -> Result<(), ToolError> {
let mut parser = Parser::new();
parser.set_language(tree_sitter_javascript::language())
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to set JavaScript language: {}", e)))?;
parsers.insert("javascript".to_string(), parser);
let complexity_query = Query::new(
tree_sitter_javascript::language(),
r#"
(if_statement) @conditional
(while_statement) @loop
(for_statement) @loop
(function_declaration) @function
(class_declaration) @class
"#
).map_err(|e| ToolError::ExecutionFailed(format!("Failed to create JavaScript query: {}", e)))?;
queries.insert("javascript_complexity".to_string(), complexity_query);
Ok(())
}
fn init_typescript_parser(
parsers: &mut HashMap<String, Parser>,
queries: &mut HashMap<String, Query>,
) -> Result<(), ToolError> {
let mut parser = Parser::new();
parser.set_language(tree_sitter_typescript::language_typescript())
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to set TypeScript language: {}", e)))?;
parsers.insert("typescript".to_string(), parser);
let complexity_query = Query::new(
tree_sitter_typescript::language_typescript(),
r#"
(if_statement) @conditional
(while_statement) @loop
(for_statement) @loop
(function_declaration) @function
(class_declaration) @class
"#
).map_err(|e| ToolError::ExecutionFailed(format!("Failed to create TypeScript query: {}", e)))?;
queries.insert("typescript_complexity".to_string(), complexity_query);
Ok(())
}
fn init_go_parser(
parsers: &mut HashMap<String, Parser>,
queries: &mut HashMap<String, Query>,
) -> Result<(), ToolError> {
let mut parser = Parser::new();
parser.set_language(tree_sitter_go::language())
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to set Go language: {}", e)))?;
parsers.insert("go".to_string(), parser);
let complexity_query = Query::new(
tree_sitter_go::language(),
r#"
(if_statement) @conditional
(for_statement) @loop
(function_declaration) @function
(type_declaration) @class
"#
).map_err(|e| ToolError::ExecutionFailed(format!("Failed to create Go query: {}", e)))?;
queries.insert("go_complexity".to_string(), complexity_query);
Ok(())
}
fn init_java_parser(
parsers: &mut HashMap<String, Parser>,
queries: &mut HashMap<String, Query>,
) -> Result<(), ToolError> {
let mut parser = Parser::new();
parser.set_language(tree_sitter_java::language())
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to set Java language: {}", e)))?;
parsers.insert("java".to_string(), parser);
let complexity_query = Query::new(
tree_sitter_java::language(),
r#"
(if_statement) @conditional
(while_statement) @loop
(for_statement) @loop
(method_declaration) @function
(class_declaration) @class
"#
).map_err(|e| ToolError::ExecutionFailed(format!("Failed to create Java query: {}", e)))?;
queries.insert("java_complexity".to_string(), complexity_query);
Ok(())
}
fn init_c_parser(
parsers: &mut HashMap<String, Parser>,
queries: &mut HashMap<String, Query>,
) -> Result<(), ToolError> {
let mut parser = Parser::new();
parser.set_language(tree_sitter_c::language())
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to set C language: {}", e)))?;
parsers.insert("c".to_string(), parser);
let complexity_query = Query::new(
tree_sitter_c::language(),
r#"
(if_statement) @conditional
(while_statement) @loop
(for_statement) @loop
(function_definition) @function
(struct_specifier) @class
"#
).map_err(|e| ToolError::ExecutionFailed(format!("Failed to create C query: {}", e)))?;
queries.insert("c_complexity".to_string(), complexity_query);
Ok(())
}
fn init_cpp_parser(
parsers: &mut HashMap<String, Parser>,
queries: &mut HashMap<String, Query>,
) -> Result<(), ToolError> {
let mut parser = Parser::new();
parser.set_language(tree_sitter_cpp::language())
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to set C++ language: {}", e)))?;
parsers.insert("cpp".to_string(), parser);
let complexity_query = Query::new(
tree_sitter_cpp::language(),
r#"
(if_statement) @conditional
(while_statement) @loop
(for_statement) @loop
(function_definition) @function
(class_specifier) @class
"#
).map_err(|e| ToolError::ExecutionFailed(format!("Failed to create C++ query: {}", e)))?;
queries.insert("cpp_complexity".to_string(), complexity_query);
Ok(())
}
fn detect_language(&self, file_path: &str, content: &str) -> String {
if let Some(ext) = std::path::Path::new(file_path).extension() {
match ext.to_str() {
Some("rs") => return "rust".to_string(),
Some("py") => return "python".to_string(),
Some("js") => return "javascript".to_string(),
Some("ts") => return "typescript".to_string(),
Some("go") => return "go".to_string(),
Some("java") => return "java".to_string(),
Some("c") => return "c".to_string(),
Some("cpp") | Some("cc") | Some("cxx") => return "cpp".to_string(),
_ => {}
}
}
if content.contains("fn main()") || content.contains("use std::") {
"rust".to_string()
} else if content.contains("def ") || content.contains("import ") {
"python".to_string()
} else if content.contains("function ") || content.contains("const ") {
"javascript".to_string()
} else if content.contains("func main()") || content.contains("package ") {
"go".to_string()
} else if content.contains("public class") || content.contains("import java") {
"java".to_string()
} else if content.contains("#include") {
"c".to_string()
} else {
"unknown".to_string()
}
}
fn analyze_complexity(&self, tree: &Tree, language: &str, code: &str) -> Result<ComplexityAnalysis, ToolError> {
let query_key = format!("{}_complexity", language);
let query = self.queries.get(&query_key)
.ok_or_else(|| ToolError::Unavailable(format!("No complexity query for language: {}", language)))?;
let mut cursor = QueryCursor::new();
let matches = cursor.matches(query, tree.root_node(), code.as_bytes());
let mut complexity = 1; let mut functions = 0;
let mut classes = 0;
let mut conditionals = 0;
let mut loops = 0;
for m in matches {
for capture in m.captures {
let capture_name = &query.capture_names()[capture.index as usize];
match capture_name.as_str() {
"conditional" => {
conditionals += 1;
complexity += 1;
}
"loop" => {
loops += 1;
complexity += 2; }
"function" => {
functions += 1;
}
"class" => {
classes += 1;
}
_ => {}
}
}
}
let nested_depth = self.calculate_nested_depth(tree.root_node());
let cognitive_complexity = self.calculate_cognitive_complexity(tree.root_node(), language);
Ok(ComplexityAnalysis {
cyclomatic_complexity: complexity,
cognitive_complexity,
lines_of_code: code.lines().count(),
functions,
classes,
conditionals,
loops,
nested_depth,
})
}
fn calculate_nested_depth(&self, node: Node) -> u32 {
let mut max_depth = 0;
let mut current_depth = 0;
fn traverse(node: Node, current_depth: &mut u32, max_depth: &mut u32) {
if node.kind().contains("block") || node.kind().contains("body") {
*current_depth += 1;
*max_depth = (*max_depth).max(*current_depth);
}
for child in node.children(&mut node.walk()) {
traverse(child, current_depth, max_depth);
}
if node.kind().contains("block") || node.kind().contains("body") {
*current_depth -= 1;
}
}
traverse(node, &mut current_depth, &mut max_depth);
max_depth
}
fn calculate_cognitive_complexity(&self, node: Node, _language: &str) -> u32 {
let mut complexity = 0;
fn traverse(node: Node, complexity: &mut u32, nesting_level: u32) {
match node.kind() {
"if_statement" | "if_expression" => {
*complexity += 1 + nesting_level;
}
"while_statement" | "while_expression" | "for_statement" | "for_expression" => {
*complexity += 1 + nesting_level;
}
"match_expression" | "switch_statement" => {
*complexity += 1 + nesting_level;
}
_ => {}
}
let new_nesting = if matches!(node.kind(),
"if_statement" | "if_expression" | "while_statement" | "while_expression" |
"for_statement" | "for_expression" | "match_expression" | "switch_statement"
) {
nesting_level + 1
} else {
nesting_level
};
for child in node.children(&mut node.walk()) {
traverse(child, complexity, new_nesting);
}
}
traverse(node, &mut complexity, 0);
complexity
}
fn analyze_quality(&self, _tree: &Tree, code: &str) -> QualityMetrics {
let lines_of_code = code.lines().count();
let comment_lines = code.lines().filter(|line| {
let trimmed = line.trim();
trimmed.starts_with("//") || trimmed.starts_with("#") ||
trimmed.starts_with("/*") || trimmed.starts_with("*")
}).count();
let documentation_ratio = if lines_of_code > 0 {
comment_lines as f64 / lines_of_code as f64
} else {
0.0
};
let maintainability_index = 171.0 - 5.2 * (lines_of_code as f64).ln()
- 0.23 * 10.0 - 16.2 * (lines_of_code as f64).ln()
+ 50.0 * (documentation_ratio * 100.0).sin();
QualityMetrics {
maintainability_index: maintainability_index.max(0.0).min(100.0),
code_duplication: 0.0, test_coverage_estimate: 0.0, documentation_ratio: documentation_ratio * 100.0,
naming_consistency: 85.0, }
}
fn analyze_security(&self, tree: &Tree, language: &str, code: &str) -> SecurityAnalysis {
let mut vulnerabilities = Vec::new();
let mut risk_score = 0;
match language {
"rust" => {
self.analyze_rust_security(tree, code, &mut vulnerabilities, &mut risk_score);
}
"python" => {
self.analyze_python_security(tree, code, &mut vulnerabilities, &mut risk_score);
}
"javascript" | "typescript" => {
self.analyze_js_security(tree, code, &mut vulnerabilities, &mut risk_score);
}
_ => {
self.analyze_generic_security(code, &mut vulnerabilities, &mut risk_score);
}
}
let recommendations = self.generate_security_recommendations(&vulnerabilities);
SecurityAnalysis {
vulnerabilities,
risk_score,
recommendations,
}
}
fn analyze_rust_security(&self, _tree: &Tree, code: &str, vulnerabilities: &mut Vec<SecurityIssue>, risk_score: &mut u32) {
for (line_num, line) in code.lines().enumerate() {
if line.contains("unsafe") {
vulnerabilities.push(SecurityIssue {
severity: "Medium".to_string(),
category: "Unsafe Code".to_string(),
description: "Unsafe block detected".to_string(),
line: line_num as u32 + 1,
column: line.find("unsafe").unwrap_or(0) as u32,
suggestion: "Review unsafe code for memory safety".to_string(),
});
*risk_score += 20;
}
if line.contains("transmute") {
vulnerabilities.push(SecurityIssue {
severity: "High".to_string(),
category: "Memory Safety".to_string(),
description: "Unsafe transmute detected".to_string(),
line: line_num as u32 + 1,
column: line.find("transmute").unwrap_or(0) as u32,
suggestion: "Consider safer alternatives to transmute".to_string(),
});
*risk_score += 40;
}
}
}
fn analyze_python_security(&self, _tree: &Tree, code: &str, vulnerabilities: &mut Vec<SecurityIssue>, risk_score: &mut u32) {
for (line_num, line) in code.lines().enumerate() {
if line.contains("eval(") || line.contains("exec(") {
vulnerabilities.push(SecurityIssue {
severity: "High".to_string(),
category: "Code Injection".to_string(),
description: "Dynamic code execution detected".to_string(),
line: line_num as u32 + 1,
column: 0,
suggestion: "Avoid eval() and exec() for security".to_string(),
});
*risk_score += 50;
}
if line.contains("pickle.loads") {
vulnerabilities.push(SecurityIssue {
severity: "Medium".to_string(),
category: "Deserialization".to_string(),
description: "Unsafe deserialization detected".to_string(),
line: line_num as u32 + 1,
column: 0,
suggestion: "Use safe serialization formats like JSON".to_string(),
});
*risk_score += 30;
}
}
}
fn analyze_js_security(&self, _tree: &Tree, code: &str, vulnerabilities: &mut Vec<SecurityIssue>, risk_score: &mut u32) {
for (line_num, line) in code.lines().enumerate() {
if line.contains("eval(") {
vulnerabilities.push(SecurityIssue {
severity: "High".to_string(),
category: "Code Injection".to_string(),
description: "eval() usage detected".to_string(),
line: line_num as u32 + 1,
column: 0,
suggestion: "Avoid eval() for security reasons".to_string(),
});
*risk_score += 50;
}
if line.contains("innerHTML") {
vulnerabilities.push(SecurityIssue {
severity: "Medium".to_string(),
category: "XSS".to_string(),
description: "Potential XSS vulnerability".to_string(),
line: line_num as u32 + 1,
column: 0,
suggestion: "Use textContent or sanitize HTML".to_string(),
});
*risk_score += 25;
}
}
}
fn analyze_generic_security(&self, code: &str, vulnerabilities: &mut Vec<SecurityIssue>, risk_score: &mut u32) {
for (line_num, line) in code.lines().enumerate() {
if line.to_lowercase().contains("password") && line.contains("=") {
vulnerabilities.push(SecurityIssue {
severity: "High".to_string(),
category: "Hardcoded Secrets".to_string(),
description: "Potential hardcoded password".to_string(),
line: line_num as u32 + 1,
column: 0,
suggestion: "Use environment variables for secrets".to_string(),
});
*risk_score += 40;
}
if line.contains("SELECT") && line.contains("+") {
vulnerabilities.push(SecurityIssue {
severity: "High".to_string(),
category: "SQL Injection".to_string(),
description: "Potential SQL injection vulnerability".to_string(),
line: line_num as u32 + 1,
column: 0,
suggestion: "Use parameterized queries".to_string(),
});
*risk_score += 45;
}
}
}
fn generate_security_recommendations(&self, vulnerabilities: &[SecurityIssue]) -> Vec<String> {
let mut recommendations = Vec::new();
if vulnerabilities.iter().any(|v| v.category == "Code Injection") {
recommendations.push("Implement input validation and sanitization".to_string());
}
if vulnerabilities.iter().any(|v| v.category == "Hardcoded Secrets") {
recommendations.push("Use a secrets management system".to_string());
}
if vulnerabilities.iter().any(|v| v.category == "SQL Injection") {
recommendations.push("Use ORM or parameterized queries".to_string());
}
if vulnerabilities.iter().any(|v| v.severity == "High") {
recommendations.push("Conduct security code review".to_string());
}
recommendations
}
fn analyze_dependencies(&self, _tree: &Tree, language: &str, code: &str) -> DependencyAnalysis {
let mut imports = Vec::new();
let mut external_dependencies = Vec::new();
let mut internal_dependencies = Vec::new();
match language {
"rust" => {
for line in code.lines() {
if line.trim().starts_with("use ") {
if let Some(import) = line.split("use ").nth(1) {
let import = import.split(';').next().unwrap_or(import).trim();
imports.push(import.to_string());
if import.starts_with("std::") || import.starts_with("core::") {
} else if import.starts_with("crate::") || import.starts_with("super::") || import.starts_with("self::") {
internal_dependencies.push(import.to_string());
} else {
external_dependencies.push(import.to_string());
}
}
}
}
}
"python" => {
for line in code.lines() {
let trimmed = line.trim();
if trimmed.starts_with("import ") || trimmed.starts_with("from ") {
imports.push(trimmed.to_string());
if trimmed.contains('.') && !trimmed.contains("..") {
external_dependencies.push(trimmed.to_string());
} else {
internal_dependencies.push(trimmed.to_string());
}
}
}
}
_ => {
for line in code.lines() {
if line.contains("import") || line.contains("require") || line.contains("#include") {
imports.push(line.trim().to_string());
}
}
}
}
DependencyAnalysis {
imports,
external_dependencies,
internal_dependencies,
circular_dependencies: Vec::new(), unused_imports: Vec::new(), }
}
}
#[async_trait]
impl Tool for CodeAnalysisTool {
async fn execute(
&self,
parameters: serde_json::Value,
host: &dyn HostIntegration,
) -> Result<ToolResponse, ToolError> {
let file_path = validation::require_string(¶meters, "file_path")?;
let language = validation::optional_string(¶meters, "language");
let content = host.get_file_content(&std::path::PathBuf::from(&file_path)).await
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to read file: {}", e)))?;
let detected_language = language.unwrap_or_else(|| self.detect_language(&file_path, &content));
let mut parser = Parser::new();
let language = match detected_language.as_str() {
"rust" => tree_sitter_rust::language(),
"python" => tree_sitter_python::language(),
"javascript" => tree_sitter_javascript::language(),
"typescript" => tree_sitter_typescript::language_typescript(),
"go" => tree_sitter_go::language(),
"java" => tree_sitter_java::language(),
"c" => tree_sitter_c::language(),
"cpp" => tree_sitter_cpp::language(),
_ => return Err(ToolError::Unavailable(format!("No parser available for language: {}", detected_language))),
};
parser.set_language(language)
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to set language: {}", e)))?;
let tree = parser.parse(&content, None)
.ok_or_else(|| ToolError::ExecutionFailed("Failed to parse code".to_string()))?;
let complexity = self.analyze_complexity(&tree, &detected_language, &content)?;
let quality = self.analyze_quality(&tree, &content);
let security = self.analyze_security(&tree, &detected_language, &content);
let dependencies = self.analyze_dependencies(&tree, &detected_language, &content);
let mut suggestions = Vec::new();
if complexity.cyclomatic_complexity > 10 {
suggestions.push("Consider breaking down complex functions".to_string());
}
if complexity.nested_depth > 4 {
suggestions.push("Reduce nesting depth for better readability".to_string());
}
if quality.documentation_ratio < 10.0 {
suggestions.push("Add more documentation and comments".to_string());
}
let result = CodeAnalysisResult {
language: detected_language,
file_path,
complexity,
quality,
security,
dependencies,
suggestions,
};
let response_content = format!(
"Code Analysis Complete\n\
Language: {}\n\
Cyclomatic Complexity: {}\n\
Lines of Code: {}\n\
Functions: {}\n\
Security Issues: {}\n\
Risk Score: {}/100",
result.language,
result.complexity.cyclomatic_complexity,
result.complexity.lines_of_code,
result.complexity.functions,
result.security.vulnerabilities.len(),
result.security.risk_score
);
Ok(ToolResponse::with_metadata(
response_content,
serde_json::to_value(result)?,
))
}
fn requires_permission(&self) -> Permission {
Permission::None }
fn description(&self) -> &str {
"Analyze code for complexity, quality, security issues, and dependencies using tree-sitter"
}
fn name(&self) -> &str {
"code_analysis"
}
fn parameter_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Path to the code file to analyze"
},
"language": {
"type": "string",
"description": "Programming language (optional, will be auto-detected)",
"enum": ["rust", "python", "javascript", "typescript", "go", "java", "c", "cpp"]
}
},
"required": ["file_path"]
})
}
fn clone_box(&self) -> Box<dyn Tool> {
Box::new(Self::new().unwrap())
}
}