Skip to main content

kowalski_tools/
code.rs

1use async_trait::async_trait;
2use kowalski_core::error::KowalskiError;
3use kowalski_core::tools::{Tool, ToolInput, ToolOutput, ToolParameter};
4use serde_json::json;
5use std::collections::HashMap;
6
7/// A tool for analyzing Java code
8pub struct JavaAnalysisTool;
9
10impl Default for JavaAnalysisTool {
11    fn default() -> Self {
12        Self::new()
13    }
14}
15
16impl JavaAnalysisTool {
17    pub fn new() -> Self {
18        Self
19    }
20
21    fn analyze_java(&self, code: &str) -> Result<serde_json::Value, KowalskiError> {
22        let mut analysis = HashMap::new();
23
24        // Basic metrics
25        let lines = code.lines().count();
26        let characters = code.chars().count();
27        let words = code.split_whitespace().count();
28
29        // Java-specific analysis
30        let classes = code.matches("class ").count();
31        let methods = code.matches("public ").count()
32            + code.matches("private ").count()
33            + code.matches("protected ").count();
34        let imports = code.matches("import ").count();
35        let comments = code.matches("//").count() + code.matches("/*").count();
36
37        // Complexity analysis
38        let complexity = self.calculate_complexity(code);
39
40        analysis.insert(
41            "metrics".to_string(),
42            json!({
43                "lines": lines,
44                "characters": characters,
45                "words": words,
46                "classes": classes,
47                "methods": methods,
48                "imports": imports,
49                "comments": comments,
50                "complexity": complexity
51            }),
52        );
53
54        // Code quality suggestions
55        let suggestions = self.generate_suggestions(code);
56        analysis.insert("suggestions".to_string(), json!(suggestions));
57
58        // Syntax check
59        let syntax_errors = self.check_syntax(code);
60        analysis.insert("syntax_errors".to_string(), json!(syntax_errors));
61
62        Ok(json!(analysis))
63    }
64
65    fn calculate_complexity(&self, code: &str) -> serde_json::Value {
66        let mut complexity = HashMap::new();
67
68        // Count control structures
69        let if_statements = code.matches("if ").count();
70        let for_loops = code.matches("for ").count();
71        let while_loops = code.matches("while ").count();
72        let switch_statements = code.matches("switch ").count();
73
74        let cyclomatic_complexity = 1 + if_statements + for_loops + while_loops + switch_statements;
75
76        complexity.insert(
77            "cyclomatic_complexity".to_string(),
78            json!(cyclomatic_complexity),
79        );
80        complexity.insert("if_statements".to_string(), json!(if_statements));
81        complexity.insert("for_loops".to_string(), json!(for_loops));
82        complexity.insert("while_loops".to_string(), json!(while_loops));
83        complexity.insert("switch_statements".to_string(), json!(switch_statements));
84
85        // Complexity level
86        let level = if cyclomatic_complexity <= 5 {
87            "Low"
88        } else if cyclomatic_complexity <= 10 {
89            "Medium"
90        } else {
91            "High"
92        };
93        complexity.insert("level".to_string(), json!(level));
94
95        json!(complexity)
96    }
97
98    fn generate_suggestions(&self, code: &str) -> Vec<String> {
99        let mut suggestions = Vec::new();
100
101        // Check for common Java issues
102        if code.matches("System.out.println").count() > 0 {
103            suggestions.push(
104                "Consider using a proper logging framework instead of System.out.println"
105                    .to_string(),
106            );
107        }
108
109        if code.matches("public static void main").count() > 0 {
110            suggestions.push("Main method found - ensure proper exception handling".to_string());
111        }
112
113        if code.matches("new ArrayList()").count() > 0 {
114            suggestions.push(
115                "Consider specifying initial capacity for ArrayList for better performance"
116                    .to_string(),
117            );
118        }
119
120        if code.matches("catch (Exception e)").count() > 0 {
121            suggestions.push(
122                "Consider catching specific exceptions instead of generic Exception".to_string(),
123            );
124        }
125
126        suggestions
127    }
128
129    fn check_syntax(&self, code: &str) -> Vec<String> {
130        let mut errors = Vec::new();
131
132        // Basic syntax checks
133        let open_braces = code.matches('{').count();
134        let close_braces = code.matches('}').count();
135        if open_braces != close_braces {
136            errors.push("Mismatched braces detected".to_string());
137        }
138
139        let open_parens = code.matches('(').count();
140        let close_parens = code.matches(')').count();
141        if open_parens != close_parens {
142            errors.push("Mismatched parentheses detected".to_string());
143        }
144
145        // Check for missing semicolons (basic check)
146        let lines = code.lines().collect::<Vec<_>>();
147        for (i, line) in lines.iter().enumerate() {
148            let trimmed = line.trim();
149            if !trimmed.is_empty()
150                && !trimmed.ends_with(';')
151                && !trimmed.ends_with('{')
152                && !trimmed.ends_with('}')
153                && !trimmed.starts_with("//")
154                && !trimmed.starts_with("/*")
155                && !trimmed.starts_with("import ")
156                && !trimmed.starts_with("package ")
157                && !trimmed.starts_with("public ")
158                && !trimmed.starts_with("private ")
159                && !trimmed.starts_with("protected ")
160                && !trimmed.starts_with("class ")
161                && !trimmed.starts_with("interface ")
162                && !trimmed.starts_with("enum ")
163            {
164                errors.push(format!("Line {}: Possible missing semicolon", i + 1));
165            }
166        }
167
168        errors
169    }
170}
171
172#[async_trait]
173impl Tool for JavaAnalysisTool {
174    async fn execute(&mut self, input: ToolInput) -> Result<ToolOutput, KowalskiError> {
175        match input.task_type.as_str() {
176            "analyze_java" => {
177                let result = self.analyze_java(&input.content)?;
178                Ok(ToolOutput::new(
179                    result,
180                    Some(json!({
181                        "tool": "java_analysis",
182                        "language": "java"
183                    })),
184                ))
185            }
186            _ => Err(KowalskiError::ToolExecution(format!(
187                "Unsupported task type: {}",
188                input.task_type
189            ))),
190        }
191    }
192
193    fn name(&self) -> &str {
194        "java_analysis"
195    }
196
197    fn description(&self) -> &str {
198        "A tool for analyzing Java code, providing metrics, complexity analysis, and code quality suggestions"
199    }
200
201    fn parameters(&self) -> Vec<ToolParameter> {
202        vec![ToolParameter {
203            name: "content".to_string(),
204            description: "Java code to analyze".to_string(),
205            required: true,
206            default_value: None,
207            parameter_type: kowalski_core::tools::ParameterType::String,
208        }]
209    }
210}
211
212/// A tool for analyzing Python code
213pub struct PythonAnalysisTool;
214
215impl Default for PythonAnalysisTool {
216    fn default() -> Self {
217        Self::new()
218    }
219}
220
221impl PythonAnalysisTool {
222    pub fn new() -> Self {
223        Self
224    }
225
226    fn analyze_python(&self, code: &str) -> Result<serde_json::Value, KowalskiError> {
227        let mut analysis = HashMap::new();
228
229        // Basic metrics
230        let lines = code.lines().count();
231        let characters = code.chars().count();
232        let words = code.split_whitespace().count();
233
234        // Python-specific analysis
235        let functions = code.matches("def ").count();
236        let classes = code.matches("class ").count();
237        let imports = code.matches("import ").count() + code.matches("from ").count();
238        let comments = code.matches("#").count();
239
240        // Complexity analysis
241        let complexity = self.calculate_complexity(code);
242
243        analysis.insert(
244            "metrics".to_string(),
245            json!({
246                "lines": lines,
247                "characters": characters,
248                "words": words,
249                "functions": functions,
250                "classes": classes,
251                "imports": imports,
252                "comments": comments,
253                "complexity": complexity
254            }),
255        );
256
257        // Code quality suggestions
258        let suggestions = self.generate_suggestions(code);
259        analysis.insert("suggestions".to_string(), json!(suggestions));
260
261        // PEP 8 compliance check
262        let pep8_issues = self.check_pep8(code);
263        analysis.insert("pep8_issues".to_string(), json!(pep8_issues));
264
265        Ok(json!(analysis))
266    }
267
268    fn calculate_complexity(&self, code: &str) -> serde_json::Value {
269        let mut complexity = HashMap::new();
270
271        // Count control structures
272        let if_statements = code.matches("if ").count();
273        let for_loops = code.matches("for ").count();
274        let while_loops = code.matches("while ").count();
275        let try_blocks = code.matches("try:").count();
276
277        let cyclomatic_complexity = 1 + if_statements + for_loops + while_loops + try_blocks;
278
279        complexity.insert(
280            "cyclomatic_complexity".to_string(),
281            json!(cyclomatic_complexity),
282        );
283        complexity.insert("if_statements".to_string(), json!(if_statements));
284        complexity.insert("for_loops".to_string(), json!(for_loops));
285        complexity.insert("while_loops".to_string(), json!(while_loops));
286        complexity.insert("try_blocks".to_string(), json!(try_blocks));
287
288        // Complexity level
289        let level = if cyclomatic_complexity <= 5 {
290            "Low"
291        } else if cyclomatic_complexity <= 10 {
292            "Medium"
293        } else {
294            "High"
295        };
296        complexity.insert("level".to_string(), json!(level));
297
298        json!(complexity)
299    }
300
301    fn generate_suggestions(&self, code: &str) -> Vec<String> {
302        let mut suggestions = Vec::new();
303
304        // Check for common Python issues
305        if code.matches("print(").count() > 0 {
306            suggestions.push("Consider using logging instead of print statements".to_string());
307        }
308
309        if code.matches("except:").count() > 0 {
310            suggestions.push("Avoid bare except clauses - specify exception types".to_string());
311        }
312
313        if code.matches("import *").count() > 0 {
314            suggestions.push("Avoid wildcard imports - import specific modules".to_string());
315        }
316
317        if code.matches("global ").count() > 0 {
318            suggestions.push(
319                "Consider avoiding global variables - use function parameters or class attributes"
320                    .to_string(),
321            );
322        }
323
324        suggestions
325    }
326
327    fn check_pep8(&self, code: &str) -> Vec<String> {
328        let mut issues = Vec::new();
329
330        // Check line length
331        for (i, line) in code.lines().enumerate() {
332            if line.len() > 79 {
333                issues.push(format!(
334                    "Line {}: Line too long ({} characters)",
335                    i + 1,
336                    line.len()
337                ));
338            }
339        }
340
341        // Check indentation
342        for (i, line) in code.lines().enumerate() {
343            if !line.is_empty() && !line.starts_with('#') {
344                let indent = line.chars().take_while(|&c| c == ' ').count();
345                if indent % 4 != 0 {
346                    issues.push(format!(
347                        "Line {}: Indentation should be multiple of 4 spaces",
348                        i + 1
349                    ));
350                }
351            }
352        }
353
354        // Check for trailing whitespace
355        for (i, line) in code.lines().enumerate() {
356            if line.ends_with(' ') {
357                issues.push(format!("Line {}: Trailing whitespace", i + 1));
358            }
359        }
360
361        issues
362    }
363}
364
365#[async_trait]
366impl Tool for PythonAnalysisTool {
367    async fn execute(&mut self, input: ToolInput) -> Result<ToolOutput, KowalskiError> {
368        match input.task_type.as_str() {
369            "analyze_python" => {
370                let result = self.analyze_python(&input.content)?;
371                Ok(ToolOutput::new(
372                    result,
373                    Some(json!({
374                        "tool": "python_analysis",
375                        "language": "python"
376                    })),
377                ))
378            }
379            _ => Err(KowalskiError::ToolExecution(format!(
380                "Unsupported task type: {}",
381                input.task_type
382            ))),
383        }
384    }
385
386    fn name(&self) -> &str {
387        "python_analysis"
388    }
389
390    fn description(&self) -> &str {
391        "A tool for analyzing Python code, providing metrics, complexity analysis, PEP 8 compliance, and code quality suggestions"
392    }
393
394    fn parameters(&self) -> Vec<ToolParameter> {
395        vec![ToolParameter {
396            name: "content".to_string(),
397            description: "Python code to analyze".to_string(),
398            required: true,
399            default_value: None,
400            parameter_type: kowalski_core::tools::ParameterType::String,
401        }]
402    }
403}
404
405/// A tool for analyzing Rust code
406pub struct RustAnalysisTool;
407
408impl Default for RustAnalysisTool {
409    fn default() -> Self {
410        Self::new()
411    }
412}
413
414impl RustAnalysisTool {
415    pub fn new() -> Self {
416        Self
417    }
418
419    fn analyze_rust(&self, code: &str) -> Result<serde_json::Value, KowalskiError> {
420        let mut analysis = HashMap::new();
421
422        // Basic metrics
423        let lines = code.lines().count();
424        let characters = code.chars().count();
425        let words = code.split_whitespace().count();
426
427        // Rust-specific analysis
428        let functions = code.matches("fn ").count();
429        let structs = code.matches("struct ").count();
430        let enums = code.matches("enum ").count();
431        let traits = code.matches("trait ").count();
432        let modules = code.matches("mod ").count();
433        let comments = code.matches("//").count() + code.matches("/*").count();
434
435        // Complexity analysis
436        let complexity = self.calculate_complexity(code);
437
438        analysis.insert(
439            "metrics".to_string(),
440            json!({
441                "lines": lines,
442                "characters": characters,
443                "words": words,
444                "functions": functions,
445                "structs": structs,
446                "enums": enums,
447                "traits": traits,
448                "modules": modules,
449                "comments": comments,
450                "complexity": complexity
451            }),
452        );
453
454        // Code quality suggestions
455        let suggestions = self.generate_suggestions(code);
456        analysis.insert("suggestions".to_string(), json!(suggestions));
457
458        // Rust-specific checks
459        let rust_issues = self.check_rust_specific(code);
460        analysis.insert("rust_issues".to_string(), json!(rust_issues));
461
462        Ok(json!(analysis))
463    }
464
465    fn calculate_complexity(&self, code: &str) -> serde_json::Value {
466        let mut complexity = HashMap::new();
467
468        // Count control structures
469        let if_statements = code.matches("if ").count();
470        let for_loops = code.matches("for ").count();
471        let while_loops = code.matches("while ").count();
472        let match_statements = code.matches("match ").count();
473        let let_statements = code.matches("let ").count();
474
475        let cyclomatic_complexity = 1 + if_statements + for_loops + while_loops + match_statements;
476
477        complexity.insert(
478            "cyclomatic_complexity".to_string(),
479            json!(cyclomatic_complexity),
480        );
481        complexity.insert("if_statements".to_string(), json!(if_statements));
482        complexity.insert("for_loops".to_string(), json!(for_loops));
483        complexity.insert("while_loops".to_string(), json!(while_loops));
484        complexity.insert("match_statements".to_string(), json!(match_statements));
485        complexity.insert("let_statements".to_string(), json!(let_statements));
486
487        // Complexity level
488        let level = if cyclomatic_complexity <= 5 {
489            "Low"
490        } else if cyclomatic_complexity <= 10 {
491            "Medium"
492        } else {
493            "High"
494        };
495        complexity.insert("level".to_string(), json!(level));
496
497        json!(complexity)
498    }
499
500    fn generate_suggestions(&self, code: &str) -> Vec<String> {
501        let mut suggestions = Vec::new();
502
503        // Check for common Rust issues
504        if code.matches("println!").count() > 0 {
505            suggestions
506                .push("Consider using a proper logging framework instead of println!".to_string());
507        }
508
509        if code.matches("unwrap()").count() > 0 {
510            suggestions
511                .push("Consider using proper error handling instead of unwrap()".to_string());
512        }
513
514        if code.matches("clone()").count() > 0 {
515            suggestions.push("Consider if clone() is necessary - Rust's ownership system might provide alternatives".to_string());
516        }
517
518        if code.matches("unsafe ").count() > 0 {
519            suggestions.push(
520                "Unsafe code detected - ensure it's properly documented and necessary".to_string(),
521            );
522        }
523
524        suggestions
525    }
526
527    fn check_rust_specific(&self, code: &str) -> Vec<String> {
528        let mut issues = Vec::new();
529
530        // Check for missing semicolons
531        let lines = code.lines().collect::<Vec<_>>();
532        for (i, line) in lines.iter().enumerate() {
533            let trimmed = line.trim();
534            if !trimmed.is_empty()
535                && !trimmed.ends_with(';')
536                && !trimmed.ends_with('{')
537                && !trimmed.ends_with('}')
538                && !trimmed.starts_with("//")
539                && !trimmed.starts_with("/*")
540                && !trimmed.starts_with("use ")
541                && !trimmed.starts_with("mod ")
542                && !trimmed.starts_with("pub ")
543                && !trimmed.starts_with("fn ")
544                && !trimmed.starts_with("struct ")
545                && !trimmed.starts_with("enum ")
546                && !trimmed.starts_with("trait ")
547                && !trimmed.starts_with("impl ")
548                && !trimmed.starts_with("let ")
549                && !trimmed.starts_with("if ")
550                && !trimmed.starts_with("for ")
551                && !trimmed.starts_with("while ")
552                && !trimmed.starts_with("match ")
553                && !trimmed.starts_with("return ")
554            {
555                issues.push(format!("Line {}: Possible missing semicolon", i + 1));
556            }
557        }
558
559        // Check for proper error handling
560        if code.matches("unwrap()").count() > 0 {
561            issues.push("Found unwrap() calls - consider using proper error handling".to_string());
562        }
563
564        // Check for unsafe code
565        if code.matches("unsafe ").count() > 0 {
566            issues.push("Unsafe code detected - review for necessity and safety".to_string());
567        }
568
569        issues
570    }
571}
572
573#[async_trait]
574impl Tool for RustAnalysisTool {
575    async fn execute(&mut self, input: ToolInput) -> Result<ToolOutput, KowalskiError> {
576        match input.task_type.as_str() {
577            "analyze_rust" => {
578                let result = self.analyze_rust(&input.content)?;
579                Ok(ToolOutput::new(
580                    result,
581                    Some(json!({
582                        "tool": "rust_analysis",
583                        "language": "rust"
584                    })),
585                ))
586            }
587            _ => Err(KowalskiError::ToolExecution(format!(
588                "Unsupported task type: {}",
589                input.task_type
590            ))),
591        }
592    }
593
594    fn name(&self) -> &str {
595        "rust_analysis"
596    }
597
598    fn description(&self) -> &str {
599        "A tool for analyzing Rust code, providing metrics, complexity analysis, and Rust-specific code quality suggestions"
600    }
601
602    fn parameters(&self) -> Vec<ToolParameter> {
603        vec![ToolParameter {
604            name: "content".to_string(),
605            description: "Rust code to analyze".to_string(),
606            required: true,
607            default_value: None,
608            parameter_type: kowalski_core::tools::ParameterType::String,
609        }]
610    }
611}
612
613#[cfg(test)]
614mod tests {
615    use super::*;
616
617    #[tokio::test]
618    async fn test_java_analysis_tool() {
619        let tool = JavaAnalysisTool::new();
620        assert_eq!(tool.name(), "java_analysis");
621    }
622
623    #[tokio::test]
624    async fn test_python_analysis_tool() {
625        let tool = PythonAnalysisTool::new();
626        assert_eq!(tool.name(), "python_analysis");
627    }
628
629    #[tokio::test]
630    async fn test_rust_analysis_tool() {
631        let tool = RustAnalysisTool::new();
632        assert_eq!(tool.name(), "rust_analysis");
633    }
634}