1use super::{Tool, Result, ToolError, common_options, parse_output_format, OutputFormat};
2use clap::{Arg, ArgMatches, Command};
3use colored::*;
4use std::fs;
5use std::path::Path;
6use std::collections::HashMap;
7use syn::{
8 parse_file, File, Item, ItemFn, ItemStruct, ItemTrait, Fields, Field, Type,
9 PathSegment, Ident, visit::Visit,
10};
11use quote::ToTokens;
12use serde::{Serialize, Deserialize};
13#[derive(Debug, Clone)]
14pub struct RustMentorTool;
15#[derive(Debug, Clone, Serialize, Deserialize)]
16struct CodeAnalysis {
17 functions: Vec<FunctionAnalysis>,
18 structs: Vec<StructAnalysis>,
19 traits: Vec<TraitAnalysis>,
20 patterns: Vec<PatternAnalysis>,
21 issues: Vec<CodeIssue>,
22 recommendations: Vec<Recommendation>,
23 learning_opportunities: Vec<LearningOpportunity>,
24}
25#[derive(Debug, Clone, Serialize, Deserialize)]
26struct FunctionAnalysis {
27 name: String,
28 complexity: u32,
29 parameters: Vec<ParameterInfo>,
30 return_type: Option<String>,
31 patterns_used: Vec<String>,
32 potential_improvements: Vec<String>,
33 explanation: String,
34}
35#[derive(Debug, Clone, Serialize, Deserialize)]
36struct StructAnalysis {
37 name: String,
38 fields: Vec<FieldInfo>,
39 patterns_used: Vec<String>,
40 design_considerations: Vec<String>,
41 explanation: String,
42}
43#[derive(Debug, Clone, Serialize, Deserialize)]
44struct TraitAnalysis {
45 name: String,
46 methods: Vec<String>,
47 purpose: String,
48 common_use_cases: Vec<String>,
49 explanation: String,
50}
51#[derive(Debug, Clone, Serialize, Deserialize)]
52struct PatternAnalysis {
53 pattern_type: String,
54 locations: Vec<String>,
55 explanation: String,
56 benefits: Vec<String>,
57 alternatives: Vec<String>,
58}
59#[derive(Debug, Clone, Serialize, Deserialize)]
60struct CodeIssue {
61 severity: String,
62 category: String,
63 location: String,
64 message: String,
65 explanation: String,
66 suggestion: String,
67}
68#[derive(Debug, Clone, Serialize, Deserialize)]
69struct Recommendation {
70 category: String,
71 priority: String,
72 title: String,
73 description: String,
74 code_example: Option<String>,
75 benefits: Vec<String>,
76}
77#[derive(Debug, Clone, Serialize, Deserialize)]
78struct LearningOpportunity {
79 topic: String,
80 current_level: String,
81 next_steps: Vec<String>,
82 resources: Vec<String>,
83 explanation: String,
84}
85#[derive(Debug, Clone, Serialize, Deserialize)]
86struct ParameterInfo {
87 name: String,
88 ty: String,
89 purpose: String,
90}
91#[derive(Debug, Clone, Serialize, Deserialize)]
92struct FieldInfo {
93 name: String,
94 ty: String,
95 purpose: String,
96 considerations: Vec<String>,
97}
98impl RustMentorTool {
99 pub fn new() -> Self {
100 Self
101 }
102 fn analyze_codebase(&self, input_path: &str) -> Result<CodeAnalysis> {
103 let mut functions = Vec::new();
104 let mut structs = Vec::new();
105 let mut traits = Vec::new();
106 if Path::new(input_path).is_dir() {
107 self.analyze_directory(
108 input_path,
109 &mut functions,
110 &mut structs,
111 &mut traits,
112 )?;
113 } else {
114 self.analyze_file(input_path, &mut functions, &mut structs, &mut traits)?;
115 }
116 let patterns = self.identify_patterns(&functions, &structs, &traits);
117 let issues = self.identify_issues(&functions, &structs, &traits);
118 let recommendations = self
119 .generate_recommendations(&functions, &structs, &traits, &issues);
120 let learning_opportunities = self
121 .identify_learning_opportunities(&functions, &structs, &traits);
122 Ok(CodeAnalysis {
123 functions,
124 structs,
125 traits,
126 patterns,
127 issues,
128 recommendations,
129 learning_opportunities,
130 })
131 }
132 fn analyze_directory(
133 &self,
134 dir_path: &str,
135 functions: &mut Vec<FunctionAnalysis>,
136 structs: &mut Vec<StructAnalysis>,
137 traits: &mut Vec<TraitAnalysis>,
138 ) -> Result<()> {
139 let entries = fs::read_dir(dir_path)
140 .map_err(|e| ToolError::ExecutionFailed(
141 format!("Failed to read directory {}: {}", dir_path, e),
142 ))?;
143 for entry in entries {
144 let entry = entry?;
145 let path = entry.path();
146 if path.is_dir() {
147 self.analyze_directory(
148 &path.to_string_lossy(),
149 functions,
150 structs,
151 traits,
152 )?;
153 } else if let Some(ext) = path.extension() {
154 if ext == "rs" && !path.ends_with("mod.rs") && !path.ends_with("lib.rs")
155 {
156 self.analyze_file(
157 &path.to_string_lossy(),
158 functions,
159 structs,
160 traits,
161 )?;
162 }
163 }
164 }
165 Ok(())
166 }
167 fn analyze_file(
168 &self,
169 file_path: &str,
170 functions: &mut Vec<FunctionAnalysis>,
171 structs: &mut Vec<StructAnalysis>,
172 traits: &mut Vec<TraitAnalysis>,
173 ) -> Result<()> {
174 let content = fs::read_to_string(file_path)?;
175 let ast = parse_file(&content)?;
176 struct CodeVisitor<'a> {
177 functions: &'a mut Vec<FunctionAnalysis>,
178 structs: &'a mut Vec<StructAnalysis>,
179 traits: &'a mut Vec<TraitAnalysis>,
180 current_file: String,
181 }
182 impl<'a> Visit<'_> for CodeVisitor<'a> {
183 fn visit_item_fn(&mut self, node: &ItemFn) {
184 if let Ok(analysis) = Self::analyze_function(node) {
185 self.functions.push(analysis);
186 }
187 }
188 fn visit_item_struct(&mut self, node: &ItemStruct) {
189 if let Ok(analysis) = Self::analyze_struct(node) {
190 self.structs.push(analysis);
191 }
192 }
193 fn visit_item_trait(&mut self, node: &ItemTrait) {
194 if let Ok(analysis) = Self::analyze_trait(node) {
195 self.traits.push(analysis);
196 }
197 }
198 }
199 impl CodeVisitor<'_> {
200 fn analyze_function(node: &ItemFn) -> Result<FunctionAnalysis> {
201 let name = node.sig.ident.to_string();
202 let complexity = Self::calculate_complexity(node);
203 let parameters = Self::extract_parameters(&node.sig.inputs);
204 let return_type = Self::extract_return_type(&node.sig.output);
205 let patterns_used = Self::identify_function_patterns(node);
206 let potential_improvements = Self::suggest_function_improvements(node);
207 let explanation = Self::explain_function(node);
208 Ok(FunctionAnalysis {
209 name,
210 complexity,
211 parameters,
212 return_type,
213 patterns_used,
214 potential_improvements,
215 explanation,
216 })
217 }
218 fn analyze_struct(node: &ItemStruct) -> Result<StructAnalysis> {
219 let name = node.ident.to_string();
220 let fields = Self::extract_fields(&node.fields);
221 let patterns_used = Self::identify_struct_patterns(node);
222 let design_considerations = Self::struct_design_considerations(node);
223 let explanation = Self::explain_struct(node);
224 Ok(StructAnalysis {
225 name,
226 fields,
227 patterns_used,
228 design_considerations,
229 explanation,
230 })
231 }
232 fn analyze_trait(node: &ItemTrait) -> Result<TraitAnalysis> {
233 let name = node.ident.to_string();
234 let methods = Self::extract_trait_methods(node);
235 let purpose = Self::identify_trait_purpose(node);
236 let common_use_cases = Self::trait_use_cases(node);
237 let explanation = Self::explain_trait(node);
238 Ok(TraitAnalysis {
239 name,
240 methods,
241 purpose,
242 common_use_cases,
243 explanation,
244 })
245 }
246 fn calculate_complexity(node: &ItemFn) -> u32 {
247 let mut complexity = 1u32;
248 let code = node.to_token_stream().to_string();
249 let control_flow_keywords = [
250 "if",
251 "else",
252 "for",
253 "while",
254 "loop",
255 "match",
256 ];
257 for keyword in &control_flow_keywords {
258 complexity += code.matches(keyword).count() as u32;
259 }
260 complexity
261 }
262 fn extract_parameters(
263 inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
264 ) -> Vec<ParameterInfo> {
265 inputs
266 .iter()
267 .filter_map(|arg| {
268 match arg {
269 syn::FnArg::Receiver(_) => {
270 Some(ParameterInfo {
271 name: "self".to_string(),
272 ty: "Self".to_string(),
273 purpose: "Reference to the current instance".to_string(),
274 })
275 }
276 syn::FnArg::Typed(pat_type) => {
277 if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
278 let name = pat_ident.ident.to_string();
279 let ty = Self::type_to_string(&*pat_type.ty);
280 let purpose = Self::infer_parameter_purpose(&name, &ty);
281 Some(ParameterInfo { name, ty, purpose })
282 } else {
283 None
284 }
285 }
286 }
287 })
288 .collect()
289 }
290 fn extract_return_type(output: &syn::ReturnType) -> Option<String> {
291 match output {
292 syn::ReturnType::Default => None,
293 syn::ReturnType::Type(_, ty) => Some(Self::type_to_string(ty)),
294 }
295 }
296 fn extract_fields(fields: &Fields) -> Vec<FieldInfo> {
297 match fields {
298 Fields::Named(named_fields) => {
299 named_fields
300 .named
301 .iter()
302 .filter_map(|field| {
303 field
304 .ident
305 .as_ref()
306 .map(|ident| {
307 let name = ident.to_string();
308 let ty = Self::type_to_string(&field.ty);
309 let purpose = Self::infer_field_purpose(&name, &ty);
310 let considerations = Self::field_considerations(&name, &ty);
311 FieldInfo {
312 name,
313 ty,
314 purpose,
315 considerations,
316 }
317 })
318 })
319 .collect()
320 }
321 _ => Vec::new(),
322 }
323 }
324 fn extract_trait_methods(node: &ItemTrait) -> Vec<String> {
325 node.items
326 .iter()
327 .filter_map(|item| {
328 match item {
329 syn::TraitItem::Fn(method) => {
330 Some(method.sig.ident.to_string())
331 }
332 _ => None,
333 }
334 })
335 .collect()
336 }
337 fn type_to_string(ty: &Type) -> String {
338 match ty {
339 Type::Path(type_path) => {
340 type_path
341 .path
342 .segments
343 .iter()
344 .map(|seg| seg.ident.to_string())
345 .collect::<Vec<_>>()
346 .join("::")
347 }
348 Type::Reference(type_ref) => {
349 let mut result = "&".to_string();
350 if type_ref.mutability.is_some() {
351 result.push_str("mut ");
352 }
353 result.push_str(&Self::type_to_string(&*type_ref.elem));
354 result
355 }
356 _ => "Unknown".to_string(),
357 }
358 }
359 fn identify_function_patterns(node: &ItemFn) -> Vec<String> {
360 let mut patterns = Vec::new();
361 let code = node.to_token_stream().to_string();
362 if code.contains("match") {
363 patterns.push("Pattern Matching".to_string());
364 }
365 if code.contains("if let") {
366 patterns.push("If-Let Pattern".to_string());
367 }
368 if code.contains("map") || code.contains("filter")
369 || code.contains("fold")
370 {
371 patterns.push("Iterator Methods".to_string());
372 }
373 if code.contains("?") {
374 patterns.push("Error Propagation".to_string());
375 }
376 if node.sig.asyncness.is_some() {
377 patterns.push("Async Function".to_string());
378 }
379 patterns
380 }
381 fn identify_struct_patterns(node: &ItemStruct) -> Vec<String> {
382 let mut patterns = Vec::new();
383 match &node.fields {
384 Fields::Named(_) => patterns.push("Named Fields".to_string()),
385 Fields::Unnamed(_) => patterns.push("Tuple Struct".to_string()),
386 Fields::Unit => patterns.push("Unit Struct".to_string()),
387 }
388 let attrs = node
389 .attrs
390 .iter()
391 .map(|attr| {
392 attr.path()
393 .segments
394 .iter()
395 .map(|seg| seg.ident.to_string())
396 .collect::<Vec<_>>()
397 .join("::")
398 })
399 .collect::<Vec<_>>();
400 if attrs.contains(&"derive".to_string()) {
401 patterns.push("Derive Macros".to_string());
402 }
403 if attrs.contains(&"serde".to_string()) {
404 patterns.push("Serialization Support".to_string());
405 }
406 patterns
407 }
408 fn infer_parameter_purpose(name: &str, ty: &str) -> String {
409 match (name, ty) {
410 ("config" | "settings" | "options", _) => {
411 "Configuration parameters".to_string()
412 }
413 ("data" | "input", _) => "Input data to process".to_string(),
414 ("callback" | "handler", _) => "Function to call back".to_string(),
415 (_, "String" | "&str") => "Text input or identifier".to_string(),
416 (_, "i32" | "i64" | "u32" | "u64") => {
417 "Numeric value or count".to_string()
418 }
419 (_, "&mut") => "Mutable reference for modification".to_string(),
420 _ => format!("{} parameter of type {}", name, ty),
421 }
422 }
423 fn infer_field_purpose(name: &str, ty: &str) -> String {
424 match (name, ty) {
425 ("id" | "uuid", _) => "Unique identifier".to_string(),
426 ("name" | "title", _) => "Descriptive name or title".to_string(),
427 ("config" | "settings", _) => "Configuration data".to_string(),
428 ("data" | "content", _) => "Main content or data".to_string(),
429 ("created_at" | "updated_at", _) => {
430 "Timestamp information".to_string()
431 }
432 (_, "Vec<T>") => "Collection of items".to_string(),
433 (_, "Option<T>") => "Optional value".to_string(),
434 _ => format!("{} field of type {}", name, ty),
435 }
436 }
437 fn field_considerations(name: &str, ty: &str) -> Vec<String> {
438 let mut considerations = Vec::new();
439 if ty.contains("&str") && !ty.contains("String") {
440 considerations
441 .push("Consider using owned String for ownership".to_string());
442 }
443 if name.ends_with("_at") && ty.contains("u64") {
444 considerations
445 .push(
446 "Consider using DateTime for better time handling"
447 .to_string(),
448 );
449 }
450 if name == "id" && ty == "String" {
451 considerations
452 .push(
453 "Consider using UUID type for better type safety".to_string(),
454 );
455 }
456 considerations
457 }
458 fn suggest_function_improvements(node: &ItemFn) -> Vec<String> {
459 let mut improvements = Vec::new();
460 let code = node.to_token_stream().to_string();
461 if node.sig.inputs.len() > 4 {
462 improvements
463 .push(
464 "Consider grouping parameters into a configuration struct"
465 .to_string(),
466 );
467 }
468 if code.len() > 1000 {
469 improvements
470 .push(
471 "Function is quite long - consider breaking into smaller functions"
472 .to_string(),
473 );
474 }
475 if !code.contains("Result") && !code.contains("Option") {
476 improvements
477 .push(
478 "Consider adding error handling for robustness".to_string(),
479 );
480 }
481 improvements
482 }
483 fn struct_design_considerations(node: &ItemStruct) -> Vec<String> {
484 let mut considerations = Vec::new();
485 match &node.fields {
486 Fields::Named(named_fields) if named_fields.named.len() > 10 => {
487 considerations
488 .push(
489 "Large struct - consider splitting into smaller structs"
490 .to_string(),
491 );
492 }
493 Fields::Unnamed(
494 unnamed_fields,
495 ) if unnamed_fields.unnamed.len() > 5 => {
496 considerations
497 .push(
498 "Many tuple fields - consider using named fields for clarity"
499 .to_string(),
500 );
501 }
502 _ => {}
503 }
504 considerations
505 }
506 fn identify_trait_purpose(node: &ItemTrait) -> String {
507 let name = node.ident.to_string();
508 let method_count = node.items.len();
509 match (name.as_str(), method_count) {
510 ("Debug", _) => "Enable debug printing and inspection".to_string(),
511 ("Clone", _) => "Allow creating copies of values".to_string(),
512 ("Display", _) => {
513 "Enable user-friendly string representation".to_string()
514 }
515 ("From" | "Into", _) => "Enable type conversions".to_string(),
516 ("Iterator", _) => "Enable iteration over collections".to_string(),
517 (_, 1) => {
518 "Single method interface - likely a callback or conversion trait"
519 .to_string()
520 }
521 (_, 2..=3) => {
522 "Small interface - focused on specific functionality".to_string()
523 }
524 _ => {
525 "Larger interface - represents a capability or behavior"
526 .to_string()
527 }
528 }
529 }
530 fn trait_use_cases(node: &ItemTrait) -> Vec<String> {
531 let name = node.ident.to_string();
532 match name.as_str() {
533 "Debug" => {
534 vec![
535 "Debugging and logging".to_string(), "Error messages"
536 .to_string()
537 ]
538 }
539 "Clone" => {
540 vec![
541 "Creating copies of data".to_string(),
542 "Working with collections".to_string()
543 ]
544 }
545 "Display" => {
546 vec![
547 "User-facing output".to_string(), "Logging and reporting"
548 .to_string()
549 ]
550 }
551 "From" | "Into" => {
552 vec![
553 "Type conversions".to_string(), "Builder patterns"
554 .to_string()
555 ]
556 }
557 "Iterator" => {
558 vec![
559 "Working with collections".to_string(), "Streaming data"
560 .to_string()
561 ]
562 }
563 _ => vec!["Domain-specific functionality".to_string()],
564 }
565 }
566 fn explain_function(node: &ItemFn) -> String {
567 let name = node.sig.ident.to_string();
568 let param_count = node.sig.inputs.len();
569 let has_return = !matches!(node.sig.output, syn::ReturnType::Default);
570 let is_async = node.sig.asyncness.is_some();
571 let mut explanation = format!("Function `{}` ", name);
572 if is_async {
573 explanation.push_str("is an asynchronous function that ");
574 } else {
575 explanation.push_str("is a synchronous function that ");
576 }
577 if has_return {
578 explanation.push_str("takes ");
579 } else {
580 explanation.push_str("performs an operation");
581 }
582 match param_count {
583 0 => explanation.push_str("no parameters and "),
584 1 => explanation.push_str("one parameter and "),
585 2..=3 => {
586 explanation.push_str(&format!("{} parameters and ", param_count))
587 }
588 _ => {
589 explanation.push_str(&format!("{} parameters and ", param_count))
590 }
591 }
592 if has_return {
593 explanation.push_str("returns a value");
594 } else {
595 explanation.push_str("doesn't return a value");
596 }
597 explanation.push_str(". ");
598 if node
599 .sig
600 .inputs
601 .iter()
602 .any(|arg| matches!(arg, syn::FnArg::Receiver(_)))
603 {
604 explanation
605 .push_str("It operates on an instance of its type (method). ");
606 } else {
607 explanation.push_str("It operates as a standalone function. ");
608 }
609 explanation
610 }
611 fn explain_struct(node: &ItemStruct) -> String {
612 let name = node.ident.to_string();
613 match &node.fields {
614 Fields::Named(named_fields) => {
615 let field_count = named_fields.named.len();
616 format!(
617 "Struct `{}` has {} named fields, representing a data structure with clear field names for better code readability and maintainability.",
618 name, field_count
619 )
620 }
621 Fields::Unnamed(unnamed_fields) => {
622 let field_count = unnamed_fields.unnamed.len();
623 format!(
624 "Struct `{}` is a tuple struct with {} unnamed fields, useful for simple data aggregation where field names aren't needed.",
625 name, field_count
626 )
627 }
628 Fields::Unit => {
629 format!(
630 "Struct `{}` is a unit struct with no fields, often used as a marker type or for implementing traits.",
631 name
632 )
633 }
634 }
635 }
636 fn explain_trait(node: &ItemTrait) -> String {
637 let name = node.ident.to_string();
638 let method_count = node.items.len();
639 format!(
640 "Trait `{}` defines an interface with {} methods that types can implement to provide specific functionality. It represents a capability that implementing types must provide.",
641 name, method_count
642 )
643 }
644 }
645 let mut visitor = CodeVisitor {
646 functions,
647 structs,
648 traits,
649 current_file: file_path.to_string(),
650 };
651 syn::visit::visit_file(&mut visitor, &ast);
652 Ok(())
653 }
654 fn identify_patterns(
655 &self,
656 functions: &[FunctionAnalysis],
657 structs: &[StructAnalysis],
658 traits: &[TraitAnalysis],
659 ) -> Vec<PatternAnalysis> {
660 let mut patterns = Vec::new();
661 let mut pattern_usage = HashMap::new();
662 for func in functions {
663 for pattern in &func.patterns_used {
664 let count = pattern_usage.entry(pattern.clone()).or_insert(0);
665 *count += 1;
666 }
667 }
668 for (pattern, count) in pattern_usage {
669 if count > 0 {
670 patterns
671 .push(PatternAnalysis {
672 pattern_type: pattern.clone(),
673 locations: vec![format!("Found in {} functions", count)],
674 explanation: self.explain_pattern(&pattern),
675 benefits: self.pattern_benefits(&pattern),
676 alternatives: self.pattern_alternatives(&pattern),
677 });
678 }
679 }
680 patterns
681 }
682 fn identify_issues(
683 &self,
684 functions: &[FunctionAnalysis],
685 structs: &[StructAnalysis],
686 traits: &[TraitAnalysis],
687 ) -> Vec<CodeIssue> {
688 let mut issues = Vec::new();
689 for func in functions {
690 if func.complexity > 10 {
691 issues
692 .push(CodeIssue {
693 severity: "warning".to_string(),
694 category: "complexity".to_string(),
695 location: func.name.clone(),
696 message: format!(
697 "High complexity function (score: {})", func.complexity
698 ),
699 explanation: "Functions with high complexity are harder to understand and maintain"
700 .to_string(),
701 suggestion: "Consider breaking into smaller functions or simplifying logic"
702 .to_string(),
703 });
704 }
705 }
706 for struct_info in structs {
707 if struct_info.fields.len() > 15 {
708 issues
709 .push(CodeIssue {
710 severity: "info".to_string(),
711 category: "design".to_string(),
712 location: struct_info.name.clone(),
713 message: format!(
714 "Large struct with {} fields", struct_info.fields.len()
715 ),
716 explanation: "Large structs can be difficult to work with and may indicate a need for better organization"
717 .to_string(),
718 suggestion: "Consider splitting into smaller, more focused structs"
719 .to_string(),
720 });
721 }
722 }
723 issues
724 }
725 fn generate_recommendations(
726 &self,
727 functions: &[FunctionAnalysis],
728 structs: &[StructAnalysis],
729 traits: &[TraitAnalysis],
730 issues: &[CodeIssue],
731 ) -> Vec<Recommendation> {
732 let mut recommendations = Vec::new();
733 for issue in issues {
734 recommendations
735 .push(Recommendation {
736 category: issue.category.clone(),
737 priority: match issue.severity.as_str() {
738 "error" => "high".to_string(),
739 "warning" => "medium".to_string(),
740 _ => "low".to_string(),
741 },
742 title: format!("Address {}", issue.category),
743 description: issue.explanation.clone(),
744 code_example: None,
745 benefits: vec![
746 "Improved code maintainability".to_string(),
747 "Better developer experience".to_string(),
748 "Reduced bug likelihood".to_string(),
749 ],
750 });
751 }
752 if functions
753 .iter()
754 .any(|f| f.patterns_used.contains(&"Error Propagation".to_string()))
755 {
756 recommendations
757 .push(Recommendation {
758 category: "error_handling".to_string(),
759 priority: "medium".to_string(),
760 title: "Consider using thiserror for better error handling"
761 .to_string(),
762 description: "Using thiserror provides better error messages and easier error handling"
763 .to_string(),
764 code_example: Some(
765 "#[derive(thiserror::Error, Debug)]\npub enum AppError {\n #[error(\"IO error: {0}\")]\n Io(#[from] std::io::Error),\n #[error(\"Parse error: {0}\")]\n Parse(String),\n}"
766 .to_string(),
767 ),
768 benefits: vec![
769 "Better error messages".to_string(), "Easier error handling"
770 .to_string(), "Consistent error types".to_string(),
771 ],
772 });
773 }
774 if structs
775 .iter()
776 .any(|s| s.patterns_used.contains(&"Serialization Support".to_string()))
777 {
778 recommendations
779 .push(Recommendation {
780 category: "serialization".to_string(),
781 priority: "low".to_string(),
782 title: "Consider adding validation for serialized data".to_string(),
783 description: "Adding validation ensures data integrity when serializing/deserializing"
784 .to_string(),
785 code_example: Some(
786 "#[derive(serde::Deserialize, validator::Validate)]\npub struct User {\n #[validate(length(min = 1, max = 100))]\n pub name: String,\n #[validate(email)]\n pub email: String,\n}"
787 .to_string(),
788 ),
789 benefits: vec![
790 "Data integrity".to_string(), "Better error messages"
791 .to_string(), "Security improvements".to_string(),
792 ],
793 });
794 }
795 recommendations
796 }
797 fn identify_learning_opportunities(
798 &self,
799 functions: &[FunctionAnalysis],
800 structs: &[StructAnalysis],
801 traits: &[TraitAnalysis],
802 ) -> Vec<LearningOpportunity> {
803 let mut opportunities = Vec::new();
804 let async_functions = functions
805 .iter()
806 .filter(|f| f.patterns_used.contains(&"Async Function".to_string()))
807 .count();
808 if async_functions == 0 {
809 opportunities
810 .push(LearningOpportunity {
811 topic: "Asynchronous Programming".to_string(),
812 current_level: "Beginner".to_string(),
813 next_steps: vec![
814 "Learn async/await syntax".to_string(),
815 "Understand futures and promises".to_string(),
816 "Practice with tokio runtime".to_string(),
817 ],
818 resources: vec![
819 "https://rust-lang.github.io/async-book/".to_string(),
820 "https://tokio.rs/".to_string(),
821 ],
822 explanation: "Async programming is becoming increasingly important in Rust for building scalable applications"
823 .to_string(),
824 });
825 }
826 let error_handling = functions
827 .iter()
828 .filter(|f| f.patterns_used.contains(&"Error Propagation".to_string()))
829 .count();
830 if error_handling == 0 {
831 opportunities
832 .push(LearningOpportunity {
833 topic: "Error Handling".to_string(),
834 current_level: "Beginner".to_string(),
835 next_steps: vec![
836 "Learn Result and Option types".to_string(),
837 "Use ? operator for error propagation".to_string(),
838 "Create custom error types".to_string(),
839 "Use thiserror for better errors".to_string(),
840 ],
841 resources: vec![
842 "https://doc.rust-lang.org/book/ch09-00-error-handling.html"
843 .to_string(), "https://docs.rs/thiserror/latest/thiserror/"
844 .to_string(),
845 ],
846 explanation: "Proper error handling is crucial for robust Rust applications"
847 .to_string(),
848 });
849 }
850 let iterator_usage = functions
851 .iter()
852 .filter(|f| f.patterns_used.contains(&"Iterator Methods".to_string()))
853 .count();
854 if iterator_usage == 0 {
855 opportunities
856 .push(LearningOpportunity {
857 topic: "Iterator Patterns".to_string(),
858 current_level: "Beginner".to_string(),
859 next_steps: vec![
860 "Learn map, filter, fold methods".to_string(),
861 "Understand iterator chains".to_string(),
862 "Create custom iterators".to_string(),
863 ],
864 resources: vec![
865 "https://doc.rust-lang.org/book/ch13-02-iterators.html"
866 .to_string(),
867 ],
868 explanation: "Iterators provide powerful and efficient ways to work with collections"
869 .to_string(),
870 });
871 }
872 opportunities
873 }
874 fn explain_pattern(&self, pattern: &str) -> String {
875 match pattern {
876 "Pattern Matching" => {
877 "Pattern matching allows you to destructure and match values against patterns, providing a powerful way to handle different cases in your code."
878 .to_string()
879 }
880 "If-Let Pattern" => {
881 "If-let is a concise way to handle optional values and single-case matches, reducing boilerplate compared to full match statements."
882 .to_string()
883 }
884 "Iterator Methods" => {
885 "Iterator methods like map, filter, and fold provide functional programming patterns that make data transformation more concise and readable."
886 .to_string()
887 }
888 "Error Propagation" => {
889 "The ? operator provides concise error propagation, automatically converting errors to the expected return type."
890 .to_string()
891 }
892 "Async Function" => {
893 "Async functions allow you to write asynchronous code that looks like synchronous code, making concurrent programming more accessible."
894 .to_string()
895 }
896 _ => format!("{} is a code pattern used in this codebase.", pattern),
897 }
898 }
899 fn pattern_benefits(&self, pattern: &str) -> Vec<String> {
900 match pattern {
901 "Pattern Matching" => {
902 vec![
903 "Exhaustive checking prevents bugs".to_string(),
904 "Clear expression of intent".to_string(),
905 "Powerful destructuring capabilities".to_string(),
906 ]
907 }
908 "Iterator Methods" => {
909 vec![
910 "More concise and readable code".to_string(), "Composable operations"
911 .to_string(), "Lazy evaluation for performance".to_string(),
912 ]
913 }
914 "Error Propagation" => {
915 vec![
916 "Reduces boilerplate error handling".to_string(),
917 "Makes error paths more visible".to_string(),
918 "Enforces error handling".to_string(),
919 ]
920 }
921 _ => vec!["Improves code quality".to_string()],
922 }
923 }
924 fn pattern_alternatives(&self, pattern: &str) -> Vec<String> {
925 match pattern {
926 "Pattern Matching" => {
927 vec!["If-else chains".to_string(), "Method dispatch".to_string(),]
928 }
929 "Iterator Methods" => {
930 vec![
931 "Traditional loops".to_string(), "Manual collection building"
932 .to_string(),
933 ]
934 }
935 "Error Propagation" => {
936 vec![
937 "Manual match statements".to_string(), "Panic on error".to_string(),
938 ]
939 }
940 _ => vec!["Alternative approaches available".to_string()],
941 }
942 }
943}
944impl Tool for RustMentorTool {
945 fn name(&self) -> &'static str {
946 "rust-mentor"
947 }
948 fn description(&self) -> &'static str {
949 "Interactive learning and guidance system for Rust developers"
950 }
951 fn command(&self) -> Command {
952 Command::new(self.name())
953 .about(self.description())
954 .long_about(
955 "An interactive learning and guidance system that analyzes your Rust code and provides personalized explanations, best practice suggestions, and learning recommendations. Perfect for developers at all levels looking to improve their Rust skills.",
956 )
957 .args(
958 &[
959 Arg::new("input")
960 .long("input")
961 .short('i')
962 .help("Input Rust file or directory to analyze")
963 .required(true),
964 Arg::new("explain")
965 .long("explain")
966 .short('e')
967 .help("Explain what specific code constructs do"),
968 Arg::new("suggest")
969 .long("suggest")
970 .help("Show improvement suggestions")
971 .action(clap::ArgAction::SetTrue),
972 Arg::new("learn")
973 .long("learn")
974 .help("Show learning opportunities")
975 .action(clap::ArgAction::SetTrue),
976 Arg::new("patterns")
977 .long("patterns")
978 .help("Analyze code patterns used")
979 .action(clap::ArgAction::SetTrue),
980 Arg::new("focus")
981 .long("focus")
982 .short('f')
983 .help("Focus on specific aspects (functions, structs, traits)")
984 .default_value("all"),
985 Arg::new("level")
986 .long("level")
987 .help("Experience level (beginner, intermediate, advanced)")
988 .default_value("intermediate"),
989 Arg::new("detailed")
990 .long("detailed")
991 .short('d')
992 .help("Show detailed explanations")
993 .action(clap::ArgAction::SetTrue),
994 ],
995 )
996 .args(&common_options())
997 }
998 fn execute(&self, matches: &ArgMatches) -> Result<()> {
999 let input = matches.get_one::<String>("input").unwrap();
1000 let explain_target = matches.get_one::<String>("explain");
1001 let suggest = matches.get_flag("suggest");
1002 let learn = matches.get_flag("learn");
1003 let patterns = matches.get_flag("patterns");
1004 let focus = matches.get_one::<String>("focus").unwrap();
1005 let level = matches.get_one::<String>("level").unwrap();
1006 let detailed = matches.get_flag("detailed");
1007 let dry_run = matches.get_flag("dry-run");
1008 let verbose = matches.get_flag("verbose");
1009 let output_format = parse_output_format(matches);
1010 println!(
1011 "š {} - {}", "CargoMate RustMentor".bold().blue(), self.description()
1012 .cyan()
1013 );
1014 if !Path::new(input).exists() {
1015 return Err(
1016 ToolError::InvalidArguments(format!("Input not found: {}", input)),
1017 );
1018 }
1019 if verbose {
1020 println!(" š Analyzing codebase for learning opportunities...");
1021 }
1022 let analysis = self.analyze_codebase(input)?;
1023 if verbose {
1024 println!(
1025 " š Found {} functions, {} structs, {} traits", analysis.functions
1026 .len(), analysis.structs.len(), analysis.traits.len()
1027 );
1028 println!(
1029 " š Identified {} patterns, {} issues, {} recommendations", analysis
1030 .patterns.len(), analysis.issues.len(), analysis.recommendations.len()
1031 );
1032 }
1033 match output_format {
1034 OutputFormat::Human => {
1035 self.display_human_analysis(
1036 &analysis,
1037 explain_target,
1038 suggest,
1039 learn,
1040 patterns,
1041 focus,
1042 level,
1043 detailed,
1044 );
1045 }
1046 OutputFormat::Json => {
1047 let json_analysis = serde_json::to_string_pretty(&analysis)?;
1048 println!("{}", json_analysis);
1049 }
1050 OutputFormat::Table => {
1051 self.display_table_analysis(&analysis);
1052 }
1053 }
1054 println!(
1055 "\nš Learning analysis complete! Use the insights above to improve your Rust skills."
1056 );
1057 Ok(())
1058 }
1059}
1060impl RustMentorTool {
1061 fn display_human_analysis(
1062 &self,
1063 analysis: &CodeAnalysis,
1064 explain_target: Option<&String>,
1065 suggest: bool,
1066 learn: bool,
1067 patterns: bool,
1068 focus: &str,
1069 level: &str,
1070 detailed: bool,
1071 ) {
1072 println!("\nš§ {}", "Rust Learning Analysis".bold().underline());
1073 println!("\nš {}", "Codebase Overview".bold());
1074 println!(" Functions: {}", analysis.functions.len());
1075 println!(" Structs: {}", analysis.structs.len());
1076 println!(" Traits: {}", analysis.traits.len());
1077 println!(" Code Issues: {}", analysis.issues.len());
1078 println!(" Recommendations: {}", analysis.recommendations.len());
1079 println!(" Learning Opportunities: {}", analysis.learning_opportunities.len());
1080 match focus {
1081 "functions" => self.display_functions(&analysis.functions, detailed),
1082 "structs" => self.display_structs(&analysis.structs, detailed),
1083 "traits" => self.display_traits(&analysis.traits, detailed),
1084 _ => {
1085 self.display_functions(&analysis.functions, detailed);
1086 self.display_structs(&analysis.structs, detailed);
1087 self.display_traits(&analysis.traits, detailed);
1088 }
1089 }
1090 if let Some(target) = explain_target {
1091 self.explain_specific_target(analysis, target);
1092 }
1093 if patterns {
1094 self.display_patterns(&analysis.patterns);
1095 }
1096 if suggest {
1097 self.display_recommendations(&analysis.recommendations);
1098 }
1099 if learn {
1100 self.display_learning_opportunities(&analysis.learning_opportunities, level);
1101 }
1102 if !analysis.issues.is_empty() {
1103 self.display_issues(&analysis.issues);
1104 }
1105 }
1106 fn display_functions(&self, functions: &[FunctionAnalysis], detailed: bool) {
1107 if functions.is_empty() {
1108 return;
1109 }
1110 println!("\nš {}", "Function Analysis".bold());
1111 for func in functions {
1112 println!(" š¹ {}", func.name.bold());
1113 println!(" {}", func.explanation);
1114 if detailed {
1115 if func.complexity > 1 {
1116 println!(
1117 " Complexity: {} (higher = more complex)", func.complexity
1118 );
1119 }
1120 if !func.patterns_used.is_empty() {
1121 println!(" Patterns: {}", func.patterns_used.join(", "));
1122 }
1123 if !func.potential_improvements.is_empty() {
1124 println!(" š” Suggestions:");
1125 for suggestion in &func.potential_improvements {
1126 println!(" ⢠{}", suggestion);
1127 }
1128 }
1129 }
1130 println!();
1131 }
1132 }
1133 fn display_structs(&self, structs: &[StructAnalysis], detailed: bool) {
1134 if structs.is_empty() {
1135 return;
1136 }
1137 println!("\nšļø {}", "Struct Analysis".bold());
1138 for struct_info in structs {
1139 println!(" šļø {}", struct_info.name.bold());
1140 println!(" {}", struct_info.explanation);
1141 if detailed {
1142 if !struct_info.patterns_used.is_empty() {
1143 println!(" Patterns: {}", struct_info.patterns_used.join(", "));
1144 }
1145 if !struct_info.design_considerations.is_empty() {
1146 println!(" š” Design Notes:");
1147 for consideration in &struct_info.design_considerations {
1148 println!(" ⢠{}", consideration);
1149 }
1150 }
1151 }
1152 println!();
1153 }
1154 }
1155 fn display_traits(&self, traits: &[TraitAnalysis], detailed: bool) {
1156 if traits.is_empty() {
1157 return;
1158 }
1159 println!("\nš {}", "Trait Analysis".bold());
1160 for trait_info in traits {
1161 println!(" šŖ {}", trait_info.name.bold());
1162 println!(" {}", trait_info.explanation);
1163 println!(" Purpose: {}", trait_info.purpose);
1164 if detailed {
1165 if !trait_info.methods.is_empty() {
1166 println!(" Methods: {}", trait_info.methods.join(", "));
1167 }
1168 if !trait_info.common_use_cases.is_empty() {
1169 println!(
1170 " Use Cases: {}", trait_info.common_use_cases.join(", ")
1171 );
1172 }
1173 }
1174 println!();
1175 }
1176 }
1177 fn explain_specific_target(&self, analysis: &CodeAnalysis, target: &str) {
1178 println!("\nš {}", format!("Deep Dive: {}", target) .bold());
1179 for func in &analysis.functions {
1180 if func.name == target {
1181 println!(" Type: Function");
1182 println!(" {}", func.explanation);
1183 if !func.patterns_used.is_empty() {
1184 println!(" Patterns Used: {}", func.patterns_used.join(", "));
1185 }
1186 return;
1187 }
1188 }
1189 for struct_info in &analysis.structs {
1190 if struct_info.name == target {
1191 println!(" Type: Struct");
1192 println!(" {}", struct_info.explanation);
1193 if !struct_info.patterns_used.is_empty() {
1194 println!(
1195 " Patterns Used: {}", struct_info.patterns_used.join(", ")
1196 );
1197 }
1198 return;
1199 }
1200 }
1201 for trait_info in &analysis.traits {
1202 if trait_info.name == target {
1203 println!(" Type: Trait");
1204 println!(" {}", trait_info.explanation);
1205 println!(" Purpose: {}", trait_info.purpose);
1206 return;
1207 }
1208 }
1209 println!(" ā Target '{}' not found in the analyzed code.", target);
1210 }
1211 fn display_patterns(&self, patterns: &[PatternAnalysis]) {
1212 if patterns.is_empty() {
1213 return;
1214 }
1215 println!("\nšØ {}", "Code Patterns Used".bold());
1216 for pattern in patterns {
1217 println!(" š {}", pattern.pattern_type.bold());
1218 println!(" {}", pattern.explanation);
1219 println!(" Benefits: {}", pattern.benefits.join(", "));
1220 if !pattern.alternatives.is_empty() {
1221 println!(" Alternatives: {}", pattern.alternatives.join(", "));
1222 }
1223 println!();
1224 }
1225 }
1226 fn display_recommendations(&self, recommendations: &[Recommendation]) {
1227 if recommendations.is_empty() {
1228 return;
1229 }
1230 println!("\nš” {}", "Recommendations".bold());
1231 for recommendation in recommendations {
1232 let priority_icon = match recommendation.priority.as_str() {
1233 "high" => "š“",
1234 "medium" => "š”",
1235 "low" => "š¢",
1236 _ => "āŖ",
1237 };
1238 println!(" {} {}", priority_icon, recommendation.title.bold());
1239 println!(" {}", recommendation.description);
1240 if let Some(code) = &recommendation.code_example {
1241 println!(" ```rust");
1242 for line in code.lines() {
1243 println!(" {}", line);
1244 }
1245 println!(" ```");
1246 }
1247 if !recommendation.benefits.is_empty() {
1248 println!(" Benefits: {}", recommendation.benefits.join(", "));
1249 }
1250 println!();
1251 }
1252 }
1253 fn display_learning_opportunities(
1254 &self,
1255 opportunities: &[LearningOpportunity],
1256 level: &str,
1257 ) {
1258 if opportunities.is_empty() {
1259 return;
1260 }
1261 println!("\nš {}", "Learning Opportunities".bold());
1262 println!(" Tailored for {} Rust developers:", level);
1263 for opportunity in opportunities {
1264 println!(" š {}", opportunity.topic.bold());
1265 println!(" {}", opportunity.explanation);
1266 println!(" Current Level: {}", opportunity.current_level);
1267 if !opportunity.next_steps.is_empty() {
1268 println!(" Next Steps:");
1269 for step in &opportunity.next_steps {
1270 println!(" ⢠{}", step);
1271 }
1272 }
1273 if !opportunity.resources.is_empty() {
1274 println!(" Resources:");
1275 for resource in &opportunity.resources {
1276 println!(" ⢠{}", resource.cyan());
1277 }
1278 }
1279 println!();
1280 }
1281 }
1282 fn display_issues(&self, issues: &[CodeIssue]) {
1283 println!("\nā ļø {}", "Code Issues Found".bold());
1284 for issue in issues {
1285 let severity_icon = match issue.severity.as_str() {
1286 "error" => "ā",
1287 "warning" => "ā ļø ",
1288 "info" => "ā¹ļø ",
1289 _ => "ā¢",
1290 };
1291 println!(" {} {}", severity_icon, issue.message.bold());
1292 println!(" Location: {}", issue.location);
1293 println!(" {}", issue.explanation);
1294 println!(" š” {}", issue.suggestion);
1295 println!();
1296 }
1297 }
1298 fn display_table_analysis(&self, analysis: &CodeAnalysis) {
1299 println!(
1300 "{:<20} {:<10} {:<10} {:<10} {:<10}", "Category", "Count", "Patterns",
1301 "Issues", "Learning"
1302 );
1303 println!("{}", "ā".repeat(70));
1304 println!(
1305 "{:<20} {:<10} {:<10} {:<10} {:<10}", "Functions", analysis.functions.len(),
1306 "-", "-", "-"
1307 );
1308 println!(
1309 "{:<20} {:<10} {:<10} {:<10} {:<10}", "Structs", analysis.structs.len(), "-",
1310 "-", "-"
1311 );
1312 println!(
1313 "{:<20} {:<10} {:<10} {:<10} {:<10}", "Traits", analysis.traits.len(), "-",
1314 "-", "-"
1315 );
1316 println!(
1317 "{:<20} {:<10} {:<10} {:<10} {:<10}", "Code Patterns", "-", analysis.patterns
1318 .len(), "-", "-"
1319 );
1320 println!(
1321 "{:<20} {:<10} {:<10} {:<10} {:<10}", "Issues", "-", "-", analysis.issues
1322 .len(), "-"
1323 );
1324 println!(
1325 "{:<20} {:<10} {:<10} {:<10} {:<10}", "Recommendations", "-", "-", "-",
1326 analysis.recommendations.len()
1327 );
1328 println!(
1329 "{:<20} {:<10} {:<10} {:<10} {:<10}", "Learning Ops", "-", "-", "-", analysis
1330 .learning_opportunities.len()
1331 );
1332 }
1333}
1334impl Default for RustMentorTool {
1335 fn default() -> Self {
1336 Self::new()
1337 }
1338}