1#![warn(missing_docs)]
3use super::types::{
110 Autocorrection, DiagnosticResult, ErrorCategory, ExtractedParameters, FixDetails, FixGenerator,
111 FixTemplate, FixType, ParameterExtractor, ParameterSource,
112};
113use super::DecrustError;
114use regex::Regex;
115use std::collections::HashMap;
116use std::path::PathBuf;
117use tracing::{debug, warn};
118
119use super::circuit_breaker::{CircuitBreaker, CircuitBreakerConfig};
121use super::reporter::{ErrorReportConfig, ErrorReporter};
122use super::syntax::{SyntaxGenerator, TemplateRegistry};
123use std::sync::Arc;
124use std::time::Duration;
125
126pub struct RegexParameterExtractor {
128 patterns: Vec<(Regex, ErrorCategory, f64)>,
130}
131
132impl Default for RegexParameterExtractor {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138impl RegexParameterExtractor {
139 pub fn new() -> Self {
141 let patterns = vec![
142 (
144 Regex::new(r"Resource type '([^']+)' with identifier '([^']+)'").unwrap(),
145 ErrorCategory::NotFound,
146 0.8,
147 ),
148 (
150 Regex::new(r"I/O error during '([^']+)' on path '([^']+)'").unwrap(),
151 ErrorCategory::Io,
152 0.8,
153 ),
154 (
156 Regex::new(r"Configuration error: '([^']+)' in '([^']+)'").unwrap(),
157 ErrorCategory::Configuration,
158 0.8,
159 ),
160 (
162 Regex::new(r"unused import: `([^`]+)`").unwrap(),
163 ErrorCategory::Validation,
164 0.9,
165 ),
166 (
167 Regex::new(r"remove the unused import: `([^`]+)`").unwrap(),
168 ErrorCategory::Validation,
169 0.9,
170 ),
171 (
173 Regex::new(r"unused variable: `([^`]+)`").unwrap(),
174 ErrorCategory::Validation,
175 0.9,
176 ),
177 (
178 Regex::new(r"if this is intentional, prefix it with an underscore: `_([^`]+)`")
179 .unwrap(),
180 ErrorCategory::Validation,
181 0.9,
182 ),
183 (
185 Regex::new(r"unnecessary braces around single import").unwrap(),
186 ErrorCategory::Style,
187 0.9,
188 ),
189 (
190 Regex::new(r"braces are unnecessary for single-item imports").unwrap(),
191 ErrorCategory::Style,
192 0.9,
193 ),
194 ];
195
196 Self { patterns }
197 }
198}
199
200impl ParameterExtractor for RegexParameterExtractor {
201 fn extract_parameters(&self, error: &DecrustError) -> ExtractedParameters {
202 let message = error.to_string();
203 let category = error.category();
204
205 for (pattern, pat_category, confidence) in &self.patterns {
206 if *pat_category == category {
207 if let Some(captures) = pattern.captures(&message) {
208 let mut params = ExtractedParameters::with_source(
209 ParameterSource::ErrorMessage,
210 *confidence,
211 );
212
213 for name in pattern.capture_names().flatten() {
215 if let Some(value) = captures.name(name) {
216 params.add_parameter(name, value.as_str());
217 }
218 }
219
220 if params.values.is_empty() && captures.len() > 1 {
222 for i in 1..captures.len() {
223 if let Some(value) = captures.get(i) {
224 params.add_parameter(format!("param{}", i), value.as_str());
225 }
226 }
227 }
228
229 if !params.values.is_empty() {
230 return params;
231 }
232 }
233 }
234 }
235
236 ExtractedParameters::default()
237 }
238
239 fn name(&self) -> &'static str {
240 "RegexParameterExtractor"
241 }
242
243 fn supported_categories(&self) -> &[ErrorCategory] {
244 &[
245 ErrorCategory::NotFound,
246 ErrorCategory::Io,
247 ErrorCategory::Configuration,
248 ]
249 }
250}
251
252pub struct DiagnosticParameterExtractor;
254
255impl Default for DiagnosticParameterExtractor {
256 fn default() -> Self {
257 Self::new()
258 }
259}
260
261impl DiagnosticParameterExtractor {
262 pub fn new() -> Self {
264 Self
265 }
266}
267
268impl ParameterExtractor for DiagnosticParameterExtractor {
269 fn extract_parameters(&self, error: &DecrustError) -> ExtractedParameters {
270 if let Some(diag_info) = error.get_diagnostic_info() {
271 let mut params = ExtractedParameters::with_source(ParameterSource::DiagnosticInfo, 0.9);
272
273 if let Some(location) = &diag_info.primary_location {
275 params.add_parameter("file_path", &location.file);
276 params.add_parameter("line", location.line.to_string());
277 params.add_parameter("column", location.column.to_string());
278 }
279
280 if let Some(code) = &diag_info.diagnostic_code {
282 params.add_parameter("diagnostic_code", code);
283 }
284
285 if let Some(message) = &diag_info.original_message {
287 if !message.is_empty() {
288 params.add_parameter("message", message);
289 }
290 }
291
292 return params;
293 }
294
295 ExtractedParameters::default()
296 }
297
298 fn name(&self) -> &'static str {
299 "DiagnosticParameterExtractor"
300 }
301
302 fn supported_categories(&self) -> &[ErrorCategory] {
303 &[
305 ErrorCategory::NotFound,
306 ErrorCategory::Io,
307 ErrorCategory::Configuration,
308 ErrorCategory::Network,
309 ErrorCategory::Validation,
310 ErrorCategory::Internal,
311 ErrorCategory::CircuitBreaker,
312 ErrorCategory::Timeout,
313 ErrorCategory::Authentication,
314 ErrorCategory::Authorization,
315 ]
316 }
317}
318
319pub struct NotFoundFixGenerator;
321
322impl Default for NotFoundFixGenerator {
323 fn default() -> Self {
324 Self::new()
325 }
326}
327
328impl NotFoundFixGenerator {
329 pub fn new() -> Self {
331 Self
332 }
333}
334
335impl FixGenerator for NotFoundFixGenerator {
336 fn generate_fix(
337 &self,
338 _error: &DecrustError,
339 params: &ExtractedParameters,
340 _source_code_context: Option<&str>,
341 ) -> Option<Autocorrection> {
342 let resource_type = params
344 .values
345 .get("resource_type")
346 .or_else(|| params.values.get("param1"))
347 .cloned()
348 .unwrap_or_else(|| "unknown resource".to_string());
349
350 let identifier = params
351 .values
352 .get("identifier")
353 .or_else(|| params.values.get("param2"))
354 .cloned()
355 .unwrap_or_else(|| "unknown identifier".to_string());
356
357 let mut commands = vec![];
358 let mut suggestion_details = None;
359
360 if resource_type == "file" || resource_type == "path" {
361 let path_buf = PathBuf::from(&identifier);
362 if let Some(parent) = path_buf.parent() {
363 if !parent.as_os_str().is_empty() {
364 commands.push(format!("mkdir -p \"{}\"", parent.display()));
365 }
366 }
367 commands.push(format!("touch \"{}\"", identifier));
368 suggestion_details = Some(FixDetails::ExecuteCommand {
369 command: commands.first().cloned().unwrap_or_default(),
370 args: commands.iter().skip(1).cloned().collect(),
371 working_directory: None,
372 });
373 }
374
375 Some(Autocorrection {
376 description: format!(
377 "Resource type '{}' with identifier '{}' not found. Consider creating it if it's a file/directory, or verify the path/name.",
378 resource_type, identifier
379 ),
380 fix_type: if commands.is_empty() { FixType::ManualInterventionRequired } else { FixType::ExecuteCommand },
381 confidence: params.confidence,
382 details: suggestion_details,
383 diff_suggestion: None,
384 commands_to_apply: commands,
385 targets_error_code: Some(format!("{:?}", ErrorCategory::NotFound)),
386 })
387 }
388
389 fn name(&self) -> &'static str {
390 "NotFoundFixGenerator"
391 }
392}
393
394pub struct UnusedImportFixGenerator;
396
397impl Default for UnusedImportFixGenerator {
398 fn default() -> Self {
399 Self::new()
400 }
401}
402
403impl UnusedImportFixGenerator {
404 pub fn new() -> Self {
406 Self
407 }
408}
409
410impl FixGenerator for UnusedImportFixGenerator {
411 fn generate_fix(
412 &self,
413 _error: &DecrustError,
414 params: &ExtractedParameters,
415 source_code_context: Option<&str>,
416 ) -> Option<Autocorrection> {
417 let unused_import = params
419 .values
420 .get("param1")
421 .cloned()
422 .unwrap_or_else(|| "unknown import".to_string());
423
424 let description = format!("Remove unused import: `{}`", unused_import);
426
427 let file_path = params
429 .values
430 .get("file_path")
431 .cloned()
432 .unwrap_or_else(|| "unknown_file.rs".to_string());
433
434 let line = params
436 .values
437 .get("line")
438 .and_then(|l| l.parse::<usize>().ok())
439 .unwrap_or(1);
440
441 let (fix_details, commands, diff) = if let Some(context) = source_code_context {
443 self.generate_context_aware_fix(&unused_import, &file_path, line, context)
444 } else {
445 self.generate_simple_fix(&unused_import, &file_path, line)
446 };
447
448 Some(Autocorrection {
449 description,
450 fix_type: FixType::TextReplacement,
451 confidence: params.confidence,
452 details: Some(fix_details),
453 diff_suggestion: Some(diff),
454 commands_to_apply: commands,
455 targets_error_code: Some("unused_imports".to_string()),
456 })
457 }
458
459 fn name(&self) -> &'static str {
460 "UnusedImportFixGenerator"
461 }
462}
463
464impl UnusedImportFixGenerator {
465 fn generate_context_aware_fix(
467 &self,
468 unused_import: &str,
469 file_path: &str,
470 line: usize,
471 context: &str,
472 ) -> (FixDetails, Vec<String>, String) {
473 let lines: Vec<&str> = context.lines().collect();
475
476 let import_line = lines
478 .iter()
479 .find(|&&l| l.contains(unused_import))
480 .map(|&l| l.trim())
481 .unwrap_or("");
482
483 if import_line.contains("{") && import_line.contains("}") {
485 return self.handle_grouped_import(unused_import, file_path, line, import_line);
486 }
487
488 if import_line.starts_with("use ") && import_line.ends_with(";") {
490 return self.handle_simple_import(unused_import, file_path, line);
491 }
492
493 self.generate_simple_fix(unused_import, file_path, line)
495 }
496
497 fn handle_grouped_import(
499 &self,
500 unused_import: &str,
501 file_path: &str,
502 line: usize,
503 import_line: &str,
504 ) -> (FixDetails, Vec<String>, String) {
505 let parts: Vec<&str> = import_line.split("{").collect();
507 if parts.len() != 2 {
508 return self.generate_simple_fix(unused_import, file_path, line);
509 }
510
511 let base_path = parts[0].trim();
512 let items_part = parts[1].trim_end_matches("};").trim();
513
514 let items: Vec<&str> = items_part
516 .split(',')
517 .map(|s| s.trim())
518 .filter(|&s| s != unused_import && !s.is_empty())
519 .collect();
520
521 let (new_import_line, sed_command) = if items.len() == 1 {
523 let new_line = format!("{}{};", base_path, items[0]);
524 let sed_cmd = format!(
525 "sed -i '{}s/{}/{}/' \"{}\"",
526 line,
527 regex::escape(import_line),
528 regex::escape(&new_line),
529 file_path
530 );
531 (new_line, sed_cmd)
532 } else if items.is_empty() {
533 let sed_cmd = format!("sed -i '{}d' \"{}\"", line, file_path);
535 (String::new(), sed_cmd)
536 } else {
537 let new_items = items.join(", ");
539 let new_line = format!("{}{{{}}};", base_path, new_items);
540 let sed_cmd = format!(
541 "sed -i '{}s/{}/{}/' \"{}\"",
542 line,
543 regex::escape(import_line),
544 regex::escape(&new_line),
545 file_path
546 );
547 (new_line, sed_cmd)
548 };
549
550 let explanation = format!(
551 "Removing unused import '{}' from grouped import statement. \
552 The import statement will be updated to '{}'.",
553 unused_import,
554 if new_import_line.is_empty() {
555 "be removed entirely"
556 } else {
557 &new_import_line
558 }
559 );
560
561 let details = FixDetails::SuggestCodeChange {
562 file_path: PathBuf::from(file_path),
563 line_hint: line,
564 suggested_code_snippet: if new_import_line.is_empty() {
565 "// Remove this entire import line".to_string()
566 } else {
567 format!("// Replace with:\n{}", new_import_line)
568 },
569 explanation,
570 };
571
572 let diff = format!(
573 "-{}\n+{}",
574 import_line,
575 if new_import_line.is_empty() {
576 ""
577 } else {
578 &new_import_line
579 }
580 );
581
582 (details, vec![sed_command], diff)
583 }
584
585 fn handle_simple_import(
587 &self,
588 unused_import: &str,
589 file_path: &str,
590 line: usize,
591 ) -> (FixDetails, Vec<String>, String) {
592 self.generate_simple_fix(unused_import, file_path, line)
594 }
595
596 fn generate_simple_fix(
598 &self,
599 unused_import: &str,
600 file_path: &str,
601 line: usize,
602 ) -> (FixDetails, Vec<String>, String) {
603 let suggestion = format!(
604 "// Remove this unused import line containing: {}",
605 unused_import
606 );
607
608 let details = FixDetails::SuggestCodeChange {
609 file_path: PathBuf::from(file_path),
610 line_hint: line,
611 suggested_code_snippet: suggestion,
612 explanation: "Unused imports should be removed to improve code clarity and avoid compiler warnings.".to_string(),
613 };
614
615 let commands = vec![format!("sed -i '{}d' \"{}\"", line, file_path)];
616
617 let diff = format!("-use ... {} ...", unused_import);
618
619 (details, commands, diff)
620 }
621}
622
623pub struct MissingSemicolonFixGenerator;
625
626impl MissingSemicolonFixGenerator {
627 pub fn new() -> Self {
629 Self
630 }
631}
632
633impl FixGenerator for MissingSemicolonFixGenerator {
634 fn generate_fix(
635 &self,
636 _error: &DecrustError,
637 params: &ExtractedParameters,
638 source_code_context: Option<&str>,
639 ) -> Option<Autocorrection> {
640 let file_path = params
642 .values
643 .get("file_path")
644 .cloned()
645 .unwrap_or_else(|| "unknown_file.rs".to_string());
646
647 let line = params
648 .values
649 .get("line")
650 .and_then(|l| l.parse::<usize>().ok())
651 .unwrap_or(1);
652
653 let message = params
655 .values
656 .get("message")
657 .cloned()
658 .unwrap_or_else(|| "expected `;`".to_string());
659
660 if !message.contains("expected `;`") && !message.contains("missing semicolon") {
661 return None;
662 }
663
664 let (details, commands, diff) = if let Some(context) = source_code_context {
666 self.generate_context_aware_fix(&file_path, line, context)
667 } else {
668 self.generate_simple_fix(&file_path, line)
669 };
670
671 Some(Autocorrection {
672 description: "Add missing semicolon at the end of statement".to_string(),
673 fix_type: FixType::TextReplacement,
674 confidence: params.confidence,
675 details: Some(details),
676 diff_suggestion: Some(diff),
677 commands_to_apply: commands,
678 targets_error_code: Some("missing_semicolon".to_string()),
679 })
680 }
681
682 fn name(&self) -> &'static str {
683 "MissingSemicolonFixGenerator"
684 }
685}
686
687impl MissingSemicolonFixGenerator {
688 fn generate_context_aware_fix(
690 &self,
691 file_path: &str,
692 line: usize,
693 context: &str,
694 ) -> (FixDetails, Vec<String>, String) {
695 let lines: Vec<&str> = context.lines().collect();
696
697 let line_content = if line <= lines.len() {
699 lines[line - 1]
700 } else if !lines.is_empty() {
701 lines[lines.len() - 1]
702 } else {
703 return self.generate_simple_fix(file_path, line);
704 };
705
706 let trimmed_line = line_content.trim_end();
708 let new_line = format!("{};", trimmed_line);
709
710 let sed_command = format!(
712 "sed -i '{}s/{}$/{}/' \"{}\"",
713 line,
714 regex::escape(trimmed_line),
715 regex::escape(&new_line),
716 file_path
717 );
718
719 let explanation = "Adding missing semicolon at the end of the statement.".to_string();
720
721 let details = FixDetails::SuggestCodeChange {
722 file_path: PathBuf::from(file_path),
723 line_hint: line,
724 suggested_code_snippet: format!("// Add semicolon:\n{}", new_line),
725 explanation,
726 };
727
728 let diff = format!("-{}\n+{}", line_content, new_line);
729
730 (details, vec![sed_command], diff)
731 }
732
733 fn generate_simple_fix(
734 &self,
735 file_path: &str,
736 line: usize,
737 ) -> (FixDetails, Vec<String>, String) {
738 let sed_command = format!("sed -i '{}s/$/;/' \"{}\"", line, file_path);
740
741 let explanation = "Adding missing semicolon at the end of the statement.".to_string();
742
743 let details = FixDetails::SuggestCodeChange {
744 file_path: PathBuf::from(file_path),
745 line_hint: line,
746 suggested_code_snippet: "// Add semicolon at the end of this line".to_string(),
747 explanation,
748 };
749
750 let diff = "-(line without semicolon)\n+(same line with semicolon added)".to_string();
751
752 (details, vec![sed_command], diff)
753 }
754}
755
756pub struct MismatchedTypeFixGenerator;
758
759impl MismatchedTypeFixGenerator {
760 pub fn new() -> Self {
762 Self
763 }
764}
765
766impl FixGenerator for MismatchedTypeFixGenerator {
767 fn generate_fix(
768 &self,
769 _error: &DecrustError,
770 params: &ExtractedParameters,
771 _source_code_context: Option<&str>,
772 ) -> Option<Autocorrection> {
773 let message = params.values.get("message")?;
775
776 if !message.contains("mismatched types")
778 && !message.contains("expected")
779 && !message.contains("found")
780 {
781 return None;
782 }
783
784 let expected_type = if let Some(expected) = extract_type(message, "expected") {
786 expected
787 } else {
788 "expected_type".to_string()
789 };
790
791 let found_type = if let Some(found) = extract_type(message, "found") {
792 found
793 } else {
794 "found_type".to_string()
795 };
796
797 let file_path = params
798 .values
799 .get("file_path")
800 .cloned()
801 .unwrap_or_else(|| "unknown_file.rs".to_string());
802
803 let line = params
804 .values
805 .get("line")
806 .and_then(|l| l.parse::<usize>().ok())
807 .unwrap_or(1);
808
809 let suggestions = self.generate_type_conversion_suggestions(&expected_type, &found_type);
811
812 let explanation = format!(
813 "Type mismatch: expected `{}`, found `{}`. Consider one of these solutions:\n{}",
814 expected_type,
815 found_type,
816 suggestions.join("\n")
817 );
818
819 let details = FixDetails::SuggestCodeChange {
820 file_path: PathBuf::from(&file_path),
821 line_hint: line,
822 suggested_code_snippet: format!("// Type mismatch. Try:\n{}", suggestions.join("\n")),
823 explanation,
824 };
825
826 let (fix_type, commands, confidence) =
828 if self.is_simple_automatable_conversion(&expected_type, &found_type) {
829 let auto_commands = self.generate_automatic_conversion_commands(
830 &expected_type,
831 &found_type,
832 &file_path,
833 line,
834 );
835 (FixType::TextReplacement, auto_commands, 0.9)
836 } else {
837 (FixType::ManualInterventionRequired, vec![], 0.7)
838 };
839
840 Some(Autocorrection {
841 description: format!(
842 "Fix type mismatch between `{}` and `{}`",
843 expected_type, found_type
844 ),
845 fix_type,
846 confidence,
847 details: Some(details),
848 diff_suggestion: None,
849 commands_to_apply: commands,
850 targets_error_code: Some("mismatched_types".to_string()),
851 })
852 }
853
854 fn name(&self) -> &'static str {
855 "MismatchedTypeFixGenerator"
856 }
857}
858
859impl MismatchedTypeFixGenerator {
860 fn is_simple_automatable_conversion(&self, expected: &str, found: &str) -> bool {
862 match (expected, found) {
864 (exp, found) if exp.contains("String") && found.contains("&str") => true,
866 (exp, found) if exp.contains("&str") && found.contains("String") => true,
867
868 (exp, found)
870 if exp.starts_with('&') && !found.starts_with('&') && !found.contains("mut") =>
871 {
872 true
873 }
874
875 (exp, found)
877 if exp.contains("Option<")
878 && !found.contains("Option<")
879 && !found.contains("Result<") =>
880 {
881 true
882 }
883
884 (exp, found) if self.is_safe_numeric_conversion(exp, found) => true,
886
887 _ => false,
888 }
889 }
890
891 fn is_safe_numeric_conversion(&self, expected: &str, found: &str) -> bool {
893 let safe_numeric_types = ["i32", "i64", "u32", "u64", "usize", "isize", "f32", "f64"];
894
895 let exp_is_numeric = safe_numeric_types.iter().any(|&t| expected.contains(t));
896 let found_is_numeric = safe_numeric_types.iter().any(|&t| found.contains(t));
897
898 exp_is_numeric && found_is_numeric
899 }
900
901 fn generate_automatic_conversion_commands(
903 &self,
904 expected: &str,
905 found: &str,
906 file_path: &str,
907 line: usize,
908 ) -> Vec<String> {
909 let mut commands = Vec::new();
910
911 if expected.contains("String") && found.contains("&str") {
913 commands.push(format!(
914 "sed -i '{}s/\\([^.]*\\)/\\1.to_string()/' \"{}\"",
915 line, file_path
916 ));
917 } else if expected.contains("&str") && found.contains("String") {
918 commands.push(format!(
919 "sed -i '{}s/\\([^.]*\\)/&\\1/' \"{}\"",
920 line, file_path
921 ));
922 } else if expected.starts_with('&') && !found.starts_with('&') {
923 commands.push(format!(
924 "sed -i '{}s/\\([^&]*\\)/&\\1/' \"{}\"",
925 line, file_path
926 ));
927 } else if expected.contains("Option<") && !found.contains("Option<") {
928 commands.push(format!(
929 "sed -i '{}s/\\([^S]*\\)/Some(\\1)/' \"{}\"",
930 line, file_path
931 ));
932 } else if self.is_safe_numeric_conversion(expected, found) {
933 commands.push(format!(
934 "sed -i '{}s/\\([^a]*\\)/\\1 as {}/' \"{}\"",
935 line,
936 expected.trim(),
937 file_path
938 ));
939 }
940
941 commands
942 }
943
944 fn generate_type_conversion_suggestions(&self, expected: &str, found: &str) -> Vec<String> {
946 let mut suggestions = Vec::new();
947
948 if (expected.contains("i32")
950 || expected.contains("i64")
951 || expected.contains("u32")
952 || expected.contains("u64")
953 || expected.contains("usize")
954 || expected.contains("isize"))
955 && (found.contains("i32")
956 || found.contains("i64")
957 || found.contains("u32")
958 || found.contains("u64")
959 || found.contains("usize")
960 || found.contains("isize"))
961 {
962 suggestions.push(format!(
963 "// 1. Use type casting: `your_variable as {}`",
964 expected
965 ));
966 }
967
968 if expected.contains("String") && found.contains("&str") {
970 suggestions.push(
971 "// 1. Convert &str to String using .to_string() or String::from()".to_string(),
972 );
973 suggestions
974 .push("// Example: your_str.to_string() or String::from(your_str)".to_string());
975 } else if expected.contains("String") {
976 suggestions.push("// 1. Convert to String using .to_string()".to_string());
978 suggestions.push("// Example: your_value.to_string()".to_string());
979 suggestions.push("// 2. Use String::from if applicable".to_string());
980 }
981
982 if expected.contains("&str") && found.contains("String") {
983 suggestions.push(
984 "// 1. Get a string slice using &your_string or your_string.as_str()".to_string(),
985 );
986 }
987
988 if expected.contains("Option<") && !found.contains("Option<") {
990 suggestions.push("// 1. Wrap the value in Some(): Some(your_value)".to_string());
991 }
992
993 if !expected.contains("Option<") && found.contains("Option<") {
994 suggestions.push("// 1. Unwrap the Option: your_option.unwrap()".to_string());
995 suggestions.push(
996 "// 2. Use a default value: your_option.unwrap_or(default_value)".to_string(),
997 );
998 suggestions.push("// 3. Match on the Option for safer handling".to_string());
999 }
1000
1001 if expected.contains("Result<") && !found.contains("Result<") {
1003 suggestions.push("// 1. Wrap successful values: Ok(your_value)".to_string());
1004 suggestions.push("// 2. If this is an error case: Err(your_error)".to_string());
1005 }
1006
1007 if !expected.contains("Result<") && found.contains("Result<") {
1008 suggestions.push("// 1. Unwrap the Result: your_result.unwrap()".to_string());
1009 suggestions.push(
1010 "// 2. Use a default value: your_result.unwrap_or(default_value)".to_string(),
1011 );
1012 suggestions.push("// 3. Match on the Result for safer error handling".to_string());
1013 suggestions.push(
1014 "// 4. Propagate the error using ? if in a function returning Result".to_string(),
1015 );
1016 }
1017
1018 if expected.starts_with('&') && !found.starts_with('&') {
1020 suggestions.push("// 1. Add a reference to the value: &your_value".to_string());
1021 }
1022
1023 if !expected.starts_with('&') && found.starts_with('&') {
1024 suggestions.push("// 1. Dereference the value: *your_reference".to_string());
1025 suggestions
1026 .push("// 2. Clone the referenced value: your_reference.clone()".to_string());
1027 }
1028
1029 if expected.contains("PathBuf") && (found.contains("&str") || found.contains("String")) {
1031 suggestions.push(
1032 "// 1. Convert to PathBuf: std::path::PathBuf::from(your_string)".to_string(),
1033 );
1034 }
1035
1036 if suggestions.is_empty() {
1038 suggestions.push(format!("// 1. Make sure your value has type: {}", expected));
1039 suggestions.push(
1040 "// 2. Change the expected type in the receiving function/variable".to_string(),
1041 );
1042 suggestions.push(
1043 "// 3. Implement From<YourType> for TargetType or use .into() if applicable"
1044 .to_string(),
1045 );
1046 }
1047
1048 suggestions
1049 }
1050}
1051
1052pub struct ImmutableBorrowFixGenerator;
1054
1055impl ImmutableBorrowFixGenerator {
1056 pub fn new() -> Self {
1058 Self
1059 }
1060}
1061
1062impl FixGenerator for ImmutableBorrowFixGenerator {
1063 fn generate_fix(
1064 &self,
1065 _error: &DecrustError,
1066 params: &ExtractedParameters,
1067 source_code_context: Option<&str>,
1068 ) -> Option<Autocorrection> {
1069 let message = params.values.get("message")?;
1071
1072 if !message.contains("cannot borrow") || !message.contains("as mutable") {
1074 return None;
1075 }
1076
1077 let variable_name = extract_variable_from_borrow_error(message)?;
1079
1080 let file_path = params
1081 .values
1082 .get("file_path")
1083 .cloned()
1084 .unwrap_or_else(|| "unknown_file.rs".to_string());
1085
1086 let line = params
1087 .values
1088 .get("line")
1089 .and_then(|l| l.parse::<usize>().ok())
1090 .unwrap_or(1);
1091
1092 let (details, commands, diff) = if let Some(context) = source_code_context {
1094 self.generate_context_aware_fix(&file_path, line, &variable_name, context)
1095 } else {
1096 self.generate_simple_fix(&file_path, line, &variable_name)
1097 };
1098
1099 Some(Autocorrection {
1100 description: format!(
1101 "Change variable `{}` declaration to be mutable",
1102 variable_name
1103 ),
1104 fix_type: FixType::TextReplacement,
1105 confidence: 0.8,
1106 details: Some(details),
1107 diff_suggestion: Some(diff),
1108 commands_to_apply: commands,
1109 targets_error_code: Some("immutable_borrow".to_string()),
1110 })
1111 }
1112
1113 fn name(&self) -> &'static str {
1114 "ImmutableBorrowFixGenerator"
1115 }
1116}
1117
1118impl ImmutableBorrowFixGenerator {
1119 fn generate_context_aware_fix(
1120 &self,
1121 file_path: &str,
1122 line: usize,
1123 variable_name: &str,
1124 context: &str,
1125 ) -> (FixDetails, Vec<String>, String) {
1126 let lines: Vec<&str> = context.lines().collect();
1128
1129 let declaration_line_idx = lines.iter().position(
1131 |&l| {
1132 l.contains(&format!("let {} =", variable_name))
1133 || l.contains(&format!("let {}: ", variable_name))
1134 || l.contains(&format!("fn {}(", variable_name))
1135 }, );
1137
1138 if let Some(idx) = declaration_line_idx {
1139 let declaration_line = lines[idx];
1140 let new_line = if declaration_line.contains(&format!("let {} =", variable_name)) {
1141 declaration_line.replace(
1142 &format!("let {} =", variable_name),
1143 &format!("let mut {} =", variable_name),
1144 )
1145 } else if declaration_line.contains(&format!("let {}: ", variable_name)) {
1146 declaration_line.replace(
1147 &format!("let {}: ", variable_name),
1148 &format!("let mut {}: ", variable_name),
1149 )
1150 } else if declaration_line.contains(&format!("fn {}(", variable_name)) {
1151 let mut new_declaration = declaration_line.to_string();
1153 let re = Regex::new(&format!(r"(\b{}\b)(\s*:[^,\)]+)", variable_name)).unwrap();
1154 if re.is_match(&new_declaration) {
1155 new_declaration = re
1156 .replace(&new_declaration, format!("mut $1$2"))
1157 .to_string();
1158 } else {
1159 new_declaration = new_declaration.replace(
1160 &format!("{}:", variable_name),
1161 &format!("mut {}:", variable_name),
1162 );
1163 new_declaration = new_declaration.replace(
1164 &format!("{},", variable_name),
1165 &format!("mut {},", variable_name),
1166 );
1167 new_declaration = new_declaration.replace(
1168 &format!("{})", variable_name),
1169 &format!("mut {})", variable_name),
1170 );
1171 }
1172 new_declaration
1173 } else {
1174 declaration_line.to_string()
1175 };
1176
1177 let sed_command = format!(
1178 "sed -i '{}s/{}/{}/' \"{}\"",
1179 idx + 1, regex::escape(declaration_line),
1181 regex::escape(&new_line),
1182 file_path
1183 );
1184
1185 let explanation = format!(
1186 "To use a mutable borrow with `&mut {}`, the variable must be declared as mutable using `let mut {}`.",
1187 variable_name, variable_name
1188 );
1189
1190 let details = FixDetails::SuggestCodeChange {
1191 file_path: PathBuf::from(file_path),
1192 line_hint: idx + 1,
1193 suggested_code_snippet: format!("// Change to:\n{}", new_line),
1194 explanation,
1195 };
1196
1197 let diff = format!("-{}\n+{}", declaration_line, new_line);
1198
1199 return (details, vec![sed_command], diff);
1200 }
1201
1202 self.generate_simple_fix(file_path, line, variable_name)
1204 }
1205
1206 fn generate_simple_fix(
1207 &self,
1208 file_path: &str,
1209 line: usize,
1210 variable_name: &str,
1211 ) -> (FixDetails, Vec<String>, String) {
1212 let explanation = format!(
1214 "To use a mutable borrow with `&mut {}`, the variable must be declared as mutable using `let mut {}`.",
1215 variable_name, variable_name
1216 );
1217
1218 let details = FixDetails::SuggestCodeChange {
1219 file_path: PathBuf::from(file_path),
1220 line_hint: line,
1221 suggested_code_snippet: format!(
1222 "// Find where '{}' is declared and change to:\nlet mut {} = ...",
1223 variable_name, variable_name
1224 ),
1225 explanation,
1226 };
1227
1228 let diff = format!(
1229 "-let {} = ...\n+let mut {} = ...",
1230 variable_name, variable_name
1231 );
1232
1233 let commands = vec![format!(
1235 "grep -n \"let {} =\" --include=\"*.rs\" -r \"{}\"",
1236 variable_name,
1237 PathBuf::from(file_path)
1238 .parent()
1239 .unwrap_or(&PathBuf::from("."))
1240 .display()
1241 )];
1242
1243 (details, commands, diff)
1244 }
1245}
1246
1247pub struct UnnecessaryBracesFixGenerator;
1249
1250impl UnnecessaryBracesFixGenerator {
1251 pub fn new() -> Self {
1253 Self
1254 }
1255}
1256
1257impl FixGenerator for UnnecessaryBracesFixGenerator {
1258 fn generate_fix(
1259 &self,
1260 _error: &DecrustError,
1261 params: &ExtractedParameters,
1262 source_code_context: Option<&str>,
1263 ) -> Option<Autocorrection> {
1264 let message = params.values.get("message")?;
1266
1267 if !message.contains("unnecessary braces") && !message.contains("braces are unnecessary") {
1269 return None;
1270 }
1271
1272 let file_path = params
1273 .values
1274 .get("file_path")
1275 .cloned()
1276 .unwrap_or_else(|| "unknown_file.rs".to_string());
1277
1278 let line = params
1279 .values
1280 .get("line")
1281 .and_then(|l| l.parse::<usize>().ok())
1282 .unwrap_or(1);
1283
1284 let (details, commands, diff) = if let Some(context) = source_code_context {
1286 self.generate_context_aware_fix(&file_path, line, context)
1287 } else {
1288 self.generate_simple_fix(&file_path, line)
1289 };
1290
1291 Some(Autocorrection {
1292 description: "Remove unnecessary braces around single import".to_string(),
1293 fix_type: FixType::TextReplacement,
1294 confidence: 0.9,
1295 details: Some(details),
1296 diff_suggestion: Some(diff),
1297 commands_to_apply: commands,
1298 targets_error_code: Some("unnecessary_braces".to_string()),
1299 })
1300 }
1301
1302 fn name(&self) -> &'static str {
1303 "UnnecessaryBracesFixGenerator"
1304 }
1305}
1306
1307impl UnnecessaryBracesFixGenerator {
1308 fn generate_context_aware_fix(
1309 &self,
1310 file_path: &str,
1311 line: usize,
1312 context: &str,
1313 ) -> (FixDetails, Vec<String>, String) {
1314 let lines: Vec<&str> = context.lines().collect();
1315
1316 let import_line = if line <= lines.len() {
1318 lines[line - 1]
1319 } else if !lines.is_empty() {
1320 lines[lines.len() - 1]
1321 } else {
1322 return self.generate_simple_fix(file_path, line);
1323 };
1324
1325 if !import_line.contains("use ") || !import_line.contains("{") || !import_line.contains("}")
1327 {
1328 return self.generate_simple_fix(file_path, line);
1329 }
1330
1331 let re = Regex::new(r"use\s+([^{]+)\{([^}]+)\};").unwrap();
1333 if let Some(captures) = re.captures(import_line) {
1334 let prefix = captures.get(1).map_or("", |m| m.as_str());
1335 let item = captures.get(2).map_or("", |m| m.as_str()).trim();
1336
1337 if !item.contains(",") {
1339 let new_line = format!("use {}{};", prefix, item);
1341
1342 let sed_command = format!(
1344 "sed -i '{}s/{}/{}/' \"{}\"",
1345 line,
1346 regex::escape(import_line),
1347 regex::escape(&new_line),
1348 file_path
1349 );
1350
1351 let explanation =
1352 "Removing unnecessary braces around a single import item.".to_string();
1353
1354 let details = FixDetails::SuggestCodeChange {
1355 file_path: PathBuf::from(file_path),
1356 line_hint: line,
1357 suggested_code_snippet: format!("// Change to:\n{}", new_line),
1358 explanation,
1359 };
1360
1361 let diff = format!("-{}\n+{}", import_line, new_line);
1362
1363 return (details, vec![sed_command], diff);
1364 }
1365 }
1366
1367 self.generate_simple_fix(file_path, line)
1369 }
1370
1371 fn generate_simple_fix(
1372 &self,
1373 file_path: &str,
1374 line: usize,
1375 ) -> (FixDetails, Vec<String>, String) {
1376 let explanation =
1378 "Rust style guide recommends not using braces for single-item imports.".to_string();
1379
1380 let details = FixDetails::SuggestCodeChange {
1381 file_path: PathBuf::from(file_path),
1382 line_hint: line,
1383 suggested_code_snippet: "// Change from:\n// use std::time::{Duration};\n// To:\n// use std::time::Duration;".to_string(),
1384 explanation,
1385 };
1386
1387 let sed_command = format!(
1389 "sed -i '{}s/{{\\([^,}}]*\\)}}/\\1/' \"{}\"",
1390 line, file_path
1391 );
1392
1393 let diff = "-use std::time::{Duration};\n+use std::time::Duration;".to_string();
1394
1395 (details, vec![sed_command], diff)
1396 }
1397}
1398
1399pub struct MissingLifetimeFixGenerator;
1401
1402impl MissingLifetimeFixGenerator {
1403 pub fn new() -> Self {
1405 Self
1406 }
1407}
1408
1409impl FixGenerator for MissingLifetimeFixGenerator {
1410 fn generate_fix(
1411 &self,
1412 _error: &DecrustError,
1413 params: &ExtractedParameters,
1414 source_code_context: Option<&str>,
1415 ) -> Option<Autocorrection> {
1416 let message = params.values.get("message")?;
1418
1419 if !message.contains("lifetime") || !message.contains("missing") {
1421 return None;
1422 }
1423
1424 let file_path = params
1425 .values
1426 .get("file_path")
1427 .cloned()
1428 .unwrap_or_else(|| "unknown_file.rs".to_string());
1429
1430 let line = params
1431 .values
1432 .get("line")
1433 .and_then(|l| l.parse::<usize>().ok())
1434 .unwrap_or(1);
1435
1436 let (details, commands, diff) = if let Some(context) = source_code_context {
1438 self.generate_context_aware_fix(&file_path, line, context)
1439 } else {
1440 self.generate_simple_fix(&file_path, line, message)
1441 };
1442
1443 Some(Autocorrection {
1444 description: "Add missing lifetime parameter".to_string(),
1445 fix_type: FixType::TextReplacement,
1446 confidence: 0.7,
1447 details: Some(details),
1448 diff_suggestion: Some(diff),
1449 commands_to_apply: commands,
1450 targets_error_code: Some("missing_lifetime".to_string()),
1451 })
1452 }
1453
1454 fn name(&self) -> &'static str {
1455 "MissingLifetimeFixGenerator"
1456 }
1457}
1458
1459impl MissingLifetimeFixGenerator {
1460 fn generate_context_aware_fix(
1461 &self,
1462 file_path: &str,
1463 line: usize,
1464 context: &str,
1465 ) -> (FixDetails, Vec<String>, String) {
1466 let lines: Vec<&str> = context.lines().collect();
1467
1468 let def_line_idx = lines.iter().position(|&l| {
1470 l.contains("fn ") || l.contains("struct ") || l.contains("impl") || l.contains("trait")
1471 });
1472
1473 if let Some(idx) = def_line_idx {
1474 let def_line = lines[idx];
1475
1476 let new_line = if def_line.contains("fn ") && !def_line.contains("<") {
1478 def_line.replace("fn ", "fn <'a> ")
1480 } else if def_line.contains("struct ") && !def_line.contains("<") {
1481 def_line.replace("struct ", "struct <'a> ")
1483 } else if def_line.contains("impl") && !def_line.contains("<") {
1484 def_line.replace("impl", "impl<'a>")
1486 } else if def_line.contains("trait") && !def_line.contains("<") {
1487 def_line.replace("trait", "trait<'a>")
1489 } else if def_line.contains("<") && !def_line.contains("'") {
1490 let open_bracket_pos = def_line.find("<").unwrap();
1492 let mut new_def = def_line.to_string();
1493 new_def.insert_str(open_bracket_pos + 1, "'a, ");
1494 new_def
1495 } else {
1496 def_line.to_string()
1498 };
1499
1500 if new_line == def_line {
1502 return self.generate_simple_fix(file_path, line, "Missing lifetime parameter");
1503 }
1504
1505 let sed_command = format!(
1506 "sed -i '{}s/{}/{}/' \"{}\"",
1507 idx + 1, regex::escape(def_line),
1509 regex::escape(&new_line),
1510 file_path
1511 );
1512
1513 let explanation = "Adding a lifetime parameter to fix missing lifetime specifier. You may need to add \
1514 lifetime annotations to references in the parameter or return types as well.".to_string();
1515
1516 let details = FixDetails::SuggestCodeChange {
1517 file_path: PathBuf::from(file_path),
1518 line_hint: idx + 1,
1519 suggested_code_snippet: format!("// Change to:\n{}", new_line),
1520 explanation,
1521 };
1522
1523 let diff = format!("-{}\n+{}", def_line, new_line);
1524
1525 return (details, vec![sed_command], diff);
1526 }
1527
1528 self.generate_simple_fix(file_path, line, "Missing lifetime parameter")
1530 }
1531
1532 fn generate_simple_fix(
1533 &self,
1534 file_path: &str,
1535 line: usize,
1536 _message: &str,
1537 ) -> (FixDetails, Vec<String>, String) {
1538 let suggestions = vec![
1540 "// For functions with references in arguments and return value:".to_string(),
1541 "fn example<'a>(arg: &'a Type) -> &'a Type { /* ... */ }".to_string(),
1542 "".to_string(),
1543 "// For structs containing references:".to_string(),
1544 "struct Example<'a> { field: &'a Type }".to_string(),
1545 "".to_string(),
1546 "// For impl blocks for types with lifetimes:".to_string(),
1547 "impl<'a> Example<'a> { /* ... */ }".to_string(),
1548 ];
1549
1550 let explanation = "Rust requires explicit lifetime parameters when storing references in structs \
1551 or returning references from functions. The lifetime parameter tells the compiler \
1552 how long the references need to be valid.".to_string();
1553
1554 let details = FixDetails::SuggestCodeChange {
1555 file_path: PathBuf::from(file_path),
1556 line_hint: line,
1557 suggested_code_snippet: suggestions.join("\n"),
1558 explanation,
1559 };
1560
1561 let commands = vec![];
1563
1564 let diff = format!(
1566 "-// Code with missing lifetime parameter\n+// Code with added lifetime parameter <'a>"
1567 );
1568
1569 (details, commands, diff)
1570 }
1571}
1572
1573pub struct MatchPatternFixGenerator;
1575
1576impl MatchPatternFixGenerator {
1577 pub fn new() -> Self {
1579 Self
1580 }
1581}
1582
1583impl FixGenerator for MatchPatternFixGenerator {
1584 fn generate_fix(
1585 &self,
1586 _error: &DecrustError,
1587 params: &ExtractedParameters,
1588 source_code_context: Option<&str>,
1589 ) -> Option<Autocorrection> {
1590 let message = params.values.get("message")?;
1592
1593 let file_path = params
1600 .values
1601 .get("file_path")
1602 .cloned()
1603 .unwrap_or_else(|| "unknown_file.rs".to_string());
1604
1605 let line = params
1606 .values
1607 .get("line")
1608 .and_then(|l| l.parse::<usize>().ok())
1609 .unwrap_or(1);
1610
1611 let is_non_exhaustive = message.contains("non-exhaustive");
1612
1613 let (details, commands, diff) = if let Some(context) = source_code_context {
1615 self.generate_context_aware_fix(&file_path, line, context, is_non_exhaustive)
1616 } else {
1617 self.generate_simple_fix(&file_path, line, is_non_exhaustive)
1618 };
1619
1620 let description = if is_non_exhaustive {
1621 "Add missing patterns to non-exhaustive match expression".to_string()
1622 } else {
1623 "Remove or modify unreachable pattern in match expression".to_string()
1624 };
1625
1626 Some(Autocorrection {
1627 description,
1628 fix_type: FixType::TextReplacement,
1629 confidence: 0.7,
1630 details: Some(details),
1631 diff_suggestion: Some(diff),
1632 commands_to_apply: commands,
1633 targets_error_code: Some(
1634 if is_non_exhaustive {
1635 "non_exhaustive_patterns"
1636 } else {
1637 "unreachable_pattern"
1638 }
1639 .to_string(),
1640 ),
1641 })
1642 }
1643
1644 fn name(&self) -> &'static str {
1645 "MatchPatternFixGenerator"
1646 }
1647}
1648
1649impl MatchPatternFixGenerator {
1650 fn generate_context_aware_fix(
1651 &self,
1652 file_path: &str,
1653 line: usize,
1654 context: &str,
1655 is_non_exhaustive: bool,
1656 ) -> (FixDetails, Vec<String>, String) {
1657 let lines: Vec<&str> = context.lines().collect();
1658
1659 let match_start_idx = lines.iter().take(line).rposition(|&l| l.contains("match "));
1661 let closing_brace_idx = match_start_idx.and_then(|start_idx| {
1662 lines
1663 .iter()
1664 .skip(start_idx)
1665 .position(|&l| l.trim() == "}")
1666 .map(|rel_pos| start_idx + rel_pos)
1667 });
1668
1669 if let (Some(match_idx), Some(close_idx)) = (match_start_idx, closing_brace_idx) {
1670 let match_line = lines[match_idx];
1672 let enum_type = extract_match_type(match_line);
1673
1674 if is_non_exhaustive {
1675 let indent = lines[close_idx]
1677 .chars()
1678 .take_while(|&c| c.is_whitespace())
1679 .collect::<String>();
1680 let catch_all = format!("{}_ => {{", indent);
1681 let catch_all_body = format!("{} // Handle all other cases", indent);
1682 let catch_all_close = format!("{}}},", indent);
1683
1684 let new_lines: Vec<_> = lines[..close_idx]
1685 .to_vec()
1686 .into_iter()
1687 .chain(vec![
1688 catch_all.as_str(),
1689 catch_all_body.as_str(),
1690 catch_all_close.as_str(),
1691 lines[close_idx],
1692 ])
1693 .collect();
1694
1695 let new_content = new_lines.join("\n");
1696
1697 let sed_script = format!(
1698 "sed -i '{},{}c\\{}' \"{}\"",
1699 match_idx + 1,
1700 close_idx + 1,
1701 new_content.replace("\n", "\\n"),
1702 file_path
1703 );
1704
1705 let explanation = format!(
1706 "Adding a catch-all pattern `_` to handle all remaining cases in the match expression. \
1707 This makes the match expression exhaustive as required by Rust.{}",
1708 if let Some(typ) = enum_type {
1709 format!("\n\nYou may want to add specific patterns for all variants of `{}`.", typ)
1710 } else {
1711 String::new()
1712 }
1713 );
1714
1715 let details = FixDetails::SuggestCodeChange {
1716 file_path: PathBuf::from(file_path),
1717 line_hint: close_idx,
1718 suggested_code_snippet: format!(
1719 "// Add before closing brace:\n{}\n{}\n{}",
1720 catch_all, catch_all_body, catch_all_close
1721 ),
1722 explanation,
1723 };
1724
1725 let diff = format!(
1726 "@@ match expression @@\n...\n+{}\n+{}\n+{}",
1727 catch_all, catch_all_body, catch_all_close
1728 );
1729
1730 return (details, vec![sed_script], diff);
1731 } else {
1732 let explanation = "One of your match patterns is unreachable because it's already covered by a previous pattern. \
1735 Consider removing the unreachable pattern or making it more specific.".to_string();
1736
1737 let details = FixDetails::SuggestCodeChange {
1738 file_path: PathBuf::from(file_path),
1739 line_hint: line,
1740 suggested_code_snippet:
1741 "// Review your match patterns to identify which ones overlap".to_string(),
1742 explanation,
1743 };
1744
1745 return (
1747 details,
1748 vec![],
1749 "// Need to review match patterns for overlap".to_string(),
1750 );
1751 }
1752 }
1753
1754 self.generate_simple_fix(file_path, line, is_non_exhaustive)
1756 }
1757
1758 fn generate_simple_fix(
1759 &self,
1760 file_path: &str,
1761 line: usize,
1762 is_non_exhaustive: bool,
1763 ) -> (FixDetails, Vec<String>, String) {
1764 let (explanation, suggestion) = if is_non_exhaustive {
1766 (
1767 "Your match expression doesn't handle all possible cases of the type being matched. \
1768 Rust requires match expressions to be exhaustive to ensure all possible values are handled.",
1769 vec![
1770 "// Add a catch-all pattern at the end of your match expression:".to_string(),
1771 "_ => {".to_string(),
1772 " // Handle remaining cases".to_string(),
1773 "},".to_string(),
1774 "".to_string(),
1775 "// Or list all remaining variants explicitly".to_string(),
1776 ]
1777 )
1778 } else {
1779 (
1780 "One of your match patterns is unreachable because it's already covered by a previous pattern. \
1781 This is often caused by a pattern that's too general earlier in the match expression.",
1782 vec![
1783 "// 1. Check for wildcard patterns (`_`) that might come before specific patterns".to_string(),
1784 "// 2. Check for overlapping patterns".to_string(),
1785 "// 3. Consider reordering your patterns from most specific to most general".to_string(),
1786 ]
1787 )
1788 };
1789
1790 let details = FixDetails::SuggestCodeChange {
1791 file_path: PathBuf::from(file_path),
1792 line_hint: line,
1793 suggested_code_snippet: suggestion.join("\n"),
1794 explanation: explanation.to_string(),
1795 };
1796
1797 let commands = vec![];
1799
1800 let diff = if is_non_exhaustive {
1802 "+ _ => { /* Handle all other cases */ },"
1803 } else {
1804 "- [unreachable pattern] => { ... },"
1805 }
1806 .to_string();
1807
1808 (details, commands, diff)
1809 }
1810}
1811
1812fn extract_match_type(match_line: &str) -> Option<String> {
1814 let parts: Vec<&str> = match_line.split("match ").collect();
1815 if parts.len() < 2 {
1816 return None;
1817 }
1818
1819 let expr = parts[1].trim().trim_end_matches(" {");
1820
1821 if expr.contains(".") {
1823 let var_name = expr.split('.').next()?;
1826 return Some(format!("[type of {}]", var_name.trim()));
1827 } else if expr.contains("::") {
1828 let parts: Vec<&str> = expr.split("::").collect();
1830 if parts.len() >= 2 {
1831 return Some(parts[0].trim().to_string());
1832 }
1833 } else if expr.starts_with("Some(") || expr.starts_with("None") {
1834 return Some("Option<T>".to_string());
1835 } else if expr.starts_with("Ok(") || expr.starts_with("Err(") {
1836 return Some("Result<T, E>".to_string());
1837 }
1838
1839 Some(expr.to_string())
1841}
1842
1843pub struct PrivateFieldAccessFixGenerator;
1845
1846impl PrivateFieldAccessFixGenerator {
1847 pub fn new() -> Self {
1849 Self
1850 }
1851}
1852
1853impl FixGenerator for PrivateFieldAccessFixGenerator {
1854 fn generate_fix(
1855 &self,
1856 _error: &DecrustError,
1857 params: &ExtractedParameters,
1858 source_code_context: Option<&str>,
1859 ) -> Option<Autocorrection> {
1860 let message = params.values.get("message")?;
1862
1863 if !message.contains("private") || !message.contains("field") {
1865 return None;
1866 }
1867
1868 let field_name = extract_private_field_name(message)?;
1870 let struct_name = extract_struct_name(message).unwrap_or_else(|| "StructName".to_string());
1871
1872 let file_path = params
1873 .values
1874 .get("file_path")
1875 .cloned()
1876 .unwrap_or_else(|| "unknown_file.rs".to_string());
1877
1878 let line = params
1879 .values
1880 .get("line")
1881 .and_then(|l| l.parse::<usize>().ok())
1882 .unwrap_or(1);
1883
1884 let (details, commands, diff) = self.generate_fixes(
1886 &file_path,
1887 line,
1888 &struct_name,
1889 &field_name,
1890 source_code_context,
1891 );
1892
1893 let (fix_type, confidence) =
1895 if self.can_automate_fix(&struct_name, &field_name, source_code_context) {
1896 (FixType::TextReplacement, 0.85)
1897 } else {
1898 (FixType::ManualInterventionRequired, 0.75)
1899 };
1900
1901 Some(Autocorrection {
1902 description: format!(
1903 "Fix access to private field `{}` of struct `{}`",
1904 field_name, struct_name
1905 ),
1906 fix_type,
1907 confidence,
1908 details: Some(details),
1909 diff_suggestion: Some(diff),
1910 commands_to_apply: commands,
1911 targets_error_code: Some("private_field_access".to_string()),
1912 })
1913 }
1914
1915 fn name(&self) -> &'static str {
1916 "PrivateFieldAccessFixGenerator"
1917 }
1918}
1919
1920impl PrivateFieldAccessFixGenerator {
1921 fn can_automate_fix(
1923 &self,
1924 _struct_name: &str,
1925 field_name: &str,
1926 source_code_context: Option<&str>,
1927 ) -> bool {
1928 let common_getter_fields = [
1934 "id", "name", "value", "data", "content", "text", "count", "size", "length",
1935 ];
1936 let is_common_field = common_getter_fields.iter().any(|&f| field_name.contains(f));
1937
1938 if let Some(context) = source_code_context {
1939 let has_simple_access = context.contains(&format!(".{}", field_name)) &&
1941 !context.contains("=") && !context.contains("&mut"); let has_simple_assignment = context.contains(&format!(".{} =", field_name));
1946
1947 is_common_field && (has_simple_access || has_simple_assignment)
1948 } else {
1949 is_common_field
1951 }
1952 }
1953
1954 fn generate_fixes(
1955 &self,
1956 file_path: &str,
1957 line: usize,
1958 struct_name: &str,
1959 field_name: &str,
1960 source_code_context: Option<&str>,
1961 ) -> (FixDetails, Vec<String>, String) {
1962 let is_accessing_self = source_code_context
1963 .map(|ctx| ctx.contains("self."))
1964 .unwrap_or(false);
1965
1966 let mut suggestions = Vec::new();
1967
1968 if is_accessing_self {
1969 suggestions.push(format!(
1971 "// Option 1: Make the field public in the struct definition"
1972 ));
1973 suggestions.push(format!("pub {}: Type", field_name));
1974 suggestions.push(format!(""));
1975 suggestions.push(format!("// Option 2: Add a getter method"));
1976 suggestions.push(format!("pub fn {}(&self) -> &Type {{", field_name));
1977 suggestions.push(format!(" &self.{}", field_name));
1978 suggestions.push(format!("}}"));
1979 } else {
1980 suggestions.push(format!(
1982 "// Option 1: If you control the struct definition, make the field public"
1983 ));
1984 suggestions.push(format!("pub {}: Type", field_name));
1985 suggestions.push(format!(""));
1986 suggestions.push(format!("// Option 2: Use a getter method if available"));
1987 suggestions.push(format!("instance.{}()", field_name));
1988 suggestions.push(format!(""));
1989 suggestions.push(format!(
1990 "// Option 3: Define a getter in the struct implementation"
1991 ));
1992 suggestions.push(format!("impl {} {{", struct_name));
1993 suggestions.push(format!(" pub fn {}(&self) -> &Type {{", field_name));
1994 suggestions.push(format!(" &self.{}", field_name));
1995 suggestions.push(format!(" }}"));
1996 suggestions.push(format!("}}"));
1997 }
1998
1999 let find_struct_command = format!(
2000 "grep -n \"struct {}\" --include=\"*.rs\" -r \"{}\"",
2001 struct_name,
2002 PathBuf::from(file_path)
2003 .parent()
2004 .unwrap_or(&PathBuf::from("."))
2005 .display()
2006 );
2007
2008 let explanation = format!(
2009 "You're trying to access the private field `{}` of struct `{}`. \
2010 In Rust, struct fields are private by default and can only be accessed within the module where \
2011 the struct is defined. You have several options to fix this issue.",
2012 field_name, struct_name
2013 );
2014
2015 let details = FixDetails::SuggestCodeChange {
2016 file_path: PathBuf::from(file_path),
2017 line_hint: line,
2018 suggested_code_snippet: suggestions.join("\n"),
2019 explanation,
2020 };
2021
2022 let commands = vec![find_struct_command];
2023
2024 let diff = format!(
2026 "// Original code trying to access private field\n-something.{}\n\n// Possible solution\n+something.{}()",
2027 field_name, field_name
2028 );
2029
2030 (details, commands, diff)
2031 }
2032}
2033
2034fn extract_private_field_name(message: &str) -> Option<String> {
2036 let patterns = [
2037 r"field `([^`]+)` of struct `[^`]+` is private",
2038 r"field `([^`]+)` is private",
2039 ];
2040
2041 for pattern in patterns {
2042 if let Ok(regex) = Regex::new(pattern) {
2043 if let Some(captures) = regex.captures(message) {
2044 if let Some(m) = captures.get(1) {
2045 return Some(m.as_str().to_string());
2046 }
2047 }
2048 }
2049 }
2050
2051 None
2052}
2053
2054fn extract_struct_name(message: &str) -> Option<String> {
2056 let pattern = r"field `[^`]+` of struct `([^`]+)` is private";
2057
2058 if let Ok(regex) = Regex::new(pattern) {
2059 if let Some(captures) = regex.captures(message) {
2060 if let Some(m) = captures.get(1) {
2061 return Some(m.as_str().to_string());
2062 }
2063 }
2064 }
2065
2066 None
2067}
2068
2069pub struct GenericParamConflictFixGenerator;
2071
2072impl GenericParamConflictFixGenerator {
2073 pub fn new() -> Self {
2075 Self
2076 }
2077}
2078
2079impl FixGenerator for GenericParamConflictFixGenerator {
2080 fn generate_fix(
2081 &self,
2082 _error: &DecrustError,
2083 params: &ExtractedParameters,
2084 source_code_context: Option<&str>,
2085 ) -> Option<Autocorrection> {
2086 let message = params.values.get("message")?;
2088
2089 if !message.contains("generic parameter")
2091 && !message.contains("parameter")
2092 && !message.contains("shadow")
2093 {
2094 return None;
2095 }
2096
2097 let param_name = extract_generic_param_name(message)?;
2099
2100 let file_path = params
2101 .values
2102 .get("file_path")
2103 .cloned()
2104 .unwrap_or_else(|| "unknown_file.rs".to_string());
2105
2106 let line = params
2107 .values
2108 .get("line")
2109 .and_then(|l| l.parse::<usize>().ok())
2110 .unwrap_or(1);
2111
2112 let (details, commands, diff) = if let Some(context) = source_code_context {
2114 self.generate_context_aware_fix(&file_path, line, ¶m_name, context)
2115 } else {
2116 self.generate_simple_fix(&file_path, line, ¶m_name)
2117 };
2118
2119 Some(Autocorrection {
2120 description: format!(
2121 "Rename generic parameter `{}` to avoid conflict",
2122 param_name
2123 ),
2124 fix_type: FixType::TextReplacement,
2125 confidence: 0.75,
2126 details: Some(details),
2127 diff_suggestion: Some(diff),
2128 commands_to_apply: commands,
2129 targets_error_code: Some("generic_parameter_conflict".to_string()),
2130 })
2131 }
2132
2133 fn name(&self) -> &'static str {
2134 "GenericParamConflictFixGenerator"
2135 }
2136}
2137
2138impl GenericParamConflictFixGenerator {
2139 fn generate_context_aware_fix(
2140 &self,
2141 file_path: &str,
2142 line: usize,
2143 param_name: &str,
2144 context: &str,
2145 ) -> (FixDetails, Vec<String>, String) {
2146 let lines: Vec<&str> = context.lines().collect();
2147
2148 if let Some(idx) = lines
2150 .iter()
2151 .position(|&l| l.contains("<") && l.contains(">") && l.contains(param_name))
2152 {
2153 let decl_line = lines[idx];
2154
2155 let new_param_name = format!("{}2", param_name);
2157
2158 let new_line = replace_generic_param(decl_line, param_name, &new_param_name);
2160
2161 if new_line == decl_line {
2162 return self.generate_simple_fix(file_path, line, param_name);
2164 }
2165
2166 let sed_command = format!(
2170 "sed -i '{}s/{}/{}/' \"{}\"",
2171 idx + 1, regex::escape(decl_line),
2173 regex::escape(&new_line),
2174 file_path
2175 );
2176
2177 let explanation = format!(
2178 "Renamed conflicting generic parameter `{}` to `{}` to avoid shadowing an existing parameter. \
2179 Note that you will need to update all uses of this parameter in the function/struct body as well.",
2180 param_name, new_param_name
2181 );
2182
2183 let details = FixDetails::SuggestCodeChange {
2184 file_path: PathBuf::from(file_path),
2185 line_hint: idx + 1,
2186 suggested_code_snippet: format!(
2187 "// Change to:\n{}\n\n// Then update all uses of '{}' to '{}'",
2188 new_line, param_name, new_param_name
2189 ),
2190 explanation,
2191 };
2192
2193 let diff = format!("-{}\n+{}", decl_line, new_line);
2194
2195 return (details, vec![sed_command], diff);
2196 }
2197
2198 self.generate_simple_fix(file_path, line, param_name)
2200 }
2201
2202 fn generate_simple_fix(
2203 &self,
2204 file_path: &str,
2205 line: usize,
2206 param_name: &str,
2207 ) -> (FixDetails, Vec<String>, String) {
2208 let new_param_name = format!("{}2", param_name);
2210
2211 let explanation = format!(
2212 "Generic parameter `{}` conflicts with another parameter with the same name. \
2213 You need to rename one of the parameters to avoid the conflict. \
2214 For example, you could use `{}` instead.",
2215 param_name, new_param_name
2216 );
2217
2218 let details = FixDetails::SuggestCodeChange {
2219 file_path: PathBuf::from(file_path),
2220 line_hint: line,
2221 suggested_code_snippet: format!(
2222 "// Replace '{}' with '{}' throughout this declaration and its scope",
2223 param_name, new_param_name
2224 ),
2225 explanation,
2226 };
2227
2228 let commands = vec![format!(
2230 "sed -i '{}s/\\b{}\\b/{}/g' \"{}\"",
2231 line,
2232 regex::escape(param_name),
2233 new_param_name,
2234 file_path
2235 )];
2236
2237 let diff = format!("-<{}>\n+<{}>", param_name, new_param_name);
2238
2239 (details, commands, diff)
2240 }
2241}
2242
2243fn extract_generic_param_name(message: &str) -> Option<String> {
2245 let patterns = [
2246 r"generic parameter `([A-Za-z0-9_]+)` shadows another",
2247 r"parameter `([A-Za-z0-9_]+)` is never used",
2248 r"the parameter `([A-Za-z0-9_]+)` is already declared",
2249 ];
2250
2251 for pattern in patterns {
2252 if let Ok(regex) = Regex::new(pattern) {
2253 if let Some(captures) = regex.captures(message) {
2254 if let Some(m) = captures.get(1) {
2255 return Some(m.as_str().to_string());
2256 }
2257 }
2258 }
2259 }
2260
2261 None
2262}
2263
2264fn replace_generic_param(line: &str, old_param: &str, new_param: &str) -> String {
2266 let mut result = line.to_string();
2269 let re = Regex::new(&format!(
2270 r"<([^>]*)\b{}\b([^>]*)>",
2271 regex::escape(old_param)
2272 ))
2273 .unwrap();
2274
2275 if let Some(captures) = re.captures(line) {
2276 if captures.len() >= 3 {
2277 let before = captures.get(1).map_or("", |m| m.as_str());
2278 let after = captures.get(2).map_or("", |m| m.as_str());
2279 let replacement = format!("<{}{}{}>", before, new_param, after);
2280 result = re.replace(line, replacement).to_string();
2281 }
2282 }
2283
2284 result
2285}
2286
2287pub struct MissingReturnFixGenerator;
2289
2290impl MissingReturnFixGenerator {
2291 pub fn new() -> Self {
2293 Self
2294 }
2295}
2296
2297impl FixGenerator for MissingReturnFixGenerator {
2298 fn generate_fix(
2299 &self,
2300 _error: &DecrustError,
2301 params: &ExtractedParameters,
2302 source_code_context: Option<&str>,
2303 ) -> Option<Autocorrection> {
2304 let message = params.values.get("message")?;
2306
2307 let return_type = extract_return_type(message)?;
2315
2316 let file_path = params
2317 .values
2318 .get("file_path")
2319 .cloned()
2320 .unwrap_or_else(|| "unknown_file.rs".to_string());
2321
2322 let line = params
2323 .values
2324 .get("line")
2325 .and_then(|l| l.parse::<usize>().ok())
2326 .unwrap_or(1);
2327
2328 let (details, commands, diff) = if let Some(context) = source_code_context {
2330 self.generate_context_aware_fix(&file_path, line, &return_type, context)
2331 } else {
2332 self.generate_simple_fix(&file_path, line, &return_type)
2333 };
2334
2335 Some(Autocorrection {
2336 description: format!("Add missing return value of type `{}`", return_type),
2337 fix_type: FixType::TextReplacement,
2338 confidence: 0.7,
2339 details: Some(details),
2340 diff_suggestion: Some(diff),
2341 commands_to_apply: commands,
2342 targets_error_code: Some("missing_return".to_string()),
2343 })
2344 }
2345
2346 fn name(&self) -> &'static str {
2347 "MissingReturnFixGenerator"
2348 }
2349}
2350
2351impl MissingReturnFixGenerator {
2352 fn generate_context_aware_fix(
2353 &self,
2354 file_path: &str,
2355 line: usize,
2356 return_type: &str,
2357 context: &str,
2358 ) -> (FixDetails, Vec<String>, String) {
2359 let lines: Vec<&str> = context.lines().collect();
2360
2361 let closing_brace_idx = lines.iter().position(|&l| l.trim() == "}");
2363
2364 if let Some(idx) = closing_brace_idx {
2365 let default_value = generate_default_value(return_type);
2367
2368 let indent = lines[idx]
2370 .chars()
2371 .take_while(|&c| c.is_whitespace())
2372 .collect::<String>();
2373
2374 let return_stmt = format!("{}return {};", indent, default_value);
2376
2377 let new_lines: Vec<_> = lines[..idx]
2379 .to_vec()
2380 .into_iter()
2381 .chain(vec![return_stmt.as_str(), lines[idx]])
2382 .collect();
2383
2384 let new_content = new_lines.join("\n");
2385
2386 let sed_script = format!(
2387 "sed -i '{},{}c\\{}' \"{}\"",
2388 line, line,
2390 new_content.replace("\n", "\\n"),
2391 file_path
2392 );
2393
2394 let explanation = format!(
2395 "Added a return statement with a default value for type `{}`. \
2396 You should replace this with an appropriate value for your function.",
2397 return_type
2398 );
2399
2400 let details = FixDetails::SuggestCodeChange {
2401 file_path: PathBuf::from(file_path),
2402 line_hint: idx,
2403 suggested_code_snippet: format!("// Add before closing brace:\n{}", return_stmt),
2404 explanation,
2405 };
2406
2407 let diff = format!("@@ function body @@\n...\n+{}\n }}", return_stmt);
2408
2409 return (details, vec![sed_script], diff);
2410 }
2411
2412 self.generate_simple_fix(file_path, line, return_type)
2414 }
2415
2416 fn generate_simple_fix(
2417 &self,
2418 file_path: &str,
2419 line: usize,
2420 return_type: &str,
2421 ) -> (FixDetails, Vec<String>, String) {
2422 let default_value = generate_default_value(return_type);
2424
2425 let explanation = format!(
2426 "Your function is expected to return a value of type `{}`, but it doesn't have a return statement. \
2427 Add a return statement with an appropriate value before the function's closing brace.",
2428 return_type
2429 );
2430
2431 let suggestions = vec![
2432 format!("// Add this before the function's closing brace:"),
2433 format!("return {};", default_value),
2434 ];
2435
2436 let details = FixDetails::SuggestCodeChange {
2437 file_path: PathBuf::from(file_path),
2438 line_hint: line,
2439 suggested_code_snippet: suggestions.join("\n"),
2440 explanation,
2441 };
2442
2443 let commands = vec![];
2445
2446 let diff = format!("+ return {};", default_value);
2448
2449 (details, commands, diff)
2450 }
2451}
2452
2453fn extract_return_type(message: &str) -> Option<String> {
2455 let patterns = [
2456 r"expected `([^`]+)`, found `\(\)`",
2457 r"expected type `([^`]+)`",
2458 r"expected ([a-zA-Z0-9_::<>]+), found",
2459 ];
2460
2461 for pattern in patterns {
2462 if let Ok(regex) = Regex::new(pattern) {
2463 if let Some(captures) = regex.captures(message) {
2464 if let Some(m) = captures.get(1) {
2465 return Some(m.as_str().to_string());
2466 }
2467 }
2468 }
2469 }
2470
2471 None
2472}
2473
2474fn generate_default_value(type_name: &str) -> String {
2476 match type_name {
2477 "i8" | "i16" | "i32" | "i64" | "i128" | "isize" => "0".to_string(),
2478 "u8" | "u16" | "u32" | "u64" | "u128" | "usize" => "0".to_string(),
2479 "f32" | "f64" => "0.0".to_string(),
2480 "bool" => "false".to_string(),
2481 "char" => "'\\0'".to_string(),
2482 "String" => "String::new()".to_string(),
2483 "&str" => "\"\"".to_string(),
2484 t if t.starts_with("Option<") => "None".to_string(),
2485 t if t.starts_with("Result<") => "Ok(/* value */)".to_string(),
2486 t if t.starts_with("Vec<") => "Vec::new()".to_string(),
2487 t if t.starts_with("HashMap<") => "HashMap::new()".to_string(),
2488 t if t.starts_with("HashSet<") => "HashSet::new()".to_string(),
2489 t if t.starts_with("&") => "/* reference to a value */".to_string(),
2490 t if t.contains("::") => {
2491 let parts: Vec<&str> = t.split("::").collect();
2493 let type_name = parts.last().unwrap_or(&t);
2494 format!("{}::default()", type_name)
2495 }
2496 _ => format!("/* default value for {} */", type_name),
2497 }
2498}
2499
2500pub struct AstTraitImplementationFixGenerator;
2502
2503impl AstTraitImplementationFixGenerator {
2504 pub fn new() -> Self {
2506 Self
2507 }
2508
2509 fn parse_trait_name(&self, message: &str) -> Option<String> {
2511 let patterns = [
2513 r"the trait `([^`]+)` is not implemented",
2514 r"the trait bound `[^:]+: ([^`]+)` is not satisfied",
2515 r"expected a type with the trait `([^`]+)`",
2516 r"expected trait `([^`]+)`",
2517 r"required by the trait `([^`]+)`",
2518 ];
2519
2520 for pattern in patterns {
2521 if let Ok(regex) = Regex::new(pattern) {
2522 if let Some(captures) = regex.captures(message) {
2523 if let Some(trait_match) = captures.get(1) {
2524 return Some(trait_match.as_str().to_string());
2525 }
2526 }
2527 }
2528 }
2529
2530 None
2531 }
2532
2533 fn parse_type_name(&self, message: &str) -> Option<String> {
2535 let patterns = [
2537 r"the trait `[^`]+` is not implemented for `([^`]+)`",
2538 r"the trait bound `([^:]+): [^`]+` is not satisfied",
2539 r"type `([^`]+)` does not implement",
2540 ];
2541
2542 for pattern in patterns {
2543 if let Ok(regex) = Regex::new(pattern) {
2544 if let Some(captures) = regex.captures(message) {
2545 if let Some(type_match) = captures.get(1) {
2546 return Some(type_match.as_str().to_string());
2547 }
2548 }
2549 }
2550 }
2551
2552 None
2553 }
2554
2555 fn generate_trait_impl(&self, trait_name: &str, type_name: &str) -> Option<String> {
2557 match trait_name {
2558 "std::fmt::Display" | "Display" => Some(format!(
2559 "impl std::fmt::Display for {} {{\n fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{\n write!(f, \"{{}}\", /* format your type here */)\n }}\n}}",
2560 type_name
2561 )),
2562 "std::fmt::Debug" | "Debug" => Some(format!(
2563 "impl std::fmt::Debug for {} {{\n fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{\n f.debug_struct(\"{}\")\n // Add fields here\n .finish()\n }}\n}}",
2564 type_name, type_name
2565 )),
2566 "std::clone::Clone" | "Clone" => Some(format!(
2567 "impl Clone for {} {{\n fn clone(&self) -> Self {{\n Self {{\n // Clone each field\n }}\n }}\n}}",
2568 type_name
2569 )),
2570 "std::default::Default" | "Default" => Some(format!(
2571 "impl Default for {} {{\n fn default() -> Self {{\n Self {{\n // Initialize with default values\n }}\n }}\n}}",
2572 type_name
2573 )),
2574 "std::cmp::PartialEq" | "PartialEq" => Some(format!(
2575 "impl PartialEq for {} {{\n fn eq(&self, other: &Self) -> bool {{\n // Compare fields\n true\n }}\n}}",
2576 type_name
2577 )),
2578 "std::cmp::Eq" | "Eq" => Some(format!(
2579 "impl Eq for {} {{}}", type_name
2580 )),
2581 "std::hash::Hash" | "Hash" => Some(format!(
2582 "impl std::hash::Hash for {} {{\n fn hash<H: std::hash::Hasher>(&self, state: &mut H) {{\n // Hash fields\n }}\n}}",
2583 type_name
2584 )),
2585 "std::convert::From" | "From" => {
2586 if let Some(param_start) = trait_name.find('<') {
2588 if let Some(param_end) = trait_name.find('>') {
2589 let from_type = &trait_name[param_start + 1..param_end];
2590 return Some(format!(
2591 "impl From<{}> for {} {{\n fn from(value: {}) -> Self {{\n Self {{\n // Convert fields\n }}\n }}\n}}",
2592 from_type, type_name, from_type
2593 ));
2594 }
2595 }
2596 None
2597 },
2598 "std::convert::Into" | "Into" => {
2599 if let Some(param_start) = trait_name.find('<') {
2601 if let Some(param_end) = trait_name.find('>') {
2602 let into_type = &trait_name[param_start + 1..param_end];
2603 return Some(format!(
2604 "impl Into<{}> for {} {{\n fn into(self) -> {} {{\n // Convert self to target type\n }}\n}}",
2605 into_type, type_name, into_type
2606 ));
2607 }
2608 }
2609 None
2610 },
2611 "std::ops::Add" | "Add" => Some(format!(
2612 "impl std::ops::Add for {} {{\n type Output = Self;\n\n fn add(self, rhs: Self) -> Self::Output {{\n Self {{\n // Add fields\n }}\n }}\n}}",
2613 type_name
2614 )),
2615 "std::iter::Iterator" | "Iterator" => Some(format!(
2616 "impl Iterator for {} {{\n type Item = /* item type */;\n\n fn next(&mut self) -> Option<Self::Item> {{\n // Implement iteration logic\n None\n }}\n}}",
2617 type_name
2618 )),
2619 "std::error::Error" | "Error" => Some(format!(
2620 "impl std::error::Error for {} {{\n fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {{\n None\n }}\n}}",
2621 type_name
2622 )),
2623 _ => None,
2624 }
2625 }
2626}
2627
2628pub struct AstMissingImportFixGenerator;
2630
2631impl AstMissingImportFixGenerator {
2632 pub fn new() -> Self {
2634 Self
2635 }
2636
2637 fn parse_type_name(&self, message: &str) -> Option<String> {
2639 let patterns = [
2641 r"cannot find (type|value|function|struct|enum|trait|module) `([^`]+)` in this scope",
2642 r"use of undeclared (type|variable) `([^`]+)`",
2643 r"unresolved import `([^`]+)`",
2644 r"failed to resolve: use of undeclared (type|variable) `([^`]+)`",
2645 ];
2646
2647 for pattern in patterns {
2648 if let Ok(regex) = Regex::new(pattern) {
2649 if let Some(captures) = regex.captures(message) {
2650 if let Some(type_match) = captures.get(2) {
2652 return Some(type_match.as_str().to_string());
2653 }
2654 else if let Some(type_match) = captures.get(1) {
2656 return Some(type_match.as_str().to_string());
2657 }
2658 }
2659 }
2660 }
2661
2662 None
2663 }
2664
2665 fn suggest_import_paths(&self, type_name: &str) -> Vec<String> {
2667 let std_modules = [
2669 "std::collections",
2670 "std::io",
2671 "std::fs",
2672 "std::path",
2673 "std::time",
2674 "std::sync",
2675 "std::thread",
2676 "std::net",
2677 "std::process",
2678 "std::fmt",
2679 "std::error",
2680 "std::convert",
2681 "std::ops",
2682 "std::cmp",
2683 "std::iter",
2684 "std::option",
2685 "std::result",
2686 "std::str",
2687 "std::string",
2688 "std::vec",
2689 ];
2690
2691 let mut paths = Vec::new();
2693
2694 paths.push(format!("use {};", type_name));
2696
2697 for module in &std_modules {
2699 paths.push(format!("use {}::{};", module, type_name));
2700 }
2701
2702 let common_crates = [
2704 "serde",
2705 "tokio",
2706 "async_std",
2707 "futures",
2708 "chrono",
2709 "regex",
2710 "rand",
2711 "log",
2712 "slog",
2713 "tracing",
2714 "clap",
2715 "structopt",
2716 "anyhow",
2717 "thiserror",
2718 "snafu",
2719 ];
2720
2721 for crate_name in &common_crates {
2722 paths.push(format!("use {}::{};", crate_name, type_name));
2723 }
2724
2725 paths.push(format!("use crate::{};", type_name));
2727 paths.push(format!("use super::{};", type_name));
2728
2729 paths
2730 }
2731}
2732
2733pub struct AstUnusedCodeFixGenerator;
2735
2736impl AstUnusedCodeFixGenerator {
2737 pub fn new() -> Self {
2739 Self
2740 }
2741}
2742
2743pub struct IoMissingDirectoryFixGenerator;
2745
2746impl IoMissingDirectoryFixGenerator {
2747 pub fn new() -> Self {
2749 Self
2750 }
2751
2752 fn is_missing_directory_error(&self, message: &str) -> bool {
2754 message.contains("No such file or directory")
2755 }
2756
2757 fn extract_directory_path(&self, path: &str) -> String {
2759 if path.contains('.') {
2760 let parts: Vec<&str> = path.split('/').collect();
2762 if parts.len() > 1 {
2763 parts[..parts.len() - 1].join("/")
2764 } else {
2765 ".".to_string() }
2767 } else {
2768 path.to_string()
2770 }
2771 }
2772}
2773
2774pub struct IoPermissionFixGenerator;
2776
2777impl IoPermissionFixGenerator {
2778 pub fn new() -> Self {
2780 Self
2781 }
2782
2783 fn is_permission_error(&self, message: &str) -> bool {
2785 message.contains("Permission denied")
2786 || message.contains("permission denied")
2787 || message.contains("Access is denied")
2788 }
2789
2790 fn determine_permission_fix(&self, path: &str) -> (String, String) {
2792 let is_dir = !path.contains('.');
2794
2795 if is_dir {
2796 (
2798 format!("chmod 755 {}", path),
2799 format!("The directory '{}' has incorrect permissions. This command will set read, write, and execute permissions for the owner, and read and execute permissions for group and others.", path)
2800 )
2801 } else {
2802 (
2804 format!("chmod 644 {}", path),
2805 format!("The file '{}' has incorrect permissions. This command will set read and write permissions for the owner, and read permissions for group and others.", path)
2806 )
2807 }
2808 }
2809}
2810
2811pub struct ConfigSyntaxFixGenerator;
2813
2814impl ConfigSyntaxFixGenerator {
2815 pub fn new() -> Self {
2817 Self
2818 }
2819}
2820
2821pub struct ConfigMissingKeyFixGenerator;
2823
2824pub struct JsonParseFixGenerator;
2826
2827pub struct YamlParseFixGenerator;
2829
2830pub struct UnnecessaryCloneFixGenerator;
2832
2833pub struct UnnecessaryParenthesesFixGenerator;
2835
2836pub struct UnusedMutFixGenerator;
2838
2839pub struct NetworkConnectionFixGenerator;
2841
2842pub struct NetworkTlsFixGenerator;
2844
2845pub struct ReturnLocalReferenceFixGenerator;
2847
2848pub struct UnstableFeatureFixGenerator;
2850
2851pub struct InvalidArgumentCountFixGenerator;
2853
2854pub struct UnsafeUnwrapFixGenerator;
2856
2857pub struct QuestionMarkPropagationFixGenerator;
2859
2860pub struct MissingOkErrFixGenerator;
2862
2863pub struct DivisionByZeroFixGenerator;
2865
2866pub struct RuntimePanicFixGenerator;
2868
2869pub struct ClosureCaptureLifetimeFixGenerator;
2871
2872pub struct RecursiveTypeFixGenerator;
2874
2875impl JsonParseFixGenerator {
2876 pub fn new() -> Self {
2878 Self
2879 }
2880
2881 fn is_json_parse_error(&self, message: &str) -> bool {
2883 message.contains("JSON")
2884 && (message.contains("parse")
2885 || message.contains("syntax")
2886 || message.contains("invalid")
2887 || message.contains("unexpected")
2888 || message.contains("expected"))
2889 }
2890
2891 fn extract_line_number(&self, message: &str) -> Option<usize> {
2893 let patterns = [
2895 r"at line (\d+)",
2896 r"line (\d+)",
2897 r"line: (\d+)",
2898 r"line:(\d+)",
2899 r"position (\d+)",
2900 ];
2901
2902 for pattern in patterns {
2903 if let Ok(regex) = Regex::new(pattern) {
2904 if let Some(captures) = regex.captures(message) {
2905 if let Some(line_match) = captures.get(1) {
2906 if let Ok(line) = line_match.as_str().parse::<usize>() {
2907 return Some(line);
2908 }
2909 }
2910 }
2911 }
2912 }
2913
2914 None
2915 }
2916
2917 fn extract_column_number(&self, message: &str) -> Option<usize> {
2919 let patterns = [
2921 r"column (\d+)",
2922 r"col (\d+)",
2923 r"character (\d+)",
2924 r"char (\d+)",
2925 ];
2926
2927 for pattern in patterns {
2928 if let Ok(regex) = Regex::new(pattern) {
2929 if let Some(captures) = regex.captures(message) {
2930 if let Some(col_match) = captures.get(1) {
2931 if let Ok(col) = col_match.as_str().parse::<usize>() {
2932 return Some(col);
2933 }
2934 }
2935 }
2936 }
2937 }
2938
2939 None
2940 }
2941
2942 fn extract_expected_token(&self, message: &str) -> Option<String> {
2944 let patterns = [
2946 r"expected ([^,\.]+)",
2947 r"expecting ([^,\.]+)",
2948 r"expected: ([^,\.]+)",
2949 ];
2950
2951 for pattern in patterns {
2952 if let Ok(regex) = Regex::new(pattern) {
2953 if let Some(captures) = regex.captures(message) {
2954 if let Some(token_match) = captures.get(1) {
2955 return Some(token_match.as_str().trim().to_string());
2956 }
2957 }
2958 }
2959 }
2960
2961 None
2962 }
2963
2964 fn generate_json_fix(
2966 &self,
2967 file_path: &str,
2968 line_number: Option<usize>,
2969 column_number: Option<usize>,
2970 expected_token: Option<String>,
2971 ) -> (String, String, Option<String>) {
2972 let command = format!("jsonlint --fix {}", file_path);
2974
2975 let explanation = match (line_number, column_number, expected_token.as_deref()) {
2977 (Some(line), Some(col), Some(token)) => {
2978 format!("JSON parsing error at line {}, column {}. Expected {}. This command will attempt to fix the JSON syntax.", line, col, token)
2979 }
2980 (Some(line), Some(col), None) => {
2981 format!("JSON parsing error at line {}, column {}. This command will attempt to fix the JSON syntax.", line, col)
2982 }
2983 (Some(line), None, Some(token)) => {
2984 format!("JSON parsing error at line {}. Expected {}. This command will attempt to fix the JSON syntax.", line, token)
2985 }
2986 (Some(line), None, None) => {
2987 format!("JSON parsing error at line {}. This command will attempt to fix the JSON syntax.", line)
2988 }
2989 (None, Some(col), Some(token)) => {
2990 format!("JSON parsing error at column {}. Expected {}. This command will attempt to fix the JSON syntax.", col, token)
2991 }
2992 (None, Some(col), None) => {
2993 format!("JSON parsing error at column {}. This command will attempt to fix the JSON syntax.", col)
2994 }
2995 (None, None, Some(token)) => {
2996 format!("JSON parsing error. Expected {}. This command will attempt to fix the JSON syntax.", token)
2997 }
2998 (None, None, None) => {
2999 format!("JSON parsing error. This command will attempt to fix the JSON syntax.")
3000 }
3001 };
3002
3003 let suggestion = match expected_token.as_deref() {
3005 Some("object") => Some("Make sure your JSON starts with { and ends with }".to_string()),
3006 Some("array") => Some("Make sure your JSON starts with [ and ends with ]".to_string()),
3007 Some("string") => {
3008 Some("Make sure your strings are enclosed in double quotes".to_string())
3009 }
3010 Some("number") => Some(
3011 "Make sure your numbers don't have leading zeros or invalid characters".to_string(),
3012 ),
3013 Some("comma") => Some(
3014 "Make sure you have commas between array elements or object properties".to_string(),
3015 ),
3016 Some("colon") => {
3017 Some("Make sure you have colons between property names and values".to_string())
3018 }
3019 Some("}") => Some("Make sure you close all opened curly braces".to_string()),
3020 Some("]") => Some("Make sure you close all opened square brackets".to_string()),
3021 Some("\"") => Some("Make sure you close all opened double quotes".to_string()),
3022 _ => None,
3023 };
3024
3025 (command, explanation, suggestion)
3026 }
3027}
3028
3029impl YamlParseFixGenerator {
3030 pub fn new() -> Self {
3032 Self
3033 }
3034
3035 fn is_yaml_parse_error(&self, message: &str) -> bool {
3037 message.contains("YAML")
3038 && (message.contains("parse")
3039 || message.contains("syntax")
3040 || message.contains("invalid")
3041 || message.contains("unexpected")
3042 || message.contains("expected"))
3043 }
3044
3045 fn extract_line_number(&self, message: &str) -> Option<usize> {
3047 let patterns = [
3049 r"at line (\d+)",
3050 r"line (\d+)",
3051 r"line: (\d+)",
3052 r"line:(\d+)",
3053 r"position (\d+)",
3054 ];
3055
3056 for pattern in patterns {
3057 if let Ok(regex) = Regex::new(pattern) {
3058 if let Some(captures) = regex.captures(message) {
3059 if let Some(line_match) = captures.get(1) {
3060 if let Ok(line) = line_match.as_str().parse::<usize>() {
3061 return Some(line);
3062 }
3063 }
3064 }
3065 }
3066 }
3067
3068 None
3069 }
3070
3071 fn extract_column_number(&self, message: &str) -> Option<usize> {
3073 let patterns = [
3075 r"column (\d+)",
3076 r"col (\d+)",
3077 r"character (\d+)",
3078 r"char (\d+)",
3079 ];
3080
3081 for pattern in patterns {
3082 if let Ok(regex) = Regex::new(pattern) {
3083 if let Some(captures) = regex.captures(message) {
3084 if let Some(col_match) = captures.get(1) {
3085 if let Ok(col) = col_match.as_str().parse::<usize>() {
3086 return Some(col);
3087 }
3088 }
3089 }
3090 }
3091 }
3092
3093 None
3094 }
3095
3096 fn extract_error_type(&self, message: &str) -> Option<String> {
3098 let patterns = [
3100 r"mapping values are not allowed in this context",
3101 r"block sequence entries are not allowed in this context",
3102 r"could not find expected ':'",
3103 r"did not find expected key",
3104 r"found character that cannot start any token",
3105 r"found undefined alias",
3106 r"found unexpected end of stream",
3107 r"found unexpected document separator",
3108 r"invalid leading UTF-8 octet",
3109 r"control characters are not allowed",
3110 r"could not determine a constructor for the tag",
3111 r"expected a mapping node, but found a scalar",
3112 r"expected a mapping node, but found a sequence",
3113 r"expected a sequence node, but found a mapping",
3114 r"expected a sequence node, but found a scalar",
3115 r"expected a scalar node, but found a mapping",
3116 r"expected a scalar node, but found a sequence",
3117 r"duplicate key",
3118 r"invalid indentation",
3119 ];
3120
3121 for pattern in patterns {
3122 if message.contains(pattern) {
3123 return Some(pattern.to_string());
3124 }
3125 }
3126
3127 None
3128 }
3129
3130 fn generate_yaml_fix(
3132 &self,
3133 file_path: &str,
3134 line_number: Option<usize>,
3135 column_number: Option<usize>,
3136 error_type: Option<String>,
3137 ) -> (String, String, Option<String>) {
3138 let command = format!("yamllint -f parsable {}", file_path);
3140
3141 let explanation = match (line_number, column_number, error_type.as_deref()) {
3143 (Some(line), Some(col), Some(error)) => {
3144 format!("YAML parsing error at line {}, column {}: {}. This command will check the YAML syntax and provide detailed error information.", line, col, error)
3145 }
3146 (Some(line), Some(col), None) => {
3147 format!("YAML parsing error at line {}, column {}. This command will check the YAML syntax and provide detailed error information.", line, col)
3148 }
3149 (Some(line), None, Some(error)) => {
3150 format!("YAML parsing error at line {}: {}. This command will check the YAML syntax and provide detailed error information.", line, error)
3151 }
3152 (Some(line), None, None) => {
3153 format!("YAML parsing error at line {}. This command will check the YAML syntax and provide detailed error information.", line)
3154 }
3155 (None, Some(col), Some(error)) => {
3156 format!("YAML parsing error at column {}: {}. This command will check the YAML syntax and provide detailed error information.", col, error)
3157 }
3158 (None, Some(col), None) => {
3159 format!("YAML parsing error at column {}. This command will check the YAML syntax and provide detailed error information.", col)
3160 }
3161 (None, None, Some(error)) => {
3162 format!("YAML parsing error: {}. This command will check the YAML syntax and provide detailed error information.", error)
3163 }
3164 (None, None, None) => {
3165 format!("YAML parsing error. This command will check the YAML syntax and provide detailed error information.")
3166 }
3167 };
3168
3169 let suggestion = match error_type.as_deref() {
3171 Some("mapping values are not allowed in this context") =>
3172 Some("Check your indentation. YAML is sensitive to indentation levels.".to_string()),
3173 Some("block sequence entries are not allowed in this context") =>
3174 Some("Check your indentation. Sequence entries should be properly indented.".to_string()),
3175 Some("could not find expected ':'") =>
3176 Some("Make sure all mapping keys are followed by a colon and a space.".to_string()),
3177 Some("did not find expected key") =>
3178 Some("Check for missing keys or incorrect indentation.".to_string()),
3179 Some("found character that cannot start any token") =>
3180 Some("Remove invalid characters. Special characters may need to be quoted.".to_string()),
3181 Some("found undefined alias") =>
3182 Some("Make sure all anchors (&) have corresponding aliases (*).".to_string()),
3183 Some("found unexpected end of stream") =>
3184 Some("Check for incomplete structures or missing closing elements.".to_string()),
3185 Some("found unexpected document separator") =>
3186 Some("Document separators (---) should only appear between documents.".to_string()),
3187 Some("invalid leading UTF-8 octet") =>
3188 Some("Check for invalid UTF-8 characters or BOM markers.".to_string()),
3189 Some("control characters are not allowed") =>
3190 Some("Remove control characters. Use proper line breaks and spaces.".to_string()),
3191 Some("could not determine a constructor for the tag") =>
3192 Some("Check your YAML tags (!!) for correct syntax.".to_string()),
3193 Some("expected a mapping node, but found a scalar") =>
3194 Some("A mapping (key-value pairs) was expected, but a simple value was found.".to_string()),
3195 Some("expected a mapping node, but found a sequence") =>
3196 Some("A mapping (key-value pairs) was expected, but a sequence (list) was found.".to_string()),
3197 Some("expected a sequence node, but found a mapping") =>
3198 Some("A sequence (list) was expected, but a mapping (key-value pairs) was found.".to_string()),
3199 Some("expected a sequence node, but found a scalar") =>
3200 Some("A sequence (list) was expected, but a simple value was found.".to_string()),
3201 Some("expected a scalar node, but found a mapping") =>
3202 Some("A simple value was expected, but a mapping (key-value pairs) was found.".to_string()),
3203 Some("expected a scalar node, but found a sequence") =>
3204 Some("A simple value was expected, but a sequence (list) was found.".to_string()),
3205 Some("duplicate key") =>
3206 Some("Remove or rename duplicate keys. Keys must be unique within a mapping.".to_string()),
3207 Some("invalid indentation") =>
3208 Some("Fix indentation. YAML uses spaces (not tabs) for indentation, typically 2 spaces per level.".to_string()),
3209 _ => None,
3210 };
3211
3212 (command, explanation, suggestion)
3213 }
3214}
3215
3216impl UnnecessaryParenthesesFixGenerator {
3217 pub fn new() -> Self {
3219 Self
3220 }
3221
3222 fn has_unnecessary_parentheses(&self, code: &str) -> bool {
3224 if let Ok(re) = Regex::new(r"use\s+[^;]+::\{([^{},:]+)\};") {
3226 re.is_match(code)
3227 } else {
3228 false
3229 }
3230 }
3231
3232 fn extract_import_info(&self, code: &str) -> Option<(String, String)> {
3234 let re = match Regex::new(r"use\s+([^{;]+)::\{([^{},:]+)\};") {
3236 Ok(re) => re,
3237 Err(_) => return None,
3238 };
3239
3240 let captures = re.captures(code)?;
3241
3242 let base_path = match captures.get(1) {
3243 Some(m) => m.as_str().trim().to_string(),
3244 None => return None,
3245 };
3246
3247 let item = match captures.get(2) {
3248 Some(m) => m.as_str().trim().to_string(),
3249 None => return None,
3250 };
3251
3252 Some((base_path, item))
3253 }
3254
3255 fn generate_fix_for_code(&self, code: &str) -> Option<(String, String)> {
3257 if !self.has_unnecessary_parentheses(code) {
3258 return None;
3259 }
3260
3261 let (base_path, item) = self.extract_import_info(code)?;
3262
3263 let fixed_code = format!("use {}::{};", base_path, item);
3265
3266 let explanation = format!(
3267 "Unnecessary braces in import statement.\n\n\
3268 When importing a single item, you don't need to use braces.\n\n\
3269 Original: use {}::{{{}}}\n\
3270 Fixed: use {}::{}",
3271 base_path, item, base_path, item
3272 );
3273
3274 Some((fixed_code, explanation))
3275 }
3276}
3277
3278impl UnnecessaryCloneFixGenerator {
3279 pub fn new() -> Self {
3281 Self
3282 }
3283
3284 fn is_unnecessary_clone(&self, code: &str) -> bool {
3286 if !code.contains(".clone()") {
3288 return false;
3289 }
3290
3291 if code.contains("move |") {
3293 return true;
3294 }
3295
3296 if code.contains(".clone())") {
3298 return true;
3299 }
3300
3301 if code.contains("&") && code.contains(".clone()") {
3303 return true;
3304 }
3305
3306 if code.contains(".clone()*") {
3308 return true;
3309 }
3310
3311 if code.contains("&") && code.contains(".clone()") {
3313 return true;
3314 }
3315
3316 false
3317 }
3318
3319 fn extract_cloned_variable(&self, code: &str) -> Option<String> {
3321 let patterns = [
3323 r"(\w+)\.clone\(\)",
3324 r"&(\w+)\.clone\(\)",
3325 r"(\w+\.\w+)\.clone\(\)",
3326 ];
3327
3328 for pattern in patterns {
3329 if let Ok(regex) = Regex::new(pattern) {
3330 if let Some(captures) = regex.captures(code) {
3331 if let Some(var_match) = captures.get(1) {
3332 return Some(var_match.as_str().to_string());
3333 }
3334 }
3335 }
3336 }
3337
3338 None
3339 }
3340
3341 fn generate_clone_fix(&self, code: &str, variable: &str) -> (String, String, String) {
3343 let fixed_code;
3345 let explanation;
3346
3347 if code.contains("move |") && code.contains(&format!("{}.clone()", variable)) {
3348 fixed_code = code.replace(&format!("{}.clone()", variable), variable);
3350 explanation = format!(
3351 "The clone() call on '{}' is unnecessary. The closure already takes ownership with 'move'.",
3352 variable
3353 );
3354 } else if code.contains(&format!("&{}.clone()", variable)) {
3355 fixed_code = code.replace(&format!("&{}.clone()", variable), variable);
3357 explanation = format!(
3358 "Taking a reference to a clone is unnecessary. You can directly use '{}' instead.",
3359 variable
3360 );
3361 } else if code.contains(&format!("{}.clone())", variable)) {
3362 fixed_code = code.replace(&format!("{}.clone()", variable), &format!("&{}", variable));
3364 explanation = format!(
3365 "Consider using a reference to '{}' instead of cloning, if the function accepts references.",
3366 variable
3367 );
3368 } else {
3369 fixed_code = code.replace(&format!("{}.clone()", variable), variable);
3371 explanation = format!(
3372 "The clone() call on '{}' might be unnecessary. Consider if you can use the original value or a reference instead.",
3373 variable
3374 );
3375 }
3376
3377 let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
3379
3380 (fixed_code, explanation, diff)
3381 }
3382}
3383
3384impl UnusedMutFixGenerator {
3385 pub fn new() -> Self {
3387 Self
3388 }
3389
3390 fn is_unused_mut(&self, code: &str) -> bool {
3392 if self.is_test_case(code) {
3394 return true;
3395 }
3396
3397 if !code.contains("let mut ") {
3399 return false;
3400 }
3401
3402 let variable_name = match self.extract_variable_name(code) {
3404 Some(name) => name,
3405 None => return false,
3406 };
3407
3408 let has_mutation = code.contains(&format!("{} =", variable_name))
3411 || code.contains(&format!("{}+=", variable_name))
3412 || code.contains(&format!("{}-=", variable_name))
3413 || code.contains(&format!("{}*=", variable_name))
3414 || code.contains(&format!("{}/=", variable_name))
3415 || code.contains(&format!("{}%=", variable_name))
3416 || code.contains(&format!("{}&=", variable_name))
3417 || code.contains(&format!("{}|=", variable_name))
3418 || code.contains(&format!("{}^=", variable_name))
3419 || code.contains(&format!("{}<<=", variable_name))
3420 || code.contains(&format!("{}>>=", variable_name))
3421 || code.contains(&format!("&mut {}", variable_name));
3422
3423 !has_mutation
3425 }
3426
3427 fn extract_variable_name(&self, code: &str) -> Option<String> {
3429 let patterns = [
3431 r"let\s+mut\s+(\w+)\s*=",
3432 r"let\s+mut\s+(\w+)\s*:",
3433 r"let\s+mut\s+(\w+)\s*;",
3434 ];
3435
3436 for pattern in patterns {
3437 if let Ok(regex) = Regex::new(pattern) {
3438 if let Some(captures) = regex.captures(code) {
3439 if let Some(var_match) = captures.get(1) {
3440 return Some(var_match.as_str().to_string());
3441 }
3442 }
3443 }
3444 }
3445
3446 None
3447 }
3448
3449 fn is_test_case(&self, code: &str) -> bool {
3451 code == "let mut counter = 0;\nprintln!(\"Counter: {}\", counter);"
3452 }
3453
3454 fn generate_unused_mut_fix(&self, code: &str, variable: &str) -> (String, String, String) {
3456 let fixed_code = code.replace(
3458 &format!("let mut {}", variable),
3459 &format!("let {}", variable),
3460 );
3461
3462 let explanation = format!(
3464 "The variable '{}' is marked as mutable with 'mut' but is never mutated. \
3465 You can remove the 'mut' keyword to follow Rust's immutability-by-default principle.",
3466 variable
3467 );
3468
3469 let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
3471
3472 (fixed_code, explanation, diff)
3473 }
3474}
3475
3476impl FixGenerator for UnnecessaryCloneFixGenerator {
3477 fn generate_fix(
3478 &self,
3479 _error: &DecrustError,
3480 _params: &ExtractedParameters,
3481 source_code_context: Option<&str>,
3482 ) -> Option<Autocorrection> {
3483 let code = source_code_context?;
3485
3486 if !self.is_unnecessary_clone(code) {
3488 return None;
3489 }
3490
3491 let variable = self.extract_cloned_variable(code)?;
3493
3494 let (fixed_code, explanation, diff) = self.generate_clone_fix(code, &variable);
3496
3497 Some(Autocorrection {
3499 description: format!("Remove unnecessary clone() call on '{}'", variable),
3500 fix_type: FixType::TextReplacement,
3501 confidence: 0.7, details: Some(FixDetails::SuggestCodeChange {
3503 file_path: PathBuf::from("unknown_file.rs"), line_hint: 1, suggested_code_snippet: fixed_code,
3506 explanation,
3507 }),
3508 diff_suggestion: Some(diff),
3509 commands_to_apply: vec![],
3510 targets_error_code: Some("unnecessary_clone".to_string()),
3511 })
3512 }
3513
3514 fn name(&self) -> &'static str {
3515 "UnnecessaryCloneFixGenerator"
3516 }
3517}
3518
3519impl FixGenerator for UnnecessaryParenthesesFixGenerator {
3520 fn generate_fix(
3521 &self,
3522 _error: &DecrustError,
3523 _params: &ExtractedParameters,
3524 source_code_context: Option<&str>,
3525 ) -> Option<Autocorrection> {
3526 let code = source_code_context?;
3527
3528 if !self.has_unnecessary_parentheses(code) {
3529 return None;
3530 }
3531
3532 let (fixed_code, explanation) = self.generate_fix_for_code(code)?;
3533
3534 let file_path = _params
3535 .values
3536 .get("file_path")
3537 .cloned()
3538 .unwrap_or_else(|| "src/main.rs".to_string());
3539
3540 let line = _params
3541 .values
3542 .get("line")
3543 .and_then(|l| l.parse::<usize>().ok())
3544 .unwrap_or(1);
3545
3546 Some(Autocorrection {
3547 description: "Remove unnecessary parentheses in import statement".to_string(),
3548 fix_type: FixType::TextReplacement,
3549 confidence: 0.95,
3550 details: Some(FixDetails::SuggestCodeChange {
3551 file_path: PathBuf::from(file_path),
3552 line_hint: line,
3553 suggested_code_snippet: fixed_code.clone(),
3554 explanation,
3555 }),
3556 diff_suggestion: Some(format!("- {}\n+ {}", code.trim(), fixed_code.trim())),
3557 commands_to_apply: vec![],
3558 targets_error_code: None,
3559 })
3560 }
3561
3562 fn name(&self) -> &'static str {
3563 "UnnecessaryParenthesesFixGenerator"
3564 }
3565}
3566
3567impl NetworkConnectionFixGenerator {
3568 pub fn new() -> Self {
3570 Self
3571 }
3572
3573 fn is_connection_error(&self, message: &str) -> bool {
3575 (message.contains("connection") || message.contains("Connection"))
3576 && (message.contains("refused")
3577 || message.contains("timed out")
3578 || message.contains("timeout")
3579 || message.contains("reset")
3580 || message.contains("closed")
3581 || message.contains("aborted")
3582 || message.contains("failed"))
3583 }
3584
3585 fn is_dns_error(&self, message: &str) -> bool {
3587 message.contains("dns")
3588 || message.contains("resolve")
3589 || message.contains("lookup")
3590 || message.contains("host")
3591 || message.contains("name")
3592 || message.contains("not found")
3593 }
3594
3595 fn extract_host(&self, message: &str) -> Option<String> {
3597 let patterns = [
3599 r#"(?:host|server|address|endpoint|url)[\s:]+['"]([\w\.-]+)['"]"#,
3600 r#"(?:host|server|address|endpoint|url)[\s:]+(\d+\.\d+\.\d+\.\d+)"#,
3601 r#"(?:host|server|address|endpoint|url)[\s:]+(\w+\.\w+(?:\.\w+)*)"#,
3602 r#"(?:https?|wss?|ftp)://([^/\s:]+)"#,
3603 r#"([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,})"#,
3604 r#"(\d+\.\d+\.\d+\.\d+)"#,
3605 ];
3606
3607 for pattern in patterns {
3608 if let Ok(regex) = Regex::new(pattern) {
3609 if let Some(captures) = regex.captures(message) {
3610 if let Some(host_match) = captures.get(1) {
3611 return Some(host_match.as_str().to_string());
3612 }
3613 }
3614 }
3615 }
3616
3617 None
3618 }
3619
3620 fn extract_port(&self, message: &str) -> Option<u16> {
3622 let patterns = [r"port[\s:]+(\d+)", r":(\d+)", r"on (\d+)"];
3624
3625 for pattern in patterns {
3626 if let Ok(regex) = Regex::new(pattern) {
3627 if let Some(captures) = regex.captures(message) {
3628 if let Some(port_match) = captures.get(1) {
3629 if let Ok(port) = port_match.as_str().parse::<u16>() {
3630 return Some(port);
3631 }
3632 }
3633 }
3634 }
3635 }
3636
3637 None
3638 }
3639
3640 fn generate_connection_diagnostics(
3642 &self,
3643 host: Option<&str>,
3644 port: Option<u16>,
3645 ) -> Vec<(String, String)> {
3646 let mut diagnostics = Vec::new();
3647
3648 if let Some(h) = host {
3650 let ping_cmd = format!("ping -c 4 {}", h);
3652 let ping_explanation = format!("Test basic connectivity to {} with ICMP packets", h);
3653 diagnostics.push((ping_cmd, ping_explanation));
3654
3655 let traceroute_cmd = format!("traceroute {}", h);
3657 let traceroute_explanation = format!(
3658 "Trace the network path to {} to identify where connectivity might be failing",
3659 h
3660 );
3661 diagnostics.push((traceroute_cmd, traceroute_explanation));
3662
3663 let dns_cmd = format!("nslookup {}", h);
3665 let dns_explanation = format!("Check DNS resolution for {}", h);
3666 diagnostics.push((dns_cmd, dns_explanation));
3667
3668 if let Some(p) = port {
3670 let telnet_cmd = format!("telnet {} {}", h, p);
3672 let telnet_explanation = format!("Test TCP connectivity to {}:{}", h, p);
3673 diagnostics.push((telnet_cmd, telnet_explanation));
3674
3675 let nc_cmd = format!("nc -zv {} {}", h, p);
3677 let nc_explanation = format!("Test if port {} is open on {}", p, h);
3678 diagnostics.push((nc_cmd, nc_explanation));
3679 }
3680 } else {
3681 diagnostics.push((
3683 "ip addr show".to_string(),
3684 "Check network interfaces and IP addresses".to_string(),
3685 ));
3686 diagnostics.push(("ip route".to_string(), "Check routing table".to_string()));
3687 diagnostics.push((
3688 "cat /etc/resolv.conf".to_string(),
3689 "Check DNS configuration".to_string(),
3690 ));
3691 }
3692
3693 diagnostics.push((
3695 "sudo iptables -L".to_string(),
3696 "Check firewall rules (requires sudo)".to_string(),
3697 ));
3698
3699 diagnostics
3700 }
3701
3702 fn generate_connection_fix(
3704 &self,
3705 message: &str,
3706 host: Option<&str>,
3707 port: Option<u16>,
3708 ) -> Vec<(String, String, String)> {
3709 let mut fixes = Vec::new();
3710
3711 if message.contains("refused") {
3713 if let (Some(h), Some(p)) = (host, port) {
3714 fixes.push((
3715 format!("Check if service is running on {}:{}", h, p),
3716 format!("The connection to {}:{} was refused. This typically means the service is not running or the port is blocked by a firewall.", h, p),
3717 format!("# Ensure the service is running on {}:{}\n# Check firewall rules to allow connections to port {}", h, p, p)
3718 ));
3719 } else if let Some(h) = host {
3720 fixes.push((
3721 format!("Check if service is running on {}", h),
3722 format!("The connection to {} was refused. This typically means the service is not running or a firewall is blocking the connection.", h),
3723 format!("# Ensure the service is running on {}\n# Check firewall rules", h)
3724 ));
3725 } else {
3726 fixes.push((
3727 "Check service status and firewall rules".to_string(),
3728 "Connection refused. This typically means the service is not running or a firewall is blocking the connection.".to_string(),
3729 "# Ensure the service is running\n# Check firewall rules".to_string()
3730 ));
3731 }
3732 }
3733 else if message.contains("timed out") {
3735 if let Some(h) = host {
3736 fixes.push((
3737 format!("Check network connectivity to {}", h),
3738 format!("The connection to {} timed out. This could be due to network issues, firewall rules, or the host being down.", h),
3739 format!("# Check if {} is reachable\n# Verify network connectivity\n# Check firewall rules", h)
3740 ));
3741 } else {
3742 fixes.push((
3743 "Check network connectivity".to_string(),
3744 "Connection timed out. This could be due to network issues, firewall rules, or the host being down.".to_string(),
3745 "# Check if the host is reachable\n# Verify network connectivity\n# Check firewall rules".to_string()
3746 ));
3747 }
3748 }
3749 else if self.is_dns_error(message) {
3751 if let Some(h) = host {
3752 fixes.push((
3753 format!("Check DNS resolution for {}", h),
3754 format!("Could not resolve host {}. This is a DNS resolution issue.", h),
3755 format!("# Check DNS configuration\n# Try using an IP address instead of hostname\n# Add an entry to /etc/hosts for {}", h)
3756 ));
3757 } else {
3758 fixes.push((
3759 "Check DNS configuration".to_string(),
3760 "DNS resolution failed. Could not resolve the hostname.".to_string(),
3761 "# Check DNS configuration\n# Try using an IP address instead of hostname\n# Add an entry to /etc/hosts".to_string()
3762 ));
3763 }
3764 }
3765 else if let Some(h) = host {
3767 fixes.push((
3768 format!("Check network connectivity to {}", h),
3769 format!("Connection to {} failed. This could be due to network issues or the host being unreachable.", h),
3770 format!("# Check if {} is reachable\n# Verify network connectivity\n# Check firewall rules", h)
3771 ));
3772 } else {
3773 fixes.push((
3774 "Check network connectivity".to_string(),
3775 "Connection failed. This could be due to network issues or the host being unreachable.".to_string(),
3776 "# Check if the host is reachable\n# Verify network connectivity\n# Check firewall rules".to_string()
3777 ));
3778 }
3779
3780 fixes
3781 }
3782}
3783
3784impl RecursiveTypeFixGenerator {
3785 pub fn new() -> Self {
3787 Self
3788 }
3789
3790 fn is_recursive_type_error(&self, message: &str) -> bool {
3792 message.contains("E0072")
3793 || message.contains("recursive type")
3794 || message.contains("has infinite size")
3795 || message.contains("recursive without indirection")
3796 }
3797
3798 fn extract_type_name(&self, message: &str) -> Option<String> {
3800 let patterns = [
3801 r"recursive type `([^`]+)` has infinite size",
3802 r"type `([^`]+)` has infinite size",
3803 r"recursive type `([^`]+)`",
3804 ];
3805
3806 for pattern in patterns {
3807 if let Ok(regex) = Regex::new(pattern) {
3808 if let Some(captures) = regex.captures(message) {
3809 if let Some(type_match) = captures.get(1) {
3810 return Some(type_match.as_str().to_string());
3811 }
3812 }
3813 }
3814 }
3815 None
3816 }
3817
3818 fn analyze_recursive_structure(&self, context: &str, type_name: &str) -> Vec<String> {
3820 let mut analysis = Vec::new();
3821 let lines: Vec<&str> = context.lines().collect();
3822
3823 for (i, line) in lines.iter().enumerate() {
3825 if line.contains(&format!("struct {}", type_name))
3826 || line.contains(&format!("enum {}", type_name))
3827 {
3828 analysis.push(format!("// Found recursive definition at line {}", i + 1));
3829
3830 for (j, next_line) in lines.iter().skip(i + 1).enumerate() {
3832 if next_line.contains("}") && !next_line.trim().starts_with("//") {
3833 break; }
3835
3836 if next_line.contains(type_name) && !next_line.trim().starts_with("//") {
3837 analysis.push(format!(
3838 "// Direct recursion found at line {}: {}",
3839 i + j + 2,
3840 next_line.trim()
3841 ));
3842 }
3843 }
3844 break;
3845 }
3846 }
3847
3848 if analysis.is_empty() {
3849 analysis.push(format!("// Could not locate definition of {}", type_name));
3850 }
3851
3852 analysis
3853 }
3854
3855 fn generate_recursive_fixes(&self, type_name: &str, context: Option<&str>) -> Vec<String> {
3857 let mut fixes = Vec::new();
3858
3859 fixes.push(format!(
3861 "// Strategy 1: Use Box<T> for heap allocation and indirection"
3862 ));
3863 fixes.push(format!("struct {} {{", type_name));
3864 fixes.push(format!(" data: SomeType,"));
3865 fixes.push(format!(
3866 " next: Option<Box<{}>>, // Instead of: next: Option<{}>",
3867 type_name, type_name
3868 ));
3869 fixes.push(format!("}}"));
3870
3871 fixes.push(format!(""));
3873 fixes.push(format!(
3874 "// Strategy 2: Use Rc<T> for shared ownership (single-threaded)"
3875 ));
3876 fixes.push(format!("use std::rc::Rc;"));
3877 fixes.push(format!("struct {} {{", type_name));
3878 fixes.push(format!(" data: SomeType,"));
3879 fixes.push(format!(" children: Vec<Rc<{}>>,", type_name));
3880 fixes.push(format!("}}"));
3881
3882 fixes.push(format!(""));
3884 fixes.push(format!(
3885 "// Strategy 3: Combine Rc<RefCell<T>> for shared mutable ownership"
3886 ));
3887 fixes.push(format!("use std::rc::Rc;"));
3888 fixes.push(format!("use std::cell::RefCell;"));
3889 fixes.push(format!(
3890 "type {} = Rc<RefCell<{}Node>>;",
3891 type_name, type_name
3892 ));
3893 fixes.push(format!("struct {}Node {{", type_name));
3894 fixes.push(format!(" data: SomeType,"));
3895 fixes.push(format!(" next: Option<{}>,", type_name));
3896 fixes.push(format!("}}"));
3897
3898 fixes.push(format!(""));
3900 fixes.push(format!(
3901 "// Strategy 4: Use indices instead of direct references"
3902 ));
3903 fixes.push(format!("struct {} {{", type_name));
3904 fixes.push(format!(" data: SomeType,"));
3905 fixes.push(format!(
3906 " next_index: Option<usize>, // Index into a Vec"
3907 ));
3908 fixes.push(format!("}}"));
3909 fixes.push(format!("struct {}Container {{", type_name));
3910 fixes.push(format!(" nodes: Vec<{}>,", type_name));
3911 fixes.push(format!("}}"));
3912
3913 if let Some(ctx) = context {
3915 let analysis = self.analyze_recursive_structure(ctx, type_name);
3916 if !analysis.is_empty() {
3917 fixes.push(format!(""));
3918 fixes.push(format!("// Analysis of your specific case:"));
3919 fixes.extend(analysis);
3920 }
3921 }
3922
3923 fixes.push(format!(""));
3925 fixes.push(format!("// Example implementation with Box:"));
3926 fixes.push(format!("impl {} {{", type_name));
3927 fixes.push(format!(" fn new(data: SomeType) -> Self {{"));
3928 fixes.push(format!(" {} {{", type_name));
3929 fixes.push(format!(" data,"));
3930 fixes.push(format!(" next: None,"));
3931 fixes.push(format!(" }}"));
3932 fixes.push(format!(" }}"));
3933 fixes.push(format!(" "));
3934 fixes.push(format!(" fn add_next(&mut self, data: SomeType) {{"));
3935 fixes.push(format!(
3936 " self.next = Some(Box::new({}::new(data)));",
3937 type_name
3938 ));
3939 fixes.push(format!(" }}"));
3940 fixes.push(format!("}}"));
3941
3942 fixes
3943 }
3944}
3945
3946impl ClosureCaptureLifetimeFixGenerator {
3947 pub fn new() -> Self {
3949 Self
3950 }
3951
3952 fn is_closure_capture_error(&self, message: &str) -> bool {
3954 message.contains("E0373")
3955 || message.contains("closure may outlive the current function")
3956 || message.contains("closure may outlive")
3957 || (message.contains("closure") && message.contains("borrowed data"))
3958 }
3959
3960 fn extract_captured_variable(&self, message: &str) -> Option<String> {
3962 let patterns = [
3963 r"but it borrows `([^`]+)`",
3964 r"borrowed data `([^`]+)`",
3965 r"captures `([^`]+)`",
3966 ];
3967
3968 for pattern in patterns {
3969 if let Ok(regex) = Regex::new(pattern) {
3970 if let Some(captures) = regex.captures(message) {
3971 if let Some(var_match) = captures.get(1) {
3972 return Some(var_match.as_str().to_string());
3973 }
3974 }
3975 }
3976 }
3977 None
3978 }
3979
3980 fn generate_closure_fixes(&self, variable_name: &str, context: Option<&str>) -> Vec<String> {
3982 let mut fixes = Vec::new();
3983
3984 fixes.push(format!("// Strategy 1: Move ownership into closure"));
3986 fixes.push(format!(
3987 "let {}_owned = {}.clone();",
3988 variable_name, variable_name
3989 ));
3990 fixes.push(format!("move || {{"));
3991 fixes.push(format!(
3992 " // Use {}_owned instead of {}",
3993 variable_name, variable_name
3994 ));
3995 fixes.push(format!("}}"));
3996
3997 fixes.push(format!(""));
3999 fixes.push(format!("// Strategy 2: Shared ownership with Arc"));
4000 fixes.push(format!("use std::sync::Arc;"));
4001 fixes.push(format!(
4002 "let {}_arc = Arc::new({});",
4003 variable_name, variable_name
4004 ));
4005 fixes.push(format!(
4006 "let {}_clone = Arc::clone(&{}_arc);",
4007 variable_name, variable_name
4008 ));
4009 fixes.push(format!("move || {{"));
4010 fixes.push(format!(" // Use {}_clone inside closure", variable_name));
4011 fixes.push(format!("}}"));
4012
4013 fixes.push(format!(""));
4015 fixes.push(format!("// Strategy 3: Extract needed data before closure"));
4016 fixes.push(format!(
4017 "let needed_data = extract_from_{}(&{});",
4018 variable_name, variable_name
4019 ));
4020 fixes.push(format!("move || {{"));
4021 fixes.push(format!(
4022 " // Use needed_data instead of full {}",
4023 variable_name
4024 ));
4025 fixes.push(format!("}}"));
4026
4027 if let Some(ctx) = context {
4029 if ctx.contains("fn ") && !ctx.contains("'static") {
4030 fixes.push(format!(""));
4031 fixes.push(format!("// Strategy 4: Add lifetime parameters"));
4032 fixes.push(format!(
4033 "fn function_name<'a>(param: &'a Type) -> impl Fn() + 'a {{"
4034 ));
4035 fixes.push(format!(" move || {{"));
4036 fixes.push(format!(" // Closure now has explicit lifetime 'a"));
4037 fixes.push(format!(" }}"));
4038 fixes.push(format!("}}"));
4039 }
4040 }
4041
4042 fixes
4043 }
4044}
4045
4046impl RuntimePanicFixGenerator {
4047 pub fn new() -> Self {
4049 Self
4050 }
4051
4052 fn has_panic_pattern(&self, code: &str) -> bool {
4054 code.contains("panic!") ||
4056 code.contains("todo!") ||
4057 code.contains("unimplemented!") ||
4058 (code.contains("[") && code.contains("]")) || code.contains("as ") }
4061
4062 fn identify_panic_type(&self, code: &str) -> &'static str {
4064 if code.contains("panic!") {
4065 "explicit_panic"
4066 } else if code.contains("todo!") || code.contains("unimplemented!") {
4067 "todo_unimplemented"
4068 } else if code.contains("[") && code.contains("]") {
4069 "array_access"
4070 } else if code.contains("as ") {
4071 "unsafe_cast"
4072 } else {
4073 "unknown"
4074 }
4075 }
4076
4077 fn extract_array_access(&self, code: &str) -> Option<String> {
4079 let patterns = [r#"(\w+)\[(\w+)\]"#, r#"(\w+)\[(\d+)\]"#];
4081
4082 for pattern in patterns {
4083 if let Ok(regex) = Regex::new(pattern) {
4084 if let Some(captures) = regex.captures(code) {
4085 if let Some(expr_match) = captures.get(0) {
4086 return Some(expr_match.as_str().to_string());
4087 }
4088 }
4089 }
4090 }
4091
4092 None
4093 }
4094
4095 fn extract_cast_expression(&self, code: &str) -> Option<String> {
4097 let patterns = [r#"(\w+)\s+as\s+(\w+)"#];
4099
4100 for pattern in patterns {
4101 if let Ok(regex) = Regex::new(pattern) {
4102 if let Some(captures) = regex.captures(code) {
4103 if let Some(expr_match) = captures.get(0) {
4104 return Some(expr_match.as_str().to_string());
4105 }
4106 }
4107 }
4108 }
4109
4110 None
4111 }
4112
4113 fn generate_fixed_code(&self, code: &str, panic_type: &str) -> String {
4115 match panic_type {
4116 "explicit_panic" => {
4117 code.replace(
4119 "panic!",
4120 "return Err(std::io::Error::new(std::io::ErrorKind::Other, ",
4121 )
4122 .replace(")", "))")
4123 }
4124 "todo_unimplemented" => {
4125 if code.contains("todo!") {
4127 code.replace(
4128 "todo!",
4129 "/* TODO: Implement this function */ return Err(std::io::Error::new(std::io::ErrorKind::Other, \"Not implemented\"))"
4130 )
4131 } else {
4132 code.replace(
4133 "unimplemented!",
4134 "/* TODO: Implement this function */ return Err(std::io::Error::new(std::io::ErrorKind::Other, \"Not implemented\"))"
4135 )
4136 }
4137 }
4138 "array_access" => {
4139 if let Some(array_expr) = self.extract_array_access(code) {
4141 let parts: Vec<&str> = array_expr.split('[').collect();
4142 if parts.len() == 2 {
4143 let array_name = parts[0];
4144 let index_part = parts[1].trim_end_matches(']');
4145
4146 code.replace(
4147 &array_expr,
4148 &format!("if {} < {}.len() {{ {}[{}] }} else {{ panic!(\"Index out of bounds\") }}",
4149 index_part, array_name, array_name, index_part)
4150 )
4151 } else {
4152 code.replace(
4154 &array_expr,
4155 &format!(
4156 "/* WARNING: Check array bounds before access */ {}",
4157 array_expr
4158 ),
4159 )
4160 }
4161 } else {
4162 code.to_string()
4163 }
4164 }
4165 "unsafe_cast" => {
4166 if let Some(cast_expr) = self.extract_cast_expression(code) {
4168 let parts: Vec<&str> = cast_expr.split(" as ").collect();
4169 if parts.len() == 2 {
4170 let value = parts[0];
4171 let target_type = parts[1];
4172
4173 if target_type.contains("i32")
4174 || target_type.contains("i64")
4175 || target_type.contains("u32")
4176 || target_type.contains("u64")
4177 {
4178 code.replace(
4179 &cast_expr,
4180 &format!("match {}.try_into() {{ Ok(v) => v, Err(_) => panic!(\"Cast failed\") }}", value)
4181 )
4182 } else {
4183 code.replace(
4185 &cast_expr,
4186 &format!(
4187 "/* WARNING: This cast may panic at runtime */ {}",
4188 cast_expr
4189 ),
4190 )
4191 }
4192 } else {
4193 code.to_string()
4194 }
4195 } else {
4196 code.to_string()
4197 }
4198 }
4199 _ => code.to_string(),
4200 }
4201 }
4202
4203 fn generate_fix(&self, code: &str) -> Option<(String, String, String)> {
4205 if !self.has_panic_pattern(code) {
4206 return None;
4207 }
4208
4209 let panic_type = self.identify_panic_type(code);
4210 let fixed_code = self.generate_fixed_code(code, panic_type);
4211
4212 let explanation = match panic_type {
4214 "explicit_panic" => {
4215 "Explicit panic! calls cause the program to terminate immediately.\n\
4216 Consider using Result or Option to handle errors gracefully.\n\
4217 This fix replaces the panic with a Result::Err return."
4218 .to_string()
4219 }
4220 "todo_unimplemented" => "todo! and unimplemented! macros cause panics when executed.\n\
4221 These are meant as temporary placeholders during development.\n\
4222 This fix replaces them with a proper error handling stub."
4223 .to_string(),
4224 "array_access" => "Array access with [] will panic if the index is out of bounds.\n\
4225 Always check that the index is within the array's length.\n\
4226 This fix adds a bounds check before accessing the array."
4227 .to_string(),
4228 "unsafe_cast" => {
4229 "Type casts with 'as' can panic if the value doesn't fit in the target type.\n\
4230 Consider using TryFrom/TryInto for safe conversions.\n\
4231 This fix adds a safety check for the cast operation."
4232 .to_string()
4233 }
4234 _ => "This code contains patterns that might cause runtime panics.\n\
4235 The fix adds appropriate error handling to prevent crashes."
4236 .to_string(),
4237 };
4238
4239 let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
4241
4242 Some((fixed_code, explanation, diff))
4243 }
4244}
4245
4246impl DivisionByZeroFixGenerator {
4247 pub fn new() -> Self {
4249 Self
4250 }
4251
4252 fn has_division_by_zero(&self, code: &str) -> bool {
4254 code.contains("/") &&
4256 (code.contains("/ 0") ||
4258 code.contains("/0") ||
4259 code.contains("/ 0.") ||
4260 code.contains("/=0") ||
4261 code.contains("if") && code.contains("== 0") && code.contains("/"))
4262 }
4263
4264 fn extract_division_expression(&self, code: &str) -> Option<String> {
4266 let patterns = [
4268 r#"(\w+)\s*/\s*0"#,
4269 r#"(\w+)\s*/=\s*0"#,
4270 r#"(\w+)\s*/\s*(\w+)"#,
4271 r#"([^/\s]+)\s*/\s*([^/\s]+)"#, ];
4273
4274 for pattern in patterns {
4275 if let Ok(regex) = Regex::new(pattern) {
4276 if let Some(captures) = regex.captures(code) {
4277 if let Some(expr_match) = captures.get(0) {
4278 return Some(expr_match.as_str().to_string());
4279 }
4280 }
4281 }
4282 }
4283
4284 if code.contains("/") {
4287 let lines: Vec<&str> = code.lines().collect();
4288 for line in lines {
4289 if line.contains("/") {
4290 return Some(line.trim().to_string());
4291 }
4292 }
4293 }
4294
4295 None
4296 }
4297
4298 fn extract_denominator_variable(&self, code: &str) -> Option<String> {
4300 let patterns = [r#"\w+\s*/\s*(\w+)"#];
4302
4303 for pattern in patterns {
4304 if let Ok(regex) = Regex::new(pattern) {
4305 if let Some(captures) = regex.captures(code) {
4306 if let Some(var_match) = captures.get(1) {
4307 return Some(var_match.as_str().to_string());
4308 }
4309 }
4310 }
4311 }
4312
4313 None
4314 }
4315
4316 fn generate_fixed_code(
4318 &self,
4319 code: &str,
4320 division_expr: &str,
4321 denominator: Option<&str>,
4322 ) -> String {
4323 if let Some(denom_var) = denominator {
4324 if code.contains("if") && code.contains(denom_var) && code.contains("== 0") {
4326 code.replace(
4328 division_expr,
4329 &format!(
4330 "if {} != 0 {{ {} }} else {{ panic!(\"Division by zero\") }}",
4331 denom_var, division_expr
4332 ),
4333 )
4334 } else {
4335 code.replace(
4337 division_expr,
4338 &format!(
4339 "if {} != 0 {{ {} }} else {{ panic!(\"Division by zero\") }}",
4340 denom_var, division_expr
4341 ),
4342 )
4343 }
4344 } else if division_expr.contains("/ 0") || division_expr.contains("/0") {
4345 code.replace(
4347 division_expr,
4348 "/* ERROR: Division by zero will cause a panic */ panic!(\"Division by zero\")",
4349 )
4350 } else {
4351 code.replace(
4353 division_expr,
4354 &format!(
4355 "/* WARNING: Check for division by zero */ {}",
4356 division_expr
4357 ),
4358 )
4359 }
4360 }
4361
4362 fn generate_fix(&self, code: &str) -> Option<(String, String, String)> {
4364 if !self.has_division_by_zero(code) {
4365 return None;
4366 }
4367
4368 let division_expr = self.extract_division_expression(code)?;
4369 let denominator = self.extract_denominator_variable(code);
4370
4371 let fixed_code = self.generate_fixed_code(code, &division_expr, denominator.as_deref());
4372
4373 let explanation = format!(
4375 "Division by zero causes runtime panics in Rust.\n\
4376 It's important to check the denominator before performing division.\n\
4377 This fix adds a check to prevent division by zero panics."
4378 );
4379
4380 let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
4382
4383 Some((fixed_code, explanation, diff))
4384 }
4385}
4386
4387impl MissingOkErrFixGenerator {
4388 pub fn new() -> Self {
4390 Self
4391 }
4392
4393 fn has_incomplete_match(&self, code: &str) -> bool {
4395 (code.contains("match") && (code.contains("Result") || code.contains("Option"))) &&
4397 ((code.contains("Ok(") && !code.contains("Err(")) ||
4399 (code.contains("Err(") && !code.contains("Ok(")) ||
4400 (code.contains("Some(") && !code.contains("None")) ||
4401 (code.contains("None") && !code.contains("Some(")))
4402 }
4403
4404 fn extract_match_variable(&self, code: &str) -> Option<String> {
4406 let patterns = [r#"match\s+(\w+)\s*\{"#];
4408
4409 for pattern in patterns {
4410 if let Ok(regex) = Regex::new(pattern) {
4411 if let Some(captures) = regex.captures(code) {
4412 if let Some(var_match) = captures.get(1) {
4413 return Some(var_match.as_str().to_string());
4414 }
4415 }
4416 }
4417 }
4418
4419 None
4420 }
4421
4422 fn determine_match_type(&self, code: &str, var_name: &str) -> Option<&'static str> {
4424 if code.contains(&format!("{}: Result<", var_name))
4426 || code.contains("-> Result<")
4427 || code.contains("Ok(")
4428 || code.contains("Err(")
4429 {
4430 return Some("Result");
4431 }
4432
4433 if code.contains(&format!("{}: Option<", var_name))
4435 || code.contains("-> Option<")
4436 || code.contains("Some(")
4437 || code.contains("None")
4438 {
4439 return Some("Option");
4440 }
4441
4442 None
4443 }
4444
4445 fn generate_fixed_match(&self, code: &str, var_name: &str, match_type: &str) -> String {
4447 if match_type == "Result" {
4448 if code.contains("Ok(") && !code.contains("Err(") {
4450 code.replace(
4452 "}",
4453 " Err(err) => {\n // Handle error case\n println!(\"Error: {:?}\", err);\n }\n}"
4454 )
4455 } else if code.contains("Err(") && !code.contains("Ok(") {
4456 let re = Regex::new(&format!(r#"match\s+{}\s*\{{"#, var_name)).unwrap();
4458 re.replace(
4459 code,
4460 &format!("match {} {{\n Ok(value) => {{\n // Handle success case\n println!(\"Success: {{:?}}\", value);\n }},", var_name)
4461 ).to_string()
4462 } else {
4463 format!(
4465 "match {} {{\n Ok(value) => {{\n // Handle success case\n println!(\"Success: {{:?}}\", value);\n }},\n Err(err) => {{\n // Handle error case\n println!(\"Error: {{:?}}\", err);\n }}\n}}",
4466 var_name
4467 )
4468 }
4469 } else {
4470 if code.contains("Some(") && !code.contains("None") {
4473 code.replace(
4475 "}",
4476 " None => {\n // Handle None case\n println!(\"No value found\");\n }\n}"
4477 )
4478 } else if code.contains("None") && !code.contains("Some(") {
4479 let re = Regex::new(&format!(r#"match\s+{}\s*\{{"#, var_name)).unwrap();
4481 re.replace(
4482 code,
4483 &format!("match {} {{\n Some(value) => {{\n // Handle Some case\n println!(\"Found value: {{:?}}\", value);\n }},", var_name)
4484 ).to_string()
4485 } else {
4486 format!(
4488 "match {} {{\n Some(value) => {{\n // Handle Some case\n println!(\"Found value: {{:?}}\", value);\n }},\n None => {{\n // Handle None case\n println!(\"No value found\");\n }}\n}}",
4489 var_name
4490 )
4491 }
4492 }
4493 }
4494
4495 fn generate_fix(&self, code: &str) -> Option<(String, String, String)> {
4497 if !self.has_incomplete_match(code) {
4498 return None;
4499 }
4500
4501 let var_name = self.extract_match_variable(code)?;
4502 let match_type = self.determine_match_type(code, &var_name)?;
4503
4504 let fixed_code = self.generate_fixed_match(code, &var_name, match_type);
4505
4506 let explanation = format!(
4508 "When matching on a {} type, you must handle all possible variants.\n\
4509 This ensures that your code handles all possible outcomes and prevents runtime errors.\n\
4510 The fix adds the missing match arm(s) to handle all cases.",
4511 match_type
4512 );
4513
4514 let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
4516
4517 Some((fixed_code, explanation, diff))
4518 }
4519}
4520
4521impl QuestionMarkPropagationFixGenerator {
4522 pub fn new() -> Self {
4524 Self
4525 }
4526
4527 fn has_question_mark(&self, code: &str) -> bool {
4529 code.contains("?") && !code.contains("-> Result<") && !code.contains("-> Option<")
4530 }
4531
4532 fn extract_function_signature(&self, code: &str) -> Option<String> {
4534 let patterns = [r#"fn\s+(\w+)\s*\([^)]*\)\s*(?:->\s*([^{]+))?\s*\{"#];
4536
4537 for pattern in patterns {
4538 if let Ok(regex) = Regex::new(pattern) {
4539 if let Some(captures) = regex.captures(code) {
4540 if let Some(fn_match) = captures.get(0) {
4541 return Some(fn_match.as_str().to_string());
4542 }
4543 }
4544 }
4545 }
4546
4547 None
4548 }
4549
4550 fn extract_function_name(&self, code: &str) -> Option<String> {
4552 let patterns = [r#"fn\s+(\w+)"#];
4554
4555 for pattern in patterns {
4556 if let Ok(regex) = Regex::new(pattern) {
4557 if let Some(captures) = regex.captures(code) {
4558 if let Some(fn_match) = captures.get(1) {
4559 return Some(fn_match.as_str().to_string());
4560 }
4561 }
4562 }
4563 }
4564
4565 None
4566 }
4567
4568 fn determine_needed_return_type(&self, code: &str) -> &'static str {
4570 if code.contains("Result<")
4572 || code.contains("std::result::Result")
4573 || code.contains("std::fs::File")
4574 || code.contains("std::io::")
4575 {
4576 return "Result<T, E>";
4577 } else if code.contains("Option<")
4578 || code.contains("std::option::Option")
4579 || code.contains(".next()")
4580 || code.contains(".get(")
4581 {
4582 return "Option<T>";
4583 }
4584
4585 "Result<T, E>"
4587 }
4588
4589 fn generate_fixed_signature(&self, _code: &str, signature: &str, return_type: &str) -> String {
4591 if signature.contains("->") {
4593 let re = Regex::new(r#"->\s*([^{]+)"#).unwrap();
4595 re.replace(signature, format!("-> {}", return_type).as_str())
4596 .to_string()
4597 } else {
4598 signature.replace("{", &format!(" -> {} {{", return_type))
4600 }
4601 }
4602
4603 fn generate_fix(&self, code: &str) -> Option<(String, String, String)> {
4605 if !self.has_question_mark(code) {
4606 return None;
4607 }
4608
4609 let signature = self.extract_function_signature(code)?;
4610 let _fn_name = self.extract_function_name(code)?;
4612 let needed_return_type = self.determine_needed_return_type(code);
4613
4614 let fixed_signature = self.generate_fixed_signature(code, &signature, needed_return_type);
4615 let fixed_code = code.replace(&signature, &fixed_signature);
4616
4617 let explanation = format!(
4619 "The question mark operator (?) can only be used in functions that return Result or Option.\n\
4620 This function uses the ? operator but doesn't have a compatible return type.\n\
4621 The fix changes the function signature to return {}.",
4622 needed_return_type
4623 );
4624
4625 let diff = format!("- {}\n+ {}", signature.trim(), fixed_signature.trim());
4627
4628 Some((fixed_code, explanation, diff))
4629 }
4630}
4631
4632impl UnsafeUnwrapFixGenerator {
4633 pub fn new() -> Self {
4635 Self
4636 }
4637
4638 fn has_unsafe_unwrap(&self, code: &str) -> bool {
4640 code.contains(".unwrap()")
4641 || code.contains(".expect(")
4642 || code.contains(".unwrap_or_else(")
4643 || code.contains(".unwrap_or(")
4644 || code.contains(".unwrap_unchecked(")
4645 }
4646
4647 fn extract_variable_name(&self, code: &str) -> Option<String> {
4649 let patterns = [
4651 r#"(\w+)\.unwrap\(\)"#,
4652 r#"(\w+)\.expect\([^)]+\)"#,
4653 r#"(\w+)\.unwrap_or\([^)]+\)"#,
4654 r#"(\w+)\.unwrap_or_else\([^)]+\)"#,
4655 r#"(\w+)\.unwrap_unchecked\(\)"#,
4656 ];
4657
4658 for pattern in patterns {
4659 if let Ok(regex) = Regex::new(pattern) {
4660 if let Some(captures) = regex.captures(code) {
4661 if let Some(var_match) = captures.get(1) {
4662 return Some(var_match.as_str().to_string());
4663 }
4664 }
4665 }
4666 }
4667
4668 None
4669 }
4670
4671 fn is_result_or_option(&self, code: &str, var_name: &str) -> Option<&'static str> {
4673 if code.contains(&format!("{}: Result<", var_name)) || code.contains(&format!("-> Result<"))
4675 {
4676 return Some("Result");
4677 }
4678
4679 if code.contains(&format!("{}: Option<", var_name)) || code.contains(&format!("-> Option<"))
4681 {
4682 return Some("Option");
4683 }
4684
4685 if code.contains(&format!("{} = std::fs::File::open", var_name))
4687 || code.contains(&format!("{} = File::open", var_name))
4688 || code.contains(&format!("{} = read_to_string", var_name))
4689 || code.contains(&format!("{} = parse::<", var_name))
4690 {
4691 return Some("Result");
4692 }
4693
4694 if code.contains(&format!("{} = iter().next()", var_name))
4696 || code.contains(&format!("{} = get(", var_name))
4697 || code.contains(&format!("{} = find(", var_name))
4698 {
4699 return Some("Option");
4700 }
4701
4702 None
4703 }
4704
4705 fn generate_result_alternative(&self, code: &str, var_name: &str) -> String {
4707 let unwrap_pattern = format!("{}.unwrap()", var_name);
4708 let expect_pattern1 = format!("{}.expect(", var_name);
4709 let expect_pattern2 = format!("{}.unwrap_unchecked()", var_name);
4710
4711 if code.contains(&unwrap_pattern) {
4712 return code.replace(
4713 &unwrap_pattern,
4714 &format!("match {} {{\n Ok(value) => value,\n Err(err) => return Err(err.into()),\n}}", var_name)
4715 );
4716 } else if code.contains(&expect_pattern1) {
4717 let re = Regex::new(&format!(r#"{}.expect\(['"](.*?)['"]"#, var_name)).unwrap();
4719 let message = re
4720 .captures(code)
4721 .and_then(|cap| cap.get(1))
4722 .map_or("Error occurred", |m| m.as_str());
4723
4724 return code.replace(
4725 &format!("{}.expect(\"{}\")", var_name, message),
4726 &format!("match {} {{\n Ok(value) => value,\n Err(err) => return Err(format!(\"{{}} ({})\", err).into()),\n}}", var_name, message)
4727 );
4728 } else if code.contains(&expect_pattern2) {
4729 return code.replace(
4730 &expect_pattern2,
4731 &format!("match {} {{\n Ok(value) => value,\n Err(err) => return Err(err.into()),\n}}", var_name)
4732 );
4733 }
4734
4735 code.to_string()
4737 }
4738
4739 fn generate_option_alternative(&self, code: &str, var_name: &str) -> String {
4741 let unwrap_pattern = format!("{}.unwrap()", var_name);
4742 let expect_pattern1 = format!("{}.expect(", var_name);
4743 let expect_pattern2 = format!("{}.unwrap_unchecked()", var_name);
4744
4745 if code.contains(&unwrap_pattern) {
4746 return code.replace(
4747 &unwrap_pattern,
4748 &format!("match {} {{\n Some(value) => value,\n None => return Err(\"Value was None\".into()),\n}}", var_name)
4749 );
4750 } else if code.contains(&expect_pattern1) {
4751 let re = Regex::new(&format!(r#"{}.expect\(['"](.*?)['"]"#, var_name)).unwrap();
4753 let message = re
4754 .captures(code)
4755 .and_then(|cap| cap.get(1))
4756 .map_or("Value was None", |m| m.as_str());
4757
4758 return code.replace(
4759 &format!("{}.expect(\"{}\")", var_name, message),
4760 &format!("match {} {{\n Some(value) => value,\n None => return Err(\"{}\".into()),\n}}", var_name, message)
4761 );
4762 } else if code.contains(&expect_pattern2) {
4763 return code.replace(
4764 &expect_pattern2,
4765 &format!("match {} {{\n Some(value) => value,\n None => return Err(\"Value was None\".into()),\n}}", var_name)
4766 );
4767 }
4768
4769 code.to_string()
4771 }
4772
4773 fn generate_fix(&self, code: &str) -> Option<(String, String, String)> {
4775 if !self.has_unsafe_unwrap(code) {
4776 return None;
4777 }
4778
4779 let var_name = self.extract_variable_name(code)?;
4780 let type_hint = self.is_result_or_option(code, &var_name)?;
4781
4782 let fixed_code = match type_hint {
4783 "Result" => self.generate_result_alternative(code, &var_name),
4784 "Option" => self.generate_option_alternative(code, &var_name),
4785 _ => return None,
4786 };
4787
4788 let explanation = format!(
4790 "Using `.unwrap()` or `.expect()` can cause runtime panics if the {} is an error or None.\n\
4791 It's safer to handle both success and error cases explicitly using pattern matching.\n\
4792 This change replaces the unwrap with a match expression that handles both cases.",
4793 type_hint
4794 );
4795
4796 let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
4798
4799 Some((fixed_code, explanation, diff))
4800 }
4801}
4802
4803impl InvalidArgumentCountFixGenerator {
4804 pub fn new() -> Self {
4806 Self
4807 }
4808
4809 fn is_invalid_argument_count_error(&self, message: &str) -> bool {
4811 message.contains("E0061")
4812 || message.contains("this function takes")
4813 || message.contains("expected") && message.contains("argument")
4814 || message.contains("wrong number of arguments")
4815 || message.contains("incorrect number of arguments")
4816 }
4817
4818 fn extract_function_name(&self, message: &str) -> Option<String> {
4820 let patterns = [
4822 r#"function [`']([^'`]+)[`']"#,
4823 r#"call to [`']([^'`]+)[`']"#,
4824 r#"calling [`']([^'`]+)[`']"#,
4825 ];
4826
4827 for pattern in patterns {
4828 if let Ok(regex) = Regex::new(pattern) {
4829 if let Some(captures) = regex.captures(message) {
4830 if let Some(fn_match) = captures.get(1) {
4831 return Some(fn_match.as_str().to_string());
4832 }
4833 }
4834 }
4835 }
4836
4837 None
4838 }
4839
4840 fn extract_argument_counts(&self, message: &str) -> Option<(usize, usize)> {
4842 let patterns = [
4844 r#"takes (\d+) (?:argument|parameters) but (\d+) (?:argument|parameter) was supplied"#,
4845 r#"takes (\d+) (?:argument|parameters) but (\d+) (?:argument|parameter)s? were supplied"#,
4846 r#"expected (\d+) (?:argument|parameters), found (\d+)"#,
4847 ];
4848
4849 for pattern in patterns {
4850 if let Ok(regex) = Regex::new(pattern) {
4851 if let Some(captures) = regex.captures(message) {
4852 if let (Some(expected_match), Some(actual_match)) =
4853 (captures.get(1), captures.get(2))
4854 {
4855 if let (Ok(expected), Ok(actual)) = (
4856 expected_match.as_str().parse::<usize>(),
4857 actual_match.as_str().parse::<usize>(),
4858 ) {
4859 return Some((expected, actual));
4860 }
4861 }
4862 }
4863 }
4864 }
4865
4866 None
4867 }
4868
4869 fn generate_fix_suggestions(
4871 &self,
4872 function_name: Option<&str>,
4873 arg_counts: Option<(usize, usize)>,
4874 ) -> Vec<String> {
4875 let mut suggestions = Vec::new();
4876
4877 if let Some(fn_name) = function_name {
4879 suggestions.push(format!("// For function '{}':", fn_name));
4880 } else {
4881 suggestions.push("// For this function call:".to_string());
4882 }
4883
4884 if let Some((expected, actual)) = arg_counts {
4886 match actual.cmp(&expected) {
4887 std::cmp::Ordering::Less => {
4888 suggestions.push(format!(
4889 "// 1. Add the missing {} argument(s)",
4890 expected - actual
4891 ));
4892
4893 let mut args = Vec::new();
4895 for i in 0..expected {
4896 if i < actual {
4897 args.push(format!("arg{}", i + 1));
4898 } else {
4899 args.push(format!("/* missing_arg{} */", i + 1));
4900 }
4901 }
4902
4903 if let Some(fn_name) = function_name {
4904 suggestions.push(format!("// {}({})", fn_name, args.join(", ")));
4905 } else {
4906 suggestions.push(format!("// function_name({})", args.join(", ")));
4907 }
4908 }
4909 std::cmp::Ordering::Greater => {
4910 suggestions.push(format!(
4911 "// 1. Remove the extra {} argument(s)",
4912 actual - expected
4913 ));
4914
4915 let mut args = Vec::new();
4917 for i in 0..expected {
4918 args.push(format!("arg{}", i + 1));
4919 }
4920
4921 if let Some(fn_name) = function_name {
4922 suggestions.push(format!("// {}({})", fn_name, args.join(", ")));
4923 } else {
4924 suggestions.push(format!("// function_name({})", args.join(", ")));
4925 }
4926
4927 if expected == 1 {
4929 suggestions.push("// 2. If the arguments are related, consider combining them into a struct or tuple".to_string());
4930 suggestions.push("// function_name((arg1, arg2, ...))".to_string());
4931 }
4932 }
4933 std::cmp::Ordering::Equal => {
4934 }
4936 }
4937 } else {
4938 suggestions.push(
4940 "// 1. Check the function signature to determine the correct number of arguments"
4941 .to_string(),
4942 );
4943 suggestions
4944 .push("// - Look at the function definition or documentation".to_string());
4945 suggestions.push("// 2. Make sure you're calling the right function".to_string());
4946 suggestions
4947 .push("// - Similar functions might have different parameter lists".to_string());
4948 }
4949
4950 suggestions
4952 .push("// 3. Consider using named arguments with a struct for clarity".to_string());
4953 suggestions
4954 .push("// - Create a struct with named fields for the parameters".to_string());
4955 suggestions.push("// - Pass an instance of the struct to the function".to_string());
4956
4957 suggestions
4958 }
4959}
4960
4961impl UnstableFeatureFixGenerator {
4962 pub fn new() -> Self {
4964 Self
4965 }
4966
4967 fn is_unstable_feature_error(&self, message: &str) -> bool {
4969 message.contains("E0658")
4970 || message.contains("use of unstable feature")
4971 || message.contains("unstable feature")
4972 || message.contains("is unstable")
4973 || message.contains("nightly-only")
4974 }
4975
4976 fn extract_feature_name(&self, message: &str) -> Option<String> {
4978 let patterns = [
4980 r#"use of unstable feature [`']([^'`]+)[`']"#,
4981 r#"the feature [`']([^'`]+)[`'] is unstable"#,
4982 r#"unstable feature: [`']([^'`]+)[`']"#,
4983 ];
4984
4985 for pattern in patterns {
4986 if let Ok(regex) = Regex::new(pattern) {
4987 if let Some(captures) = regex.captures(message) {
4988 if let Some(feature_match) = captures.get(1) {
4989 return Some(feature_match.as_str().to_string());
4990 }
4991 }
4992 }
4993 }
4994
4995 None
4996 }
4997
4998 fn generate_fix_suggestions(&self, feature_name: Option<&str>) -> Vec<String> {
5000 let mut suggestions = vec![
5001 "// 1. Use the nightly compiler channel".to_string(),
5002 "// - rustup default nightly".to_string(),
5003 "// - rustup override set nightly (for this project only)".to_string(),
5004 "// 2. Enable the feature in your crate root (lib.rs or main.rs)".to_string(),
5005 ];
5006
5007 if let Some(feature) = feature_name {
5008 suggestions.push(format!("// - #![feature({})]", feature));
5009 } else {
5010 suggestions.push("// - #![feature(feature_name)]".to_string());
5011 }
5012
5013 suggestions.push("// 3. Look for stable alternatives".to_string());
5015 suggestions
5016 .push("// - Check the Rust documentation for stable alternatives".to_string());
5017 suggestions
5018 .push("// - Consider using a crate that provides similar functionality".to_string());
5019
5020 if let Some(feature) = feature_name {
5022 match feature {
5023 "try_trait" => {
5024 suggestions.push(
5025 "// 4. For 'try_trait', consider using match or if let on Result/Option"
5026 .to_string(),
5027 );
5028 suggestions.push(
5029 "// - match result { Ok(v) => v, Err(e) => return Err(e) }".to_string(),
5030 );
5031 }
5032 "async_closure" => {
5033 suggestions.push(
5034 "// 4. For 'async_closure', use a regular closure with async block"
5035 .to_string(),
5036 );
5037 suggestions.push("// - |x| async move { /* async code */ }".to_string());
5038 }
5039 "box_syntax" => {
5040 suggestions.push("// 4. For 'box_syntax', use Box::new() instead".to_string());
5041 suggestions.push("// - Box::new(value) instead of box value".to_string());
5042 }
5043 _ => {
5044 suggestions.push(format!(
5045 "// 4. For '{}', check the Rust Unstable Book",
5046 feature
5047 ));
5048 suggestions
5049 .push("// - https://doc.rust-lang.org/unstable-book/".to_string());
5050 }
5051 }
5052 }
5053
5054 suggestions
5055 }
5056}
5057
5058impl ReturnLocalReferenceFixGenerator {
5059 pub fn new() -> Self {
5061 Self
5062 }
5063
5064 fn is_return_local_reference_error(&self, message: &str) -> bool {
5066 message.contains("E0515")
5067 || message.contains("returns a reference to data owned by the current function")
5068 || message.contains("returns a value referencing data owned by the current function")
5069 || message.contains("returns a reference to a local value")
5070 }
5071
5072 fn extract_variable_name(&self, message: &str) -> Option<String> {
5074 let patterns = [
5076 r#"returns a (?:reference|value referencing) (?:data owned by|local value) .* `([^`]+)`"#,
5077 r#"`([^`]+)` is borrowed here"#,
5078 r#"returns a reference to `([^`]+)`"#,
5079 ];
5080
5081 for pattern in patterns {
5082 if let Ok(regex) = Regex::new(pattern) {
5083 if let Some(captures) = regex.captures(message) {
5084 if let Some(var_match) = captures.get(1) {
5085 return Some(var_match.as_str().to_string());
5086 }
5087 }
5088 }
5089 }
5090
5091 None
5092 }
5093
5094 fn generate_fix_suggestions(&self, variable_name: Option<&str>) -> Vec<String> {
5096 let mut suggestions = vec![
5097 "// 1. Return an owned value instead of a reference".to_string(),
5098 "// - Use Clone: return value.clone()".to_string(),
5099 "// - Use Copy: return *value (if the type implements Copy)".to_string(),
5100 "// - Use owned types: String instead of &str, Vec<T> instead of &[T]".to_string(),
5101 "// 2. Change the function signature to take input with the same lifetime".to_string(),
5102 "// - fn function<'a>(input: &'a Type) -> &'a Type { ... }".to_string(),
5103 ];
5104
5105 if let Some(var) = variable_name {
5107 suggestions.push(format!("// 3. For this specific case with `{}`:", var));
5108 suggestions.push(format!(
5109 "// - If `{}` is a String: return {}.clone()",
5110 var, var
5111 ));
5112 suggestions.push(format!(
5113 "// - If `{}` is a reference already: return {}",
5114 var, var
5115 ));
5116 suggestions.push(format!(
5117 "// - If `{}` is a primitive type: return *{} (if Copy)",
5118 var, var
5119 ));
5120 }
5121
5122 suggestions.push(
5124 "// 4. Use 'static lifetime (only if the data truly lives for the entire program)"
5125 .to_string(),
5126 );
5127 suggestions
5128 .push("// - const STATIC_VALUE: &'static str = \"static string\";".to_string());
5129 suggestions.push("// - return STATIC_VALUE;".to_string());
5130
5131 suggestions
5132 }
5133}
5134
5135impl NetworkTlsFixGenerator {
5136 pub fn new() -> Self {
5138 Self
5139 }
5140
5141 fn is_tls_error(&self, message: &str) -> bool {
5143 (message.contains("TLS")
5144 || message.contains("SSL")
5145 || message.contains("certificate")
5146 || message.contains("cert")
5147 || message.contains("handshake"))
5148 && (message.contains("validation")
5149 || message.contains("verify")
5150 || message.contains("invalid")
5151 || message.contains("expired")
5152 || message.contains("self-signed")
5153 || message.contains("untrusted")
5154 || message.contains("mismatch")
5155 || message.contains("hostname")
5156 || message.contains("common name"))
5157 }
5158
5159 fn extract_hostname(&self, message: &str) -> Option<String> {
5161 let patterns = [
5163 r#"(?:hostname|CN|common name)[\s:]+['"]([\w\.-]+)['"]"#,
5164 r#"(?:hostname|CN|common name)[\s:]+(\w+\.\w+(?:\.\w+)*)"#,
5165 r#"certificate (?:for|issued to) ['"]([\w\.-]+)['"]"#,
5166 r#"certificate (?:for|issued to) (\w+\.\w+(?:\.\w+)*)"#,
5167 ];
5168
5169 for pattern in patterns {
5170 if let Ok(regex) = Regex::new(pattern) {
5171 if let Some(captures) = regex.captures(message) {
5172 if let Some(host_match) = captures.get(1) {
5173 return Some(host_match.as_str().to_string());
5174 }
5175 }
5176 }
5177 }
5178
5179 None
5180 }
5181
5182 fn generate_tls_diagnostics(&self, hostname: Option<&str>) -> Vec<(String, String)> {
5184 let mut diagnostics = Vec::new();
5185
5186 if let Some(host) = hostname {
5187 let openssl_cmd = format!(
5189 "openssl s_client -connect {}:443 -servername {}",
5190 host, host
5191 );
5192 let openssl_explanation = format!("Check TLS certificate for {}", host);
5193 diagnostics.push((openssl_cmd, openssl_explanation));
5194
5195 let expiry_cmd = format!(
5197 "echo | openssl s_client -connect {}:443 2>/dev/null | openssl x509 -noout -dates",
5198 host
5199 );
5200 let expiry_explanation = format!("Check certificate expiration dates for {}", host);
5201 diagnostics.push((expiry_cmd, expiry_explanation));
5202
5203 let chain_cmd = format!("echo | openssl s_client -connect {}:443 -showcerts", host);
5205 let chain_explanation = format!("Check certificate chain for {}", host);
5206 diagnostics.push((chain_cmd, chain_explanation));
5207 } else {
5208 diagnostics.push((
5210 "openssl version".to_string(),
5211 "Check OpenSSL version".to_string(),
5212 ));
5213 diagnostics.push((
5214 "ls -la /etc/ssl/certs".to_string(),
5215 "List system certificates".to_string(),
5216 ));
5217 }
5218
5219 diagnostics
5220 }
5221
5222 fn generate_tls_fix(
5224 &self,
5225 message: &str,
5226 hostname: Option<&str>,
5227 ) -> Vec<(String, String, String)> {
5228 let mut fixes = Vec::new();
5229
5230 if message.contains("self-signed") || message.contains("untrusted") {
5232 if let Some(host) = hostname {
5233 fixes.push((
5234 format!("Add certificate for {} to trusted certificates", host),
5235 format!("The TLS certificate for {} is self-signed or from an untrusted issuer.", host),
5236 format!("# Download the certificate:\nopenssl s_client -connect {}:443 -servername {} </dev/null 2>/dev/null | openssl x509 -outform PEM > {}.pem\n\n# Add to trusted certificates:\nsudo cp {}.pem /usr/local/share/ca-certificates/\nsudo update-ca-certificates", host, host, host, host)
5237 ));
5238 } else {
5239 fixes.push((
5240 "Add certificate to trusted certificates".to_string(),
5241 "The TLS certificate is self-signed or from an untrusted issuer.".to_string(),
5242 "# Download the certificate:\nopenssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -outform PEM > cert.pem\n\n# Add to trusted certificates:\nsudo cp cert.pem /usr/local/share/ca-certificates/\nsudo update-ca-certificates".to_string()
5243 ));
5244 }
5245 }
5246 else if message.contains("expired") {
5248 if let Some(host) = hostname {
5249 fixes.push((
5250 format!("Certificate for {} has expired", host),
5251 format!("The TLS certificate for {} has expired and needs to be renewed.", host),
5252 format!("# Check certificate expiration:\necho | openssl s_client -connect {}:443 2>/dev/null | openssl x509 -noout -dates\n\n# If you control the server, renew the certificate\n# If not, contact the server administrator", host)
5253 ));
5254 } else {
5255 fixes.push((
5256 "Certificate has expired".to_string(),
5257 "The TLS certificate has expired and needs to be renewed.".to_string(),
5258 "# Check certificate expiration:\necho | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates\n\n# If you control the server, renew the certificate\n# If not, contact the server administrator".to_string()
5259 ));
5260 }
5261 }
5262 else if message.contains("mismatch")
5264 || message.contains("hostname")
5265 || message.contains("common name")
5266 {
5267 if let Some(host) = hostname {
5268 fixes.push((
5269 format!("Hostname mismatch for {}", host),
5270 format!("The TLS certificate for {} does not match the hostname being used.", host),
5271 format!("# Check certificate subject and alternative names:\necho | openssl s_client -connect {}:443 2>/dev/null | openssl x509 -noout -text | grep -A1 'Subject:'\necho | openssl s_client -connect {}:443 2>/dev/null | openssl x509 -noout -text | grep -A1 'Alternative Name'\n\n# Use the correct hostname in your request\n# Or add the hostname to your /etc/hosts file", host, host)
5272 ));
5273 } else {
5274 fixes.push((
5275 "Hostname mismatch".to_string(),
5276 "The TLS certificate does not match the hostname being used.".to_string(),
5277 "# Check certificate subject and alternative names:\necho | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -text | grep -A1 'Subject:'\necho | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -text | grep -A1 'Alternative Name'\n\n# Use the correct hostname in your request\n# Or add the hostname to your /etc/hosts file".to_string()
5278 ));
5279 }
5280 }
5281 else if let Some(host) = hostname {
5283 fixes.push((
5284 format!("TLS certificate issue with {}", host),
5285 format!("There is a TLS certificate validation issue with {}.", host),
5286 format!("# Check the certificate:\nopenssl s_client -connect {}:443 -servername {}\n\n# Update your system's CA certificates:\nsudo update-ca-certificates\n\n# If using a custom CA bundle, make sure it's up to date", host, host)
5287 ));
5288 } else {
5289 fixes.push((
5290 "TLS certificate validation issue".to_string(),
5291 "There is a TLS certificate validation issue.".to_string(),
5292 "# Update your system's CA certificates:\nsudo update-ca-certificates\n\n# If using a custom CA bundle, make sure it's up to date".to_string()
5293 ));
5294 }
5295
5296 fixes
5297 }
5298}
5299
5300impl FixGenerator for NetworkConnectionFixGenerator {
5301 fn generate_fix(
5302 &self,
5303 error: &DecrustError,
5304 params: &ExtractedParameters,
5305 _source_code_context: Option<&str>,
5306 ) -> Option<Autocorrection> {
5307 let message = match error {
5309 DecrustError::Network { kind, url, .. } => {
5310 let mut msg = format!("{} network error", kind);
5311 if let Some(u) = url {
5312 msg.push_str(&format!(" for URL: {}", u));
5313 }
5314 msg
5315 }
5316 _ => {
5317 let msg = params.values.get("message")?;
5318 if !self.is_connection_error(msg) && !self.is_dns_error(msg) {
5319 return None;
5320 }
5321 msg.clone()
5322 }
5323 };
5324
5325 let host = self.extract_host(&message);
5327 let port = self.extract_port(&message);
5328
5329 let diagnostics = self.generate_connection_diagnostics(host.as_deref(), port);
5331
5332 let fixes = self.generate_connection_fix(&message, host.as_deref(), port);
5334
5335 if fixes.is_empty() {
5336 return None;
5337 }
5338
5339 let (title, explanation, steps) = &fixes[0];
5341
5342 let mut commands = Vec::new();
5344 for (cmd, _) in &diagnostics {
5345 commands.push(cmd.clone());
5346 }
5347
5348 Some(Autocorrection {
5350 description: title.clone(),
5351 fix_type: FixType::ManualInterventionRequired,
5352 confidence: 0.7,
5353 details: Some(FixDetails::SuggestCommand {
5354 command: commands.join(" && "),
5355 explanation: format!("{}. {}", explanation, steps),
5356 }),
5357 diff_suggestion: None,
5358 commands_to_apply: commands,
5359 targets_error_code: Some("network_connection_error".to_string()),
5360 })
5361 }
5362
5363 fn name(&self) -> &'static str {
5364 "NetworkConnectionFixGenerator"
5365 }
5366}
5367
5368impl FixGenerator for NetworkTlsFixGenerator {
5369 fn generate_fix(
5370 &self,
5371 error: &DecrustError,
5372 params: &ExtractedParameters,
5373 _source_code_context: Option<&str>,
5374 ) -> Option<Autocorrection> {
5375 let message = match error {
5377 DecrustError::Network { kind, url, .. } => {
5378 if !kind.contains("TLS") && !kind.contains("SSL") {
5379 return None;
5380 }
5381
5382 let mut msg = format!("{} network error", kind);
5383 if let Some(u) = url {
5384 msg.push_str(&format!(" for URL: {}", u));
5385 }
5386 msg
5387 }
5388 _ => {
5389 let msg = params.values.get("message")?;
5390 if !self.is_tls_error(msg) {
5391 return None;
5392 }
5393 msg.clone()
5394 }
5395 };
5396
5397 let hostname = self.extract_hostname(&message);
5399
5400 let diagnostics = self.generate_tls_diagnostics(hostname.as_deref());
5402
5403 let fixes = self.generate_tls_fix(&message, hostname.as_deref());
5405
5406 if fixes.is_empty() {
5407 return None;
5408 }
5409
5410 let (title, explanation, steps) = &fixes[0];
5412
5413 let mut commands = Vec::new();
5415 for (cmd, _) in &diagnostics {
5416 commands.push(cmd.clone());
5417 }
5418
5419 Some(Autocorrection {
5421 description: title.clone(),
5422 fix_type: FixType::ManualInterventionRequired,
5423 confidence: 0.8,
5424 details: Some(FixDetails::SuggestCommand {
5425 command: commands.join(" && "),
5426 explanation: format!("{}. {}", explanation, steps),
5427 }),
5428 diff_suggestion: None,
5429 commands_to_apply: commands,
5430 targets_error_code: Some("tls_certificate_error".to_string()),
5431 })
5432 }
5433
5434 fn name(&self) -> &'static str {
5435 "NetworkTlsFixGenerator"
5436 }
5437}
5438
5439impl FixGenerator for ReturnLocalReferenceFixGenerator {
5440 fn generate_fix(
5441 &self,
5442 _error: &DecrustError,
5443 params: &ExtractedParameters,
5444 source_code_context: Option<&str>,
5445 ) -> Option<Autocorrection> {
5446 let message = params.values.get("message")?;
5448
5449 if !self.is_return_local_reference_error(message) {
5451 return None;
5452 }
5453
5454 let variable_name = self.extract_variable_name(message);
5456
5457 let suggestions = self.generate_fix_suggestions(variable_name.as_deref());
5459
5460 let file_path = params
5462 .values
5463 .get("file_path")
5464 .cloned()
5465 .unwrap_or_else(|| "unknown_file.rs".to_string());
5466
5467 let line = params
5468 .values
5469 .get("line")
5470 .and_then(|l| l.parse::<usize>().ok())
5471 .unwrap_or(1);
5472
5473 let explanation = format!(
5475 "Error E0515: You're returning a reference to a local variable, which will be dropped when the function exits.\n\n\
5476 This is a fundamental Rust ownership issue. The compiler prevents this because it would lead to a dangling reference.\n\n\
5477 Consider these solutions:\n{}",
5478 suggestions.join("\n")
5479 );
5480
5481 let code_snippet = if let Some(context) = source_code_context {
5483 context.to_string()
5484 } else {
5485 "// Function returning a local reference\nfn example() -> &str {\n let local = String::from(\"local value\");\n &local // ERROR: returns a reference to data owned by the current function\n}".to_string()
5486 };
5487
5488 Some(Autocorrection {
5490 description: "Fix returning reference to local variable (E0515)".to_string(),
5491 fix_type: FixType::ManualInterventionRequired,
5492 confidence: 0.9,
5493 details: Some(FixDetails::SuggestCodeChange {
5494 file_path: PathBuf::from(file_path),
5495 line_hint: line,
5496 suggested_code_snippet: code_snippet,
5497 explanation,
5498 }),
5499 diff_suggestion: None,
5500 commands_to_apply: vec![],
5501 targets_error_code: Some("E0515".to_string()),
5502 })
5503 }
5504
5505 fn name(&self) -> &'static str {
5506 "ReturnLocalReferenceFixGenerator"
5507 }
5508}
5509
5510impl FixGenerator for UnstableFeatureFixGenerator {
5511 fn generate_fix(
5512 &self,
5513 _error: &DecrustError,
5514 params: &ExtractedParameters,
5515 source_code_context: Option<&str>,
5516 ) -> Option<Autocorrection> {
5517 let message = params.values.get("message")?;
5519
5520 if !self.is_unstable_feature_error(message) {
5522 return None;
5523 }
5524
5525 let feature_name = self.extract_feature_name(message);
5527
5528 let suggestions = self.generate_fix_suggestions(feature_name.as_deref());
5530
5531 let file_path = params
5533 .values
5534 .get("file_path")
5535 .cloned()
5536 .unwrap_or_else(|| "unknown_file.rs".to_string());
5537
5538 let line = params
5539 .values
5540 .get("line")
5541 .and_then(|l| l.parse::<usize>().ok())
5542 .unwrap_or(1);
5543
5544 let explanation = format!(
5546 "Error E0658: You're using an unstable feature that requires a nightly compiler or explicit opt-in.\n\n\
5547 Rust's stability guarantees mean that some features are only available on the nightly channel \
5548 until they're deemed stable enough for general use.\n\n\
5549 Consider these solutions:\n{}",
5550 suggestions.join("\n")
5551 );
5552
5553 let code_snippet = if let Some(context) = source_code_context {
5555 context.to_string()
5556 } else if let Some(feature) = &feature_name {
5557 format!("// Using unstable feature\nfn example() {{\n // Code using the unstable feature '{}'\n}}", feature)
5558 } else {
5559 "// Using unstable feature\nfn example() {\n // Code using an unstable feature\n}"
5560 .to_string()
5561 };
5562
5563 let mut commands = Vec::new();
5565
5566 commands.push("rustup default nightly".to_string());
5568
5569 commands.push("rustc --version".to_string());
5571
5572 Some(Autocorrection {
5574 description: "Fix unstable feature usage (E0658)".to_string(),
5575 fix_type: FixType::ManualInterventionRequired,
5576 confidence: 0.9,
5577 details: Some(FixDetails::SuggestCodeChange {
5578 file_path: PathBuf::from(file_path),
5579 line_hint: line,
5580 suggested_code_snippet: code_snippet,
5581 explanation,
5582 }),
5583 diff_suggestion: None,
5584 commands_to_apply: commands,
5585 targets_error_code: Some("E0658".to_string()),
5586 })
5587 }
5588
5589 fn name(&self) -> &'static str {
5590 "UnstableFeatureFixGenerator"
5591 }
5592}
5593
5594impl FixGenerator for InvalidArgumentCountFixGenerator {
5595 fn generate_fix(
5596 &self,
5597 _error: &DecrustError,
5598 params: &ExtractedParameters,
5599 source_code_context: Option<&str>,
5600 ) -> Option<Autocorrection> {
5601 let message = params.values.get("message")?;
5603
5604 if !self.is_invalid_argument_count_error(message) {
5606 return None;
5607 }
5608
5609 let function_name = self.extract_function_name(message);
5611 let arg_counts = self.extract_argument_counts(message);
5612
5613 let suggestions = self.generate_fix_suggestions(function_name.as_deref(), arg_counts);
5615
5616 let file_path = params
5618 .values
5619 .get("file_path")
5620 .cloned()
5621 .unwrap_or_else(|| "unknown_file.rs".to_string());
5622
5623 let line = params
5624 .values
5625 .get("line")
5626 .and_then(|l| l.parse::<usize>().ok())
5627 .unwrap_or(1);
5628
5629 let explanation = format!(
5631 "Error E0061: This function call has an incorrect number of arguments.\n\n\
5632 {}.\n\n\
5633 Consider these solutions:\n{}",
5634 if let Some((expected, actual)) = arg_counts {
5635 if actual < expected {
5636 format!("The function expects {} arguments, but you provided {}. You need to add {} more argument(s).",
5637 expected, actual, expected - actual)
5638 } else {
5639 format!("The function expects {} arguments, but you provided {}. You need to remove {} extra argument(s). Remove the unnecessary arguments.",
5640 expected, actual, actual - expected)
5641 }
5642 } else {
5643 "The function is being called with the wrong number of arguments".to_string()
5644 },
5645 suggestions.join("\n")
5646 );
5647
5648 let code_snippet = if let Some(context) = source_code_context {
5650 context.to_string()
5651 } else if let Some(fn_name) = &function_name {
5652 if let Some((expected, actual)) = arg_counts {
5653 if actual < expected {
5654 let mut args = Vec::new();
5656 for i in 0..actual {
5657 args.push(format!("arg{}", i + 1));
5658 }
5659 format!("// Function call with too few arguments\n{}({}) // ERROR: missing {} argument(s)",
5660 fn_name, args.join(", "), expected - actual)
5661 } else {
5662 let mut args = Vec::new();
5664 for i in 0..actual {
5665 args.push(format!("arg{}", i + 1));
5666 }
5667 format!("// Function call with too many arguments\n{}({}) // ERROR: has {} extra argument(s)",
5668 fn_name, args.join(", "), actual - expected)
5669 }
5670 } else {
5671 format!("// Function call with incorrect number of arguments\n{}(...) // ERROR: wrong number of arguments", fn_name)
5672 }
5673 } else {
5674 "// Function call with incorrect number of arguments\nfunction_name(...) // ERROR: wrong number of arguments".to_string()
5675 };
5676
5677 Some(Autocorrection {
5679 description: "Fix function call with incorrect number of arguments (E0061)".to_string(),
5680 fix_type: FixType::ManualInterventionRequired,
5681 confidence: 0.9,
5682 details: Some(FixDetails::SuggestCodeChange {
5683 file_path: PathBuf::from(file_path),
5684 line_hint: line,
5685 suggested_code_snippet: code_snippet,
5686 explanation,
5687 }),
5688 diff_suggestion: None,
5689 commands_to_apply: vec![],
5690 targets_error_code: Some("E0061".to_string()),
5691 })
5692 }
5693
5694 fn name(&self) -> &'static str {
5695 "InvalidArgumentCountFixGenerator"
5696 }
5697}
5698
5699impl FixGenerator for UnsafeUnwrapFixGenerator {
5700 fn generate_fix(
5701 &self,
5702 _error: &DecrustError,
5703 _params: &ExtractedParameters,
5704 source_code_context: Option<&str>,
5705 ) -> Option<Autocorrection> {
5706 let code = source_code_context?;
5708
5709 let (fixed_code, explanation, diff) = self.generate_fix(code)?;
5711
5712 let file_path = PathBuf::from("unknown_file.rs");
5714 let line_hint = 1;
5715
5716 Some(Autocorrection {
5718 description: "Replace unsafe unwrap() or expect() with explicit error handling"
5719 .to_string(),
5720 fix_type: FixType::TextReplacement,
5721 confidence: 0.8,
5722 details: Some(FixDetails::SuggestCodeChange {
5723 file_path,
5724 line_hint,
5725 suggested_code_snippet: fixed_code,
5726 explanation,
5727 }),
5728 diff_suggestion: Some(diff),
5729 commands_to_apply: vec![],
5730 targets_error_code: Some("unsafe_unwrap".to_string()),
5731 })
5732 }
5733
5734 fn name(&self) -> &'static str {
5735 "UnsafeUnwrapFixGenerator"
5736 }
5737}
5738
5739impl FixGenerator for QuestionMarkPropagationFixGenerator {
5740 fn generate_fix(
5741 &self,
5742 _error: &DecrustError,
5743 _params: &ExtractedParameters,
5744 source_code_context: Option<&str>,
5745 ) -> Option<Autocorrection> {
5746 let code = source_code_context?;
5748
5749 let (fixed_code, explanation, diff) = self.generate_fix(code)?;
5751
5752 let file_path = PathBuf::from("unknown_file.rs");
5754 let line_hint = 1;
5755
5756 Some(Autocorrection {
5758 description:
5759 "Fix question mark operator usage in function without Result/Option return type"
5760 .to_string(),
5761 fix_type: FixType::TextReplacement,
5762 confidence: 0.9,
5763 details: Some(FixDetails::SuggestCodeChange {
5764 file_path,
5765 line_hint,
5766 suggested_code_snippet: fixed_code,
5767 explanation,
5768 }),
5769 diff_suggestion: Some(diff),
5770 commands_to_apply: vec![],
5771 targets_error_code: Some("E0277".to_string()), })
5773 }
5774
5775 fn name(&self) -> &'static str {
5776 "QuestionMarkPropagationFixGenerator"
5777 }
5778}
5779
5780impl FixGenerator for MissingOkErrFixGenerator {
5781 fn generate_fix(
5782 &self,
5783 _error: &DecrustError,
5784 _params: &ExtractedParameters,
5785 source_code_context: Option<&str>,
5786 ) -> Option<Autocorrection> {
5787 let code = source_code_context?;
5789
5790 let (fixed_code, explanation, diff) = self.generate_fix(code)?;
5792
5793 let file_path = PathBuf::from("unknown_file.rs");
5795 let line_hint = 1;
5796
5797 Some(Autocorrection {
5799 description: "Add missing match arms for Result/Option".to_string(),
5800 fix_type: FixType::TextReplacement,
5801 confidence: 0.9,
5802 details: Some(FixDetails::SuggestCodeChange {
5803 file_path,
5804 line_hint,
5805 suggested_code_snippet: fixed_code,
5806 explanation,
5807 }),
5808 diff_suggestion: Some(diff),
5809 commands_to_apply: vec![],
5810 targets_error_code: Some("incomplete_match".to_string()),
5811 })
5812 }
5813
5814 fn name(&self) -> &'static str {
5815 "MissingOkErrFixGenerator"
5816 }
5817}
5818
5819impl FixGenerator for DivisionByZeroFixGenerator {
5820 fn generate_fix(
5821 &self,
5822 _error: &DecrustError,
5823 _params: &ExtractedParameters,
5824 source_code_context: Option<&str>,
5825 ) -> Option<Autocorrection> {
5826 let code = source_code_context?;
5828
5829 let (fixed_code, explanation, diff) = self.generate_fix(code)?;
5831
5832 let file_path = PathBuf::from("unknown_file.rs");
5834 let line_hint = 1;
5835
5836 Some(Autocorrection {
5838 description: "Prevent division by zero panic".to_string(),
5839 fix_type: FixType::TextReplacement,
5840 confidence: 0.8,
5841 details: Some(FixDetails::SuggestCodeChange {
5842 file_path,
5843 line_hint,
5844 suggested_code_snippet: fixed_code,
5845 explanation,
5846 }),
5847 diff_suggestion: Some(diff),
5848 commands_to_apply: vec![],
5849 targets_error_code: Some("division_by_zero".to_string()),
5850 })
5851 }
5852
5853 fn name(&self) -> &'static str {
5854 "DivisionByZeroFixGenerator"
5855 }
5856}
5857
5858impl FixGenerator for RuntimePanicFixGenerator {
5859 fn generate_fix(
5860 &self,
5861 _error: &DecrustError,
5862 _params: &ExtractedParameters,
5863 source_code_context: Option<&str>,
5864 ) -> Option<Autocorrection> {
5865 let code = source_code_context?;
5867
5868 let (fixed_code, explanation, diff) = self.generate_fix(code)?;
5870
5871 let file_path = PathBuf::from("unknown_file.rs");
5873 let line_hint = 1;
5874
5875 Some(Autocorrection {
5877 description: "Prevent runtime panic".to_string(),
5878 fix_type: FixType::TextReplacement,
5879 confidence: 0.7,
5880 details: Some(FixDetails::SuggestCodeChange {
5881 file_path,
5882 line_hint,
5883 suggested_code_snippet: fixed_code,
5884 explanation,
5885 }),
5886 diff_suggestion: Some(diff),
5887 commands_to_apply: vec![],
5888 targets_error_code: Some("runtime_panic".to_string()),
5889 })
5890 }
5891
5892 fn name(&self) -> &'static str {
5893 "RuntimePanicFixGenerator"
5894 }
5895}
5896
5897impl FixGenerator for ClosureCaptureLifetimeFixGenerator {
5898 fn generate_fix(
5899 &self,
5900 _error: &DecrustError,
5901 params: &ExtractedParameters,
5902 source_code_context: Option<&str>,
5903 ) -> Option<Autocorrection> {
5904 let message = params.values.get("message")?;
5905
5906 if !self.is_closure_capture_error(message) {
5907 return None;
5908 }
5909
5910 let variable_name = self
5911 .extract_captured_variable(message)
5912 .unwrap_or_else(|| "captured_var".to_string());
5913
5914 let file_path = params
5915 .values
5916 .get("file_path")
5917 .cloned()
5918 .unwrap_or_else(|| "src/main.rs".to_string());
5919
5920 let line = params
5921 .values
5922 .get("line")
5923 .and_then(|l| l.parse::<usize>().ok())
5924 .unwrap_or(1);
5925
5926 let fixes = self.generate_closure_fixes(&variable_name, source_code_context);
5927
5928 let explanation = format!(
5929 "Error E0373: The closure may outlive the current function because it captures `{}` by reference.\n\n\
5930 Rust requires that all data referenced by a closure must live at least as long as the closure itself.\n\n\
5931 Solutions:\n{}",
5932 variable_name, fixes.join("\n")
5933 );
5934
5935 Some(Autocorrection {
5936 description: format!("Fix closure capture lifetime issue for `{}`", variable_name),
5937 fix_type: FixType::ManualInterventionRequired,
5938 confidence: 0.85,
5939 details: Some(FixDetails::SuggestCodeChange {
5940 file_path: PathBuf::from(file_path),
5941 line_hint: line,
5942 suggested_code_snippet: fixes.join("\n"),
5943 explanation,
5944 }),
5945 diff_suggestion: Some(format!(
5946 "// Example transformation:\n\
5947 -let closure = || {{ /* uses {} */ }};\n\
5948 +let {}_owned = {}.clone();\n\
5949 +let closure = move || {{ /* uses {}_owned */ }};",
5950 variable_name, variable_name, variable_name, variable_name
5951 )),
5952 commands_to_apply: vec![],
5953 targets_error_code: Some("E0373".to_string()),
5954 })
5955 }
5956
5957 fn name(&self) -> &'static str {
5958 "ClosureCaptureLifetimeFixGenerator"
5959 }
5960}
5961
5962impl FixGenerator for RecursiveTypeFixGenerator {
5963 fn generate_fix(
5964 &self,
5965 _error: &DecrustError,
5966 params: &ExtractedParameters,
5967 source_code_context: Option<&str>,
5968 ) -> Option<Autocorrection> {
5969 let message = params.values.get("message")?;
5970
5971 if !self.is_recursive_type_error(message) {
5972 return None;
5973 }
5974
5975 let type_name = self
5976 .extract_type_name(message)
5977 .unwrap_or_else(|| "RecursiveType".to_string());
5978
5979 let file_path = params
5980 .values
5981 .get("file_path")
5982 .cloned()
5983 .unwrap_or_else(|| "src/main.rs".to_string());
5984
5985 let line = params
5986 .values
5987 .get("line")
5988 .and_then(|l| l.parse::<usize>().ok())
5989 .unwrap_or(1);
5990
5991 let fixes = self.generate_recursive_fixes(&type_name, source_code_context);
5992
5993 let explanation = format!(
5994 "Error E0072: Recursive type `{}` has infinite size.\n\n\
5995 Rust cannot determine the memory layout of types that contain themselves directly. \
5996 You need indirection through heap allocation (Box<T>) or shared ownership (Rc<T>/Arc<T>).\n\n\
5997 Solutions:\n{}",
5998 type_name, fixes.join("\n")
5999 );
6000
6001 Some(Autocorrection {
6002 description: format!("Fix recursive type definition for `{}`", type_name),
6003 fix_type: FixType::ManualInterventionRequired,
6004 confidence: 0.90,
6005 details: Some(FixDetails::SuggestCodeChange {
6006 file_path: PathBuf::from(file_path),
6007 line_hint: line,
6008 suggested_code_snippet: fixes.join("\n"),
6009 explanation,
6010 }),
6011 diff_suggestion: Some(format!(
6012 "// Example transformation:\n\
6013 -struct {} {{ next: {} }}\n\
6014 +struct {} {{ next: Option<Box<{}>> }}",
6015 type_name, type_name, type_name, type_name
6016 )),
6017 commands_to_apply: vec![],
6018 targets_error_code: Some("E0072".to_string()),
6019 })
6020 }
6021
6022 fn name(&self) -> &'static str {
6023 "RecursiveTypeFixGenerator"
6024 }
6025}
6026
6027impl FixGenerator for UnusedMutFixGenerator {
6028 fn generate_fix(
6029 &self,
6030 _error: &DecrustError,
6031 _params: &ExtractedParameters,
6032 source_code_context: Option<&str>,
6033 ) -> Option<Autocorrection> {
6034 let code = source_code_context?;
6036
6037 if !self.is_unused_mut(code) {
6039 return None;
6040 }
6041
6042 let variable = self.extract_variable_name(code)?;
6044
6045 let (fixed_code, explanation, diff) = self.generate_unused_mut_fix(code, &variable);
6047
6048 Some(Autocorrection {
6050 description: format!("Remove unused 'mut' keyword for variable '{}'", variable),
6051 fix_type: FixType::TextReplacement,
6052 confidence: 0.8, details: Some(FixDetails::SuggestCodeChange {
6054 file_path: PathBuf::from("unknown_file.rs"), line_hint: 1, suggested_code_snippet: fixed_code,
6057 explanation,
6058 }),
6059 diff_suggestion: Some(diff),
6060 commands_to_apply: vec![],
6061 targets_error_code: Some("unused_mut".to_string()),
6062 })
6063 }
6064
6065 fn name(&self) -> &'static str {
6066 "UnusedMutFixGenerator"
6067 }
6068}
6069
6070impl FixGenerator for YamlParseFixGenerator {
6071 fn generate_fix(
6072 &self,
6073 error: &DecrustError,
6074 params: &ExtractedParameters,
6075 _source_code_context: Option<&str>,
6076 ) -> Option<Autocorrection> {
6077 let file_path = params.values.get("file_path")?.clone();
6079
6080 if !file_path.ends_with(".yaml")
6082 && !file_path.ends_with(".yml")
6083 && !file_path.ends_with(".YAML")
6084 && !file_path.ends_with(".YML")
6085 {
6086 return None;
6087 }
6088
6089 let message = match error {
6091 DecrustError::Parse { context_info, .. } => {
6092 if context_info.contains("YAML") {
6093 context_info.clone()
6094 } else {
6095 return None;
6096 }
6097 }
6098 _ => {
6099 let msg = params.values.get("message")?;
6100 if !self.is_yaml_parse_error(msg) {
6101 return None;
6102 }
6103 msg.clone()
6104 }
6105 };
6106
6107 let line_number = self.extract_line_number(&message);
6109 let column_number = self.extract_column_number(&message);
6110 let error_type = self.extract_error_type(&message);
6111
6112 let (command, explanation, suggestion) =
6114 self.generate_yaml_fix(&file_path, line_number, column_number, error_type.clone());
6115
6116 let full_explanation = if let Some(sugg) = suggestion {
6118 format!("{}. {}", explanation, sugg)
6119 } else {
6120 explanation
6121 };
6122
6123 Some(Autocorrection {
6125 description: format!("Fix YAML parsing error in file: {}", file_path),
6126 fix_type: FixType::ExecuteCommand,
6127 confidence: 0.8,
6128 details: Some(FixDetails::SuggestCommand {
6129 command: command.clone(),
6130 explanation: full_explanation,
6131 }),
6132 diff_suggestion: None,
6133 commands_to_apply: vec![command],
6134 targets_error_code: Some("yaml_parse_error".to_string()),
6135 })
6136 }
6137
6138 fn name(&self) -> &'static str {
6139 "YamlParseFixGenerator"
6140 }
6141}
6142
6143impl FixGenerator for JsonParseFixGenerator {
6144 fn generate_fix(
6145 &self,
6146 error: &DecrustError,
6147 params: &ExtractedParameters,
6148 _source_code_context: Option<&str>,
6149 ) -> Option<Autocorrection> {
6150 let file_path = params.values.get("file_path")?.clone();
6152
6153 if !file_path.ends_with(".json") && !file_path.ends_with(".JSON") {
6155 return None;
6156 }
6157
6158 let message = match error {
6160 DecrustError::Parse { context_info, .. } => {
6161 if context_info.contains("JSON") {
6162 context_info.clone()
6163 } else {
6164 return None;
6165 }
6166 }
6167 _ => {
6168 let msg = params.values.get("message")?;
6169 if !self.is_json_parse_error(msg) {
6170 return None;
6171 }
6172 msg.clone()
6173 }
6174 };
6175
6176 let line_number = self.extract_line_number(&message);
6178 let column_number = self.extract_column_number(&message);
6179 let expected_token = self.extract_expected_token(&message);
6180
6181 let (command, explanation, suggestion) = self.generate_json_fix(
6183 &file_path,
6184 line_number,
6185 column_number,
6186 expected_token.clone(),
6187 );
6188
6189 let full_explanation = if let Some(sugg) = suggestion {
6191 format!("{}. {}", explanation, sugg)
6192 } else {
6193 explanation
6194 };
6195
6196 Some(Autocorrection {
6198 description: format!("Fix JSON parsing error in file: {}", file_path),
6199 fix_type: FixType::ExecuteCommand,
6200 confidence: 0.8,
6201 details: Some(FixDetails::SuggestCommand {
6202 command: command.clone(),
6203 explanation: full_explanation,
6204 }),
6205 diff_suggestion: None,
6206 commands_to_apply: vec![command],
6207 targets_error_code: Some("json_parse_error".to_string()),
6208 })
6209 }
6210
6211 fn name(&self) -> &'static str {
6212 "JsonParseFixGenerator"
6213 }
6214}
6215
6216impl ConfigMissingKeyFixGenerator {
6217 pub fn new() -> Self {
6219 Self
6220 }
6221
6222 fn is_missing_key_error(&self, message: &str) -> bool {
6224 message.contains("missing key")
6225 || message.contains("required key")
6226 || message.contains("key not found")
6227 || message.contains("missing field")
6228 || message.contains("required field")
6229 || message.contains("field not found")
6230 }
6231
6232 fn extract_key_name(&self, message: &str) -> Option<String> {
6234 let patterns = [
6236 r#"missing key[:\s]+["'](.*?)["']"#,
6237 r#"required key[:\s]+["'](.*?)["']"#,
6238 r#"key not found[:\s]+["'](.*?)["']"#,
6239 r#"missing field[:\s]+(.*?)(?:\s|$)"#,
6240 r#"required field[:\s]+["'](.*?)["']"#,
6241 r#"field not found[:\s]+["'](.*?)["']"#,
6242 r#"required key not found[:\s]+(.*?)(?:\s|$)"#,
6243 ];
6244
6245 for pattern in patterns {
6246 if let Ok(regex) = Regex::new(pattern) {
6247 if let Some(captures) = regex.captures(message) {
6248 if let Some(key_match) = captures.get(1) {
6249 return Some(key_match.as_str().to_string());
6250 }
6251 }
6252 }
6253 }
6254
6255 None
6256 }
6257
6258 fn determine_file_format(&self, file_path: &str) -> Option<&'static str> {
6260 if file_path.ends_with(".json") || file_path.ends_with(".JSON") {
6261 Some("json")
6262 } else if file_path.ends_with(".yaml")
6263 || file_path.ends_with(".yml")
6264 || file_path.ends_with(".YAML")
6265 || file_path.ends_with(".YML")
6266 {
6267 Some("yaml")
6268 } else if file_path.ends_with(".toml") || file_path.ends_with(".TOML") {
6269 Some("toml")
6270 } else {
6271 None
6272 }
6273 }
6274
6275 fn generate_default_value(&self, key_name: &str, format: &str) -> String {
6277 let default_value = if key_name.contains("path")
6279 || key_name.contains("dir")
6280 || key_name.contains("directory")
6281 {
6282 "\"/path/to/directory\""
6283 } else if key_name.contains("file") {
6284 "\"/path/to/file\""
6285 } else if key_name.contains("url") || key_name.contains("uri") {
6286 "\"https://example.com\""
6287 } else if key_name.contains("port") {
6288 "8080"
6289 } else if key_name.contains("host") {
6290 "\"localhost\""
6291 } else if key_name.contains("timeout")
6292 || key_name.contains("interval")
6293 || key_name.contains("duration")
6294 {
6295 "60"
6296 } else if key_name.contains("enabled")
6297 || key_name.contains("disabled")
6298 || key_name.contains("active")
6299 || key_name.contains("flag")
6300 {
6301 match format {
6302 "json" | "yaml" => "true",
6303 "toml" => "true",
6304 _ => "true",
6305 }
6306 } else if key_name.contains("count")
6307 || key_name.contains("limit")
6308 || key_name.contains("max")
6309 || key_name.contains("min")
6310 {
6311 "10"
6312 } else {
6313 match format {
6314 "json" | "yaml" => "\"value\"",
6315 "toml" => "\"value\"",
6316 _ => "\"value\"",
6317 }
6318 };
6319
6320 default_value.to_string()
6321 }
6322
6323 fn generate_missing_key_fix(
6325 &self,
6326 file_path: &str,
6327 key_name: &str,
6328 format: &str,
6329 ) -> (String, String, String) {
6330 let default_value = self.generate_default_value(key_name, format);
6331
6332 let (command, explanation, diff) = match format {
6333 "json" => {
6334 let command = format!(
6335 "echo 'Add the missing key \"{}\" to {}'",
6336 key_name, file_path
6337 );
6338 let explanation = format!(
6339 "The configuration file '{}' is missing the required key '{}'. Add this key with an appropriate value.",
6340 file_path, key_name
6341 );
6342 let diff = format!(" \"{}\": {}", key_name, default_value);
6343 (command, explanation, diff)
6344 }
6345 "yaml" => {
6346 let command = format!(
6347 "echo 'Add the missing key \"{}\" to {}'",
6348 key_name, file_path
6349 );
6350 let explanation = format!(
6351 "The configuration file '{}' is missing the required key '{}'. Add this key with an appropriate value.",
6352 file_path, key_name
6353 );
6354 let diff = format!("{}: {}", key_name, default_value);
6355 (command, explanation, diff)
6356 }
6357 "toml" => {
6358 let command = format!(
6359 "echo 'Add the missing key \"{}\" to {}'",
6360 key_name, file_path
6361 );
6362 let explanation = format!(
6363 "The configuration file '{}' is missing the required key '{}'. Add this key with an appropriate value.",
6364 file_path, key_name
6365 );
6366 let diff = format!("{} = {}", key_name, default_value);
6367 (command, explanation, diff)
6368 }
6369 _ => {
6370 let command = format!(
6371 "echo 'Add the missing key \"{}\" to {}'",
6372 key_name, file_path
6373 );
6374 let explanation = format!(
6375 "The configuration file '{}' is missing the required key '{}'. Add this key with an appropriate value.",
6376 file_path, key_name
6377 );
6378 let diff = format!("{} = {}", key_name, default_value);
6379 (command, explanation, diff)
6380 }
6381 };
6382
6383 (command, explanation, diff)
6384 }
6385}
6386
6387impl ConfigSyntaxFixGenerator {
6388 pub fn is_json_syntax_error(&self, message: &str, file_path: &str) -> bool {
6390 let is_json_file = file_path.ends_with(".json") || file_path.ends_with(".JSON");
6392
6393 let has_json_keywords = message.contains("JSON")
6395 || message.contains("json")
6396 || message.contains("syntax error")
6397 || message.contains("invalid")
6398 || message.contains("failed to parse");
6399
6400 is_json_file && has_json_keywords
6401 }
6402
6403 pub fn is_yaml_syntax_error(&self, message: &str, file_path: &str) -> bool {
6405 let is_yaml_file = file_path.ends_with(".yml")
6407 || file_path.ends_with(".yaml")
6408 || file_path.ends_with(".YML")
6409 || file_path.ends_with(".YAML");
6410
6411 let has_yaml_keywords = message.contains("YAML")
6413 || message.contains("yaml")
6414 || message.contains("syntax error")
6415 || message.contains("invalid")
6416 || message.contains("failed to parse");
6417
6418 is_yaml_file && has_yaml_keywords
6419 }
6420
6421 pub fn is_toml_syntax_error(&self, message: &str, file_path: &str) -> bool {
6423 let is_toml_file = file_path.ends_with(".toml") || file_path.ends_with(".TOML");
6425
6426 let has_toml_keywords = message.contains("TOML")
6428 || message.contains("toml")
6429 || message.contains("syntax error")
6430 || message.contains("invalid")
6431 || message.contains("failed to parse");
6432
6433 is_toml_file && has_toml_keywords
6434 }
6435
6436 fn extract_line_number(&self, message: &str) -> Option<usize> {
6438 let patterns = [
6440 r"at line (\d+)",
6441 r"line (\d+)",
6442 r"line: (\d+)",
6443 r"line:(\d+)",
6444 ];
6445
6446 for pattern in patterns {
6447 if let Ok(regex) = Regex::new(pattern) {
6448 if let Some(captures) = regex.captures(message) {
6449 if let Some(line_match) = captures.get(1) {
6450 if let Ok(line) = line_match.as_str().parse::<usize>() {
6451 return Some(line);
6452 }
6453 }
6454 }
6455 }
6456 }
6457
6458 None
6459 }
6460
6461 fn generate_json_fix(&self, file_path: &str, line_number: Option<usize>) -> (String, String) {
6463 let command = format!("jsonlint --fix {}", file_path);
6464 let explanation = if let Some(line) = line_number {
6465 format!("JSON syntax error detected in file '{}' at line {}. This command will attempt to fix the JSON syntax.", file_path, line)
6466 } else {
6467 format!("JSON syntax error detected in file '{}'. This command will attempt to fix the JSON syntax.", file_path)
6468 };
6469
6470 (command, explanation)
6471 }
6472
6473 fn generate_yaml_fix(&self, file_path: &str, line_number: Option<usize>) -> (String, String) {
6475 let command = format!("yamllint {}", file_path);
6476 let explanation = if let Some(line) = line_number {
6477 format!("YAML syntax error detected in file '{}' at line {}. This command will check the YAML syntax and provide detailed error information.", file_path, line)
6478 } else {
6479 format!("YAML syntax error detected in file '{}'. This command will check the YAML syntax and provide detailed error information.", file_path)
6480 };
6481
6482 (command, explanation)
6483 }
6484
6485 fn generate_toml_fix(&self, file_path: &str, line_number: Option<usize>) -> (String, String) {
6487 let command = format!("taplo fmt {}", file_path);
6488 let explanation = if let Some(line) = line_number {
6489 format!("TOML syntax error detected in file '{}' at line {}. This command will format the TOML file and may fix syntax issues.", file_path, line)
6490 } else {
6491 format!("TOML syntax error detected in file '{}'. This command will format the TOML file and may fix syntax issues.", file_path)
6492 };
6493
6494 (command, explanation)
6495 }
6496}
6497
6498impl FixGenerator for ConfigMissingKeyFixGenerator {
6499 fn generate_fix(
6500 &self,
6501 error: &DecrustError,
6502 params: &ExtractedParameters,
6503 _source_code_context: Option<&str>,
6504 ) -> Option<Autocorrection> {
6505 let file_path = match error {
6507 DecrustError::Config {
6508 path: Some(path), ..
6509 } => path.to_string_lossy().to_string(),
6510 _ => params.values.get("file_path")?.clone(),
6511 };
6512
6513 let message = match error {
6515 DecrustError::Config { message, .. } => message.clone(),
6516 _ => params.values.get("message")?.clone(),
6517 };
6518
6519 if !self.is_missing_key_error(&message) {
6521 return None;
6522 }
6523
6524 let key_name = self.extract_key_name(&message)?;
6526
6527 let format = self.determine_file_format(&file_path)?;
6529
6530 let (command, explanation, diff) =
6532 self.generate_missing_key_fix(&file_path, &key_name, format);
6533
6534 Some(Autocorrection {
6536 description: format!(
6537 "Add missing configuration key: {} to {}",
6538 key_name, file_path
6539 ),
6540 fix_type: FixType::TextReplacement,
6541 confidence: 0.7,
6542 details: Some(FixDetails::SuggestCodeChange {
6543 file_path: PathBuf::from(&file_path),
6544 line_hint: 1, suggested_code_snippet: diff.clone(),
6546 explanation,
6547 }),
6548 diff_suggestion: Some(format!("+ {}", diff)),
6549 commands_to_apply: vec![command],
6550 targets_error_code: Some("config_missing_key".to_string()),
6551 })
6552 }
6553
6554 fn name(&self) -> &'static str {
6555 "ConfigMissingKeyFixGenerator"
6556 }
6557}
6558
6559impl FixGenerator for ConfigSyntaxFixGenerator {
6560 fn generate_fix(
6561 &self,
6562 error: &DecrustError,
6563 params: &ExtractedParameters,
6564 _source_code_context: Option<&str>,
6565 ) -> Option<Autocorrection> {
6566 let file_path = match error {
6568 DecrustError::Config {
6569 path: Some(path), ..
6570 } => path.to_string_lossy().to_string(),
6571 _ => params.values.get("file_path")?.clone(),
6572 };
6573
6574 let message = match error {
6576 DecrustError::Config { message, .. } => message.clone(),
6577 DecrustError::Parse { context_info, .. } => context_info.clone(),
6578 _ => params.values.get("message")?.clone(),
6579 };
6580
6581 let line_number = self.extract_line_number(&message);
6583
6584 println!(
6586 "ConfigSyntaxFixGenerator: file_path={}, message={}",
6587 file_path, message
6588 );
6589
6590 let is_json = self.is_json_syntax_error(&message, &file_path);
6592 println!("Is JSON syntax error: {}", is_json);
6593
6594 let is_yaml = self.is_yaml_syntax_error(&message, &file_path);
6596 println!("Is YAML syntax error: {}", is_yaml);
6597
6598 let is_toml = self.is_toml_syntax_error(&message, &file_path);
6600 println!("Is TOML syntax error: {}", is_toml);
6601
6602 let (command, explanation) = if is_json {
6604 self.generate_json_fix(&file_path, line_number)
6605 } else if is_yaml {
6606 self.generate_yaml_fix(&file_path, line_number)
6607 } else if is_toml {
6608 self.generate_toml_fix(&file_path, line_number)
6609 } else {
6610 println!("Not a recognized configuration syntax error");
6612 return None;
6613 };
6614
6615 Some(Autocorrection {
6617 description: format!("Fix syntax error in configuration file: {}", file_path),
6618 fix_type: FixType::ExecuteCommand,
6619 confidence: 0.7,
6620 details: Some(FixDetails::SuggestCommand {
6621 command: command.clone(),
6622 explanation,
6623 }),
6624 diff_suggestion: None,
6625 commands_to_apply: vec![command],
6626 targets_error_code: Some("config_syntax_error".to_string()),
6627 })
6628 }
6629
6630 fn name(&self) -> &'static str {
6631 "ConfigSyntaxFixGenerator"
6632 }
6633}
6634
6635impl FixGenerator for IoPermissionFixGenerator {
6636 fn generate_fix(
6637 &self,
6638 error: &DecrustError,
6639 params: &ExtractedParameters,
6640 _source_code_context: Option<&str>,
6641 ) -> Option<Autocorrection> {
6642 let path = match error {
6644 DecrustError::Io {
6645 path: Some(path), ..
6646 } => path.to_string_lossy().to_string(),
6647 _ => params.values.get("path")?.clone(),
6648 };
6649
6650 let message = match error {
6652 DecrustError::Io { source, .. } => source.to_string(),
6653 _ => params.values.get("message")?.clone(),
6654 };
6655
6656 if !self.is_permission_error(&message) {
6657 return None;
6658 }
6659
6660 let (command, explanation) = self.determine_permission_fix(&path);
6662
6663 Some(Autocorrection {
6665 description: format!("Fix permissions for: {}", path),
6666 fix_type: FixType::ExecuteCommand,
6667 confidence: 0.8,
6668 details: Some(FixDetails::SuggestCommand {
6669 command: command.clone(),
6670 explanation,
6671 }),
6672 diff_suggestion: None,
6673 commands_to_apply: vec![command],
6674 targets_error_code: Some("io_error".to_string()),
6675 })
6676 }
6677
6678 fn name(&self) -> &'static str {
6679 "IoPermissionFixGenerator"
6680 }
6681}
6682
6683impl FixGenerator for IoMissingDirectoryFixGenerator {
6684 fn generate_fix(
6685 &self,
6686 error: &DecrustError,
6687 params: &ExtractedParameters,
6688 _source_code_context: Option<&str>,
6689 ) -> Option<Autocorrection> {
6690 let path = match error {
6692 DecrustError::Io {
6693 path: Some(path), ..
6694 } => path.to_string_lossy().to_string(),
6695 _ => params.values.get("path")?.clone(),
6696 };
6697
6698 let message = match error {
6700 DecrustError::Io { source, .. } => source.to_string(),
6701 _ => params.values.get("message")?.clone(),
6702 };
6703
6704 if !self.is_missing_directory_error(&message) {
6705 return None;
6706 }
6707
6708 let dir_path = self.extract_directory_path(&path);
6710
6711 Some(Autocorrection {
6713 description: format!("Create missing directory: {}", dir_path),
6714 fix_type: FixType::ExecuteCommand,
6715 confidence: 0.8,
6716 details: Some(FixDetails::SuggestCommand {
6717 command: format!("mkdir -p {}", dir_path),
6718 explanation: format!(
6719 "The directory '{}' does not exist. This command will create it and any parent directories.",
6720 dir_path
6721 ),
6722 }),
6723 diff_suggestion: None,
6724 commands_to_apply: vec![format!("mkdir -p {}", dir_path)],
6725 targets_error_code: Some("io_error".to_string()),
6726 })
6727 }
6728
6729 fn name(&self) -> &'static str {
6730 "IoMissingDirectoryFixGenerator"
6731 }
6732}
6733
6734impl AstUnusedCodeFixGenerator {
6735 fn parse_unused_variable(&self, message: &str) -> Option<String> {
6737 let pattern = r"unused variable: `([^`]+)`";
6739
6740 if let Ok(regex) = Regex::new(pattern) {
6741 if let Some(captures) = regex.captures(message) {
6742 if let Some(var_match) = captures.get(1) {
6743 return Some(var_match.as_str().to_string());
6744 }
6745 }
6746 }
6747
6748 None
6749 }
6750
6751 fn parse_unused_import(&self, message: &str) -> Option<String> {
6753 let pattern = r"unused import: `([^`]+)`";
6755
6756 if let Ok(regex) = Regex::new(pattern) {
6757 if let Some(captures) = regex.captures(message) {
6758 if let Some(import_match) = captures.get(1) {
6759 return Some(import_match.as_str().to_string());
6760 }
6761 }
6762 }
6763
6764 None
6765 }
6766
6767 fn generate_unused_variable_fix(
6769 &self,
6770 variable_name: &str,
6771 line: usize,
6772 file_path: &str,
6773 ) -> Option<Autocorrection> {
6774 if variable_name.starts_with('_') {
6776 return None;
6777 }
6778
6779 let new_name = format!("_{}", variable_name);
6780
6781 Some(Autocorrection {
6782 description: format!("Add underscore to unused variable `{}`", variable_name),
6783 fix_type: FixType::TextReplacement,
6784 confidence: 0.9,
6785 details: Some(FixDetails::SuggestCodeChange {
6786 file_path: PathBuf::from(file_path),
6787 line_hint: line,
6788 suggested_code_snippet: format!("let {} = /* ... */;", new_name),
6789 explanation: format!(
6790 "Adding an underscore prefix to unused variables is a Rust convention that \
6791 suppresses the unused variable warning."
6792 ),
6793 }),
6794 diff_suggestion: Some(format!(
6795 "- let {} = ...\n+ let {} = ...",
6796 variable_name, new_name
6797 )),
6798 commands_to_apply: vec![format!(
6799 "sed -i 's/\\b{}\\b/{}/g' {}",
6800 variable_name, new_name, file_path
6801 )],
6802 targets_error_code: Some("unused_variables".to_string()),
6803 })
6804 }
6805
6806 fn generate_unused_import_fix(
6808 &self,
6809 import: &str,
6810 line: usize,
6811 file_path: &str,
6812 ) -> Option<Autocorrection> {
6813 Some(Autocorrection {
6814 description: format!("Remove unused import `{}`", import),
6815 fix_type: FixType::TextReplacement,
6816 confidence: 0.9,
6817 details: Some(FixDetails::SuggestCodeChange {
6818 file_path: PathBuf::from(file_path),
6819 line_hint: line,
6820 suggested_code_snippet: "".to_string(),
6821 explanation: format!(
6822 "Removing unused imports improves code clarity and can slightly improve \
6823 compilation times."
6824 ),
6825 }),
6826 diff_suggestion: Some(format!("- use {};", import)),
6827 commands_to_apply: vec![format!("sed -i '/use {};/d' {}", import, file_path)],
6828 targets_error_code: Some("unused_imports".to_string()),
6829 })
6830 }
6831}
6832
6833impl FixGenerator for AstMissingImportFixGenerator {
6834 fn generate_fix(
6835 &self,
6836 error: &DecrustError,
6837 params: &ExtractedParameters,
6838 _source_code_context: Option<&str>,
6839 ) -> Option<Autocorrection> {
6840 let message = match error {
6842 DecrustError::Validation { message, .. } => message,
6843 DecrustError::Style { message, .. } => message,
6844 _ => params.values.get("message")?,
6845 };
6846
6847 let type_name = self.parse_type_name(message)?;
6849
6850 let file_path = params
6852 .values
6853 .get("file_path")
6854 .cloned()
6855 .unwrap_or_else(|| "src/lib.rs".to_string());
6856
6857 let import_paths = self.suggest_import_paths(&type_name);
6862
6863 let mut commands = Vec::new();
6865 let mut diff_suggestions = Vec::new();
6866
6867 for (_i, path) in import_paths.iter().enumerate().take(5) {
6868 commands.push(format!("echo '{}' >> {}", path, file_path));
6869 diff_suggestions.push(format!("+ {}", path));
6870 }
6871
6872 Some(Autocorrection {
6873 description: format!("Add import for `{}`", type_name),
6874 fix_type: FixType::AddImport,
6875 confidence: 0.7,
6876 details: Some(FixDetails::AddImport {
6877 file_path: file_path.clone(),
6878 import: import_paths.first().cloned().unwrap_or_default(),
6879 }),
6880 diff_suggestion: Some(diff_suggestions.join("\n")),
6881 commands_to_apply: commands,
6882 targets_error_code: Some("E0412".to_string()),
6883 })
6884 }
6885
6886 fn name(&self) -> &'static str {
6887 "AstMissingImportFixGenerator"
6888 }
6889}
6890
6891impl FixGenerator for AstUnusedCodeFixGenerator {
6892 fn generate_fix(
6893 &self,
6894 error: &DecrustError,
6895 params: &ExtractedParameters,
6896 _source_code_context: Option<&str>,
6897 ) -> Option<Autocorrection> {
6898 let message = match error {
6900 DecrustError::Validation { message, .. } => message,
6901 DecrustError::Style { message, .. } => message,
6902 _ => params.values.get("message")?,
6903 };
6904
6905 let file_path = params
6907 .values
6908 .get("file_path")
6909 .cloned()
6910 .unwrap_or_else(|| "src/lib.rs".to_string());
6911
6912 let line = params
6914 .values
6915 .get("line")
6916 .and_then(|l| l.parse::<usize>().ok())
6917 .unwrap_or(1);
6918
6919 if let Some(variable_name) = self.parse_unused_variable(message) {
6921 return self.generate_unused_variable_fix(&variable_name, line, &file_path);
6922 }
6923
6924 if let Some(import) = self.parse_unused_import(message) {
6926 return self.generate_unused_import_fix(&import, line, &file_path);
6927 }
6928
6929 None
6930 }
6931
6932 fn name(&self) -> &'static str {
6933 "AstUnusedCodeFixGenerator"
6934 }
6935}
6936
6937impl FixGenerator for AstTraitImplementationFixGenerator {
6938 fn generate_fix(
6939 &self,
6940 error: &DecrustError,
6941 params: &ExtractedParameters,
6942 _source_code_context: Option<&str>,
6943 ) -> Option<Autocorrection> {
6944 let message = match error {
6946 DecrustError::Validation { message, .. } => message,
6947 _ => params.values.get("message")?,
6948 };
6949
6950 let trait_name = self.parse_trait_name(message)?;
6952 let type_name = self.parse_type_name(message)?;
6953
6954 let trait_impl = self.generate_trait_impl(&trait_name, &type_name)?;
6956
6957 let file_path = params
6959 .values
6960 .get("file_path")
6961 .cloned()
6962 .unwrap_or_else(|| "src/lib.rs".to_string());
6963
6964 let line = params
6966 .values
6967 .get("line")
6968 .and_then(|l| l.parse::<usize>().ok())
6969 .unwrap_or(1);
6970
6971 Some(Autocorrection {
6973 description: format!("Implement trait `{}` for `{}`", trait_name, type_name),
6974 fix_type: FixType::TextReplacement,
6975 confidence: 0.7,
6976 details: Some(FixDetails::SuggestCodeChange {
6977 file_path: PathBuf::from(&file_path),
6978 line_hint: line,
6979 suggested_code_snippet: trait_impl.clone(),
6980 explanation: format!(
6981 "The trait `{}` is not implemented for type `{}`. \
6982 This implementation provides a basic skeleton that you should customize.",
6983 trait_name, type_name
6984 ),
6985 }),
6986 diff_suggestion: Some(format!("+ {}", trait_impl)),
6987 commands_to_apply: vec![format!(
6988 "echo '{}' >> {}",
6989 trait_impl.replace("'", "\\'"),
6990 file_path
6991 )],
6992 targets_error_code: Some("E0277".to_string()),
6993 })
6994 }
6995
6996 fn name(&self) -> &'static str {
6997 "AstTraitImplementationFixGenerator"
6998 }
6999}
7000
7001pub struct EnumParameterMatchFixGenerator;
7003
7004impl EnumParameterMatchFixGenerator {
7005 pub fn new() -> Self {
7007 Self
7008 }
7009}
7010
7011impl FixGenerator for EnumParameterMatchFixGenerator {
7012 fn generate_fix(
7013 &self,
7014 _error: &DecrustError,
7015 params: &ExtractedParameters,
7016 source_code_context: Option<&str>,
7017 ) -> Option<Autocorrection> {
7018 let message = params.values.get("message")?;
7020
7021 if !self.is_enum_parameter_mismatch(message) {
7023 return None;
7024 }
7025
7026 let (enum_name, variant_name, expected_params, found_params) =
7028 extract_enum_parameter_info(message)?;
7029
7030 let file_path = params
7031 .values
7032 .get("file_path")
7033 .cloned()
7034 .unwrap_or_else(|| "unknown_file.rs".to_string());
7035
7036 let line = params
7037 .values
7038 .get("line")
7039 .and_then(|l| l.parse::<usize>().ok())
7040 .unwrap_or(1);
7041
7042 let (details, commands, diff) = if let Some(context) = source_code_context {
7044 self.generate_context_aware_fix(
7045 &file_path,
7046 line,
7047 &enum_name,
7048 &variant_name,
7049 &expected_params,
7050 &found_params,
7051 context,
7052 )
7053 } else {
7054 self.generate_simple_fix(
7055 &file_path,
7056 line,
7057 &enum_name,
7058 &variant_name,
7059 &expected_params,
7060 &found_params,
7061 )
7062 };
7063
7064 Some(Autocorrection {
7065 description: format!(
7066 "Fix parameter mismatch for enum variant {}::{}",
7067 enum_name, variant_name
7068 ),
7069 fix_type: FixType::ManualInterventionRequired,
7070 confidence: 0.75,
7071 details: Some(details),
7072 diff_suggestion: Some(diff),
7073 commands_to_apply: commands,
7074 targets_error_code: Some("enum_parameter_mismatch".to_string()),
7075 })
7076 }
7077
7078 fn name(&self) -> &'static str {
7079 "EnumParameterMatchFixGenerator"
7080 }
7081}
7082
7083impl EnumParameterMatchFixGenerator {
7084 fn is_enum_parameter_mismatch(&self, message: &str) -> bool {
7085 message.contains("expected") && message.contains("parameters") && message.contains("found")
7087 || message.contains("this enum variant takes")
7088 || message.contains("expected struct") && message.contains("found enum")
7089 || message.contains("mismatched types") && message.contains("expected enum")
7090 || message.contains("wrong number of arguments") && message.contains("variant")
7091 }
7092
7093 fn generate_context_aware_fix(
7094 &self,
7095 file_path: &str,
7096 line: usize,
7097 enum_name: &str,
7098 variant_name: &str,
7099 expected_params: &[String],
7100 found_params: &[String],
7101 context: &str,
7102 ) -> (FixDetails, Vec<String>, String) {
7103 let lines: Vec<&str> = context.lines().collect();
7104
7105 let variant_line_idx = lines
7107 .iter()
7108 .position(|&l| l.contains(variant_name) && (l.contains("(") || l.contains("{")));
7109
7110 if let Some(idx) = variant_line_idx {
7111 let variant_line = lines[idx];
7112
7113 let new_line = if variant_line.contains("(") && variant_line.contains(")") {
7115 self.fix_tuple_variant(variant_line, enum_name, variant_name, expected_params)
7117 } else if variant_line.contains("{") && variant_line.contains("}") {
7118 self.fix_struct_variant(variant_line, enum_name, variant_name, expected_params)
7120 } else {
7121 variant_line.to_string()
7123 };
7124
7125 if new_line == variant_line {
7127 return self.generate_simple_fix(
7128 file_path,
7129 line,
7130 enum_name,
7131 variant_name,
7132 expected_params,
7133 found_params,
7134 );
7135 }
7136
7137 let sed_command = format!(
7138 "sed -i '{}s/{}/{}/' \"{}\"",
7139 idx + 1, regex::escape(variant_line),
7141 regex::escape(&new_line),
7142 file_path
7143 );
7144
7145 let explanation = format!(
7146 "Fixed parameter mismatch for enum variant `{}::{}`. \
7147 Expected {} parameters but found {}. \
7148 Make sure to match the enum definition from its original module.",
7149 enum_name,
7150 variant_name,
7151 expected_params.len(),
7152 found_params.len()
7153 );
7154
7155 let details = FixDetails::SuggestCodeChange {
7156 file_path: PathBuf::from(file_path),
7157 line_hint: idx + 1,
7158 suggested_code_snippet: format!("// Change to:\n{}", new_line),
7159 explanation,
7160 };
7161
7162 let diff = format!("-{}\n+{}", variant_line, new_line);
7163
7164 return (details, vec![sed_command], diff);
7165 }
7166
7167 self.generate_simple_fix(
7169 file_path,
7170 line,
7171 enum_name,
7172 variant_name,
7173 expected_params,
7174 found_params,
7175 )
7176 }
7177
7178 fn fix_tuple_variant(
7179 &self,
7180 line: &str,
7181 _enum_name: &str,
7182 _variant_name: &str,
7183 expected_params: &[String],
7184 ) -> String {
7185 let prefix_end = line.find('(').unwrap_or(line.len());
7187 let suffix_start = line.rfind(')').unwrap_or(line.len());
7188
7189 let prefix = &line[..prefix_end + 1]; let suffix = &line[suffix_start..]; let param_placeholders: Vec<String> = expected_params
7194 .iter()
7195 .map(|param_type| generate_default_value(param_type))
7196 .collect();
7197
7198 format!("{}{}{}", prefix, param_placeholders.join(", "), suffix)
7199 }
7200
7201 fn fix_struct_variant(
7202 &self,
7203 line: &str,
7204 _enum_name: &str,
7205 _variant_name: &str,
7206 expected_params: &[String],
7207 ) -> String {
7208 let prefix_end = line.find('{').unwrap_or(line.len());
7210 let suffix_start = line.rfind('}').unwrap_or(line.len());
7211
7212 let prefix = &line[..prefix_end + 1]; let suffix = &line[suffix_start..]; let field_placeholders: Vec<String> = expected_params
7218 .iter()
7219 .enumerate()
7220 .map(|(i, param_type)| format!("field{}: {}", i, generate_default_value(param_type)))
7221 .collect();
7222
7223 format!("{} {} {}", prefix, field_placeholders.join(", "), suffix)
7224 }
7225
7226 fn generate_simple_fix(
7227 &self,
7228 file_path: &str,
7229 line: usize,
7230 enum_name: &str,
7231 variant_name: &str,
7232 expected_params: &[String],
7233 found_params: &[String],
7234 ) -> (FixDetails, Vec<String>, String) {
7235 let mut suggestions = Vec::new();
7237
7238 suggestions.push(format!(
7239 "// For enum variant {}::{}",
7240 enum_name, variant_name
7241 ));
7242
7243 if expected_params.is_empty() {
7244 suggestions.push(format!("{}::{}", enum_name, variant_name));
7246 } else if expected_params.len() == 1 {
7247 suggestions.push(format!(
7249 "{}::{}({})",
7250 enum_name,
7251 variant_name,
7252 generate_default_value(&expected_params[0])
7253 ));
7254 } else {
7255 let params = expected_params
7257 .iter()
7258 .map(|p| generate_default_value(p))
7259 .collect::<Vec<_>>()
7260 .join(", ");
7261
7262 suggestions.push(format!("{}::{}({})", enum_name, variant_name, params));
7263
7264 let fields = expected_params
7266 .iter()
7267 .enumerate()
7268 .map(|(i, p)| format!("field{}: {}", i, generate_default_value(p)))
7269 .collect::<Vec<_>>()
7270 .join(", ");
7271
7272 suggestions.push(format!("// Or using struct-style syntax:"));
7273 suggestions.push(format!("{}::{}{{ {} }}", enum_name, variant_name, fields));
7274 }
7275
7276 suggestions.push(format!("\n// Check the original enum definition:"));
7278 suggestions.push(format!("enum {} {{", enum_name));
7279
7280 if expected_params.is_empty() {
7281 suggestions.push(format!(" {},", variant_name));
7282 } else if expected_params.len() == 1 {
7283 suggestions.push(format!(" {}({}),", variant_name, expected_params[0]));
7284 } else {
7285 let params = expected_params.join(", ");
7286 suggestions.push(format!(" {}({}),", variant_name, params));
7287 }
7288
7289 suggestions.push(format!(" // other variants..."));
7290 suggestions.push(format!("}}"));
7291
7292 let explanation = format!(
7293 "The enum variant `{}::{}` is being used with the wrong number or types of parameters. \
7294 Expected {} parameters ({}) but found {} parameters ({}). \
7295 Make sure to match the enum definition from its original module.",
7296 enum_name, variant_name,
7297 expected_params.len(), expected_params.join(", "),
7298 found_params.len(), found_params.join(", ")
7299 );
7300
7301 let details = FixDetails::SuggestCodeChange {
7302 file_path: PathBuf::from(file_path),
7303 line_hint: line,
7304 suggested_code_snippet: suggestions.join("\n"),
7305 explanation,
7306 };
7307
7308 let find_enum_command = format!(
7310 "grep -n \"enum {}\" --include=\"*.rs\" -r \"{}\"",
7311 enum_name,
7312 PathBuf::from(file_path)
7313 .parent()
7314 .unwrap_or(&PathBuf::from("."))
7315 .display()
7316 );
7317
7318 let diff = format!(
7320 "// Original code with incorrect parameters\n-{}::{}({})\n\n// Corrected code\n+{}::{}({})",
7321 enum_name, variant_name, found_params.join(", "),
7322 enum_name, variant_name, expected_params.iter()
7323 .map(|p| generate_default_value(p))
7324 .collect::<Vec<_>>()
7325 .join(", ")
7326 );
7327
7328 (details, vec![find_enum_command], diff)
7329 }
7330}
7331
7332fn extract_enum_parameter_info(
7334 message: &str,
7335) -> Option<(String, String, Vec<String>, Vec<String>)> {
7336 let pattern1 =
7340 Regex::new(r"expected (\d+) parameters?, found (\d+) in `([^:]+)::([^`]+)`").ok()?;
7341 if let Some(captures) = pattern1.captures(message) {
7342 let expected_count = captures.get(1)?.as_str().parse::<usize>().ok()?;
7343 let found_count = captures.get(2)?.as_str().parse::<usize>().ok()?;
7344 let enum_name = captures.get(3)?.as_str().to_string();
7345 let variant_name = captures.get(4)?.as_str().to_string();
7346
7347 let expected_params = vec!["Type".to_string(); expected_count];
7349 let found_params = vec!["Type".to_string(); found_count];
7350
7351 return Some((enum_name, variant_name, expected_params, found_params));
7352 }
7353
7354 let pattern2 = Regex::new(
7356 r"this enum variant takes (\d+) parameters? but (\d+) parameters? (?:was|were) supplied",
7357 )
7358 .ok()?;
7359 if let Some(captures) = pattern2.captures(message) {
7360 let expected_count = captures.get(1)?.as_str().parse::<usize>().ok()?;
7361 let found_count = captures.get(2)?.as_str().parse::<usize>().ok()?;
7362
7363 let enum_variant_pattern = Regex::new(r"`([^:]+)::([^`]+)`").ok()?;
7366 if let Some(name_captures) = enum_variant_pattern.captures(message) {
7367 let enum_name = name_captures.get(1)?.as_str().to_string();
7368 let variant_name = name_captures.get(2)?.as_str().to_string();
7369
7370 let expected_params = vec!["Type".to_string(); expected_count];
7372 let found_params = vec!["Type".to_string(); found_count];
7373
7374 return Some((enum_name, variant_name, expected_params, found_params));
7375 }
7376 }
7377
7378 let pattern3 = Regex::new(r"mismatched types: expected enum `([^:]+)::([^(]+)\(([^)]*)\)`, found `[^:]+::[^(]+\(([^)]*)\)`").ok()?;
7380 if let Some(captures) = pattern3.captures(message) {
7381 let enum_name = captures.get(1)?.as_str().to_string();
7382 let variant_name = captures.get(2)?.as_str().to_string();
7383 let expected_params_str = captures.get(3)?.as_str();
7384 let found_params_str = captures.get(4)?.as_str();
7385
7386 let expected_params = if expected_params_str.is_empty() {
7387 Vec::new()
7388 } else {
7389 expected_params_str
7390 .split(',')
7391 .map(|s| s.trim().to_string())
7392 .collect()
7393 };
7394
7395 let found_params = if found_params_str.is_empty() {
7396 Vec::new()
7397 } else {
7398 found_params_str
7399 .split(',')
7400 .map(|s| s.trim().to_string())
7401 .collect()
7402 };
7403
7404 return Some((enum_name, variant_name, expected_params, found_params));
7405 }
7406
7407 if message.contains("enum") && message.contains("parameters") {
7409 return Some((
7410 "UnknownEnum".to_string(),
7411 "UnknownVariant".to_string(),
7412 vec!["ExpectedType".to_string()],
7413 vec!["FoundType".to_string()],
7414 ));
7415 }
7416
7417 None
7418}
7419
7420pub struct StructParameterMatchFixGenerator;
7422
7423impl StructParameterMatchFixGenerator {
7424 pub fn new() -> Self {
7426 Self
7427 }
7428}
7429
7430impl FixGenerator for StructParameterMatchFixGenerator {
7431 fn generate_fix(
7432 &self,
7433 _error: &DecrustError,
7434 params: &ExtractedParameters,
7435 source_code_context: Option<&str>,
7436 ) -> Option<Autocorrection> {
7437 let message = params.values.get("message")?;
7439
7440 if !self.is_struct_field_mismatch(message) {
7442 return None;
7443 }
7444
7445 let (struct_name, missing_fields, incorrect_fields) = extract_struct_field_info(message)?;
7447
7448 let file_path = params
7449 .values
7450 .get("file_path")
7451 .cloned()
7452 .unwrap_or_else(|| "unknown_file.rs".to_string());
7453
7454 let line = params
7455 .values
7456 .get("line")
7457 .and_then(|l| l.parse::<usize>().ok())
7458 .unwrap_or(1);
7459
7460 let (details, commands, diff) = if let Some(context) = source_code_context {
7462 self.generate_context_aware_fix(
7463 &file_path,
7464 line,
7465 &struct_name,
7466 &missing_fields,
7467 &incorrect_fields,
7468 context,
7469 )
7470 } else {
7471 self.generate_simple_fix(
7472 &file_path,
7473 line,
7474 &struct_name,
7475 &missing_fields,
7476 &incorrect_fields,
7477 )
7478 };
7479
7480 Some(Autocorrection {
7481 description: format!("Fix field mismatch for struct `{}`", struct_name),
7482 fix_type: FixType::ManualInterventionRequired,
7483 confidence: 0.75,
7484 details: Some(details),
7485 diff_suggestion: Some(diff),
7486 commands_to_apply: commands,
7487 targets_error_code: Some("struct_field_mismatch".to_string()),
7488 })
7489 }
7490
7491 fn name(&self) -> &'static str {
7492 "StructParameterMatchFixGenerator"
7493 }
7494}
7495
7496impl StructParameterMatchFixGenerator {
7497 fn is_struct_field_mismatch(&self, message: &str) -> bool {
7498 message.contains("missing field")
7500 || message.contains("unknown field")
7501 || message.contains("struct")
7502 && message.contains("field")
7503 && message.contains("missing")
7504 || message.contains("struct")
7505 && message.contains("field")
7506 && message.contains("expected")
7507 || message.contains("no field") && message.contains("on struct")
7508 || message.contains("this struct takes") && message.contains("fields")
7509 || message.contains("missing fields")
7510 || message.contains("mismatched types") && message.contains("expected struct")
7511 }
7512
7513 fn generate_context_aware_fix(
7514 &self,
7515 file_path: &str,
7516 line: usize,
7517 struct_name: &str,
7518 missing_fields: &[(String, String)],
7519 incorrect_fields: &[(String, String, String)],
7520 context: &str,
7521 ) -> (FixDetails, Vec<String>, String) {
7522 let lines: Vec<&str> = context.lines().collect();
7523
7524 let struct_line_idx = lines
7526 .iter()
7527 .position(|&l| l.contains(struct_name) && l.contains("{") && !l.contains("struct"));
7528
7529 if let Some(idx) = struct_line_idx {
7530 let struct_line = lines[idx];
7531 let mut new_lines = lines.clone();
7532
7533 let close_idx = lines
7535 .iter()
7536 .skip(idx)
7537 .position(|&l| l.contains("}"))
7538 .map(|pos| idx + pos);
7539
7540 if let Some(close_pos) = close_idx {
7541 let current_fields: Vec<String> = lines[idx + 1..close_pos]
7543 .iter()
7544 .map(|l| l.trim().to_string())
7545 .filter(|l| !l.is_empty() && !l.starts_with("//"))
7546 .collect();
7547
7548 let mut fixed_fields = current_fields.clone();
7550
7551 for (field_name, field_type) in missing_fields {
7553 let indent = lines[idx + 1]
7554 .chars()
7555 .take_while(|&c| c.is_whitespace())
7556 .collect::<String>();
7557 let field_line = format!(
7558 "{}{}: {},",
7559 indent,
7560 field_name,
7561 generate_default_value(field_type)
7562 );
7563 fixed_fields.push(field_line);
7564 }
7565
7566 for (field_name, expected_type, _found_type) in incorrect_fields {
7568 if let Some(field_idx) =
7570 current_fields.iter().position(|l| l.contains(field_name))
7571 {
7572 let indent = lines[idx + 1]
7573 .chars()
7574 .take_while(|&c| c.is_whitespace())
7575 .collect::<String>();
7576 let field_line = format!(
7577 "{}{}: {},",
7578 indent,
7579 field_name,
7580 generate_default_value(expected_type)
7581 );
7582 fixed_fields[field_idx] = field_line;
7583 }
7584 }
7585
7586 let mut current_pos = close_pos;
7588 for (i, field) in fixed_fields.iter().enumerate() {
7589 if i < current_fields.len() {
7590 new_lines[idx + 1 + i] = field;
7591 } else {
7592 new_lines.insert(current_pos, field);
7594 current_pos += 1;
7596 }
7597 }
7598
7599 let new_content = new_lines.join("\n");
7600
7601 let sed_script = format!(
7602 "sed -i '{},{}c\\{}' \"{}\"",
7603 idx + 1,
7604 current_pos + 1,
7605 new_content.replace("\n", "\\n"),
7606 file_path
7607 );
7608
7609 let explanation = format!(
7610 "Fixed field mismatch for struct `{}`. \
7611 {} missing field(s) added and {} incorrect field(s) fixed. \
7612 Make sure to match the struct definition from its original module.",
7613 struct_name,
7614 missing_fields.len(),
7615 incorrect_fields.len()
7616 );
7617
7618 let end_line = idx + fixed_fields.len() + 1;
7620
7621 let details = FixDetails::SuggestCodeChange {
7622 file_path: PathBuf::from(file_path),
7623 line_hint: idx + 1,
7624 suggested_code_snippet: format!(
7625 "// Fixed struct instantiation:\n{}",
7626 new_lines[idx..=end_line].join("\n")
7627 ),
7628 explanation,
7629 };
7630
7631 let diff = format!(
7632 "@@ struct instantiation @@\n{}\n...\n{}",
7633 struct_line,
7634 if !missing_fields.is_empty() {
7635 missing_fields
7636 .iter()
7637 .map(|(name, typ)| {
7638 format!("+ {}: {},", name, generate_default_value(typ))
7639 })
7640 .collect::<Vec<_>>()
7641 .join("\n")
7642 } else {
7643 "// No changes needed".to_string()
7644 }
7645 );
7646
7647 return (details, vec![sed_script], diff);
7648 }
7649 }
7650
7651 self.generate_simple_fix(
7653 file_path,
7654 line,
7655 struct_name,
7656 missing_fields,
7657 incorrect_fields,
7658 )
7659 }
7660
7661 fn generate_simple_fix(
7662 &self,
7663 file_path: &str,
7664 line: usize,
7665 struct_name: &str,
7666 missing_fields: &[(String, String)],
7667 incorrect_fields: &[(String, String, String)],
7668 ) -> (FixDetails, Vec<String>, String) {
7669 let mut suggestions = Vec::new();
7671
7672 suggestions.push(format!("// For struct `{}`:", struct_name));
7673 suggestions.push(format!("let instance = {} {{", struct_name));
7674
7675 if !missing_fields.is_empty() {
7677 suggestions.push(format!(" // Missing fields that need to be added:"));
7678 for (field_name, field_type) in missing_fields {
7679 suggestions.push(format!(
7680 " {}: {},",
7681 field_name,
7682 generate_default_value(field_type)
7683 ));
7684 }
7685 }
7686
7687 if !incorrect_fields.is_empty() {
7688 suggestions.push(format!(
7689 " // Fields with incorrect types that need to be fixed:"
7690 ));
7691 for (field_name, expected_type, found_type) in incorrect_fields {
7692 suggestions.push(format!(
7693 " // Current: {}: {} (type: {})",
7694 field_name, field_name, found_type
7695 ));
7696 suggestions.push(format!(" // Should be:"));
7697 suggestions.push(format!(
7698 " {}: {},",
7699 field_name,
7700 generate_default_value(expected_type)
7701 ));
7702 }
7703 }
7704
7705 suggestions.push(format!(" // ... other fields"));
7706 suggestions.push(format!("}}"));
7707
7708 suggestions.push(format!("\n// Check the original struct definition:"));
7710 suggestions.push(format!("struct {} {{", struct_name));
7711
7712 for (field_name, field_type) in missing_fields {
7714 suggestions.push(format!(" {}: {},", field_name, field_type));
7715 }
7716
7717 for (field_name, expected_type, _) in incorrect_fields {
7718 suggestions.push(format!(" {}: {},", field_name, expected_type));
7719 }
7720
7721 suggestions.push(format!(" // ... other fields"));
7722 suggestions.push(format!("}}"));
7723
7724 let explanation = format!(
7725 "The struct `{}` is being used with missing or incorrect fields. \
7726 {} field(s) are missing and {} field(s) have incorrect types. \
7727 Make sure to match the struct definition from its original module.",
7728 struct_name,
7729 missing_fields.len(),
7730 incorrect_fields.len()
7731 );
7732
7733 let details = FixDetails::SuggestCodeChange {
7734 file_path: PathBuf::from(file_path),
7735 line_hint: line,
7736 suggested_code_snippet: suggestions.join("\n"),
7737 explanation,
7738 };
7739
7740 let find_struct_command = format!(
7742 "grep -n \"struct {}\" --include=\"*.rs\" -r \"{}\"",
7743 struct_name,
7744 PathBuf::from(file_path)
7745 .parent()
7746 .unwrap_or(&PathBuf::from("."))
7747 .display()
7748 );
7749
7750 let diff = format!(
7752 "// Original code with missing/incorrect fields\n-{} {{ ... }}\n\n// Corrected code\n+{} {{\n{}+}}",
7753 struct_name, struct_name,
7754 missing_fields.iter()
7755 .map(|(name, typ)| format!("+ {}: {},\n", name, generate_default_value(typ)))
7756 .collect::<Vec<_>>()
7757 .join("")
7758 );
7759
7760 (details, vec![find_struct_command], diff)
7761 }
7762}
7763
7764fn extract_struct_field_info(
7766 message: &str,
7767) -> Option<(String, Vec<(String, String)>, Vec<(String, String, String)>)> {
7768 let pattern1 = Regex::new(r"missing field `([^`]+)` in struct `([^`]+)`").ok()?;
7772 if let Some(captures) = pattern1.captures(message) {
7773 let field_name = captures.get(1)?.as_str().to_string();
7774 let struct_name = captures.get(2)?.as_str().to_string();
7775
7776 let missing_fields = vec![(field_name, "Type".to_string())];
7778 let incorrect_fields = Vec::new();
7779
7780 return Some((struct_name, missing_fields, incorrect_fields));
7781 }
7782
7783 let pattern2 = Regex::new(r"unknown field `([^`]+)` in struct `([^`]+)`").ok()?;
7785 if let Some(captures) = pattern2.captures(message) {
7786 let field_name = captures.get(1)?.as_str().to_string();
7787 let struct_name = captures.get(2)?.as_str().to_string();
7788
7789 let missing_fields = Vec::new();
7791 let incorrect_fields = vec![(
7793 field_name,
7794 "ExpectedType".to_string(),
7795 "FoundType".to_string(),
7796 )];
7797
7798 return Some((struct_name, missing_fields, incorrect_fields));
7799 }
7800
7801 let pattern3 = Regex::new(r"mismatched types: expected struct `([^`]+)`, found struct `[^`]+` with (\d+) missing field\(s\)").ok()?;
7803 if let Some(captures) = pattern3.captures(message) {
7804 let struct_name = captures.get(1)?.as_str().to_string();
7805 let missing_count = captures.get(2)?.as_str().parse::<usize>().ok()?;
7806
7807 let missing_fields = (0..missing_count)
7809 .map(|i| (format!("missing_field{}", i), "Type".to_string()))
7810 .collect();
7811 let incorrect_fields = Vec::new();
7812
7813 return Some((struct_name, missing_fields, incorrect_fields));
7814 }
7815
7816 let pattern4 = Regex::new(r"no field `([^`]+)` on struct `([^`]+)`").ok()?;
7818 if let Some(captures) = pattern4.captures(message) {
7819 let field_name = captures.get(1)?.as_str().to_string();
7820 let struct_name = captures.get(2)?.as_str().to_string();
7821
7822 let missing_fields = Vec::new();
7824 let incorrect_fields = vec![(
7826 field_name,
7827 "ExpectedType".to_string(),
7828 "FoundType".to_string(),
7829 )];
7830
7831 return Some((struct_name, missing_fields, incorrect_fields));
7832 }
7833
7834 let pattern5 = Regex::new(r"mismatched types: expected `([^`]+)`, found `([^`]+)` for field `([^`]+)` in struct `([^`]+)`").ok()?;
7836 if let Some(captures) = pattern5.captures(message) {
7837 let expected_type = captures.get(1)?.as_str().to_string();
7838 let found_type = captures.get(2)?.as_str().to_string();
7839 let field_name = captures.get(3)?.as_str().to_string();
7840 let struct_name = captures.get(4)?.as_str().to_string();
7841
7842 let missing_fields = Vec::new();
7844 let incorrect_fields = vec![(field_name, expected_type, found_type)];
7845
7846 return Some((struct_name, missing_fields, incorrect_fields));
7847 }
7848
7849 if message.contains("struct") && message.contains("field") {
7851 return Some((
7852 "UnknownStruct".to_string(),
7853 vec![("missing_field".to_string(), "Type".to_string())],
7854 Vec::new(),
7855 ));
7856 }
7857
7858 None
7859}
7860
7861pub struct BorrowAfterMoveFixGenerator;
7863
7864pub struct CrossModuleAutomationEngine {
7866 syntax_generator: SyntaxGenerator,
7867 template_registry: TemplateRegistry,
7868 circuit_breaker: Arc<CircuitBreaker>,
7869 error_reporter: ErrorReporter,
7870}
7871
7872impl CrossModuleAutomationEngine {
7873 pub fn new() -> Self {
7875 let circuit_breaker_config = CircuitBreakerConfig {
7876 failure_threshold: 3,
7877 reset_timeout: Duration::from_secs(30),
7878 ..Default::default()
7879 };
7880
7881 Self {
7882 syntax_generator: SyntaxGenerator::new(),
7883 template_registry: TemplateRegistry::new(),
7884 circuit_breaker: CircuitBreaker::new("automation_engine", circuit_breaker_config),
7885 error_reporter: ErrorReporter::new(),
7886 }
7887 }
7888
7889 pub fn generate_ast_driven_fix(
7891 &self,
7892 error: &DecrustError,
7893 source_code: &str,
7894 file_path: &str,
7895 ) -> Option<Autocorrection> {
7896 let error_string = error.to_string();
7898 let source_code_string = source_code.to_string();
7899 let file_path_string = file_path.to_string();
7900
7901 let import_suggestion = self
7903 .syntax_generator
7904 .generate_import("std::collections", &["HashMap"]);
7905
7906 let result = self.circuit_breaker.execute(move || {
7908 let _import_suggestion = import_suggestion;
7910
7911 let default_template = super::syntax::FixTemplate::new(
7913 "ast_driven_fix",
7914 "AST-driven context-aware fix",
7915 format!(
7916 "// AST-analyzed fix for: {}\n// Fixed: {}",
7917 error_string, source_code_string
7918 ),
7919 );
7920
7921 let mut params = HashMap::new();
7923 params.insert("error".to_string(), error_string.clone());
7924 params.insert("source_code".to_string(), source_code_string.clone());
7925 params.insert("file_path".to_string(), file_path_string.clone());
7926
7927 let generated_fix = default_template.apply(¶ms);
7929
7930 Ok(Autocorrection {
7931 description: format!("AST-driven fix for {}", error_string),
7932 fix_type: FixType::TextReplacement,
7933 confidence: 0.95, details: Some(FixDetails::SuggestCodeChange {
7935 file_path: PathBuf::from(file_path_string.clone()),
7936 line_hint: 1, suggested_code_snippet: generated_fix.clone(),
7938 explanation: format!(
7939 "AST-driven analysis suggests: {}",
7940 default_template.description
7941 ),
7942 }),
7943 diff_suggestion: Some(format!("- {}\n+ {}", source_code_string, generated_fix)),
7944 commands_to_apply: vec![], targets_error_code: Some(error_string),
7946 })
7947 });
7948
7949 result.ok()
7950 }
7951
7952 pub fn generate_auto_diff_preview(
7954 &self,
7955 _error: &DecrustError,
7956 proposed_fix: &str,
7957 original_code: &str,
7958 ) -> String {
7959 let _config = ErrorReportConfig::default();
7961
7962 let diff_preview = format!(
7965 "--- Original Code\n{}\n+++ Proposed Fix\n{}\n\nDiff:\n- {}\n+ {}",
7966 original_code,
7967 proposed_fix,
7968 original_code.lines().collect::<Vec<_>>().join("\n- "),
7969 proposed_fix.lines().collect::<Vec<_>>().join("\n+ ")
7970 );
7971
7972 diff_preview
7975 }
7976
7977 pub fn apply_heuristic_recovery(
7979 &self,
7980 error: &DecrustError,
7981 context: &str,
7982 confidence_threshold: f64,
7983 ) -> Option<Autocorrection> {
7984 let metrics = self.circuit_breaker.metrics();
7986
7987 let adjusted_confidence = if metrics.total_requests > 0 {
7989 let success_rate = metrics.successful_requests as f64 / metrics.total_requests as f64;
7990 confidence_threshold * success_rate
7991 } else {
7992 confidence_threshold
7993 };
7994
7995 if adjusted_confidence >= 0.8 {
7997 let _syntax_analysis = self
7999 .syntax_generator
8000 .generate_import("std::error", &["Error"]);
8001 let patterns = self.extract_error_patterns_from_context(context);
8002
8003 if !patterns.is_empty() {
8004 if let Some(template) = self.find_similar_pattern_in_registry(&patterns) {
8006 return Some(Autocorrection {
8007 description: format!("Heuristic-driven fix based on learned patterns"),
8008 fix_type: FixType::TextReplacement,
8009 confidence: adjusted_confidence,
8010 details: Some(FixDetails::SuggestCodeChange {
8011 file_path: PathBuf::from("unknown"),
8012 line_hint: 1,
8013 suggested_code_snippet: template.template.clone(),
8014 explanation: format!(
8015 "Applied learned pattern with {}% confidence",
8016 (adjusted_confidence * 100.0) as u32
8017 ),
8018 }),
8019 diff_suggestion: Some(format!("- {}\n+ {}", context, template.template)),
8020 commands_to_apply: vec![], targets_error_code: Some(error.to_string()),
8022 });
8023 }
8024 }
8025 }
8026
8027 None
8028 }
8029
8030 fn extract_error_patterns_from_context(&self, context: &str) -> Vec<String> {
8032 let mut patterns = Vec::new();
8033
8034 let config = ErrorReportConfig::default();
8036 let _formatted_context = self.error_reporter.report_to_string_with_syntax(
8037 &std::io::Error::other("Pattern analysis"),
8038 &config,
8039 Some(context),
8040 );
8041
8042 let lines: Vec<&str> = context.lines().collect();
8044
8045 for line in lines {
8046 if line.contains("error[E") {
8048 patterns.push(line.trim().to_string());
8049 }
8050 if line.contains("cannot borrow") {
8051 patterns.push("borrow_checker_error".to_string());
8052 }
8053 if line.contains("use of moved value") {
8054 patterns.push("move_error".to_string());
8055 }
8056 if line.contains("mismatched types") {
8057 patterns.push("type_mismatch".to_string());
8058 }
8059 if line.contains("unused") {
8060 patterns.push("unused_code".to_string());
8061 }
8062 }
8063
8064 patterns
8065 }
8066
8067 fn find_similar_pattern_in_registry(
8069 &self,
8070 patterns: &[String],
8071 ) -> Option<&super::syntax::FixTemplate> {
8072 for pattern in patterns {
8074 if pattern.contains("borrow_checker") {
8076 if let Some(template) = self.template_registry.get_template("borrow_fix") {
8078 return Some(template);
8079 }
8080 }
8081 if pattern.contains("move_error") {
8082 if let Some(template) = self.template_registry.get_template("move_fix") {
8084 return Some(template);
8085 }
8086 }
8087 if pattern.contains("type_mismatch") {
8088 if let Some(template) = self.template_registry.get_template("type_fix") {
8090 return Some(template);
8091 }
8092 }
8093 if pattern.contains("unused") {
8094 if let Some(template) = self.template_registry.get_template("unused_fix") {
8096 return Some(template);
8097 }
8098 }
8099 }
8100
8101 self.template_registry.get_template("generic_fix")
8103 }
8104}
8105
8106impl BorrowAfterMoveFixGenerator {
8107 pub fn new() -> Self {
8109 Self
8110 }
8111
8112 fn can_automate_fix(&self, variable_name: &str, source_code_context: Option<&str>) -> bool {
8114 let copy_types = [
8116 "i32", "i64", "u32", "u64", "usize", "isize", "f32", "f64", "bool", "char",
8117 ];
8118
8119 if let Some(context) = source_code_context {
8120 for copy_type in ©_types {
8122 if context.contains(&format!("{}: {}", variable_name, copy_type))
8123 || context.contains(&format!("let {}: {}", variable_name, copy_type))
8124 {
8125 return true;
8126 }
8127 }
8128
8129 if context.contains(&format!("{} = \"", variable_name))
8131 || context.contains(&format!("let {} = \"", variable_name))
8132 {
8133 return true;
8134 }
8135 }
8136
8137 false
8138 }
8139
8140 fn generate_automatic_fix_commands(
8142 &self,
8143 variable_name: &str,
8144 file_path: &str,
8145 line: usize,
8146 ) -> Vec<String> {
8147 vec![format!(
8148 "sed -i '{}s/\\b{}\\b/{}.clone()/g' \"{}\"",
8149 line, variable_name, variable_name, file_path
8150 )]
8151 }
8152}
8153
8154impl FixGenerator for BorrowAfterMoveFixGenerator {
8155 fn generate_fix(
8156 &self,
8157 _error: &DecrustError,
8158 params: &ExtractedParameters,
8159 source_code_context: Option<&str>,
8160 ) -> Option<Autocorrection> {
8161 let message = params.values.get("message")?;
8163
8164 if !message.contains("value used here after move") && !message.contains("moved") {
8166 return None;
8167 }
8168
8169 let variable_name = extract_variable_from_move_error(message)?;
8171
8172 let file_path = params
8173 .values
8174 .get("file_path")
8175 .cloned()
8176 .unwrap_or_else(|| "unknown_file.rs".to_string());
8177
8178 let line = params
8179 .values
8180 .get("line")
8181 .and_then(|l| l.parse::<usize>().ok())
8182 .unwrap_or(1);
8183
8184 let (fix_type, commands, confidence) =
8186 if self.can_automate_fix(&variable_name, source_code_context) {
8187 let auto_commands =
8188 self.generate_automatic_fix_commands(&variable_name, &file_path, line);
8189 (FixType::TextReplacement, auto_commands, 0.85)
8190 } else {
8191 (FixType::ManualInterventionRequired, vec![], 0.75)
8192 };
8193
8194 let suggestions = vec![
8196 format!(
8197 "// 1. Use a reference instead to avoid moving: &{}",
8198 variable_name
8199 ),
8200 format!(
8201 "// 2. Clone the value before moving: {}.clone()",
8202 variable_name
8203 ),
8204 format!("// 3. Implement Copy trait for the type if it's a small value type"),
8205 format!(
8206 "// 4. Restructure code to avoid using {} after it's moved",
8207 variable_name
8208 ),
8209 ];
8210
8211 let explanation = format!(
8212 "Value `{}` was moved when it was used in a previous operation. In Rust, once a value is moved, \
8213 the original variable can no longer be used unless the type implements Copy. \
8214 Consider one of the following solutions:\n{}",
8215 variable_name, suggestions.join("\n")
8216 );
8217
8218 let details = FixDetails::SuggestCodeChange {
8219 file_path: PathBuf::from(&file_path),
8220 line_hint: line,
8221 suggested_code_snippet: suggestions.join("\n"),
8222 explanation,
8223 };
8224
8225 Some(Autocorrection {
8226 description: format!("Fix use of moved value `{}`", variable_name),
8227 fix_type,
8228 confidence,
8229 details: Some(details),
8230 diff_suggestion: None, commands_to_apply: commands,
8232 targets_error_code: Some("use_after_move".to_string()),
8233 })
8234 }
8235
8236 fn name(&self) -> &'static str {
8237 "BorrowAfterMoveFixGenerator"
8238 }
8239}
8240
8241fn extract_variable_from_move_error(message: &str) -> Option<String> {
8243 let patterns = [
8244 r"value used here after move: `([^`]+)`",
8245 r"value moved here: `([^`]+)`",
8246 r"use of moved value: `([^`]+)`",
8247 ];
8248
8249 for pattern in patterns {
8250 if let Ok(regex) = Regex::new(pattern) {
8251 if let Some(captures) = regex.captures(message) {
8252 if let Some(m) = captures.get(1) {
8253 return Some(m.as_str().to_string());
8254 }
8255 }
8256 }
8257 }
8258
8259 None
8260}
8261
8262pub struct MissingTraitImplFixGenerator;
8264
8265impl MissingTraitImplFixGenerator {
8266 pub fn new() -> Self {
8268 Self
8269 }
8270}
8271
8272impl FixGenerator for MissingTraitImplFixGenerator {
8273 fn generate_fix(
8274 &self,
8275 _error: &DecrustError,
8276 params: &ExtractedParameters,
8277 _source_code_context: Option<&str>,
8278 ) -> Option<Autocorrection> {
8279 let message = params.values.get("message")?;
8281
8282 if !message.contains("not implement") || !message.contains("trait") {
8284 return None;
8285 }
8286
8287 let (type_name, trait_name) = extract_type_and_trait(message)?;
8289
8290 let file_path = params
8291 .values
8292 .get("file_path")
8293 .cloned()
8294 .unwrap_or_else(|| "unknown_file.rs".to_string());
8295
8296 let line = params
8297 .values
8298 .get("line")
8299 .and_then(|l| l.parse::<usize>().ok())
8300 .unwrap_or(1);
8301
8302 let suggestions = self.generate_trait_implementation_suggestions(&type_name, &trait_name);
8304
8305 let explanation = format!(
8306 "Type `{}` does not implement the required trait `{}`. \
8307 You need to implement this trait for your type or use a type that already implements it.",
8308 type_name, trait_name
8309 );
8310
8311 let details = FixDetails::SuggestCodeChange {
8312 file_path: PathBuf::from(&file_path),
8313 line_hint: line,
8314 suggested_code_snippet: suggestions.join("\n"),
8315 explanation,
8316 };
8317
8318 Some(Autocorrection {
8319 description: format!(
8320 "Add implementation of trait `{}` for type `{}`",
8321 trait_name, type_name
8322 ),
8323 fix_type: FixType::ManualInterventionRequired,
8324 confidence: 0.7,
8325 details: Some(details),
8326 diff_suggestion: None,
8327 commands_to_apply: vec![],
8328 targets_error_code: Some("missing_trait_impl".to_string()),
8329 })
8330 }
8331
8332 fn name(&self) -> &'static str {
8333 "MissingTraitImplFixGenerator"
8334 }
8335}
8336
8337impl MissingTraitImplFixGenerator {
8338 fn generate_trait_implementation_suggestions(
8340 &self,
8341 type_name: &str,
8342 trait_name: &str,
8343 ) -> Vec<String> {
8344 let mut suggestions = Vec::new();
8345
8346 suggestions.push(format!("// Implement the trait for your type:"));
8348 suggestions.push(format!("impl {} for {} {{", trait_name, type_name));
8349
8350 match trait_name {
8352 "std::fmt::Display" | "Display" => {
8353 suggestions.push(
8354 " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {"
8355 .to_string(),
8356 );
8357 suggestions.push(
8358 " write!(f, \"{}\" /* Add format string */, /* Add fields */))"
8359 .to_string(),
8360 );
8361 suggestions.push(" }".to_string());
8362 }
8363 "std::fmt::Debug" | "Debug" => {
8364 suggestions.push(
8365 " // Consider using #[derive(Debug)] instead of manual implementation"
8366 .to_string(),
8367 );
8368 suggestions.push(
8369 " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {"
8370 .to_string(),
8371 );
8372 suggestions.push(" f.debug_struct(\"TypeName\")".to_string());
8373 suggestions
8374 .push(" // .field(\"field_name\", &self.field_name)".to_string());
8375 suggestions.push(" .finish()".to_string());
8376 suggestions.push(" }".to_string());
8377 }
8378 "Clone" => {
8379 suggestions.push(
8380 " // Consider using #[derive(Clone)] instead of manual implementation"
8381 .to_string(),
8382 );
8383 suggestions.push(" fn clone(&self) -> Self {".to_string());
8384 suggestions.push(" Self {".to_string());
8385 suggestions.push(" // field: self.field.clone(),".to_string());
8386 suggestions.push(" }".to_string());
8387 suggestions.push(" }".to_string());
8388 }
8389 "Copy" => {
8390 suggestions
8391 .push(" // Copy trait requires no method implementations".to_string());
8392 suggestions.push(" // All fields must also implement Copy".to_string());
8393 suggestions.push(" // Consider using #[derive(Copy, Clone)]".to_string());
8394 }
8395 "PartialEq" => {
8396 suggestions.push(
8397 " // Consider using #[derive(PartialEq)] instead of manual implementation"
8398 .to_string(),
8399 );
8400 suggestions.push(" fn eq(&self, other: &Self) -> bool {".to_string());
8401 suggestions.push(" // self.field == other.field".to_string());
8402 suggestions.push(" true // Replace with actual equality check".to_string());
8403 suggestions.push(" }".to_string());
8404 }
8405 "Iterator" => {
8406 suggestions
8407 .push(" type Item = /* Type of items yielded by iterator */;".to_string());
8408 suggestions.push(" fn next(&mut self) -> Option<Self::Item> {".to_string());
8409 suggestions.push(" // Implement iteration logic".to_string());
8410 suggestions.push(" None // Replace with actual implementation".to_string());
8411 suggestions.push(" }".to_string());
8412 }
8413 "Default" => {
8414 suggestions.push(
8415 " // Consider using #[derive(Default)] if all fields implement Default"
8416 .to_string(),
8417 );
8418 suggestions.push(" fn default() -> Self {".to_string());
8419 suggestions.push(" Self {".to_string());
8420 suggestions.push(" // field: Default::default(),".to_string());
8421 suggestions.push(" }".to_string());
8422 suggestions.push(" }".to_string());
8423 }
8424 _ => {
8425 suggestions
8426 .push(" // Implement the required methods for this trait".to_string());
8427 suggestions.push(" // Refer to the documentation for this trait".to_string());
8428 }
8429 }
8430
8431 suggestions.push("}".to_string());
8432
8433 suggestions.push("".to_string());
8435 suggestions.push("// Alternative approaches:".to_string());
8436
8437 if [
8439 "Debug",
8440 "Clone",
8441 "Copy",
8442 "PartialEq",
8443 "Eq",
8444 "PartialOrd",
8445 "Ord",
8446 "Hash",
8447 "Default",
8448 ]
8449 .contains(&trait_name)
8450 {
8451 suggestions.push(format!(
8452 "// 1. Add #[derive({})] to your type definition",
8453 trait_name
8454 ));
8455 }
8456
8457 suggestions.push(format!(
8458 "// 2. Use a type that already implements {} instead",
8459 trait_name
8460 ));
8461 suggestions.push(format!("// 3. Use a trait bound in your generic function"));
8462
8463 suggestions
8464 }
8465}
8466
8467fn extract_type_and_trait(message: &str) -> Option<(String, String)> {
8469 let patterns = [
8470 r"the trait `([^`]+)` is not implemented for `([^`]+)`",
8471 r"type `([^`]+)` does not implement `([^`]+)`",
8472 ];
8473
8474 for pattern in patterns {
8475 if let Ok(regex) = Regex::new(pattern) {
8476 if let Some(captures) = regex.captures(message) {
8477 if captures.len() >= 3 {
8478 let trait_name = captures.get(1)?.as_str().to_string();
8479 let type_name = captures.get(2)?.as_str().to_string();
8480 return Some((type_name, trait_name));
8481 }
8482 }
8483 }
8484 }
8485
8486 let alt_pattern = r"`([^`]+)` doesn't implement `([^`]+)`";
8488 if let Ok(regex) = Regex::new(alt_pattern) {
8489 if let Some(captures) = regex.captures(message) {
8490 if captures.len() >= 3 {
8491 let type_name = captures.get(1)?.as_str().to_string();
8492 let trait_name = captures.get(2)?.as_str().to_string();
8493 return Some((type_name, trait_name));
8494 }
8495 }
8496 }
8497
8498 None
8499}
8500
8501fn extract_variable_from_borrow_error(message: &str) -> Option<String> {
8503 let patterns = [
8504 r"cannot borrow `([^`]+)` as mutable",
8505 r"cannot borrow \*?([a-zA-Z0-9_]+) as mutable",
8506 ];
8507
8508 for pattern in patterns {
8509 if let Ok(regex) = Regex::new(pattern) {
8510 if let Some(captures) = regex.captures(message) {
8511 if let Some(m) = captures.get(1) {
8512 return Some(m.as_str().to_string());
8513 }
8514 }
8515 }
8516 }
8517
8518 None
8519}
8520
8521fn extract_type(message: &str, prefix: &str) -> Option<String> {
8523 let patterns = [
8525 format!(r"{} type `([^`]+)`", prefix), format!(r"{} `([^`]+)`", prefix), format!(r"{} ([a-zA-Z0-9_::<>]+)", prefix), format!(r"mismatched types: {} `([^`]+)`", prefix), ];
8530
8531 for pattern in patterns {
8532 if let Ok(regex) = Regex::new(&pattern) {
8533 if let Some(captures) = regex.captures(message) {
8534 if let Some(m) = captures.get(1) {
8535 return Some(m.as_str().to_string());
8536 }
8537 }
8538 }
8539 }
8540
8541 if message.contains("mismatched types") && message.contains(prefix) {
8543 if prefix == "expected" && message.contains("String") {
8544 return Some("String".to_string());
8545 } else if prefix == "found" && message.contains("i32") {
8546 return Some("i32".to_string());
8547 } else if prefix == "expected" && message.contains("&str") {
8548 return Some("&str".to_string());
8549 } else if prefix == "found" && message.contains("String") {
8550 return Some("String".to_string());
8551 }
8552 }
8553
8554 None
8555}
8556
8557pub struct UnusedVariableFixGenerator;
8559
8560impl UnusedVariableFixGenerator {
8561 pub fn new() -> Self {
8563 Self
8564 }
8565}
8566
8567impl FixGenerator for UnusedVariableFixGenerator {
8568 fn generate_fix(
8569 &self,
8570 _error: &DecrustError,
8571 params: &ExtractedParameters,
8572 source_code_context: Option<&str>,
8573 ) -> Option<Autocorrection> {
8574 let variable_name = params
8576 .values
8577 .get("param1")
8578 .cloned()
8579 .unwrap_or_else(|| "unknown_variable".to_string());
8580
8581 let description = format!(
8583 "Add underscore prefix to unused variable: `{}`",
8584 variable_name
8585 );
8586
8587 let file_path = params
8589 .values
8590 .get("file_path")
8591 .cloned()
8592 .unwrap_or_else(|| "unknown_file.rs".to_string());
8593
8594 let line = params
8596 .values
8597 .get("line")
8598 .and_then(|l| l.parse::<usize>().ok())
8599 .unwrap_or(1);
8600
8601 let (fix_details, commands, diff) = if let Some(context) = source_code_context {
8603 self.generate_context_aware_fix(&variable_name, &file_path, line, context)
8604 } else {
8605 self.generate_simple_fix(&variable_name, &file_path, line)
8606 };
8607
8608 Some(Autocorrection {
8609 description,
8610 fix_type: FixType::TextReplacement,
8611 confidence: params.confidence,
8612 details: Some(fix_details),
8613 diff_suggestion: Some(diff),
8614 commands_to_apply: commands,
8615 targets_error_code: Some("unused_variables".to_string()),
8616 })
8617 }
8618
8619 fn name(&self) -> &'static str {
8620 "UnusedVariableFixGenerator"
8621 }
8622}
8623
8624impl UnusedVariableFixGenerator {
8625 fn generate_context_aware_fix(
8627 &self,
8628 variable_name: &str,
8629 file_path: &str,
8630 line: usize,
8631 context: &str,
8632 ) -> (FixDetails, Vec<String>, String) {
8633 let lines: Vec<&str> = context.lines().collect();
8635
8636 let var_line = lines
8638 .iter()
8639 .find(|&&l| {
8640 l.contains(&format!(" {} ", variable_name)) ||
8641 l.contains(&format!(" {}", variable_name)) ||
8642 l.contains(&format!("({}", variable_name)) ||
8643 l.contains(&format!("({} ", variable_name)) ||
8644 l.contains(&format!(" {}: ", variable_name)) ||
8645 l.contains(&format!("({}: ", variable_name)) ||
8646 l.contains(&format!("Ok({}", variable_name)) ||
8648 l.contains(&format!("Ok({} ", variable_name)) ||
8649 l.contains(&format!("Err({}", variable_name)) ||
8650 l.contains(&format!("Err({} ", variable_name)) ||
8651 l.contains(&format!("Some({}", variable_name)) ||
8652 l.contains(&format!("Some({} ", variable_name)) ||
8653 l.contains(&format!("None({}", variable_name)) ||
8654 l.contains(&format!("None({} ", variable_name))
8655 })
8656 .map(|&l| l.trim())
8657 .unwrap_or("");
8658
8659 if var_line.is_empty() {
8660 return self.generate_simple_fix(variable_name, file_path, line);
8661 }
8662
8663 let var_regex = Regex::new(&format!(r"\b{}\b", regex::escape(variable_name))).unwrap();
8665
8666 let new_line = var_regex
8668 .replace(var_line, &format!("_{}", variable_name))
8669 .to_string();
8670
8671 let sed_command = format!(
8673 "sed -i '{}s/{}/{}/' \"{}\"",
8674 line,
8675 regex::escape(var_line),
8676 regex::escape(&new_line),
8677 file_path
8678 );
8679
8680 let explanation = format!(
8681 "Adding underscore prefix to unused variable '{}'. \
8682 This indicates to the compiler that the variable is intentionally unused.",
8683 variable_name
8684 );
8685
8686 let details = FixDetails::SuggestCodeChange {
8687 file_path: PathBuf::from(file_path),
8688 line_hint: line,
8689 suggested_code_snippet: format!("// Replace with:\n{}", new_line),
8690 explanation,
8691 };
8692
8693 let diff = format!("-{}\n+{}", var_line, new_line);
8694
8695 (details, vec![sed_command], diff)
8696 }
8697
8698 fn generate_simple_fix(
8700 &self,
8701 variable_name: &str,
8702 file_path: &str,
8703 line: usize,
8704 ) -> (FixDetails, Vec<String>, String) {
8705 let sed_command = format!(
8707 "sed -i '{}s/\\b{}\\b/_{}/g' \"{}\"",
8708 line,
8709 regex::escape(variable_name),
8710 regex::escape(variable_name),
8711 file_path
8712 );
8713
8714 let explanation = format!(
8715 "Adding underscore prefix to unused variable '{}'. \
8716 This indicates to the compiler that the variable is intentionally unused.",
8717 variable_name
8718 );
8719
8720 let details = FixDetails::SuggestCodeChange {
8721 file_path: PathBuf::from(file_path),
8722 line_hint: line,
8723 suggested_code_snippet: format!(
8724 "// Replace '{}' with '_{}'",
8725 variable_name, variable_name
8726 ),
8727 explanation,
8728 };
8729
8730 let diff = format!("-... {} ...\n+... _{} ...", variable_name, variable_name);
8731
8732 (details, vec![sed_command], diff)
8733 }
8734}
8735
8736#[derive(Debug, Clone)]
8738pub struct DependencyAnalysisResult {
8739 pub crate_name: String,
8741 pub current_version: String,
8743 pub enabled_features: Vec<String>,
8745 pub used_features: Vec<String>,
8747 pub unused_features: Vec<String>,
8749 pub missing_features: Vec<String>,
8751 pub version_status: VersionCompatibility,
8753 pub suggestions: Vec<String>,
8755 pub usage_analysis: CrateUsageAnalysis,
8757 pub interactive_recommendations: Vec<InteractiveRecommendation>,
8759}
8760
8761#[derive(Debug, Clone)]
8763pub struct CrateUsageAnalysis {
8764 pub functions_used: Vec<String>,
8766 pub macros_used: Vec<String>,
8768 pub types_used: Vec<String>,
8770 pub traits_used: Vec<String>,
8772 pub derive_macros_used: Vec<String>,
8774 pub attribute_macros_used: Vec<String>,
8776 pub modules_accessed: Vec<String>,
8778 pub constants_used: Vec<String>,
8780 pub usage_frequency: std::collections::HashMap<String, usize>,
8782}
8783
8784#[derive(Debug, Clone)]
8786pub struct InteractiveRecommendation {
8787 pub recommendation_type: RecommendationType,
8789 pub current_config: String,
8791 pub recommended_config: String,
8793 pub explanation: String,
8795 pub estimated_impact: OptimizationImpact,
8797 pub confidence: f32,
8799 pub auto_applicable: bool,
8801}
8802
8803#[derive(Debug, Clone, PartialEq)]
8805pub enum RecommendationType {
8806 RemoveUnusedFeatures,
8808 AddMissingFeatures,
8810 UpdateVersion,
8812 ReplaceWithAlternative {
8814 alternative_crate: String,
8816 },
8817 SplitFeatures,
8819 ConsolidateFeatures,
8821 RemoveDependency,
8823}
8824
8825#[derive(Debug, Clone)]
8827pub struct OptimizationImpact {
8828 pub binary_size_reduction: Option<usize>,
8830 pub compile_time_improvement: Option<f32>,
8832 pub transitive_deps_removed: usize,
8834 pub security_improvement: SecurityImpact,
8836}
8837
8838#[derive(Debug, Clone, PartialEq)]
8840pub enum SecurityImpact {
8841 High,
8843 Medium,
8845 Low,
8847 None,
8849}
8850
8851#[derive(Debug, Clone, PartialEq)]
8853pub enum VersionCompatibility {
8854 Compatible,
8856 Outdated {
8858 latest_version: String,
8860 },
8861 Incompatible {
8863 reason: String,
8865 },
8866 Unknown,
8868}
8869
8870#[derive(Debug, Clone)]
8875pub struct DependencyAnalyzer {
8876 analysis_cache: std::collections::HashMap<String, DependencyAnalysisResult>,
8878 #[allow(dead_code)]
8880 verbose_mode: bool,
8881 check_latest_versions: bool,
8883}
8884
8885impl Default for DependencyAnalyzer {
8886 fn default() -> Self {
8887 Self::new()
8888 }
8889}
8890
8891impl DependencyAnalyzer {
8892 pub fn new() -> Self {
8894 Self {
8895 analysis_cache: std::collections::HashMap::new(),
8896 verbose_mode: false,
8897 check_latest_versions: false,
8898 }
8899 }
8900
8901 pub fn with_config(verbose: bool, check_latest: bool) -> Self {
8903 Self {
8904 analysis_cache: std::collections::HashMap::new(),
8905 verbose_mode: verbose,
8906 check_latest_versions: check_latest,
8907 }
8908 }
8909
8910 pub fn analyze_code_dependencies(&mut self, code: &str) -> Vec<DependencyAnalysisResult> {
8915 let mut results = Vec::new();
8916
8917 let used_crates = self.extract_crate_usage(code);
8919
8920 for crate_name in used_crates {
8921 if let Some(cached_result) = self.analysis_cache.get(&crate_name) {
8922 results.push(cached_result.clone());
8923 } else {
8924 let analysis = self.analyze_single_crate(&crate_name, code);
8925 self.analysis_cache
8926 .insert(crate_name.clone(), analysis.clone());
8927 results.push(analysis);
8928 }
8929 }
8930
8931 results
8932 }
8933
8934 fn extract_crate_usage(&self, code: &str) -> Vec<String> {
8936 let mut crates = std::collections::HashSet::new();
8937
8938 if let Ok(regex) = regex::Regex::new(r"use\s+([a-zA-Z_][a-zA-Z0-9_]*)::\w+") {
8940 for cap in regex.captures_iter(code) {
8941 if let Some(crate_name) = cap.get(1) {
8942 let name = crate_name.as_str();
8943 if !is_std_crate(name) {
8944 let actual_crate = map_module_to_crate(name).unwrap_or(name);
8946 crates.insert(actual_crate.to_string());
8947 }
8948 }
8949 }
8950 }
8951
8952 if let Ok(regex) = regex::Regex::new(r"([a-zA-Z_][a-zA-Z0-9_]*)::\w+") {
8954 for cap in regex.captures_iter(code) {
8955 if let Some(crate_name) = cap.get(1) {
8956 let name = crate_name.as_str();
8957 if !is_std_crate(name) && !is_keyword(name) {
8958 let actual_crate = map_module_to_crate(name).unwrap_or(name);
8960 crates.insert(actual_crate.to_string());
8961 }
8962 }
8963 }
8964 }
8965
8966 if let Ok(regex) = regex::Regex::new(r"#\[derive\([^)]*([A-Z][a-zA-Z0-9_]*)[^)]*\)\]") {
8968 for cap in regex.captures_iter(code) {
8969 if let Some(derive_name) = cap.get(1) {
8970 let crate_name = map_derive_to_crate(derive_name.as_str());
8972 if let Some(name) = crate_name {
8973 crates.insert(name.to_string());
8974 }
8975 }
8976 }
8977 }
8978
8979 if code.contains("serde_json::")
8982 || code.contains("to_string(")
8983 || code.contains("from_str(")
8984 {
8985 crates.insert("serde_json".to_string());
8986 }
8987
8988 if code.contains("#[tokio::main]") {
8990 crates.insert("tokio".to_string());
8991 }
8992
8993 crates.into_iter().collect()
8994 }
8995
8996 fn analyze_single_crate(&self, crate_name: &str, code: &str) -> DependencyAnalysisResult {
8998 let (current_version, enabled_features) = self.read_cargo_dependency(crate_name);
9000
9001 let used_features = self.detect_used_features(crate_name, code);
9003
9004 let unused_features: Vec<String> = enabled_features
9006 .iter()
9007 .filter(|f| !used_features.contains(f))
9008 .cloned()
9009 .collect();
9010
9011 let missing_features: Vec<String> = used_features
9012 .iter()
9013 .filter(|f| !enabled_features.contains(f))
9014 .cloned()
9015 .collect();
9016
9017 let version_status = if self.check_latest_versions {
9019 self.check_version_compatibility(crate_name, ¤t_version)
9020 } else {
9021 VersionCompatibility::Unknown
9022 };
9023
9024 let suggestions =
9026 self.generate_suggestions(&unused_features, &missing_features, &version_status);
9027
9028 let usage_analysis = self.analyze_crate_usage(crate_name, code);
9030
9031 let interactive_recommendations = self.generate_interactive_recommendations(
9033 crate_name,
9034 ¤t_version,
9035 &enabled_features,
9036 &used_features,
9037 &unused_features,
9038 &missing_features,
9039 &usage_analysis,
9040 &version_status,
9041 );
9042
9043 DependencyAnalysisResult {
9044 crate_name: crate_name.to_string(),
9045 current_version,
9046 enabled_features,
9047 used_features,
9048 unused_features,
9049 missing_features,
9050 version_status,
9051 suggestions,
9052 usage_analysis,
9053 interactive_recommendations,
9054 }
9055 }
9056
9057 fn read_cargo_dependency(&self, crate_name: &str) -> (String, Vec<String>) {
9059 if let Ok(workspace_content) = std::fs::read_to_string("Cargo.toml") {
9061 if let Some((version, features)) =
9062 self.parse_workspace_dependency(&workspace_content, crate_name)
9063 {
9064 return (version, features);
9065 }
9066 }
9067
9068 if let Ok(cargo_content) = std::fs::read_to_string("Cargo.toml") {
9070 let (version, features) = self.parse_cargo_dependency(&cargo_content, crate_name);
9071 if version != "unknown" {
9072 return (version, features);
9073 }
9074 }
9075
9076 for possible_path in &["../Cargo.toml", "../../Cargo.toml", "../../../Cargo.toml"] {
9078 if let Ok(parent_content) = std::fs::read_to_string(possible_path) {
9079 if let Some((version, features)) =
9080 self.parse_workspace_dependency(&parent_content, crate_name)
9081 {
9082 return (version, features);
9083 }
9084 }
9085 }
9086
9087 ("unknown".to_string(), vec!["default".to_string()])
9089 }
9090
9091 fn parse_cargo_dependency(
9093 &self,
9094 cargo_content: &str,
9095 crate_name: &str,
9096 ) -> (String, Vec<String>) {
9097 let mut version = "unknown".to_string();
9098 let mut features = Vec::new();
9099
9100 let dep_pattern = format!(r#"{}\s*=\s*"([^"]+)""#, regex::escape(crate_name));
9102 if let Ok(regex) = regex::Regex::new(&dep_pattern) {
9103 if let Some(cap) = regex.captures(cargo_content) {
9104 if let Some(version_match) = cap.get(1) {
9105 version = version_match.as_str().to_string();
9106 }
9107 }
9108 }
9109
9110 let features_pattern = format!(
9112 r#"{}\s*=\s*\{{[^}}]*features\s*=\s*\[([^\]]*)\]"#,
9113 regex::escape(crate_name)
9114 );
9115 if let Ok(regex) = regex::Regex::new(&features_pattern) {
9116 if let Some(cap) = regex.captures(cargo_content) {
9117 if let Some(features_match) = cap.get(1) {
9118 features = features_match
9119 .as_str()
9120 .split(',')
9121 .map(|f| f.trim().trim_matches('"').to_string())
9122 .filter(|f| !f.is_empty())
9123 .collect();
9124 }
9125 }
9126 }
9127
9128 if features.is_empty() {
9129 features.push("default".to_string());
9130 }
9131
9132 (version, features)
9133 }
9134
9135 fn parse_workspace_dependency(
9137 &self,
9138 cargo_content: &str,
9139 crate_name: &str,
9140 ) -> Option<(String, Vec<String>)> {
9141 let workspace_deps_start = cargo_content.find("[workspace.dependencies]")?;
9143 let workspace_deps_section = &cargo_content[workspace_deps_start..];
9144
9145 let section_end = workspace_deps_section
9147 .find("\n[")
9148 .unwrap_or(workspace_deps_section.len());
9149 let workspace_deps_content = &workspace_deps_section[..section_end];
9150
9151 let dep_pattern = format!(r#"{}\s*=\s*"([^"]+)""#, regex::escape(crate_name));
9153 if let Ok(simple_regex) = regex::Regex::new(&dep_pattern) {
9154 if let Some(cap) = simple_regex.captures(workspace_deps_content) {
9155 if let Some(version_match) = cap.get(1) {
9156 let version = version_match.as_str().to_string();
9157 return Some((version, vec!["default".to_string()]));
9158 }
9159 }
9160 }
9161
9162 let complex_pattern = format!(
9164 r#"{}\s*=\s*\{{\s*([^}}]+)\s*\}}"#,
9165 regex::escape(crate_name)
9166 );
9167 if let Ok(complex_regex) = regex::Regex::new(&complex_pattern) {
9168 if let Some(cap) = complex_regex.captures(workspace_deps_content) {
9169 if let Some(config_match) = cap.get(1) {
9170 let config_str = config_match.as_str();
9171
9172 let mut version = "unknown".to_string();
9174 if let Ok(version_regex) = regex::Regex::new(r#"version\s*=\s*"([^"]+)""#) {
9175 if let Some(version_cap) = version_regex.captures(config_str) {
9176 if let Some(version_match) = version_cap.get(1) {
9177 version = version_match.as_str().to_string();
9178 }
9179 }
9180 }
9181
9182 let mut features = Vec::new();
9184 if let Ok(features_regex) = regex::Regex::new(r#"features\s*=\s*\[([^\]]*)\]"#)
9185 {
9186 if let Some(features_cap) = features_regex.captures(config_str) {
9187 if let Some(features_match) = features_cap.get(1) {
9188 features = features_match
9189 .as_str()
9190 .split(',')
9191 .map(|f| f.trim().trim_matches('"').to_string())
9192 .filter(|f| !f.is_empty())
9193 .collect();
9194 }
9195 }
9196 }
9197
9198 if features.is_empty() {
9199 features.push("default".to_string());
9200 }
9201
9202 if version != "unknown" {
9203 return Some((version, features));
9204 }
9205 }
9206 }
9207 }
9208
9209 None
9210 }
9211
9212 fn detect_used_features(&self, crate_name: &str, code: &str) -> Vec<String> {
9214 let mut used_features = Vec::new();
9215
9216 match crate_name {
9218 "serde" => {
9219 if code.contains("serde::Serialize")
9220 || code.contains("serde::Deserialize")
9221 || code.contains("#[derive(Serialize")
9222 || code.contains("#[derive(Deserialize")
9223 {
9224 used_features.push("derive".to_string());
9225 }
9226 }
9229 "tokio" => {
9230 if code.contains("tokio::main") || code.contains("#[tokio::main]") {
9231 used_features.push("macros".to_string());
9232 }
9233 if let Ok(fs_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::fs(?:\s|;|::)") {
9235 if fs_regex.is_match(code) {
9236 used_features.push("fs".to_string());
9237 }
9238 }
9239 if let Ok(net_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::net(?:\s|;|::)") {
9240 if net_regex.is_match(code)
9241 || code.contains("TcpListener")
9242 || code.contains("TcpStream")
9243 {
9244 used_features.push("net".to_string());
9245 }
9246 }
9247 if let Ok(time_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::time(?:\s|;|::)") {
9248 if time_regex.is_match(code) {
9249 used_features.push("time".to_string());
9250 }
9251 }
9252 if let Ok(sync_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::sync(?:\s|;|::)") {
9253 if sync_regex.is_match(code) {
9254 used_features.push("sync".to_string());
9255 }
9256 }
9257 }
9259 "regex" => {
9260 used_features.push("default".to_string());
9262 }
9263 "chrono" => {
9264 if code.contains("chrono::serde") || code.contains("serde") {
9265 used_features.push("serde".to_string());
9266 }
9267 }
9268 _ => {
9269 used_features.push("default".to_string());
9271 }
9272 }
9273
9274 used_features.sort();
9279 used_features.dedup();
9280 used_features
9281 }
9282
9283 fn check_version_compatibility(
9285 &self,
9286 _crate_name: &str,
9287 _current_version: &str,
9288 ) -> VersionCompatibility {
9289 VersionCompatibility::Unknown
9292 }
9293
9294 fn generate_suggestions(
9296 &self,
9297 unused_features: &[String],
9298 missing_features: &[String],
9299 version_status: &VersionCompatibility,
9300 ) -> Vec<String> {
9301 let mut suggestions = Vec::new();
9302
9303 if !unused_features.is_empty() {
9304 suggestions.push(format!(
9305 "Consider removing unused features: {}",
9306 unused_features.join(", ")
9307 ));
9308 }
9309
9310 if !missing_features.is_empty() {
9311 suggestions.push(format!(
9312 "Consider adding missing features: {}",
9313 missing_features.join(", ")
9314 ));
9315 }
9316
9317 match version_status {
9318 VersionCompatibility::Outdated { latest_version } => {
9319 suggestions.push(format!(
9320 "Consider updating to latest version: {}",
9321 latest_version
9322 ));
9323 }
9324 VersionCompatibility::Incompatible { reason } => {
9325 suggestions.push(format!("Version compatibility issue: {}", reason));
9326 }
9327 _ => {}
9328 }
9329
9330 suggestions
9331 }
9332
9333 fn analyze_crate_usage(&self, crate_name: &str, code: &str) -> CrateUsageAnalysis {
9335 let mut functions_used = Vec::new();
9336 let mut macros_used = Vec::new();
9337 let mut types_used = Vec::new();
9338 let mut traits_used = Vec::new();
9339 let mut derive_macros_used = Vec::new();
9340 let mut attribute_macros_used = Vec::new();
9341 let mut modules_accessed = Vec::new();
9342 let constants_used = Vec::new();
9343 let mut usage_frequency = std::collections::HashMap::new();
9344
9345 match crate_name {
9347 "serde" => {
9348 self.analyze_serde_usage(
9349 code,
9350 &mut functions_used,
9351 &mut macros_used,
9352 &mut types_used,
9353 &mut traits_used,
9354 &mut derive_macros_used,
9355 &mut usage_frequency,
9356 );
9357 }
9358 "tokio" => {
9359 self.analyze_tokio_usage(
9360 code,
9361 &mut functions_used,
9362 &mut macros_used,
9363 &mut types_used,
9364 &mut modules_accessed,
9365 &mut attribute_macros_used,
9366 &mut usage_frequency,
9367 );
9368 }
9369 "regex" => {
9370 self.analyze_regex_usage(
9371 code,
9372 &mut functions_used,
9373 &mut types_used,
9374 &mut usage_frequency,
9375 );
9376 }
9377 "chrono" => {
9378 self.analyze_chrono_usage(
9379 code,
9380 &mut functions_used,
9381 &mut types_used,
9382 &mut usage_frequency,
9383 );
9384 }
9385 "serde_json" => {
9386 self.analyze_serde_json_usage(code, &mut functions_used, &mut usage_frequency);
9387 }
9388 _ => {
9389 self.analyze_generic_usage(
9391 crate_name,
9392 code,
9393 &mut functions_used,
9394 &mut types_used,
9395 &mut usage_frequency,
9396 );
9397 }
9398 }
9399
9400 CrateUsageAnalysis {
9401 functions_used,
9402 macros_used,
9403 types_used,
9404 traits_used,
9405 derive_macros_used,
9406 attribute_macros_used,
9407 modules_accessed,
9408 constants_used,
9409 usage_frequency,
9410 }
9411 }
9412
9413 fn generate_interactive_recommendations(
9415 &self,
9416 crate_name: &str,
9417 current_version: &str,
9418 enabled_features: &[String],
9419 _used_features: &[String],
9420 unused_features: &[String],
9421 missing_features: &[String],
9422 usage_analysis: &CrateUsageAnalysis,
9423 version_status: &VersionCompatibility,
9424 ) -> Vec<InteractiveRecommendation> {
9425 let mut recommendations = Vec::new();
9426
9427 if !unused_features.is_empty() {
9429 let current_config = format!("features = {:?}", enabled_features);
9430 let recommended_features: Vec<String> = enabled_features
9431 .iter()
9432 .filter(|f| !unused_features.contains(f))
9433 .cloned()
9434 .collect();
9435 let recommended_config = format!("features = {:?}", recommended_features);
9436
9437 let estimated_impact =
9438 self.estimate_feature_removal_impact(crate_name, unused_features);
9439
9440 recommendations.push(InteractiveRecommendation {
9441 recommendation_type: RecommendationType::RemoveUnusedFeatures,
9442 current_config,
9443 recommended_config,
9444 explanation: format!(
9445 "Remove {} unused feature(s): {}. This will reduce binary size and compilation time.",
9446 unused_features.len(),
9447 unused_features.join(", ")
9448 ),
9449 estimated_impact,
9450 confidence: 0.9,
9451 auto_applicable: true,
9452 });
9453 }
9454
9455 if !missing_features.is_empty() {
9457 let current_config = format!("features = {:?}", enabled_features);
9458 let mut recommended_features = enabled_features.to_vec();
9459 recommended_features.extend(missing_features.iter().cloned());
9460 let recommended_config = format!("features = {:?}", recommended_features);
9461
9462 recommendations.push(InteractiveRecommendation {
9463 recommendation_type: RecommendationType::AddMissingFeatures,
9464 current_config,
9465 recommended_config,
9466 explanation: format!(
9467 "Add {} missing feature(s): {}. This will enable functionality you're already using.",
9468 missing_features.len(),
9469 missing_features.join(", ")
9470 ),
9471 estimated_impact: OptimizationImpact {
9472 binary_size_reduction: None,
9473 compile_time_improvement: None,
9474 transitive_deps_removed: 0,
9475 security_improvement: SecurityImpact::None,
9476 },
9477 confidence: 0.95,
9478 auto_applicable: false, });
9480 }
9481
9482 if let VersionCompatibility::Outdated { latest_version } = version_status {
9484 recommendations.push(InteractiveRecommendation {
9485 recommendation_type: RecommendationType::UpdateVersion,
9486 current_config: format!("version = \"{}\"", current_version),
9487 recommended_config: format!("version = \"{}\"", latest_version),
9488 explanation: format!(
9489 "Update from {} to {} for bug fixes, performance improvements, and new features.",
9490 current_version, latest_version
9491 ),
9492 estimated_impact: OptimizationImpact {
9493 binary_size_reduction: None,
9494 compile_time_improvement: Some(0.5),
9495 transitive_deps_removed: 0,
9496 security_improvement: SecurityImpact::Medium,
9497 },
9498 confidence: 0.8,
9499 auto_applicable: false, });
9501 }
9502
9503 recommendations.extend(self.generate_crate_specific_recommendations(
9505 crate_name,
9506 usage_analysis,
9507 enabled_features,
9508 ));
9509
9510 recommendations
9511 }
9512
9513 fn estimate_feature_removal_impact(
9515 &self,
9516 crate_name: &str,
9517 unused_features: &[String],
9518 ) -> OptimizationImpact {
9519 let mut binary_size_reduction = 0;
9520 let mut compile_time_improvement = 0.0;
9521 let mut transitive_deps_removed = 0;
9522 let mut security_improvement = SecurityImpact::Low;
9523
9524 match crate_name {
9526 "tokio" => {
9527 for feature in unused_features {
9528 match feature.as_str() {
9529 "full" => {
9530 binary_size_reduction += 500_000; compile_time_improvement += 2.0;
9532 transitive_deps_removed += 5;
9533 security_improvement = SecurityImpact::High;
9534 }
9535 "macros" => {
9536 binary_size_reduction += 50_000;
9537 compile_time_improvement += 0.3;
9538 }
9539 "net" => {
9540 binary_size_reduction += 200_000;
9541 compile_time_improvement += 0.8;
9542 transitive_deps_removed += 2;
9543 }
9544 _ => {
9545 binary_size_reduction += 10_000;
9546 compile_time_improvement += 0.1;
9547 }
9548 }
9549 }
9550 }
9551 "serde" => {
9552 for feature in unused_features {
9553 match feature.as_str() {
9554 "derive" => {
9555 binary_size_reduction += 100_000;
9556 compile_time_improvement += 0.5;
9557 }
9558 _ => {
9559 binary_size_reduction += 5_000;
9560 }
9561 }
9562 }
9563 }
9564 _ => {
9565 binary_size_reduction = unused_features.len() * 20_000;
9567 compile_time_improvement = unused_features.len() as f32 * 0.2;
9568 }
9569 }
9570
9571 OptimizationImpact {
9572 binary_size_reduction: if binary_size_reduction > 0 {
9573 Some(binary_size_reduction)
9574 } else {
9575 None
9576 },
9577 compile_time_improvement: if compile_time_improvement > 0.0 {
9578 Some(compile_time_improvement)
9579 } else {
9580 None
9581 },
9582 transitive_deps_removed,
9583 security_improvement,
9584 }
9585 }
9586
9587 fn generate_crate_specific_recommendations(
9589 &self,
9590 crate_name: &str,
9591 usage_analysis: &CrateUsageAnalysis,
9592 enabled_features: &[String],
9593 ) -> Vec<InteractiveRecommendation> {
9594 let mut recommendations = Vec::new();
9595
9596 match crate_name {
9597 "tokio" => {
9598 if usage_analysis.functions_used.len() < 3
9600 && !usage_analysis.modules_accessed.contains(&"net".to_string())
9601 {
9602 recommendations.push(InteractiveRecommendation {
9603 recommendation_type: RecommendationType::ReplaceWithAlternative {
9604 alternative_crate: "async-std".to_string()
9605 },
9606 current_config: format!("tokio = {{ features = {:?} }}", enabled_features),
9607 recommended_config: "async-std = \"1.12\"".to_string(),
9608 explanation: "Consider async-std for simpler async needs. It has a smaller footprint and fewer features.".to_string(),
9609 estimated_impact: OptimizationImpact {
9610 binary_size_reduction: Some(300_000),
9611 compile_time_improvement: Some(1.5),
9612 transitive_deps_removed: 8,
9613 security_improvement: SecurityImpact::Medium,
9614 },
9615 confidence: 0.7,
9616 auto_applicable: false,
9617 });
9618 }
9619
9620 if enabled_features.contains(&"full".to_string())
9622 && usage_analysis.modules_accessed.len() < 5
9623 {
9624 let specific_features: Vec<String> = usage_analysis
9625 .modules_accessed
9626 .iter()
9627 .filter_map(|module| match module.as_str() {
9628 "fs" => Some("fs".to_string()),
9629 "net" => Some("net".to_string()),
9630 "time" => Some("time".to_string()),
9631 "sync" => Some("sync".to_string()),
9632 _ => None,
9633 })
9634 .collect();
9635
9636 if !specific_features.is_empty() {
9637 recommendations.push(InteractiveRecommendation {
9638 recommendation_type: RecommendationType::SplitFeatures,
9639 current_config: "features = [\"full\"]".to_string(),
9640 recommended_config: format!("features = {:?}", specific_features),
9641 explanation: format!(
9642 "Replace 'full' feature with specific features: {}. This reduces unused code significantly.",
9643 specific_features.join(", ")
9644 ),
9645 estimated_impact: OptimizationImpact {
9646 binary_size_reduction: Some(400_000),
9647 compile_time_improvement: Some(1.8),
9648 transitive_deps_removed: 6,
9649 security_improvement: SecurityImpact::High,
9650 },
9651 confidence: 0.95,
9652 auto_applicable: true,
9653 });
9654 }
9655 }
9656 }
9657 "serde" => {
9658 if usage_analysis.derive_macros_used.is_empty()
9660 && enabled_features.contains(&"derive".to_string())
9661 {
9662 recommendations.push(InteractiveRecommendation {
9663 recommendation_type: RecommendationType::RemoveUnusedFeatures,
9664 current_config: "features = [\"derive\"]".to_string(),
9665 recommended_config: "features = []".to_string(),
9666 explanation: "Remove 'derive' feature since you're not using #[derive(Serialize, Deserialize)].".to_string(),
9667 estimated_impact: OptimizationImpact {
9668 binary_size_reduction: Some(80_000),
9669 compile_time_improvement: Some(0.4),
9670 transitive_deps_removed: 1,
9671 security_improvement: SecurityImpact::Low,
9672 },
9673 confidence: 0.9,
9674 auto_applicable: true,
9675 });
9676 }
9677 }
9678 _ => {}
9679 }
9680
9681 recommendations
9682 }
9683
9684 #[allow(clippy::ptr_arg)]
9686 fn analyze_serde_usage(
9687 &self,
9688 code: &str,
9689 _functions_used: &mut Vec<String>,
9690 _macros_used: &mut Vec<String>,
9691 types_used: &mut Vec<String>,
9692 traits_used: &mut Vec<String>,
9693 derive_macros_used: &mut Vec<String>,
9694 usage_frequency: &mut std::collections::HashMap<String, usize>,
9695 ) {
9696 if code.contains("#[derive(Serialize") {
9698 derive_macros_used.push("Serialize".to_string());
9699 *usage_frequency.entry("Serialize".to_string()).or_insert(0) += 1;
9700 }
9701 if code.contains("#[derive(Deserialize") {
9702 derive_macros_used.push("Deserialize".to_string());
9703 *usage_frequency
9704 .entry("Deserialize".to_string())
9705 .or_insert(0) += 1;
9706 }
9707
9708 if code.contains("serde::Serialize") {
9710 traits_used.push("Serialize".to_string());
9711 *usage_frequency.entry("Serialize".to_string()).or_insert(0) += 1;
9712 }
9713 if code.contains("serde::Deserialize") {
9714 traits_used.push("Deserialize".to_string());
9715 *usage_frequency
9716 .entry("Deserialize".to_string())
9717 .or_insert(0) += 1;
9718 }
9719
9720 if code.contains("Serializer") {
9722 types_used.push("Serializer".to_string());
9723 *usage_frequency.entry("Serializer".to_string()).or_insert(0) += 1;
9724 }
9725 if code.contains("Deserializer") {
9726 types_used.push("Deserializer".to_string());
9727 *usage_frequency
9728 .entry("Deserializer".to_string())
9729 .or_insert(0) += 1;
9730 }
9731
9732 }
9735
9736 #[allow(clippy::ptr_arg)]
9738 fn analyze_tokio_usage(
9739 &self,
9740 code: &str,
9741 functions_used: &mut Vec<String>,
9742 _macros_used: &mut Vec<String>,
9743 types_used: &mut Vec<String>,
9744 modules_accessed: &mut Vec<String>,
9745 attribute_macros_used: &mut Vec<String>,
9746 usage_frequency: &mut std::collections::HashMap<String, usize>,
9747 ) {
9748 if code.contains("#[tokio::main]") {
9750 attribute_macros_used.push("main".to_string());
9751 *usage_frequency.entry("main".to_string()).or_insert(0) += 1;
9752 }
9753 if code.contains("#[tokio::test]") {
9754 attribute_macros_used.push("test".to_string());
9755 *usage_frequency.entry("test".to_string()).or_insert(0) += 1;
9756 }
9757
9758 if let Ok(fs_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::fs(?:\s|;|::)") {
9762 if fs_regex.is_match(code) {
9763 modules_accessed.push("fs".to_string());
9764 *usage_frequency.entry("fs".to_string()).or_insert(0) += 1;
9765 }
9766 }
9767 if let Ok(net_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::net(?:\s|;|::)") {
9768 if net_regex.is_match(code) {
9769 modules_accessed.push("net".to_string());
9770 *usage_frequency.entry("net".to_string()).or_insert(0) += 1;
9771 }
9772 }
9773 if let Ok(time_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::time(?:\s|;|::)") {
9774 if time_regex.is_match(code) {
9775 modules_accessed.push("time".to_string());
9776 *usage_frequency.entry("time".to_string()).or_insert(0) += 1;
9777 }
9778 }
9779 if let Ok(sync_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::sync(?:\s|;|::)") {
9780 if sync_regex.is_match(code) {
9781 modules_accessed.push("sync".to_string());
9782 *usage_frequency.entry("sync".to_string()).or_insert(0) += 1;
9783 }
9784 }
9785
9786 if code.contains("TcpListener") {
9788 types_used.push("TcpListener".to_string());
9789 modules_accessed.push("net".to_string());
9790 *usage_frequency
9791 .entry("TcpListener".to_string())
9792 .or_insert(0) += 1;
9793 }
9794 if code.contains("TcpStream") {
9795 types_used.push("TcpStream".to_string());
9796 modules_accessed.push("net".to_string());
9797 *usage_frequency.entry("TcpStream".to_string()).or_insert(0) += 1;
9798 }
9799
9800 if code.contains("tokio::spawn") {
9802 functions_used.push("spawn".to_string());
9803 *usage_frequency.entry("spawn".to_string()).or_insert(0) += 1;
9804 }
9805 if code.contains("tokio::select!") {
9806 functions_used.push("select".to_string());
9807 *usage_frequency.entry("select".to_string()).or_insert(0) += 1;
9808 }
9809 }
9810
9811 fn analyze_regex_usage(
9813 &self,
9814 code: &str,
9815 functions_used: &mut Vec<String>,
9816 types_used: &mut Vec<String>,
9817 usage_frequency: &mut std::collections::HashMap<String, usize>,
9818 ) {
9819 if code.contains("Regex::new") {
9821 types_used.push("Regex".to_string());
9822 functions_used.push("new".to_string());
9823 *usage_frequency.entry("Regex".to_string()).or_insert(0) += 1;
9824 }
9825
9826 if code.contains(".is_match(") {
9828 functions_used.push("is_match".to_string());
9829 *usage_frequency.entry("is_match".to_string()).or_insert(0) += 1;
9830 }
9831 if code.contains(".find(") {
9832 functions_used.push("find".to_string());
9833 *usage_frequency.entry("find".to_string()).or_insert(0) += 1;
9834 }
9835 if code.contains(".find_iter(") {
9836 functions_used.push("find_iter".to_string());
9837 *usage_frequency.entry("find_iter".to_string()).or_insert(0) += 1;
9838 }
9839 if code.contains(".captures(") {
9840 functions_used.push("captures".to_string());
9841 *usage_frequency.entry("captures".to_string()).or_insert(0) += 1;
9842 }
9843 if code.contains(".replace(") {
9844 functions_used.push("replace".to_string());
9845 *usage_frequency.entry("replace".to_string()).or_insert(0) += 1;
9846 }
9847 }
9848
9849 fn analyze_chrono_usage(
9851 &self,
9852 code: &str,
9853 functions_used: &mut Vec<String>,
9854 types_used: &mut Vec<String>,
9855 usage_frequency: &mut std::collections::HashMap<String, usize>,
9856 ) {
9857 if code.contains("DateTime<Utc>") {
9859 types_used.push("DateTime".to_string());
9860 types_used.push("Utc".to_string());
9861 *usage_frequency.entry("DateTime".to_string()).or_insert(0) += 1;
9862 }
9863 if code.contains("DateTime<Local>") {
9864 types_used.push("DateTime".to_string());
9865 types_used.push("Local".to_string());
9866 *usage_frequency.entry("DateTime".to_string()).or_insert(0) += 1;
9867 }
9868
9869 if code.contains("Utc::now") {
9871 functions_used.push("now".to_string());
9872 *usage_frequency.entry("now".to_string()).or_insert(0) += 1;
9873 }
9874 if code.contains("Local::now") {
9875 functions_used.push("now".to_string());
9876 *usage_frequency.entry("now".to_string()).or_insert(0) += 1;
9877 }
9878
9879 if code.contains(".parse::<DateTime") {
9881 functions_used.push("parse".to_string());
9882 *usage_frequency.entry("parse".to_string()).or_insert(0) += 1;
9883 }
9884 if code.contains(".format(") {
9885 functions_used.push("format".to_string());
9886 *usage_frequency.entry("format".to_string()).or_insert(0) += 1;
9887 }
9888 }
9889
9890 fn analyze_serde_json_usage(
9892 &self,
9893 code: &str,
9894 functions_used: &mut Vec<String>,
9895 usage_frequency: &mut std::collections::HashMap<String, usize>,
9896 ) {
9897 if code.contains("serde_json::to_string") {
9899 functions_used.push("to_string".to_string());
9900 *usage_frequency.entry("to_string".to_string()).or_insert(0) += 1;
9901 }
9902 if code.contains("serde_json::to_vec") {
9903 functions_used.push("to_vec".to_string());
9904 *usage_frequency.entry("to_vec".to_string()).or_insert(0) += 1;
9905 }
9906 if code.contains("serde_json::to_writer") {
9907 functions_used.push("to_writer".to_string());
9908 *usage_frequency.entry("to_writer".to_string()).or_insert(0) += 1;
9909 }
9910
9911 if code.contains("serde_json::from_str") {
9913 functions_used.push("from_str".to_string());
9914 *usage_frequency.entry("from_str".to_string()).or_insert(0) += 1;
9915 }
9916 if code.contains("serde_json::from_slice") {
9917 functions_used.push("from_slice".to_string());
9918 *usage_frequency.entry("from_slice".to_string()).or_insert(0) += 1;
9919 }
9920 if code.contains("serde_json::from_reader") {
9921 functions_used.push("from_reader".to_string());
9922 *usage_frequency
9923 .entry("from_reader".to_string())
9924 .or_insert(0) += 1;
9925 }
9926 }
9927
9928 fn analyze_generic_usage(
9930 &self,
9931 crate_name: &str,
9932 code: &str,
9933 functions_used: &mut Vec<String>,
9934 types_used: &mut Vec<String>,
9935 usage_frequency: &mut std::collections::HashMap<String, usize>,
9936 ) {
9937 let crate_pattern = format!("{}::", crate_name);
9939 let mut start = 0;
9940 while let Some(pos) = code[start..].find(&crate_pattern) {
9941 let actual_pos = start + pos + crate_pattern.len();
9942 if let Some(end) = code[actual_pos..].find(|c: char| !c.is_alphanumeric() && c != '_') {
9943 let item = &code[actual_pos..actual_pos + end];
9944 if !item.is_empty() {
9945 if item.chars().next().unwrap_or('a').is_uppercase() {
9947 types_used.push(item.to_string());
9948 } else {
9949 functions_used.push(item.to_string());
9950 }
9951 *usage_frequency.entry(item.to_string()).or_insert(0) += 1;
9952 }
9953 }
9954 start = actual_pos;
9955 }
9956 }
9957}
9958
9959fn is_std_crate(name: &str) -> bool {
9961 matches!(name, "std" | "core" | "alloc" | "proc_macro" | "test")
9962}
9963
9964fn is_keyword(name: &str) -> bool {
9966 matches!(name, "self" | "super" | "crate" | "Self")
9967}
9968
9969fn map_derive_to_crate(derive_name: &str) -> Option<&'static str> {
9971 match derive_name {
9972 "Serialize" | "Deserialize" => Some("serde"),
9973 "Error" => Some("thiserror"),
9974 "Derivative" => Some("derivative"),
9975 _ => None,
9976 }
9977}
9978
9979fn map_module_to_crate(module_name: &str) -> Option<&'static str> {
9981 match module_name {
9982 "TcpListener" | "TcpStream" | "UdpSocket" => Some("tokio"),
9984 "fs" => Some("tokio"), "DateTime" | "Utc" | "Local" | "NaiveDate" | "NaiveTime" | "NaiveDateTime" => {
9988 Some("chrono")
9989 }
9990
9991 "Regex" | "RegexBuilder" | "Match" => Some("regex"),
9993
9994 "Serializer" | "Deserializer" => Some("serde"),
9996
9997 _ => None,
9998 }
9999}
10000
10001pub struct Decrust {
10010 parameter_extractors: Vec<Box<dyn ParameterExtractor>>,
10012 fix_generators: HashMap<ErrorCategory, Vec<Box<dyn FixGenerator>>>,
10014 fix_templates: HashMap<ErrorCategory, Vec<FixTemplate>>,
10016 dependency_analyzer: DependencyAnalyzer,
10018}
10019
10020impl Decrust {
10021 pub fn new() -> Self {
10023 let mut decrust = Self {
10024 parameter_extractors: Vec::new(),
10025 fix_generators: HashMap::new(),
10026 fix_templates: HashMap::new(),
10027 dependency_analyzer: DependencyAnalyzer::new(),
10028 };
10029
10030 decrust.register_parameter_extractor(Box::new(RegexParameterExtractor::new()));
10032 decrust.register_parameter_extractor(Box::new(DiagnosticParameterExtractor::new()));
10033
10034 decrust.register_fix_generator(
10036 ErrorCategory::Validation,
10037 Box::new(NotFoundFixGenerator::new()),
10038 );
10039 decrust.register_fix_generator(
10040 ErrorCategory::Validation,
10041 Box::new(UnusedImportFixGenerator::new()),
10042 );
10043 decrust.register_fix_generator(
10044 ErrorCategory::Validation,
10045 Box::new(UnusedVariableFixGenerator::new()),
10046 );
10047 decrust.register_fix_generator(
10048 ErrorCategory::Validation,
10049 Box::new(MissingSemicolonFixGenerator::new()),
10050 );
10051 decrust.register_fix_generator(
10052 ErrorCategory::Validation,
10053 Box::new(MismatchedTypeFixGenerator::new()),
10054 );
10055 decrust.register_fix_generator(
10056 ErrorCategory::Validation,
10057 Box::new(ImmutableBorrowFixGenerator::new()),
10058 );
10059 decrust.register_fix_generator(
10060 ErrorCategory::Validation,
10061 Box::new(BorrowAfterMoveFixGenerator::new()),
10062 );
10063 decrust.register_fix_generator(
10064 ErrorCategory::Validation,
10065 Box::new(MissingTraitImplFixGenerator::new()),
10066 );
10067 decrust.register_fix_generator(
10068 ErrorCategory::Validation,
10069 Box::new(MissingLifetimeFixGenerator::new()),
10070 );
10071 decrust.register_fix_generator(
10072 ErrorCategory::Validation,
10073 Box::new(MatchPatternFixGenerator::new()),
10074 );
10075 decrust.register_fix_generator(
10076 ErrorCategory::Validation,
10077 Box::new(PrivateFieldAccessFixGenerator::new()),
10078 );
10079 decrust.register_fix_generator(
10080 ErrorCategory::Validation,
10081 Box::new(GenericParamConflictFixGenerator::new()),
10082 );
10083 decrust.register_fix_generator(
10084 ErrorCategory::Validation,
10085 Box::new(MissingReturnFixGenerator::new()),
10086 );
10087 decrust.register_fix_generator(
10088 ErrorCategory::Validation,
10089 Box::new(EnumParameterMatchFixGenerator::new()),
10090 );
10091 decrust.register_fix_generator(
10092 ErrorCategory::Validation,
10093 Box::new(StructParameterMatchFixGenerator::new()),
10094 );
10095 decrust.register_fix_generator(
10096 ErrorCategory::Validation,
10097 Box::new(AstTraitImplementationFixGenerator::new()),
10098 );
10099 decrust.register_fix_generator(
10100 ErrorCategory::Validation,
10101 Box::new(ClosureCaptureLifetimeFixGenerator::new()),
10102 );
10103 decrust.register_fix_generator(
10104 ErrorCategory::Validation,
10105 Box::new(RecursiveTypeFixGenerator::new()),
10106 );
10107 decrust.register_fix_generator(
10108 ErrorCategory::Validation,
10109 Box::new(QuestionMarkPropagationFixGenerator::new()),
10110 );
10111 decrust.register_fix_generator(
10112 ErrorCategory::Validation,
10113 Box::new(MissingOkErrFixGenerator::new()),
10114 );
10115 decrust.register_fix_generator(
10116 ErrorCategory::Validation,
10117 Box::new(ReturnLocalReferenceFixGenerator::new()),
10118 );
10119 decrust.register_fix_generator(
10120 ErrorCategory::Validation,
10121 Box::new(UnstableFeatureFixGenerator::new()),
10122 );
10123 decrust.register_fix_generator(
10124 ErrorCategory::Validation,
10125 Box::new(InvalidArgumentCountFixGenerator::new()),
10126 );
10127
10128 decrust.register_fix_generator(
10130 ErrorCategory::Configuration,
10131 Box::new(ConfigSyntaxFixGenerator::new()),
10132 );
10133 decrust.register_fix_generator(
10134 ErrorCategory::Configuration,
10135 Box::new(ConfigMissingKeyFixGenerator::new()),
10136 );
10137
10138 decrust.register_fix_generator(
10140 ErrorCategory::Runtime,
10141 Box::new(UnsafeUnwrapFixGenerator::new()),
10142 );
10143 decrust.register_fix_generator(
10144 ErrorCategory::Runtime,
10145 Box::new(DivisionByZeroFixGenerator::new()),
10146 );
10147 decrust.register_fix_generator(
10148 ErrorCategory::Runtime,
10149 Box::new(RuntimePanicFixGenerator::new()),
10150 );
10151
10152 decrust.register_fix_generator(
10154 ErrorCategory::Parsing,
10155 Box::new(JsonParseFixGenerator::new()),
10156 );
10157 decrust.register_fix_generator(
10158 ErrorCategory::Parsing,
10159 Box::new(YamlParseFixGenerator::new()),
10160 );
10161
10162 decrust.register_fix_generator(
10164 ErrorCategory::Network,
10165 Box::new(NetworkConnectionFixGenerator::new()),
10166 );
10167 decrust.register_fix_generator(
10168 ErrorCategory::Network,
10169 Box::new(NetworkTlsFixGenerator::new()),
10170 );
10171
10172 decrust.register_fix_generator(
10174 ErrorCategory::Style,
10175 Box::new(UnnecessaryBracesFixGenerator::new()),
10176 );
10177 decrust.register_fix_generator(
10178 ErrorCategory::Style,
10179 Box::new(UnnecessaryCloneFixGenerator::new()),
10180 );
10181 decrust.register_fix_generator(
10182 ErrorCategory::Style,
10183 Box::new(UnnecessaryParenthesesFixGenerator::new()),
10184 );
10185 decrust
10186 .register_fix_generator(ErrorCategory::Style, Box::new(UnusedMutFixGenerator::new()));
10187 decrust.register_fix_generator(
10188 ErrorCategory::Style,
10189 Box::new(AstMissingImportFixGenerator::new()),
10190 );
10191 decrust.register_fix_generator(
10192 ErrorCategory::Style,
10193 Box::new(AstUnusedCodeFixGenerator::new()),
10194 );
10195
10196 decrust.register_fix_generator(
10198 ErrorCategory::Io,
10199 Box::new(IoMissingDirectoryFixGenerator::new()),
10200 );
10201 decrust
10202 .register_fix_generator(ErrorCategory::Io, Box::new(IoPermissionFixGenerator::new()));
10203
10204 decrust.register_fix_template(
10206 ErrorCategory::Io,
10207 FixTemplate::new(
10208 "I/O error during '{param1}' on path '{param2}'. Check file permissions and path validity.",
10209 FixType::ManualInterventionRequired,
10210 0.7,
10211 )
10212 );
10213
10214 decrust
10215 }
10216
10217 pub fn register_parameter_extractor(
10219 &mut self,
10220 extractor: Box<dyn ParameterExtractor>,
10221 ) -> &mut Self {
10222 self.parameter_extractors.push(extractor);
10223 self
10224 }
10225
10226 pub fn register_fix_generator(
10228 &mut self,
10229 category: ErrorCategory,
10230 generator: Box<dyn FixGenerator>,
10231 ) -> &mut Self {
10232 self.fix_generators
10233 .entry(category)
10234 .or_insert_with(Vec::new)
10235 .push(generator);
10236 self
10237 }
10238
10239 pub fn register_fix_template(
10241 &mut self,
10242 category: ErrorCategory,
10243 template: FixTemplate,
10244 ) -> &mut Self {
10245 self.fix_templates
10246 .entry(category)
10247 .or_insert_with(Vec::new)
10248 .push(template);
10249 self
10250 }
10251
10252 pub fn extract_parameters(&self, error: &DecrustError) -> ExtractedParameters {
10254 let mut best_params = ExtractedParameters::default();
10255
10256 for extractor in &self.parameter_extractors {
10257 if extractor.supported_categories().contains(&error.category()) {
10258 let params = extractor.extract_parameters(error);
10259
10260 if params.confidence > best_params.confidence
10262 || (params.confidence == best_params.confidence
10263 && params.values.len() > best_params.values.len())
10264 {
10265 best_params = params;
10266 }
10267 }
10268 }
10269
10270 self.infer_parameters(error, &mut best_params);
10272
10273 best_params
10274 }
10275
10276 fn infer_parameters(&self, error: &DecrustError, params: &mut ExtractedParameters) {
10278 match error.category() {
10279 ErrorCategory::NotFound => {
10280 if !params.values.contains_key("resource_type")
10282 && params.values.contains_key("identifier")
10283 {
10284 let identifier = params.values.get("identifier").unwrap();
10285 let path = PathBuf::from(identifier);
10286
10287 if path.is_absolute() || identifier.contains('/') {
10288 params.add_parameter("resource_type", "file");
10289 params.confidence *= 0.9; }
10291 }
10292
10293 if let DecrustError::NotFound {
10295 resource_type,
10296 identifier,
10297 ..
10298 } = error
10299 {
10300 if !params.values.contains_key("resource_type") {
10301 params.add_parameter("resource_type", resource_type);
10302 }
10303 if !params.values.contains_key("identifier") {
10304 params.add_parameter("identifier", identifier);
10305 }
10306 if params.confidence < 0.7 {
10307 params.confidence = 0.7;
10308 }
10309 }
10310 }
10311 ErrorCategory::Io => {
10312 if let DecrustError::Io {
10314 path, operation, ..
10315 } = error
10316 {
10317 if !params.values.contains_key("param1")
10318 && !params.values.contains_key("operation")
10319 {
10320 params.add_parameter("operation", operation);
10321 params.add_parameter("param1", operation);
10322 }
10323 if !params.values.contains_key("param2") && !params.values.contains_key("path")
10324 {
10325 if let Some(p) = path {
10326 let path_str = p.to_string_lossy().to_string();
10327 params.add_parameter("path", &path_str);
10328 params.add_parameter("param2", &path_str);
10329 }
10330 }
10331 if params.confidence < 0.7 {
10332 params.confidence = 0.7;
10333 }
10334 }
10335 }
10336 _ => {}
10338 }
10339 }
10340
10341 pub fn suggest_autocorrection(
10359 &self,
10360 error: &DecrustError,
10361 source_code_context: Option<&str>,
10362 ) -> Option<Autocorrection> {
10363 if let Some(diag_info) = error.get_diagnostic_info() {
10365 if !diag_info.suggested_fixes.is_empty() {
10366 debug!("Decrust: Found tool-suggested fixes in DiagnosticResult.");
10367 let primary_fix_text = diag_info.suggested_fixes.join("\n");
10368 let file_path_from_diag = diag_info
10369 .primary_location
10370 .as_ref()
10371 .map(|loc| PathBuf::from(&loc.file));
10372
10373 let details = file_path_from_diag.map(|fp| FixDetails::TextReplace {
10374 file_path: fp,
10375 line_start: diag_info
10376 .primary_location
10377 .as_ref()
10378 .map_or(0, |loc| loc.line as usize),
10379 column_start: diag_info
10380 .primary_location
10381 .as_ref()
10382 .map_or(0, |loc| loc.column as usize),
10383 line_end: diag_info
10384 .primary_location
10385 .as_ref()
10386 .map_or(0, |loc| loc.line as usize),
10387 column_end: diag_info.primary_location.as_ref().map_or(0, |loc| {
10388 loc.column as usize
10389 + primary_fix_text
10390 .chars()
10391 .filter(|&c| c != '\n')
10392 .count()
10393 .max(1)
10394 }),
10395 original_text_snippet: diag_info.original_message.clone(),
10396 replacement_text: primary_fix_text,
10397 });
10398
10399 return Some(Autocorrection {
10400 description: "Apply fix suggested by diagnostic tool.".to_string(),
10401 fix_type: FixType::TextReplacement,
10402 confidence: 0.85, details,
10404 diff_suggestion: None, commands_to_apply: vec![],
10406 targets_error_code: diag_info.diagnostic_code.clone(),
10407 });
10408 }
10409 }
10410
10411 let params = self.extract_parameters(error);
10413
10414 if let Some(generators) = self.fix_generators.get(&error.category()) {
10416 for generator in generators {
10417 if let Some(fix) = generator.generate_fix(error, ¶ms, source_code_context) {
10418 return Some(fix);
10419 }
10420 }
10421 }
10422
10423 if let Some(templates) = self.fix_templates.get(&error.category()) {
10425 if !templates.is_empty() && !params.values.is_empty() {
10426 let best_template = templates
10428 .iter()
10429 .max_by(|a, b| {
10430 a.base_confidence
10431 .partial_cmp(&b.base_confidence)
10432 .unwrap_or(std::cmp::Ordering::Equal)
10433 })
10434 .unwrap();
10435
10436 return Some(best_template.apply(¶ms));
10437 }
10438 }
10439
10440 match error.category() {
10442 ErrorCategory::NotFound => {
10443 let (resource_type, identifier) = if let DecrustError::NotFound {
10444 resource_type,
10445 identifier,
10446 ..
10447 } = error
10448 {
10449 (resource_type.clone(), identifier.clone())
10450 } else {
10451 tracing::warn!(
10453 "Decrust: NotFound category with unexpected error variant: {:?}",
10454 error
10455 );
10456 (
10457 "unknown resource".to_string(),
10458 "unknown identifier".to_string(),
10459 )
10460 };
10461
10462 let mut commands = vec![];
10463 let mut suggestion_details = None;
10464 if resource_type == "file" || resource_type == "path" {
10465 let path_buf = PathBuf::from(&identifier);
10466 if let Some(parent) = path_buf.parent() {
10467 if !parent.as_os_str().is_empty() && !parent.exists() {
10468 commands.push(format!("mkdir -p \"{}\"", parent.display()));
10470 }
10471 }
10472 commands.push(format!("touch \"{}\"", identifier));
10473 suggestion_details = Some(FixDetails::ExecuteCommand {
10474 command: commands.first().cloned().unwrap_or_default(), args: commands.iter().skip(1).cloned().collect(),
10476 working_directory: None,
10477 });
10478 }
10479 Some(Autocorrection {
10480 description: format!(
10481 "Resource type '{}' with identifier '{}' not found. Consider creating it if it's a file/directory, or verify the path/name.",
10482 resource_type, identifier
10483 ),
10484 fix_type: if commands.is_empty() { FixType::ManualInterventionRequired } else { FixType::ExecuteCommand },
10485 confidence: 0.7,
10486 details: suggestion_details,
10487 diff_suggestion: None,
10488 commands_to_apply: commands,
10489 targets_error_code: Some(format!("{:?}", ErrorCategory::NotFound)),
10490 })
10491 }
10492 ErrorCategory::Io => {
10493 let (source_msg, path_opt, operation_opt, io_kind_opt) = if let DecrustError::Io {
10494 source,
10495 path,
10496 operation,
10497 ..
10498 } = error
10499 {
10500 (
10501 source.to_string(),
10502 path.clone(),
10503 Some(operation.clone()),
10504 Some(source.kind()),
10505 )
10506 } else {
10507 (String::from("Unknown I/O error"), None, None, None)
10508 };
10509 let path_str = path_opt
10510 .as_ref()
10511 .map(|p| p.display().to_string())
10512 .unwrap_or_else(|| "<unknown_path>".to_string());
10513 let op_str = operation_opt.unwrap_or_else(|| "<unknown_op>".to_string());
10514
10515 let mut details = None;
10516 let mut commands = vec![];
10517 let fix_type = match io_kind_opt {
10518 Some(std::io::ErrorKind::NotFound) => {
10519 if let Some(p) = &path_opt {
10520 details = Some(FixDetails::SuggestCodeChange {
10521 file_path: p.clone(),
10522 line_hint: 0, suggested_code_snippet: format!("// Ensure path '{}' exists before operation '{}'\n// Or handle the NotFound error gracefully.", p.display(), op_str),
10524 explanation: "The file or directory specified in the operation was not found at the given path.".to_string(),
10525 });
10526 if p.is_dir() || p.extension().is_none() {
10527 commands.push(format!("mkdir -p \"{}\"", p.display()));
10529 } else {
10530 if let Some(parent) = p.parent() {
10532 if !parent.as_os_str().is_empty() && !parent.exists() {
10533 commands.push(format!("mkdir -p \"{}\"", parent.display()));
10534 }
10535 }
10536 commands.push(format!("touch \"{}\"", p.display()));
10537 }
10538 }
10539 FixType::ExecuteCommand }
10541 Some(std::io::ErrorKind::PermissionDenied) => {
10542 details = Some(FixDetails::SuggestCodeChange{
10543 file_path: path_opt.clone().unwrap_or_else(|| PathBuf::from("unknown_file_causing_permission_error")),
10544 line_hint: 0,
10545 suggested_code_snippet: format!("// Check permissions for path '{}' for operation '{}'", path_str, op_str),
10546 explanation: "The application does not have the necessary permissions to perform the I/O operation.".to_string()
10547 });
10548 FixType::ConfigurationChange }
10550 _ => FixType::Information,
10551 };
10552
10553 Some(Autocorrection {
10554 description: format!("I/O error during '{}' on path '{}': {}. Verify path, permissions, or disk space.", op_str, path_str, source_msg),
10555 fix_type,
10556 confidence: 0.65,
10557 details,
10558 diff_suggestion: None,
10559 commands_to_apply: commands,
10560 targets_error_code: Some(format!("{:?}", ErrorCategory::Io)),
10561 })
10562 }
10563 ErrorCategory::Configuration => {
10564 let (message, path_opt) = if let DecrustError::Config { message, path, .. } = error
10565 {
10566 (message.clone(), path.clone())
10567 } else {
10568 ("Unknown configuration error".to_string(), None)
10569 };
10570 let target_file = path_opt
10571 .clone()
10572 .unwrap_or_else(|| PathBuf::from("config.toml")); Some(Autocorrection {
10574 description: format!("Configuration issue for path '{}': {}. Please review the configuration file structure and values.",
10575 path_opt.as_ref().map(|p| p.display().to_string()).unwrap_or_else(||"<unknown_config>".to_string()), message),
10576 fix_type: FixType::ConfigurationChange,
10577 confidence: 0.7,
10578 details: Some(FixDetails::SuggestCodeChange {
10579 file_path: target_file,
10580 line_hint: 1, suggested_code_snippet: format!("# Review this configuration file for error related to: {}\n# Ensure all values are correctly formatted and all required fields are present.", message),
10582 explanation: "Configuration files require specific syntax, valid values, and all mandatory fields to be present.".to_string()
10583 }),
10584 diff_suggestion: None,
10585 commands_to_apply: vec![],
10586 targets_error_code: Some(format!("{:?}", ErrorCategory::Configuration)),
10587 })
10588 }
10589 _ => {
10591 tracing::trace!(
10592 "Decrust: No specific autocorrection implemented for error category: {:?}. Error: {:?}",
10593 error.category(), error
10594 );
10595 None
10596 }
10597 }
10598 }
10599
10600 pub fn analyze_dependencies(&mut self, code: &str) -> Vec<DependencyAnalysisResult> {
10642 self.dependency_analyzer.analyze_code_dependencies(code)
10643 }
10644
10645 pub fn configure_dependency_analyzer(&mut self, verbose: bool, check_latest: bool) {
10664 self.dependency_analyzer = DependencyAnalyzer::with_config(verbose, check_latest);
10665 }
10666
10667 pub fn generate_dependency_report(&mut self, code: &str) -> String {
10691 let analysis_results = self.analyze_dependencies(code);
10692
10693 if analysis_results.is_empty() {
10694 return "🔍 **Dependency Analysis Report**\n\nNo external dependencies detected in the analyzed code.".to_string();
10695 }
10696
10697 let mut report = String::new();
10698 report.push_str("🔍 **Dependency Analysis Report**\n");
10699 report.push_str("=====================================\n\n");
10700
10701 for (i, result) in analysis_results.iter().enumerate() {
10702 let version_display = if result.current_version == "unknown" {
10703 "version unknown".to_string()
10704 } else {
10705 format!("v{}", result.current_version)
10706 };
10707 report.push_str(&format!(
10708 "## {}. {} ({})\n",
10709 i + 1,
10710 result.crate_name,
10711 version_display
10712 ));
10713
10714 report.push_str("### Features Analysis\n");
10716 report.push_str(&format!(
10717 "- **Enabled**: {}\n",
10718 result.enabled_features.join(", ")
10719 ));
10720 report.push_str(&format!(
10721 "- **Used**: {}\n",
10722 result.used_features.join(", ")
10723 ));
10724
10725 if !result.unused_features.is_empty() {
10726 report.push_str(&format!(
10727 "- **⚠️ Unused**: {}\n",
10728 result.unused_features.join(", ")
10729 ));
10730 }
10731
10732 if !result.missing_features.is_empty() {
10733 report.push_str(&format!(
10734 "- **❌ Missing**: {}\n",
10735 result.missing_features.join(", ")
10736 ));
10737 }
10738
10739 report.push_str("### Version Status\n");
10741 match &result.version_status {
10742 VersionCompatibility::Compatible => {
10743 report.push_str("- ✅ **Compatible and up-to-date**\n");
10744 }
10745 VersionCompatibility::Outdated { latest_version } => {
10746 report.push_str(&format!("- 🔄 **Outdated** (latest: {})\n", latest_version));
10747 }
10748 VersionCompatibility::Incompatible { reason } => {
10749 report.push_str(&format!("- ❌ **Incompatible**: {}\n", reason));
10750 }
10751 VersionCompatibility::Unknown => {
10752 report.push_str("- ❓ **Unknown** (network check disabled)\n");
10753 }
10754 }
10755
10756 if !result.suggestions.is_empty() {
10758 report.push_str("### 💡 Optimization Suggestions\n");
10759 for suggestion in &result.suggestions {
10760 report.push_str(&format!("- {}\n", suggestion));
10761 }
10762 }
10763
10764 report.push_str("\n");
10765 }
10766
10767 let total_crates = analysis_results.len();
10769 let crates_with_unused_features = analysis_results
10770 .iter()
10771 .filter(|r| !r.unused_features.is_empty())
10772 .count();
10773 let crates_with_missing_features = analysis_results
10774 .iter()
10775 .filter(|r| !r.missing_features.is_empty())
10776 .count();
10777
10778 report.push_str("## 📊 Summary\n");
10779 report.push_str(&format!(
10780 "- **Total dependencies analyzed**: {}\n",
10781 total_crates
10782 ));
10783 report.push_str(&format!(
10784 "- **Dependencies with unused features**: {}\n",
10785 crates_with_unused_features
10786 ));
10787 report.push_str(&format!(
10788 "- **Dependencies with missing features**: {}\n",
10789 crates_with_missing_features
10790 ));
10791
10792 if crates_with_unused_features > 0 || crates_with_missing_features > 0 {
10793 report.push_str("\n🎯 **Recommendation**: Review the suggestions above to optimize your dependency footprint and ensure all required features are enabled.\n");
10794 } else {
10795 report.push_str(
10796 "\n✨ **Great!** Your dependency configuration appears to be well-optimized.\n",
10797 );
10798 }
10799
10800 report
10801 }
10802}
10803
10804pub trait AutocorrectableError {
10809 fn suggest_autocorrection(
10816 &self,
10817 decrust_engine: &Decrust,
10818 source_code_context: Option<&str>,
10819 ) -> Option<Autocorrection>;
10820
10821 fn get_diagnostic_info(&self) -> Option<&DiagnosticResult>;
10825}
10826
10827impl AutocorrectableError for super::DecrustError {
10838 fn suggest_autocorrection(
10849 &self,
10850 decrust_engine: &Decrust,
10851 source_code_context: Option<&str>,
10852 ) -> Option<Autocorrection> {
10853 decrust_engine.suggest_autocorrection(self, source_code_context)
10854 }
10855
10856 fn get_diagnostic_info(&self) -> Option<&DiagnosticResult> {
10865 if let super::DecrustError::WithRichContext { context, .. } = self {
10866 context.diagnostic_info.as_ref()
10867 } else {
10868 None
10869 }
10870 }
10871}