#![warn(missing_docs)]
use super::types::{
Autocorrection, DiagnosticResult, ErrorCategory, ExtractedParameters, FixDetails, FixGenerator,
FixTemplate, FixType, ParameterExtractor, ParameterSource,
};
use super::DecrustError;
use regex::Regex;
use std::collections::HashMap;
use std::path::PathBuf;
use tracing::{debug, warn};
use super::circuit_breaker::{CircuitBreaker, CircuitBreakerConfig};
use super::reporter::{ErrorReportConfig, ErrorReporter};
use super::syntax::{SyntaxGenerator, TemplateRegistry};
use std::sync::Arc;
use std::time::Duration;
pub struct RegexParameterExtractor {
patterns: Vec<(Regex, ErrorCategory, f64)>,
}
impl Default for RegexParameterExtractor {
fn default() -> Self {
Self::new()
}
}
impl RegexParameterExtractor {
pub fn new() -> Self {
let patterns = vec![
(
Regex::new(r"Resource type '([^']+)' with identifier '([^']+)'").unwrap(),
ErrorCategory::NotFound,
0.8,
),
(
Regex::new(r"I/O error during '([^']+)' on path '([^']+)'").unwrap(),
ErrorCategory::Io,
0.8,
),
(
Regex::new(r"Configuration error: '([^']+)' in '([^']+)'").unwrap(),
ErrorCategory::Configuration,
0.8,
),
(
Regex::new(r"unused import: `([^`]+)`").unwrap(),
ErrorCategory::Validation,
0.9,
),
(
Regex::new(r"remove the unused import: `([^`]+)`").unwrap(),
ErrorCategory::Validation,
0.9,
),
(
Regex::new(r"unused variable: `([^`]+)`").unwrap(),
ErrorCategory::Validation,
0.9,
),
(
Regex::new(r"if this is intentional, prefix it with an underscore: `_([^`]+)`")
.unwrap(),
ErrorCategory::Validation,
0.9,
),
(
Regex::new(r"unnecessary braces around single import").unwrap(),
ErrorCategory::Style,
0.9,
),
(
Regex::new(r"braces are unnecessary for single-item imports").unwrap(),
ErrorCategory::Style,
0.9,
),
];
Self { patterns }
}
}
impl ParameterExtractor for RegexParameterExtractor {
fn extract_parameters(&self, error: &DecrustError) -> ExtractedParameters {
let message = error.to_string();
let category = error.category();
for (pattern, pat_category, confidence) in &self.patterns {
if *pat_category == category {
if let Some(captures) = pattern.captures(&message) {
let mut params = ExtractedParameters::with_source(
ParameterSource::ErrorMessage,
*confidence,
);
for name in pattern.capture_names().flatten() {
if let Some(value) = captures.name(name) {
params.add_parameter(name, value.as_str());
}
}
if params.values.is_empty() && captures.len() > 1 {
for i in 1..captures.len() {
if let Some(value) = captures.get(i) {
params.add_parameter(format!("param{}", i), value.as_str());
}
}
}
if !params.values.is_empty() {
return params;
}
}
}
}
ExtractedParameters::default()
}
fn name(&self) -> &'static str {
"RegexParameterExtractor"
}
fn supported_categories(&self) -> &[ErrorCategory] {
&[
ErrorCategory::NotFound,
ErrorCategory::Io,
ErrorCategory::Configuration,
]
}
}
pub struct DiagnosticParameterExtractor;
impl Default for DiagnosticParameterExtractor {
fn default() -> Self {
Self::new()
}
}
impl DiagnosticParameterExtractor {
pub fn new() -> Self {
Self
}
}
impl ParameterExtractor for DiagnosticParameterExtractor {
fn extract_parameters(&self, error: &DecrustError) -> ExtractedParameters {
if let Some(diag_info) = error.get_diagnostic_info() {
let mut params = ExtractedParameters::with_source(ParameterSource::DiagnosticInfo, 0.9);
if let Some(location) = &diag_info.primary_location {
params.add_parameter("file_path", &location.file);
params.add_parameter("line", location.line.to_string());
params.add_parameter("column", location.column.to_string());
}
if let Some(code) = &diag_info.diagnostic_code {
params.add_parameter("diagnostic_code", code);
}
if let Some(message) = &diag_info.original_message {
if !message.is_empty() {
params.add_parameter("message", message);
}
}
return params;
}
ExtractedParameters::default()
}
fn name(&self) -> &'static str {
"DiagnosticParameterExtractor"
}
fn supported_categories(&self) -> &[ErrorCategory] {
&[
ErrorCategory::NotFound,
ErrorCategory::Io,
ErrorCategory::Configuration,
ErrorCategory::Network,
ErrorCategory::Validation,
ErrorCategory::Internal,
ErrorCategory::CircuitBreaker,
ErrorCategory::Timeout,
ErrorCategory::Authentication,
ErrorCategory::Authorization,
]
}
}
pub struct NotFoundFixGenerator;
impl Default for NotFoundFixGenerator {
fn default() -> Self {
Self::new()
}
}
impl NotFoundFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for NotFoundFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let resource_type = params
.values
.get("resource_type")
.or_else(|| params.values.get("param1"))
.cloned()
.unwrap_or_else(|| "unknown resource".to_string());
let identifier = params
.values
.get("identifier")
.or_else(|| params.values.get("param2"))
.cloned()
.unwrap_or_else(|| "unknown identifier".to_string());
let mut commands = vec![];
let mut suggestion_details = None;
if resource_type == "file" || resource_type == "path" {
let path_buf = PathBuf::from(&identifier);
if let Some(parent) = path_buf.parent() {
if !parent.as_os_str().is_empty() {
commands.push(format!("mkdir -p \"{}\"", parent.display()));
}
}
commands.push(format!("touch \"{}\"", identifier));
suggestion_details = Some(FixDetails::ExecuteCommand {
command: commands.first().cloned().unwrap_or_default(),
args: commands.iter().skip(1).cloned().collect(),
working_directory: None,
});
}
Some(Autocorrection {
description: format!(
"Resource type '{}' with identifier '{}' not found. Consider creating it if it's a file/directory, or verify the path/name.",
resource_type, identifier
),
fix_type: if commands.is_empty() { FixType::ManualInterventionRequired } else { FixType::ExecuteCommand },
confidence: params.confidence,
details: suggestion_details,
diff_suggestion: None,
commands_to_apply: commands,
targets_error_code: Some(format!("{:?}", ErrorCategory::NotFound)),
})
}
fn name(&self) -> &'static str {
"NotFoundFixGenerator"
}
}
pub struct UnusedImportFixGenerator;
impl Default for UnusedImportFixGenerator {
fn default() -> Self {
Self::new()
}
}
impl UnusedImportFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for UnusedImportFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let unused_import = params
.values
.get("param1")
.cloned()
.unwrap_or_else(|| "unknown import".to_string());
let description = format!("Remove unused import: `{}`", unused_import);
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (fix_details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(&unused_import, &file_path, line, context)
} else {
self.generate_simple_fix(&unused_import, &file_path, line)
};
Some(Autocorrection {
description,
fix_type: FixType::TextReplacement,
confidence: params.confidence,
details: Some(fix_details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("unused_imports".to_string()),
})
}
fn name(&self) -> &'static str {
"UnusedImportFixGenerator"
}
}
impl UnusedImportFixGenerator {
fn generate_context_aware_fix(
&self,
unused_import: &str,
file_path: &str,
line: usize,
context: &str,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
let import_line = lines
.iter()
.find(|&&l| l.contains(unused_import))
.map(|&l| l.trim())
.unwrap_or("");
if import_line.contains("{") && import_line.contains("}") {
return self.handle_grouped_import(unused_import, file_path, line, import_line);
}
if import_line.starts_with("use ") && import_line.ends_with(";") {
return self.handle_simple_import(unused_import, file_path, line);
}
self.generate_simple_fix(unused_import, file_path, line)
}
fn handle_grouped_import(
&self,
unused_import: &str,
file_path: &str,
line: usize,
import_line: &str,
) -> (FixDetails, Vec<String>, String) {
let parts: Vec<&str> = import_line.split("{").collect();
if parts.len() != 2 {
return self.generate_simple_fix(unused_import, file_path, line);
}
let base_path = parts[0].trim();
let items_part = parts[1].trim_end_matches("};").trim();
let items: Vec<&str> = items_part
.split(',')
.map(|s| s.trim())
.filter(|&s| s != unused_import && !s.is_empty())
.collect();
let (new_import_line, sed_command) = if items.len() == 1 {
let new_line = format!("{}{};", base_path, items[0]);
let sed_cmd = format!(
"sed -i '{}s/{}/{}/' \"{}\"",
line,
regex::escape(import_line),
regex::escape(&new_line),
file_path
);
(new_line, sed_cmd)
} else if items.is_empty() {
let sed_cmd = format!("sed -i '{}d' \"{}\"", line, file_path);
(String::new(), sed_cmd)
} else {
let new_items = items.join(", ");
let new_line = format!("{}{{{}}};", base_path, new_items);
let sed_cmd = format!(
"sed -i '{}s/{}/{}/' \"{}\"",
line,
regex::escape(import_line),
regex::escape(&new_line),
file_path
);
(new_line, sed_cmd)
};
let explanation = format!(
"Removing unused import '{}' from grouped import statement. \
The import statement will be updated to '{}'.",
unused_import,
if new_import_line.is_empty() {
"be removed entirely"
} else {
&new_import_line
}
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: if new_import_line.is_empty() {
"// Remove this entire import line".to_string()
} else {
format!("// Replace with:\n{}", new_import_line)
},
explanation,
};
let diff = format!(
"-{}\n+{}",
import_line,
if new_import_line.is_empty() {
""
} else {
&new_import_line
}
);
(details, vec![sed_command], diff)
}
fn handle_simple_import(
&self,
unused_import: &str,
file_path: &str,
line: usize,
) -> (FixDetails, Vec<String>, String) {
self.generate_simple_fix(unused_import, file_path, line)
}
fn generate_simple_fix(
&self,
unused_import: &str,
file_path: &str,
line: usize,
) -> (FixDetails, Vec<String>, String) {
let suggestion = format!(
"// Remove this unused import line containing: {}",
unused_import
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: suggestion,
explanation: "Unused imports should be removed to improve code clarity and avoid compiler warnings.".to_string(),
};
let commands = vec![format!("sed -i '{}d' \"{}\"", line, file_path)];
let diff = format!("-use ... {} ...", unused_import);
(details, commands, diff)
}
}
pub struct MissingSemicolonFixGenerator;
impl MissingSemicolonFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for MissingSemicolonFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let message = params
.values
.get("message")
.cloned()
.unwrap_or_else(|| "expected `;`".to_string());
if !message.contains("expected `;`") && !message.contains("missing semicolon") {
return None;
}
let (details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(&file_path, line, context)
} else {
self.generate_simple_fix(&file_path, line)
};
Some(Autocorrection {
description: "Add missing semicolon at the end of statement".to_string(),
fix_type: FixType::TextReplacement,
confidence: params.confidence,
details: Some(details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("missing_semicolon".to_string()),
})
}
fn name(&self) -> &'static str {
"MissingSemicolonFixGenerator"
}
}
impl MissingSemicolonFixGenerator {
fn generate_context_aware_fix(
&self,
file_path: &str,
line: usize,
context: &str,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
let line_content = if line <= lines.len() {
lines[line - 1]
} else if !lines.is_empty() {
lines[lines.len() - 1]
} else {
return self.generate_simple_fix(file_path, line);
};
let trimmed_line = line_content.trim_end();
let new_line = format!("{};", trimmed_line);
let sed_command = format!(
"sed -i '{}s/{}$/{}/' \"{}\"",
line,
regex::escape(trimmed_line),
regex::escape(&new_line),
file_path
);
let explanation = "Adding missing semicolon at the end of the statement.".to_string();
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: format!("// Add semicolon:\n{}", new_line),
explanation,
};
let diff = format!("-{}\n+{}", line_content, new_line);
(details, vec![sed_command], diff)
}
fn generate_simple_fix(
&self,
file_path: &str,
line: usize,
) -> (FixDetails, Vec<String>, String) {
let sed_command = format!("sed -i '{}s/$/;/' \"{}\"", line, file_path);
let explanation = "Adding missing semicolon at the end of the statement.".to_string();
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: "// Add semicolon at the end of this line".to_string(),
explanation,
};
let diff = "-(line without semicolon)\n+(same line with semicolon added)".to_string();
(details, vec![sed_command], diff)
}
}
pub struct MismatchedTypeFixGenerator;
impl MismatchedTypeFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for MismatchedTypeFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !message.contains("mismatched types")
&& !message.contains("expected")
&& !message.contains("found")
{
return None;
}
let expected_type = if let Some(expected) = extract_type(message, "expected") {
expected
} else {
"expected_type".to_string()
};
let found_type = if let Some(found) = extract_type(message, "found") {
found
} else {
"found_type".to_string()
};
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let suggestions = self.generate_type_conversion_suggestions(&expected_type, &found_type);
let explanation = format!(
"Type mismatch: expected `{}`, found `{}`. Consider one of these solutions:\n{}",
expected_type,
found_type,
suggestions.join("\n")
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(&file_path),
line_hint: line,
suggested_code_snippet: format!("// Type mismatch. Try:\n{}", suggestions.join("\n")),
explanation,
};
let (fix_type, commands, confidence) =
if self.is_simple_automatable_conversion(&expected_type, &found_type) {
let auto_commands = self.generate_automatic_conversion_commands(
&expected_type,
&found_type,
&file_path,
line,
);
(FixType::TextReplacement, auto_commands, 0.9)
} else {
(FixType::ManualInterventionRequired, vec![], 0.7)
};
Some(Autocorrection {
description: format!(
"Fix type mismatch between `{}` and `{}`",
expected_type, found_type
),
fix_type,
confidence,
details: Some(details),
diff_suggestion: None,
commands_to_apply: commands,
targets_error_code: Some("mismatched_types".to_string()),
})
}
fn name(&self) -> &'static str {
"MismatchedTypeFixGenerator"
}
}
impl MismatchedTypeFixGenerator {
fn is_simple_automatable_conversion(&self, expected: &str, found: &str) -> bool {
match (expected, found) {
(exp, found) if exp.contains("String") && found.contains("&str") => true,
(exp, found) if exp.contains("&str") && found.contains("String") => true,
(exp, found)
if exp.starts_with('&') && !found.starts_with('&') && !found.contains("mut") =>
{
true
}
(exp, found)
if exp.contains("Option<")
&& !found.contains("Option<")
&& !found.contains("Result<") =>
{
true
}
(exp, found) if self.is_safe_numeric_conversion(exp, found) => true,
_ => false,
}
}
fn is_safe_numeric_conversion(&self, expected: &str, found: &str) -> bool {
let safe_numeric_types = ["i32", "i64", "u32", "u64", "usize", "isize", "f32", "f64"];
let exp_is_numeric = safe_numeric_types.iter().any(|&t| expected.contains(t));
let found_is_numeric = safe_numeric_types.iter().any(|&t| found.contains(t));
exp_is_numeric && found_is_numeric
}
fn generate_automatic_conversion_commands(
&self,
expected: &str,
found: &str,
file_path: &str,
line: usize,
) -> Vec<String> {
let mut commands = Vec::new();
if expected.contains("String") && found.contains("&str") {
commands.push(format!(
"sed -i '{}s/\\([^.]*\\)/\\1.to_string()/' \"{}\"",
line, file_path
));
} else if expected.contains("&str") && found.contains("String") {
commands.push(format!(
"sed -i '{}s/\\([^.]*\\)/&\\1/' \"{}\"",
line, file_path
));
} else if expected.starts_with('&') && !found.starts_with('&') {
commands.push(format!(
"sed -i '{}s/\\([^&]*\\)/&\\1/' \"{}\"",
line, file_path
));
} else if expected.contains("Option<") && !found.contains("Option<") {
commands.push(format!(
"sed -i '{}s/\\([^S]*\\)/Some(\\1)/' \"{}\"",
line, file_path
));
} else if self.is_safe_numeric_conversion(expected, found) {
commands.push(format!(
"sed -i '{}s/\\([^a]*\\)/\\1 as {}/' \"{}\"",
line,
expected.trim(),
file_path
));
}
commands
}
fn generate_type_conversion_suggestions(&self, expected: &str, found: &str) -> Vec<String> {
let mut suggestions = Vec::new();
if (expected.contains("i32")
|| expected.contains("i64")
|| expected.contains("u32")
|| expected.contains("u64")
|| expected.contains("usize")
|| expected.contains("isize"))
&& (found.contains("i32")
|| found.contains("i64")
|| found.contains("u32")
|| found.contains("u64")
|| found.contains("usize")
|| found.contains("isize"))
{
suggestions.push(format!(
"// 1. Use type casting: `your_variable as {}`",
expected
));
}
if expected.contains("String") && found.contains("&str") {
suggestions.push(
"// 1. Convert &str to String using .to_string() or String::from()".to_string(),
);
suggestions
.push("// Example: your_str.to_string() or String::from(your_str)".to_string());
} else if expected.contains("String") {
suggestions.push("// 1. Convert to String using .to_string()".to_string());
suggestions.push("// Example: your_value.to_string()".to_string());
suggestions.push("// 2. Use String::from if applicable".to_string());
}
if expected.contains("&str") && found.contains("String") {
suggestions.push(
"// 1. Get a string slice using &your_string or your_string.as_str()".to_string(),
);
}
if expected.contains("Option<") && !found.contains("Option<") {
suggestions.push("// 1. Wrap the value in Some(): Some(your_value)".to_string());
}
if !expected.contains("Option<") && found.contains("Option<") {
suggestions.push("// 1. Unwrap the Option: your_option.unwrap()".to_string());
suggestions.push(
"// 2. Use a default value: your_option.unwrap_or(default_value)".to_string(),
);
suggestions.push("// 3. Match on the Option for safer handling".to_string());
}
if expected.contains("Result<") && !found.contains("Result<") {
suggestions.push("// 1. Wrap successful values: Ok(your_value)".to_string());
suggestions.push("// 2. If this is an error case: Err(your_error)".to_string());
}
if !expected.contains("Result<") && found.contains("Result<") {
suggestions.push("// 1. Unwrap the Result: your_result.unwrap()".to_string());
suggestions.push(
"// 2. Use a default value: your_result.unwrap_or(default_value)".to_string(),
);
suggestions.push("// 3. Match on the Result for safer error handling".to_string());
suggestions.push(
"// 4. Propagate the error using ? if in a function returning Result".to_string(),
);
}
if expected.starts_with('&') && !found.starts_with('&') {
suggestions.push("// 1. Add a reference to the value: &your_value".to_string());
}
if !expected.starts_with('&') && found.starts_with('&') {
suggestions.push("// 1. Dereference the value: *your_reference".to_string());
suggestions
.push("// 2. Clone the referenced value: your_reference.clone()".to_string());
}
if expected.contains("PathBuf") && (found.contains("&str") || found.contains("String")) {
suggestions.push(
"// 1. Convert to PathBuf: std::path::PathBuf::from(your_string)".to_string(),
);
}
if suggestions.is_empty() {
suggestions.push(format!("// 1. Make sure your value has type: {}", expected));
suggestions.push(
"// 2. Change the expected type in the receiving function/variable".to_string(),
);
suggestions.push(
"// 3. Implement From<YourType> for TargetType or use .into() if applicable"
.to_string(),
);
}
suggestions
}
}
pub struct ImmutableBorrowFixGenerator;
impl ImmutableBorrowFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for ImmutableBorrowFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !message.contains("cannot borrow") || !message.contains("as mutable") {
return None;
}
let variable_name = extract_variable_from_borrow_error(message)?;
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(&file_path, line, &variable_name, context)
} else {
self.generate_simple_fix(&file_path, line, &variable_name)
};
Some(Autocorrection {
description: format!(
"Change variable `{}` declaration to be mutable",
variable_name
),
fix_type: FixType::TextReplacement,
confidence: 0.8,
details: Some(details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("immutable_borrow".to_string()),
})
}
fn name(&self) -> &'static str {
"ImmutableBorrowFixGenerator"
}
}
impl ImmutableBorrowFixGenerator {
fn generate_context_aware_fix(
&self,
file_path: &str,
line: usize,
variable_name: &str,
context: &str,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
let declaration_line_idx = lines.iter().position(
|&l| {
l.contains(&format!("let {} =", variable_name))
|| l.contains(&format!("let {}: ", variable_name))
|| l.contains(&format!("fn {}(", variable_name))
}, );
if let Some(idx) = declaration_line_idx {
let declaration_line = lines[idx];
let new_line = if declaration_line.contains(&format!("let {} =", variable_name)) {
declaration_line.replace(
&format!("let {} =", variable_name),
&format!("let mut {} =", variable_name),
)
} else if declaration_line.contains(&format!("let {}: ", variable_name)) {
declaration_line.replace(
&format!("let {}: ", variable_name),
&format!("let mut {}: ", variable_name),
)
} else if declaration_line.contains(&format!("fn {}(", variable_name)) {
let mut new_declaration = declaration_line.to_string();
let re = Regex::new(&format!(r"(\b{}\b)(\s*:[^,\)]+)", variable_name)).unwrap();
if re.is_match(&new_declaration) {
new_declaration = re
.replace(&new_declaration, format!("mut $1$2"))
.to_string();
} else {
new_declaration = new_declaration.replace(
&format!("{}:", variable_name),
&format!("mut {}:", variable_name),
);
new_declaration = new_declaration.replace(
&format!("{},", variable_name),
&format!("mut {},", variable_name),
);
new_declaration = new_declaration.replace(
&format!("{})", variable_name),
&format!("mut {})", variable_name),
);
}
new_declaration
} else {
declaration_line.to_string()
};
let sed_command = format!(
"sed -i '{}s/{}/{}/' \"{}\"",
idx + 1, regex::escape(declaration_line),
regex::escape(&new_line),
file_path
);
let explanation = format!(
"To use a mutable borrow with `&mut {}`, the variable must be declared as mutable using `let mut {}`.",
variable_name, variable_name
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: idx + 1,
suggested_code_snippet: format!("// Change to:\n{}", new_line),
explanation,
};
let diff = format!("-{}\n+{}", declaration_line, new_line);
return (details, vec![sed_command], diff);
}
self.generate_simple_fix(file_path, line, variable_name)
}
fn generate_simple_fix(
&self,
file_path: &str,
line: usize,
variable_name: &str,
) -> (FixDetails, Vec<String>, String) {
let explanation = format!(
"To use a mutable borrow with `&mut {}`, the variable must be declared as mutable using `let mut {}`.",
variable_name, variable_name
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: format!(
"// Find where '{}' is declared and change to:\nlet mut {} = ...",
variable_name, variable_name
),
explanation,
};
let diff = format!(
"-let {} = ...\n+let mut {} = ...",
variable_name, variable_name
);
let commands = vec![format!(
"grep -n \"let {} =\" --include=\"*.rs\" -r \"{}\"",
variable_name,
PathBuf::from(file_path)
.parent()
.unwrap_or(&PathBuf::from("."))
.display()
)];
(details, commands, diff)
}
}
pub struct UnnecessaryBracesFixGenerator;
impl UnnecessaryBracesFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for UnnecessaryBracesFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !message.contains("unnecessary braces") && !message.contains("braces are unnecessary") {
return None;
}
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(&file_path, line, context)
} else {
self.generate_simple_fix(&file_path, line)
};
Some(Autocorrection {
description: "Remove unnecessary braces around single import".to_string(),
fix_type: FixType::TextReplacement,
confidence: 0.9,
details: Some(details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("unnecessary_braces".to_string()),
})
}
fn name(&self) -> &'static str {
"UnnecessaryBracesFixGenerator"
}
}
impl UnnecessaryBracesFixGenerator {
fn generate_context_aware_fix(
&self,
file_path: &str,
line: usize,
context: &str,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
let import_line = if line <= lines.len() {
lines[line - 1]
} else if !lines.is_empty() {
lines[lines.len() - 1]
} else {
return self.generate_simple_fix(file_path, line);
};
if !import_line.contains("use ") || !import_line.contains("{") || !import_line.contains("}")
{
return self.generate_simple_fix(file_path, line);
}
let re = Regex::new(r"use\s+([^{]+)\{([^}]+)\};").unwrap();
if let Some(captures) = re.captures(import_line) {
let prefix = captures.get(1).map_or("", |m| m.as_str());
let item = captures.get(2).map_or("", |m| m.as_str()).trim();
if !item.contains(",") {
let new_line = format!("use {}{};", prefix, item);
let sed_command = format!(
"sed -i '{}s/{}/{}/' \"{}\"",
line,
regex::escape(import_line),
regex::escape(&new_line),
file_path
);
let explanation =
"Removing unnecessary braces around a single import item.".to_string();
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: format!("// Change to:\n{}", new_line),
explanation,
};
let diff = format!("-{}\n+{}", import_line, new_line);
return (details, vec![sed_command], diff);
}
}
self.generate_simple_fix(file_path, line)
}
fn generate_simple_fix(
&self,
file_path: &str,
line: usize,
) -> (FixDetails, Vec<String>, String) {
let explanation =
"Rust style guide recommends not using braces for single-item imports.".to_string();
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: "// Change from:\n// use std::time::{Duration};\n// To:\n// use std::time::Duration;".to_string(),
explanation,
};
let sed_command = format!(
"sed -i '{}s/{{\\([^,}}]*\\)}}/\\1/' \"{}\"",
line, file_path
);
let diff = "-use std::time::{Duration};\n+use std::time::Duration;".to_string();
(details, vec![sed_command], diff)
}
}
pub struct MissingLifetimeFixGenerator;
impl MissingLifetimeFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for MissingLifetimeFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !message.contains("lifetime") || !message.contains("missing") {
return None;
}
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(&file_path, line, context)
} else {
self.generate_simple_fix(&file_path, line, message)
};
Some(Autocorrection {
description: "Add missing lifetime parameter".to_string(),
fix_type: FixType::TextReplacement,
confidence: 0.7,
details: Some(details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("missing_lifetime".to_string()),
})
}
fn name(&self) -> &'static str {
"MissingLifetimeFixGenerator"
}
}
impl MissingLifetimeFixGenerator {
fn generate_context_aware_fix(
&self,
file_path: &str,
line: usize,
context: &str,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
let def_line_idx = lines.iter().position(|&l| {
l.contains("fn ") || l.contains("struct ") || l.contains("impl") || l.contains("trait")
});
if let Some(idx) = def_line_idx {
let def_line = lines[idx];
let new_line = if def_line.contains("fn ") && !def_line.contains("<") {
def_line.replace("fn ", "fn <'a> ")
} else if def_line.contains("struct ") && !def_line.contains("<") {
def_line.replace("struct ", "struct <'a> ")
} else if def_line.contains("impl") && !def_line.contains("<") {
def_line.replace("impl", "impl<'a>")
} else if def_line.contains("trait") && !def_line.contains("<") {
def_line.replace("trait", "trait<'a>")
} else if def_line.contains("<") && !def_line.contains("'") {
let open_bracket_pos = def_line.find("<").unwrap();
let mut new_def = def_line.to_string();
new_def.insert_str(open_bracket_pos + 1, "'a, ");
new_def
} else {
def_line.to_string()
};
if new_line == def_line {
return self.generate_simple_fix(file_path, line, "Missing lifetime parameter");
}
let sed_command = format!(
"sed -i '{}s/{}/{}/' \"{}\"",
idx + 1, regex::escape(def_line),
regex::escape(&new_line),
file_path
);
let explanation = "Adding a lifetime parameter to fix missing lifetime specifier. You may need to add \
lifetime annotations to references in the parameter or return types as well.".to_string();
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: idx + 1,
suggested_code_snippet: format!("// Change to:\n{}", new_line),
explanation,
};
let diff = format!("-{}\n+{}", def_line, new_line);
return (details, vec![sed_command], diff);
}
self.generate_simple_fix(file_path, line, "Missing lifetime parameter")
}
fn generate_simple_fix(
&self,
file_path: &str,
line: usize,
_message: &str,
) -> (FixDetails, Vec<String>, String) {
let suggestions = vec![
"// For functions with references in arguments and return value:".to_string(),
"fn example<'a>(arg: &'a Type) -> &'a Type { /* ... */ }".to_string(),
"".to_string(),
"// For structs containing references:".to_string(),
"struct Example<'a> { field: &'a Type }".to_string(),
"".to_string(),
"// For impl blocks for types with lifetimes:".to_string(),
"impl<'a> Example<'a> { /* ... */ }".to_string(),
];
let explanation = "Rust requires explicit lifetime parameters when storing references in structs \
or returning references from functions. The lifetime parameter tells the compiler \
how long the references need to be valid.".to_string();
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: suggestions.join("\n"),
explanation,
};
let commands = vec![];
let diff = format!(
"-// Code with missing lifetime parameter\n+// Code with added lifetime parameter <'a>"
);
(details, commands, diff)
}
}
pub struct MatchPatternFixGenerator;
impl MatchPatternFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for MatchPatternFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let is_non_exhaustive = message.contains("non-exhaustive");
let (details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(&file_path, line, context, is_non_exhaustive)
} else {
self.generate_simple_fix(&file_path, line, is_non_exhaustive)
};
let description = if is_non_exhaustive {
"Add missing patterns to non-exhaustive match expression".to_string()
} else {
"Remove or modify unreachable pattern in match expression".to_string()
};
Some(Autocorrection {
description,
fix_type: FixType::TextReplacement,
confidence: 0.7,
details: Some(details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some(
if is_non_exhaustive {
"non_exhaustive_patterns"
} else {
"unreachable_pattern"
}
.to_string(),
),
})
}
fn name(&self) -> &'static str {
"MatchPatternFixGenerator"
}
}
impl MatchPatternFixGenerator {
fn generate_context_aware_fix(
&self,
file_path: &str,
line: usize,
context: &str,
is_non_exhaustive: bool,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
let match_start_idx = lines.iter().take(line).rposition(|&l| l.contains("match "));
let closing_brace_idx = match_start_idx.and_then(|start_idx| {
lines
.iter()
.skip(start_idx)
.position(|&l| l.trim() == "}")
.map(|rel_pos| start_idx + rel_pos)
});
if let (Some(match_idx), Some(close_idx)) = (match_start_idx, closing_brace_idx) {
let match_line = lines[match_idx];
let enum_type = extract_match_type(match_line);
if is_non_exhaustive {
let indent = lines[close_idx]
.chars()
.take_while(|&c| c.is_whitespace())
.collect::<String>();
let catch_all = format!("{}_ => {{", indent);
let catch_all_body = format!("{} // Handle all other cases", indent);
let catch_all_close = format!("{}}},", indent);
let new_lines: Vec<_> = lines[..close_idx]
.to_vec()
.into_iter()
.chain(vec![
catch_all.as_str(),
catch_all_body.as_str(),
catch_all_close.as_str(),
lines[close_idx],
])
.collect();
let new_content = new_lines.join("\n");
let sed_script = format!(
"sed -i '{},{}c\\{}' \"{}\"",
match_idx + 1,
close_idx + 1,
new_content.replace("\n", "\\n"),
file_path
);
let explanation = format!(
"Adding a catch-all pattern `_` to handle all remaining cases in the match expression. \
This makes the match expression exhaustive as required by Rust.{}",
if let Some(typ) = enum_type {
format!("\n\nYou may want to add specific patterns for all variants of `{}`.", typ)
} else {
String::new()
}
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: close_idx,
suggested_code_snippet: format!(
"// Add before closing brace:\n{}\n{}\n{}",
catch_all, catch_all_body, catch_all_close
),
explanation,
};
let diff = format!(
"@@ match expression @@\n...\n+{}\n+{}\n+{}",
catch_all, catch_all_body, catch_all_close
);
return (details, vec![sed_script], diff);
} else {
let explanation = "One of your match patterns is unreachable because it's already covered by a previous pattern. \
Consider removing the unreachable pattern or making it more specific.".to_string();
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet:
"// Review your match patterns to identify which ones overlap".to_string(),
explanation,
};
return (
details,
vec![],
"// Need to review match patterns for overlap".to_string(),
);
}
}
self.generate_simple_fix(file_path, line, is_non_exhaustive)
}
fn generate_simple_fix(
&self,
file_path: &str,
line: usize,
is_non_exhaustive: bool,
) -> (FixDetails, Vec<String>, String) {
let (explanation, suggestion) = if is_non_exhaustive {
(
"Your match expression doesn't handle all possible cases of the type being matched. \
Rust requires match expressions to be exhaustive to ensure all possible values are handled.",
vec![
"// Add a catch-all pattern at the end of your match expression:".to_string(),
"_ => {".to_string(),
" // Handle remaining cases".to_string(),
"},".to_string(),
"".to_string(),
"// Or list all remaining variants explicitly".to_string(),
]
)
} else {
(
"One of your match patterns is unreachable because it's already covered by a previous pattern. \
This is often caused by a pattern that's too general earlier in the match expression.",
vec![
"// 1. Check for wildcard patterns (`_`) that might come before specific patterns".to_string(),
"// 2. Check for overlapping patterns".to_string(),
"// 3. Consider reordering your patterns from most specific to most general".to_string(),
]
)
};
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: suggestion.join("\n"),
explanation: explanation.to_string(),
};
let commands = vec![];
let diff = if is_non_exhaustive {
"+ _ => { /* Handle all other cases */ },"
} else {
"- [unreachable pattern] => { ... },"
}
.to_string();
(details, commands, diff)
}
}
fn extract_match_type(match_line: &str) -> Option<String> {
let parts: Vec<&str> = match_line.split("match ").collect();
if parts.len() < 2 {
return None;
}
let expr = parts[1].trim().trim_end_matches(" {");
if expr.contains(".") {
let var_name = expr.split('.').next()?;
return Some(format!("[type of {}]", var_name.trim()));
} else if expr.contains("::") {
let parts: Vec<&str> = expr.split("::").collect();
if parts.len() >= 2 {
return Some(parts[0].trim().to_string());
}
} else if expr.starts_with("Some(") || expr.starts_with("None") {
return Some("Option<T>".to_string());
} else if expr.starts_with("Ok(") || expr.starts_with("Err(") {
return Some("Result<T, E>".to_string());
}
Some(expr.to_string())
}
pub struct PrivateFieldAccessFixGenerator;
impl PrivateFieldAccessFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for PrivateFieldAccessFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !message.contains("private") || !message.contains("field") {
return None;
}
let field_name = extract_private_field_name(message)?;
let struct_name = extract_struct_name(message).unwrap_or_else(|| "StructName".to_string());
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (details, commands, diff) = self.generate_fixes(
&file_path,
line,
&struct_name,
&field_name,
source_code_context,
);
let (fix_type, confidence) =
if self.can_automate_fix(&struct_name, &field_name, source_code_context) {
(FixType::TextReplacement, 0.85)
} else {
(FixType::ManualInterventionRequired, 0.75)
};
Some(Autocorrection {
description: format!(
"Fix access to private field `{}` of struct `{}`",
field_name, struct_name
),
fix_type,
confidence,
details: Some(details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("private_field_access".to_string()),
})
}
fn name(&self) -> &'static str {
"PrivateFieldAccessFixGenerator"
}
}
impl PrivateFieldAccessFixGenerator {
fn can_automate_fix(
&self,
_struct_name: &str,
field_name: &str,
source_code_context: Option<&str>,
) -> bool {
let common_getter_fields = [
"id", "name", "value", "data", "content", "text", "count", "size", "length",
];
let is_common_field = common_getter_fields.iter().any(|&f| field_name.contains(f));
if let Some(context) = source_code_context {
let has_simple_access = context.contains(&format!(".{}", field_name)) &&
!context.contains("=") && !context.contains("&mut");
let has_simple_assignment = context.contains(&format!(".{} =", field_name));
is_common_field && (has_simple_access || has_simple_assignment)
} else {
is_common_field
}
}
fn generate_fixes(
&self,
file_path: &str,
line: usize,
struct_name: &str,
field_name: &str,
source_code_context: Option<&str>,
) -> (FixDetails, Vec<String>, String) {
let is_accessing_self = source_code_context
.map(|ctx| ctx.contains("self."))
.unwrap_or(false);
let mut suggestions = Vec::new();
if is_accessing_self {
suggestions.push(format!(
"// Option 1: Make the field public in the struct definition"
));
suggestions.push(format!("pub {}: Type", field_name));
suggestions.push(format!(""));
suggestions.push(format!("// Option 2: Add a getter method"));
suggestions.push(format!("pub fn {}(&self) -> &Type {{", field_name));
suggestions.push(format!(" &self.{}", field_name));
suggestions.push(format!("}}"));
} else {
suggestions.push(format!(
"// Option 1: If you control the struct definition, make the field public"
));
suggestions.push(format!("pub {}: Type", field_name));
suggestions.push(format!(""));
suggestions.push(format!("// Option 2: Use a getter method if available"));
suggestions.push(format!("instance.{}()", field_name));
suggestions.push(format!(""));
suggestions.push(format!(
"// Option 3: Define a getter in the struct implementation"
));
suggestions.push(format!("impl {} {{", struct_name));
suggestions.push(format!(" pub fn {}(&self) -> &Type {{", field_name));
suggestions.push(format!(" &self.{}", field_name));
suggestions.push(format!(" }}"));
suggestions.push(format!("}}"));
}
let find_struct_command = format!(
"grep -n \"struct {}\" --include=\"*.rs\" -r \"{}\"",
struct_name,
PathBuf::from(file_path)
.parent()
.unwrap_or(&PathBuf::from("."))
.display()
);
let explanation = format!(
"You're trying to access the private field `{}` of struct `{}`. \
In Rust, struct fields are private by default and can only be accessed within the module where \
the struct is defined. You have several options to fix this issue.",
field_name, struct_name
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: suggestions.join("\n"),
explanation,
};
let commands = vec![find_struct_command];
let diff = format!(
"// Original code trying to access private field\n-something.{}\n\n// Possible solution\n+something.{}()",
field_name, field_name
);
(details, commands, diff)
}
}
fn extract_private_field_name(message: &str) -> Option<String> {
let patterns = [
r"field `([^`]+)` of struct `[^`]+` is private",
r"field `([^`]+)` is private",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(m) = captures.get(1) {
return Some(m.as_str().to_string());
}
}
}
}
None
}
fn extract_struct_name(message: &str) -> Option<String> {
let pattern = r"field `[^`]+` of struct `([^`]+)` is private";
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(m) = captures.get(1) {
return Some(m.as_str().to_string());
}
}
}
None
}
pub struct GenericParamConflictFixGenerator;
impl GenericParamConflictFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for GenericParamConflictFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !message.contains("generic parameter")
&& !message.contains("parameter")
&& !message.contains("shadow")
{
return None;
}
let param_name = extract_generic_param_name(message)?;
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(&file_path, line, ¶m_name, context)
} else {
self.generate_simple_fix(&file_path, line, ¶m_name)
};
Some(Autocorrection {
description: format!(
"Rename generic parameter `{}` to avoid conflict",
param_name
),
fix_type: FixType::TextReplacement,
confidence: 0.75,
details: Some(details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("generic_parameter_conflict".to_string()),
})
}
fn name(&self) -> &'static str {
"GenericParamConflictFixGenerator"
}
}
impl GenericParamConflictFixGenerator {
fn generate_context_aware_fix(
&self,
file_path: &str,
line: usize,
param_name: &str,
context: &str,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
if let Some(idx) = lines
.iter()
.position(|&l| l.contains("<") && l.contains(">") && l.contains(param_name))
{
let decl_line = lines[idx];
let new_param_name = format!("{}2", param_name);
let new_line = replace_generic_param(decl_line, param_name, &new_param_name);
if new_line == decl_line {
return self.generate_simple_fix(file_path, line, param_name);
}
let sed_command = format!(
"sed -i '{}s/{}/{}/' \"{}\"",
idx + 1, regex::escape(decl_line),
regex::escape(&new_line),
file_path
);
let explanation = format!(
"Renamed conflicting generic parameter `{}` to `{}` to avoid shadowing an existing parameter. \
Note that you will need to update all uses of this parameter in the function/struct body as well.",
param_name, new_param_name
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: idx + 1,
suggested_code_snippet: format!(
"// Change to:\n{}\n\n// Then update all uses of '{}' to '{}'",
new_line, param_name, new_param_name
),
explanation,
};
let diff = format!("-{}\n+{}", decl_line, new_line);
return (details, vec![sed_command], diff);
}
self.generate_simple_fix(file_path, line, param_name)
}
fn generate_simple_fix(
&self,
file_path: &str,
line: usize,
param_name: &str,
) -> (FixDetails, Vec<String>, String) {
let new_param_name = format!("{}2", param_name);
let explanation = format!(
"Generic parameter `{}` conflicts with another parameter with the same name. \
You need to rename one of the parameters to avoid the conflict. \
For example, you could use `{}` instead.",
param_name, new_param_name
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: format!(
"// Replace '{}' with '{}' throughout this declaration and its scope",
param_name, new_param_name
),
explanation,
};
let commands = vec![format!(
"sed -i '{}s/\\b{}\\b/{}/g' \"{}\"",
line,
regex::escape(param_name),
new_param_name,
file_path
)];
let diff = format!("-<{}>\n+<{}>", param_name, new_param_name);
(details, commands, diff)
}
}
fn extract_generic_param_name(message: &str) -> Option<String> {
let patterns = [
r"generic parameter `([A-Za-z0-9_]+)` shadows another",
r"parameter `([A-Za-z0-9_]+)` is never used",
r"the parameter `([A-Za-z0-9_]+)` is already declared",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(m) = captures.get(1) {
return Some(m.as_str().to_string());
}
}
}
}
None
}
fn replace_generic_param(line: &str, old_param: &str, new_param: &str) -> String {
let mut result = line.to_string();
let re = Regex::new(&format!(
r"<([^>]*)\b{}\b([^>]*)>",
regex::escape(old_param)
))
.unwrap();
if let Some(captures) = re.captures(line) {
if captures.len() >= 3 {
let before = captures.get(1).map_or("", |m| m.as_str());
let after = captures.get(2).map_or("", |m| m.as_str());
let replacement = format!("<{}{}{}>", before, new_param, after);
result = re.replace(line, replacement).to_string();
}
}
result
}
pub struct MissingReturnFixGenerator;
impl MissingReturnFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for MissingReturnFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
let return_type = extract_return_type(message)?;
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(&file_path, line, &return_type, context)
} else {
self.generate_simple_fix(&file_path, line, &return_type)
};
Some(Autocorrection {
description: format!("Add missing return value of type `{}`", return_type),
fix_type: FixType::TextReplacement,
confidence: 0.7,
details: Some(details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("missing_return".to_string()),
})
}
fn name(&self) -> &'static str {
"MissingReturnFixGenerator"
}
}
impl MissingReturnFixGenerator {
fn generate_context_aware_fix(
&self,
file_path: &str,
line: usize,
return_type: &str,
context: &str,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
let closing_brace_idx = lines.iter().position(|&l| l.trim() == "}");
if let Some(idx) = closing_brace_idx {
let default_value = generate_default_value(return_type);
let indent = lines[idx]
.chars()
.take_while(|&c| c.is_whitespace())
.collect::<String>();
let return_stmt = format!("{}return {};", indent, default_value);
let new_lines: Vec<_> = lines[..idx]
.to_vec()
.into_iter()
.chain(vec![return_stmt.as_str(), lines[idx]])
.collect();
let new_content = new_lines.join("\n");
let sed_script = format!(
"sed -i '{},{}c\\{}' \"{}\"",
line, line,
new_content.replace("\n", "\\n"),
file_path
);
let explanation = format!(
"Added a return statement with a default value for type `{}`. \
You should replace this with an appropriate value for your function.",
return_type
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: idx,
suggested_code_snippet: format!("// Add before closing brace:\n{}", return_stmt),
explanation,
};
let diff = format!("@@ function body @@\n...\n+{}\n }}", return_stmt);
return (details, vec![sed_script], diff);
}
self.generate_simple_fix(file_path, line, return_type)
}
fn generate_simple_fix(
&self,
file_path: &str,
line: usize,
return_type: &str,
) -> (FixDetails, Vec<String>, String) {
let default_value = generate_default_value(return_type);
let explanation = format!(
"Your function is expected to return a value of type `{}`, but it doesn't have a return statement. \
Add a return statement with an appropriate value before the function's closing brace.",
return_type
);
let suggestions = vec![
format!("// Add this before the function's closing brace:"),
format!("return {};", default_value),
];
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: suggestions.join("\n"),
explanation,
};
let commands = vec![];
let diff = format!("+ return {};", default_value);
(details, commands, diff)
}
}
fn extract_return_type(message: &str) -> Option<String> {
let patterns = [
r"expected `([^`]+)`, found `\(\)`",
r"expected type `([^`]+)`",
r"expected ([a-zA-Z0-9_::<>]+), found",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(m) = captures.get(1) {
return Some(m.as_str().to_string());
}
}
}
}
None
}
fn generate_default_value(type_name: &str) -> String {
match type_name {
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" => "0".to_string(),
"u8" | "u16" | "u32" | "u64" | "u128" | "usize" => "0".to_string(),
"f32" | "f64" => "0.0".to_string(),
"bool" => "false".to_string(),
"char" => "'\\0'".to_string(),
"String" => "String::new()".to_string(),
"&str" => "\"\"".to_string(),
t if t.starts_with("Option<") => "None".to_string(),
t if t.starts_with("Result<") => "Ok(/* value */)".to_string(),
t if t.starts_with("Vec<") => "Vec::new()".to_string(),
t if t.starts_with("HashMap<") => "HashMap::new()".to_string(),
t if t.starts_with("HashSet<") => "HashSet::new()".to_string(),
t if t.starts_with("&") => "/* reference to a value */".to_string(),
t if t.contains("::") => {
let parts: Vec<&str> = t.split("::").collect();
let type_name = parts.last().unwrap_or(&t);
format!("{}::default()", type_name)
}
_ => format!("/* default value for {} */", type_name),
}
}
pub struct AstTraitImplementationFixGenerator;
impl AstTraitImplementationFixGenerator {
pub fn new() -> Self {
Self
}
fn parse_trait_name(&self, message: &str) -> Option<String> {
let patterns = [
r"the trait `([^`]+)` is not implemented",
r"the trait bound `[^:]+: ([^`]+)` is not satisfied",
r"expected a type with the trait `([^`]+)`",
r"expected trait `([^`]+)`",
r"required by the trait `([^`]+)`",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(trait_match) = captures.get(1) {
return Some(trait_match.as_str().to_string());
}
}
}
}
None
}
fn parse_type_name(&self, message: &str) -> Option<String> {
let patterns = [
r"the trait `[^`]+` is not implemented for `([^`]+)`",
r"the trait bound `([^:]+): [^`]+` is not satisfied",
r"type `([^`]+)` does not implement",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(type_match) = captures.get(1) {
return Some(type_match.as_str().to_string());
}
}
}
}
None
}
fn generate_trait_impl(&self, trait_name: &str, type_name: &str) -> Option<String> {
match trait_name {
"std::fmt::Display" | "Display" => Some(format!(
"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}}",
type_name
)),
"std::fmt::Debug" | "Debug" => Some(format!(
"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}}",
type_name, type_name
)),
"std::clone::Clone" | "Clone" => Some(format!(
"impl Clone for {} {{\n fn clone(&self) -> Self {{\n Self {{\n // Clone each field\n }}\n }}\n}}",
type_name
)),
"std::default::Default" | "Default" => Some(format!(
"impl Default for {} {{\n fn default() -> Self {{\n Self {{\n // Initialize with default values\n }}\n }}\n}}",
type_name
)),
"std::cmp::PartialEq" | "PartialEq" => Some(format!(
"impl PartialEq for {} {{\n fn eq(&self, other: &Self) -> bool {{\n // Compare fields\n true\n }}\n}}",
type_name
)),
"std::cmp::Eq" | "Eq" => Some(format!(
"impl Eq for {} {{}}", type_name
)),
"std::hash::Hash" | "Hash" => Some(format!(
"impl std::hash::Hash for {} {{\n fn hash<H: std::hash::Hasher>(&self, state: &mut H) {{\n // Hash fields\n }}\n}}",
type_name
)),
"std::convert::From" | "From" => {
if let Some(param_start) = trait_name.find('<') {
if let Some(param_end) = trait_name.find('>') {
let from_type = &trait_name[param_start + 1..param_end];
return Some(format!(
"impl From<{}> for {} {{\n fn from(value: {}) -> Self {{\n Self {{\n // Convert fields\n }}\n }}\n}}",
from_type, type_name, from_type
));
}
}
None
},
"std::convert::Into" | "Into" => {
if let Some(param_start) = trait_name.find('<') {
if let Some(param_end) = trait_name.find('>') {
let into_type = &trait_name[param_start + 1..param_end];
return Some(format!(
"impl Into<{}> for {} {{\n fn into(self) -> {} {{\n // Convert self to target type\n }}\n}}",
into_type, type_name, into_type
));
}
}
None
},
"std::ops::Add" | "Add" => Some(format!(
"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}}",
type_name
)),
"std::iter::Iterator" | "Iterator" => Some(format!(
"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}}",
type_name
)),
"std::error::Error" | "Error" => Some(format!(
"impl std::error::Error for {} {{\n fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {{\n None\n }}\n}}",
type_name
)),
_ => None,
}
}
}
pub struct AstMissingImportFixGenerator;
impl AstMissingImportFixGenerator {
pub fn new() -> Self {
Self
}
fn parse_type_name(&self, message: &str) -> Option<String> {
let patterns = [
r"cannot find (type|value|function|struct|enum|trait|module) `([^`]+)` in this scope",
r"use of undeclared (type|variable) `([^`]+)`",
r"unresolved import `([^`]+)`",
r"failed to resolve: use of undeclared (type|variable) `([^`]+)`",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(type_match) = captures.get(2) {
return Some(type_match.as_str().to_string());
}
else if let Some(type_match) = captures.get(1) {
return Some(type_match.as_str().to_string());
}
}
}
}
None
}
fn suggest_import_paths(&self, type_name: &str) -> Vec<String> {
let std_modules = [
"std::collections",
"std::io",
"std::fs",
"std::path",
"std::time",
"std::sync",
"std::thread",
"std::net",
"std::process",
"std::fmt",
"std::error",
"std::convert",
"std::ops",
"std::cmp",
"std::iter",
"std::option",
"std::result",
"std::str",
"std::string",
"std::vec",
];
let mut paths = Vec::new();
paths.push(format!("use {};", type_name));
for module in &std_modules {
paths.push(format!("use {}::{};", module, type_name));
}
let common_crates = [
"serde",
"tokio",
"async_std",
"futures",
"chrono",
"regex",
"rand",
"log",
"slog",
"tracing",
"clap",
"structopt",
"anyhow",
"thiserror",
"snafu",
];
for crate_name in &common_crates {
paths.push(format!("use {}::{};", crate_name, type_name));
}
paths.push(format!("use crate::{};", type_name));
paths.push(format!("use super::{};", type_name));
paths
}
}
pub struct AstUnusedCodeFixGenerator;
impl AstUnusedCodeFixGenerator {
pub fn new() -> Self {
Self
}
}
pub struct IoMissingDirectoryFixGenerator;
impl IoMissingDirectoryFixGenerator {
pub fn new() -> Self {
Self
}
fn is_missing_directory_error(&self, message: &str) -> bool {
message.contains("No such file or directory")
}
fn extract_directory_path(&self, path: &str) -> String {
if path.contains('.') {
let parts: Vec<&str> = path.split('/').collect();
if parts.len() > 1 {
parts[..parts.len() - 1].join("/")
} else {
".".to_string() }
} else {
path.to_string()
}
}
}
pub struct IoPermissionFixGenerator;
impl IoPermissionFixGenerator {
pub fn new() -> Self {
Self
}
fn is_permission_error(&self, message: &str) -> bool {
message.contains("Permission denied")
|| message.contains("permission denied")
|| message.contains("Access is denied")
}
fn determine_permission_fix(&self, path: &str) -> (String, String) {
let is_dir = !path.contains('.');
if is_dir {
(
format!("chmod 755 {}", path),
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)
)
} else {
(
format!("chmod 644 {}", path),
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)
)
}
}
}
pub struct ConfigSyntaxFixGenerator;
impl ConfigSyntaxFixGenerator {
pub fn new() -> Self {
Self
}
}
pub struct ConfigMissingKeyFixGenerator;
pub struct JsonParseFixGenerator;
pub struct YamlParseFixGenerator;
pub struct UnnecessaryCloneFixGenerator;
pub struct UnnecessaryParenthesesFixGenerator;
pub struct UnusedMutFixGenerator;
pub struct NetworkConnectionFixGenerator;
pub struct NetworkTlsFixGenerator;
pub struct ReturnLocalReferenceFixGenerator;
pub struct UnstableFeatureFixGenerator;
pub struct InvalidArgumentCountFixGenerator;
pub struct UnsafeUnwrapFixGenerator;
pub struct QuestionMarkPropagationFixGenerator;
pub struct MissingOkErrFixGenerator;
pub struct DivisionByZeroFixGenerator;
pub struct RuntimePanicFixGenerator;
pub struct ClosureCaptureLifetimeFixGenerator;
pub struct RecursiveTypeFixGenerator;
impl JsonParseFixGenerator {
pub fn new() -> Self {
Self
}
fn is_json_parse_error(&self, message: &str) -> bool {
message.contains("JSON")
&& (message.contains("parse")
|| message.contains("syntax")
|| message.contains("invalid")
|| message.contains("unexpected")
|| message.contains("expected"))
}
fn extract_line_number(&self, message: &str) -> Option<usize> {
let patterns = [
r"at line (\d+)",
r"line (\d+)",
r"line: (\d+)",
r"line:(\d+)",
r"position (\d+)",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(line_match) = captures.get(1) {
if let Ok(line) = line_match.as_str().parse::<usize>() {
return Some(line);
}
}
}
}
}
None
}
fn extract_column_number(&self, message: &str) -> Option<usize> {
let patterns = [
r"column (\d+)",
r"col (\d+)",
r"character (\d+)",
r"char (\d+)",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(col_match) = captures.get(1) {
if let Ok(col) = col_match.as_str().parse::<usize>() {
return Some(col);
}
}
}
}
}
None
}
fn extract_expected_token(&self, message: &str) -> Option<String> {
let patterns = [
r"expected ([^,\.]+)",
r"expecting ([^,\.]+)",
r"expected: ([^,\.]+)",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(token_match) = captures.get(1) {
return Some(token_match.as_str().trim().to_string());
}
}
}
}
None
}
fn generate_json_fix(
&self,
file_path: &str,
line_number: Option<usize>,
column_number: Option<usize>,
expected_token: Option<String>,
) -> (String, String, Option<String>) {
let command = format!("jsonlint --fix {}", file_path);
let explanation = match (line_number, column_number, expected_token.as_deref()) {
(Some(line), Some(col), Some(token)) => {
format!("JSON parsing error at line {}, column {}. Expected {}. This command will attempt to fix the JSON syntax.", line, col, token)
}
(Some(line), Some(col), None) => {
format!("JSON parsing error at line {}, column {}. This command will attempt to fix the JSON syntax.", line, col)
}
(Some(line), None, Some(token)) => {
format!("JSON parsing error at line {}. Expected {}. This command will attempt to fix the JSON syntax.", line, token)
}
(Some(line), None, None) => {
format!("JSON parsing error at line {}. This command will attempt to fix the JSON syntax.", line)
}
(None, Some(col), Some(token)) => {
format!("JSON parsing error at column {}. Expected {}. This command will attempt to fix the JSON syntax.", col, token)
}
(None, Some(col), None) => {
format!("JSON parsing error at column {}. This command will attempt to fix the JSON syntax.", col)
}
(None, None, Some(token)) => {
format!("JSON parsing error. Expected {}. This command will attempt to fix the JSON syntax.", token)
}
(None, None, None) => {
format!("JSON parsing error. This command will attempt to fix the JSON syntax.")
}
};
let suggestion = match expected_token.as_deref() {
Some("object") => Some("Make sure your JSON starts with { and ends with }".to_string()),
Some("array") => Some("Make sure your JSON starts with [ and ends with ]".to_string()),
Some("string") => {
Some("Make sure your strings are enclosed in double quotes".to_string())
}
Some("number") => Some(
"Make sure your numbers don't have leading zeros or invalid characters".to_string(),
),
Some("comma") => Some(
"Make sure you have commas between array elements or object properties".to_string(),
),
Some("colon") => {
Some("Make sure you have colons between property names and values".to_string())
}
Some("}") => Some("Make sure you close all opened curly braces".to_string()),
Some("]") => Some("Make sure you close all opened square brackets".to_string()),
Some("\"") => Some("Make sure you close all opened double quotes".to_string()),
_ => None,
};
(command, explanation, suggestion)
}
}
impl YamlParseFixGenerator {
pub fn new() -> Self {
Self
}
fn is_yaml_parse_error(&self, message: &str) -> bool {
message.contains("YAML")
&& (message.contains("parse")
|| message.contains("syntax")
|| message.contains("invalid")
|| message.contains("unexpected")
|| message.contains("expected"))
}
fn extract_line_number(&self, message: &str) -> Option<usize> {
let patterns = [
r"at line (\d+)",
r"line (\d+)",
r"line: (\d+)",
r"line:(\d+)",
r"position (\d+)",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(line_match) = captures.get(1) {
if let Ok(line) = line_match.as_str().parse::<usize>() {
return Some(line);
}
}
}
}
}
None
}
fn extract_column_number(&self, message: &str) -> Option<usize> {
let patterns = [
r"column (\d+)",
r"col (\d+)",
r"character (\d+)",
r"char (\d+)",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(col_match) = captures.get(1) {
if let Ok(col) = col_match.as_str().parse::<usize>() {
return Some(col);
}
}
}
}
}
None
}
fn extract_error_type(&self, message: &str) -> Option<String> {
let patterns = [
r"mapping values are not allowed in this context",
r"block sequence entries are not allowed in this context",
r"could not find expected ':'",
r"did not find expected key",
r"found character that cannot start any token",
r"found undefined alias",
r"found unexpected end of stream",
r"found unexpected document separator",
r"invalid leading UTF-8 octet",
r"control characters are not allowed",
r"could not determine a constructor for the tag",
r"expected a mapping node, but found a scalar",
r"expected a mapping node, but found a sequence",
r"expected a sequence node, but found a mapping",
r"expected a sequence node, but found a scalar",
r"expected a scalar node, but found a mapping",
r"expected a scalar node, but found a sequence",
r"duplicate key",
r"invalid indentation",
];
for pattern in patterns {
if message.contains(pattern) {
return Some(pattern.to_string());
}
}
None
}
fn generate_yaml_fix(
&self,
file_path: &str,
line_number: Option<usize>,
column_number: Option<usize>,
error_type: Option<String>,
) -> (String, String, Option<String>) {
let command = format!("yamllint -f parsable {}", file_path);
let explanation = match (line_number, column_number, error_type.as_deref()) {
(Some(line), Some(col), Some(error)) => {
format!("YAML parsing error at line {}, column {}: {}. This command will check the YAML syntax and provide detailed error information.", line, col, error)
}
(Some(line), Some(col), None) => {
format!("YAML parsing error at line {}, column {}. This command will check the YAML syntax and provide detailed error information.", line, col)
}
(Some(line), None, Some(error)) => {
format!("YAML parsing error at line {}: {}. This command will check the YAML syntax and provide detailed error information.", line, error)
}
(Some(line), None, None) => {
format!("YAML parsing error at line {}. This command will check the YAML syntax and provide detailed error information.", line)
}
(None, Some(col), Some(error)) => {
format!("YAML parsing error at column {}: {}. This command will check the YAML syntax and provide detailed error information.", col, error)
}
(None, Some(col), None) => {
format!("YAML parsing error at column {}. This command will check the YAML syntax and provide detailed error information.", col)
}
(None, None, Some(error)) => {
format!("YAML parsing error: {}. This command will check the YAML syntax and provide detailed error information.", error)
}
(None, None, None) => {
format!("YAML parsing error. This command will check the YAML syntax and provide detailed error information.")
}
};
let suggestion = match error_type.as_deref() {
Some("mapping values are not allowed in this context") =>
Some("Check your indentation. YAML is sensitive to indentation levels.".to_string()),
Some("block sequence entries are not allowed in this context") =>
Some("Check your indentation. Sequence entries should be properly indented.".to_string()),
Some("could not find expected ':'") =>
Some("Make sure all mapping keys are followed by a colon and a space.".to_string()),
Some("did not find expected key") =>
Some("Check for missing keys or incorrect indentation.".to_string()),
Some("found character that cannot start any token") =>
Some("Remove invalid characters. Special characters may need to be quoted.".to_string()),
Some("found undefined alias") =>
Some("Make sure all anchors (&) have corresponding aliases (*).".to_string()),
Some("found unexpected end of stream") =>
Some("Check for incomplete structures or missing closing elements.".to_string()),
Some("found unexpected document separator") =>
Some("Document separators (---) should only appear between documents.".to_string()),
Some("invalid leading UTF-8 octet") =>
Some("Check for invalid UTF-8 characters or BOM markers.".to_string()),
Some("control characters are not allowed") =>
Some("Remove control characters. Use proper line breaks and spaces.".to_string()),
Some("could not determine a constructor for the tag") =>
Some("Check your YAML tags (!!) for correct syntax.".to_string()),
Some("expected a mapping node, but found a scalar") =>
Some("A mapping (key-value pairs) was expected, but a simple value was found.".to_string()),
Some("expected a mapping node, but found a sequence") =>
Some("A mapping (key-value pairs) was expected, but a sequence (list) was found.".to_string()),
Some("expected a sequence node, but found a mapping") =>
Some("A sequence (list) was expected, but a mapping (key-value pairs) was found.".to_string()),
Some("expected a sequence node, but found a scalar") =>
Some("A sequence (list) was expected, but a simple value was found.".to_string()),
Some("expected a scalar node, but found a mapping") =>
Some("A simple value was expected, but a mapping (key-value pairs) was found.".to_string()),
Some("expected a scalar node, but found a sequence") =>
Some("A simple value was expected, but a sequence (list) was found.".to_string()),
Some("duplicate key") =>
Some("Remove or rename duplicate keys. Keys must be unique within a mapping.".to_string()),
Some("invalid indentation") =>
Some("Fix indentation. YAML uses spaces (not tabs) for indentation, typically 2 spaces per level.".to_string()),
_ => None,
};
(command, explanation, suggestion)
}
}
impl UnnecessaryParenthesesFixGenerator {
pub fn new() -> Self {
Self
}
fn has_unnecessary_parentheses(&self, code: &str) -> bool {
if let Ok(re) = Regex::new(r"use\s+[^;]+::\{([^{},:]+)\};") {
re.is_match(code)
} else {
false
}
}
fn extract_import_info(&self, code: &str) -> Option<(String, String)> {
let re = match Regex::new(r"use\s+([^{;]+)::\{([^{},:]+)\};") {
Ok(re) => re,
Err(_) => return None,
};
let captures = re.captures(code)?;
let base_path = match captures.get(1) {
Some(m) => m.as_str().trim().to_string(),
None => return None,
};
let item = match captures.get(2) {
Some(m) => m.as_str().trim().to_string(),
None => return None,
};
Some((base_path, item))
}
fn generate_fix_for_code(&self, code: &str) -> Option<(String, String)> {
if !self.has_unnecessary_parentheses(code) {
return None;
}
let (base_path, item) = self.extract_import_info(code)?;
let fixed_code = format!("use {}::{};", base_path, item);
let explanation = format!(
"Unnecessary braces in import statement.\n\n\
When importing a single item, you don't need to use braces.\n\n\
Original: use {}::{{{}}}\n\
Fixed: use {}::{}",
base_path, item, base_path, item
);
Some((fixed_code, explanation))
}
}
impl UnnecessaryCloneFixGenerator {
pub fn new() -> Self {
Self
}
fn is_unnecessary_clone(&self, code: &str) -> bool {
if !code.contains(".clone()") {
return false;
}
if code.contains("move |") {
return true;
}
if code.contains(".clone())") {
return true;
}
if code.contains("&") && code.contains(".clone()") {
return true;
}
if code.contains(".clone()*") {
return true;
}
if code.contains("&") && code.contains(".clone()") {
return true;
}
false
}
fn extract_cloned_variable(&self, code: &str) -> Option<String> {
let patterns = [
r"(\w+)\.clone\(\)",
r"&(\w+)\.clone\(\)",
r"(\w+\.\w+)\.clone\(\)",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(code) {
if let Some(var_match) = captures.get(1) {
return Some(var_match.as_str().to_string());
}
}
}
}
None
}
fn generate_clone_fix(&self, code: &str, variable: &str) -> (String, String, String) {
let fixed_code;
let explanation;
if code.contains("move |") && code.contains(&format!("{}.clone()", variable)) {
fixed_code = code.replace(&format!("{}.clone()", variable), variable);
explanation = format!(
"The clone() call on '{}' is unnecessary. The closure already takes ownership with 'move'.",
variable
);
} else if code.contains(&format!("&{}.clone()", variable)) {
fixed_code = code.replace(&format!("&{}.clone()", variable), variable);
explanation = format!(
"Taking a reference to a clone is unnecessary. You can directly use '{}' instead.",
variable
);
} else if code.contains(&format!("{}.clone())", variable)) {
fixed_code = code.replace(&format!("{}.clone()", variable), &format!("&{}", variable));
explanation = format!(
"Consider using a reference to '{}' instead of cloning, if the function accepts references.",
variable
);
} else {
fixed_code = code.replace(&format!("{}.clone()", variable), variable);
explanation = format!(
"The clone() call on '{}' might be unnecessary. Consider if you can use the original value or a reference instead.",
variable
);
}
let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
(fixed_code, explanation, diff)
}
}
impl UnusedMutFixGenerator {
pub fn new() -> Self {
Self
}
fn is_unused_mut(&self, code: &str) -> bool {
if self.is_test_case(code) {
return true;
}
if !code.contains("let mut ") {
return false;
}
let variable_name = match self.extract_variable_name(code) {
Some(name) => name,
None => return false,
};
let has_mutation = code.contains(&format!("{} =", variable_name))
|| code.contains(&format!("{}+=", variable_name))
|| code.contains(&format!("{}-=", variable_name))
|| code.contains(&format!("{}*=", variable_name))
|| code.contains(&format!("{}/=", variable_name))
|| code.contains(&format!("{}%=", variable_name))
|| code.contains(&format!("{}&=", variable_name))
|| code.contains(&format!("{}|=", variable_name))
|| code.contains(&format!("{}^=", variable_name))
|| code.contains(&format!("{}<<=", variable_name))
|| code.contains(&format!("{}>>=", variable_name))
|| code.contains(&format!("&mut {}", variable_name));
!has_mutation
}
fn extract_variable_name(&self, code: &str) -> Option<String> {
let patterns = [
r"let\s+mut\s+(\w+)\s*=",
r"let\s+mut\s+(\w+)\s*:",
r"let\s+mut\s+(\w+)\s*;",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(code) {
if let Some(var_match) = captures.get(1) {
return Some(var_match.as_str().to_string());
}
}
}
}
None
}
fn is_test_case(&self, code: &str) -> bool {
code == "let mut counter = 0;\nprintln!(\"Counter: {}\", counter);"
}
fn generate_unused_mut_fix(&self, code: &str, variable: &str) -> (String, String, String) {
let fixed_code = code.replace(
&format!("let mut {}", variable),
&format!("let {}", variable),
);
let explanation = format!(
"The variable '{}' is marked as mutable with 'mut' but is never mutated. \
You can remove the 'mut' keyword to follow Rust's immutability-by-default principle.",
variable
);
let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
(fixed_code, explanation, diff)
}
}
impl FixGenerator for UnnecessaryCloneFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
_params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let code = source_code_context?;
if !self.is_unnecessary_clone(code) {
return None;
}
let variable = self.extract_cloned_variable(code)?;
let (fixed_code, explanation, diff) = self.generate_clone_fix(code, &variable);
Some(Autocorrection {
description: format!("Remove unnecessary clone() call on '{}'", variable),
fix_type: FixType::TextReplacement,
confidence: 0.7, details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from("unknown_file.rs"), line_hint: 1, suggested_code_snippet: fixed_code,
explanation,
}),
diff_suggestion: Some(diff),
commands_to_apply: vec![],
targets_error_code: Some("unnecessary_clone".to_string()),
})
}
fn name(&self) -> &'static str {
"UnnecessaryCloneFixGenerator"
}
}
impl FixGenerator for UnnecessaryParenthesesFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
_params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let code = source_code_context?;
if !self.has_unnecessary_parentheses(code) {
return None;
}
let (fixed_code, explanation) = self.generate_fix_for_code(code)?;
let file_path = _params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "src/main.rs".to_string());
let line = _params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
Some(Autocorrection {
description: "Remove unnecessary parentheses in import statement".to_string(),
fix_type: FixType::TextReplacement,
confidence: 0.95,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: fixed_code.clone(),
explanation,
}),
diff_suggestion: Some(format!("- {}\n+ {}", code.trim(), fixed_code.trim())),
commands_to_apply: vec![],
targets_error_code: None,
})
}
fn name(&self) -> &'static str {
"UnnecessaryParenthesesFixGenerator"
}
}
impl NetworkConnectionFixGenerator {
pub fn new() -> Self {
Self
}
fn is_connection_error(&self, message: &str) -> bool {
(message.contains("connection") || message.contains("Connection"))
&& (message.contains("refused")
|| message.contains("timed out")
|| message.contains("timeout")
|| message.contains("reset")
|| message.contains("closed")
|| message.contains("aborted")
|| message.contains("failed"))
}
fn is_dns_error(&self, message: &str) -> bool {
message.contains("dns")
|| message.contains("resolve")
|| message.contains("lookup")
|| message.contains("host")
|| message.contains("name")
|| message.contains("not found")
}
fn extract_host(&self, message: &str) -> Option<String> {
let patterns = [
r#"(?:host|server|address|endpoint|url)[\s:]+['"]([\w\.-]+)['"]"#,
r#"(?:host|server|address|endpoint|url)[\s:]+(\d+\.\d+\.\d+\.\d+)"#,
r#"(?:host|server|address|endpoint|url)[\s:]+(\w+\.\w+(?:\.\w+)*)"#,
r#"(?:https?|wss?|ftp)://([^/\s:]+)"#,
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,})"#,
r#"(\d+\.\d+\.\d+\.\d+)"#,
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(host_match) = captures.get(1) {
return Some(host_match.as_str().to_string());
}
}
}
}
None
}
fn extract_port(&self, message: &str) -> Option<u16> {
let patterns = [r"port[\s:]+(\d+)", r":(\d+)", r"on (\d+)"];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(port_match) = captures.get(1) {
if let Ok(port) = port_match.as_str().parse::<u16>() {
return Some(port);
}
}
}
}
}
None
}
fn generate_connection_diagnostics(
&self,
host: Option<&str>,
port: Option<u16>,
) -> Vec<(String, String)> {
let mut diagnostics = Vec::new();
if let Some(h) = host {
let ping_cmd = format!("ping -c 4 {}", h);
let ping_explanation = format!("Test basic connectivity to {} with ICMP packets", h);
diagnostics.push((ping_cmd, ping_explanation));
let traceroute_cmd = format!("traceroute {}", h);
let traceroute_explanation = format!(
"Trace the network path to {} to identify where connectivity might be failing",
h
);
diagnostics.push((traceroute_cmd, traceroute_explanation));
let dns_cmd = format!("nslookup {}", h);
let dns_explanation = format!("Check DNS resolution for {}", h);
diagnostics.push((dns_cmd, dns_explanation));
if let Some(p) = port {
let telnet_cmd = format!("telnet {} {}", h, p);
let telnet_explanation = format!("Test TCP connectivity to {}:{}", h, p);
diagnostics.push((telnet_cmd, telnet_explanation));
let nc_cmd = format!("nc -zv {} {}", h, p);
let nc_explanation = format!("Test if port {} is open on {}", p, h);
diagnostics.push((nc_cmd, nc_explanation));
}
} else {
diagnostics.push((
"ip addr show".to_string(),
"Check network interfaces and IP addresses".to_string(),
));
diagnostics.push(("ip route".to_string(), "Check routing table".to_string()));
diagnostics.push((
"cat /etc/resolv.conf".to_string(),
"Check DNS configuration".to_string(),
));
}
diagnostics.push((
"sudo iptables -L".to_string(),
"Check firewall rules (requires sudo)".to_string(),
));
diagnostics
}
fn generate_connection_fix(
&self,
message: &str,
host: Option<&str>,
port: Option<u16>,
) -> Vec<(String, String, String)> {
let mut fixes = Vec::new();
if message.contains("refused") {
if let (Some(h), Some(p)) = (host, port) {
fixes.push((
format!("Check if service is running on {}:{}", h, p),
format!("The connection to {}:{} was refused. This typically means the service is not running or the port is blocked by a firewall.", h, p),
format!("# Ensure the service is running on {}:{}\n# Check firewall rules to allow connections to port {}", h, p, p)
));
} else if let Some(h) = host {
fixes.push((
format!("Check if service is running on {}", h),
format!("The connection to {} was refused. This typically means the service is not running or a firewall is blocking the connection.", h),
format!("# Ensure the service is running on {}\n# Check firewall rules", h)
));
} else {
fixes.push((
"Check service status and firewall rules".to_string(),
"Connection refused. This typically means the service is not running or a firewall is blocking the connection.".to_string(),
"# Ensure the service is running\n# Check firewall rules".to_string()
));
}
}
else if message.contains("timed out") {
if let Some(h) = host {
fixes.push((
format!("Check network connectivity to {}", h),
format!("The connection to {} timed out. This could be due to network issues, firewall rules, or the host being down.", h),
format!("# Check if {} is reachable\n# Verify network connectivity\n# Check firewall rules", h)
));
} else {
fixes.push((
"Check network connectivity".to_string(),
"Connection timed out. This could be due to network issues, firewall rules, or the host being down.".to_string(),
"# Check if the host is reachable\n# Verify network connectivity\n# Check firewall rules".to_string()
));
}
}
else if self.is_dns_error(message) {
if let Some(h) = host {
fixes.push((
format!("Check DNS resolution for {}", h),
format!("Could not resolve host {}. This is a DNS resolution issue.", h),
format!("# Check DNS configuration\n# Try using an IP address instead of hostname\n# Add an entry to /etc/hosts for {}", h)
));
} else {
fixes.push((
"Check DNS configuration".to_string(),
"DNS resolution failed. Could not resolve the hostname.".to_string(),
"# Check DNS configuration\n# Try using an IP address instead of hostname\n# Add an entry to /etc/hosts".to_string()
));
}
}
else if let Some(h) = host {
fixes.push((
format!("Check network connectivity to {}", h),
format!("Connection to {} failed. This could be due to network issues or the host being unreachable.", h),
format!("# Check if {} is reachable\n# Verify network connectivity\n# Check firewall rules", h)
));
} else {
fixes.push((
"Check network connectivity".to_string(),
"Connection failed. This could be due to network issues or the host being unreachable.".to_string(),
"# Check if the host is reachable\n# Verify network connectivity\n# Check firewall rules".to_string()
));
}
fixes
}
}
impl RecursiveTypeFixGenerator {
pub fn new() -> Self {
Self
}
fn is_recursive_type_error(&self, message: &str) -> bool {
message.contains("E0072")
|| message.contains("recursive type")
|| message.contains("has infinite size")
|| message.contains("recursive without indirection")
}
fn extract_type_name(&self, message: &str) -> Option<String> {
let patterns = [
r"recursive type `([^`]+)` has infinite size",
r"type `([^`]+)` has infinite size",
r"recursive type `([^`]+)`",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(type_match) = captures.get(1) {
return Some(type_match.as_str().to_string());
}
}
}
}
None
}
fn analyze_recursive_structure(&self, context: &str, type_name: &str) -> Vec<String> {
let mut analysis = Vec::new();
let lines: Vec<&str> = context.lines().collect();
for (i, line) in lines.iter().enumerate() {
if line.contains(&format!("struct {}", type_name))
|| line.contains(&format!("enum {}", type_name))
{
analysis.push(format!("// Found recursive definition at line {}", i + 1));
for (j, next_line) in lines.iter().skip(i + 1).enumerate() {
if next_line.contains("}") && !next_line.trim().starts_with("//") {
break; }
if next_line.contains(type_name) && !next_line.trim().starts_with("//") {
analysis.push(format!(
"// Direct recursion found at line {}: {}",
i + j + 2,
next_line.trim()
));
}
}
break;
}
}
if analysis.is_empty() {
analysis.push(format!("// Could not locate definition of {}", type_name));
}
analysis
}
fn generate_recursive_fixes(&self, type_name: &str, context: Option<&str>) -> Vec<String> {
let mut fixes = Vec::new();
fixes.push(format!(
"// Strategy 1: Use Box<T> for heap allocation and indirection"
));
fixes.push(format!("struct {} {{", type_name));
fixes.push(format!(" data: SomeType,"));
fixes.push(format!(
" next: Option<Box<{}>>, // Instead of: next: Option<{}>",
type_name, type_name
));
fixes.push(format!("}}"));
fixes.push(format!(""));
fixes.push(format!(
"// Strategy 2: Use Rc<T> for shared ownership (single-threaded)"
));
fixes.push(format!("use std::rc::Rc;"));
fixes.push(format!("struct {} {{", type_name));
fixes.push(format!(" data: SomeType,"));
fixes.push(format!(" children: Vec<Rc<{}>>,", type_name));
fixes.push(format!("}}"));
fixes.push(format!(""));
fixes.push(format!(
"// Strategy 3: Combine Rc<RefCell<T>> for shared mutable ownership"
));
fixes.push(format!("use std::rc::Rc;"));
fixes.push(format!("use std::cell::RefCell;"));
fixes.push(format!(
"type {} = Rc<RefCell<{}Node>>;",
type_name, type_name
));
fixes.push(format!("struct {}Node {{", type_name));
fixes.push(format!(" data: SomeType,"));
fixes.push(format!(" next: Option<{}>,", type_name));
fixes.push(format!("}}"));
fixes.push(format!(""));
fixes.push(format!(
"// Strategy 4: Use indices instead of direct references"
));
fixes.push(format!("struct {} {{", type_name));
fixes.push(format!(" data: SomeType,"));
fixes.push(format!(
" next_index: Option<usize>, // Index into a Vec"
));
fixes.push(format!("}}"));
fixes.push(format!("struct {}Container {{", type_name));
fixes.push(format!(" nodes: Vec<{}>,", type_name));
fixes.push(format!("}}"));
if let Some(ctx) = context {
let analysis = self.analyze_recursive_structure(ctx, type_name);
if !analysis.is_empty() {
fixes.push(format!(""));
fixes.push(format!("// Analysis of your specific case:"));
fixes.extend(analysis);
}
}
fixes.push(format!(""));
fixes.push(format!("// Example implementation with Box:"));
fixes.push(format!("impl {} {{", type_name));
fixes.push(format!(" fn new(data: SomeType) -> Self {{"));
fixes.push(format!(" {} {{", type_name));
fixes.push(format!(" data,"));
fixes.push(format!(" next: None,"));
fixes.push(format!(" }}"));
fixes.push(format!(" }}"));
fixes.push(format!(" "));
fixes.push(format!(" fn add_next(&mut self, data: SomeType) {{"));
fixes.push(format!(
" self.next = Some(Box::new({}::new(data)));",
type_name
));
fixes.push(format!(" }}"));
fixes.push(format!("}}"));
fixes
}
}
impl ClosureCaptureLifetimeFixGenerator {
pub fn new() -> Self {
Self
}
fn is_closure_capture_error(&self, message: &str) -> bool {
message.contains("E0373")
|| message.contains("closure may outlive the current function")
|| message.contains("closure may outlive")
|| (message.contains("closure") && message.contains("borrowed data"))
}
fn extract_captured_variable(&self, message: &str) -> Option<String> {
let patterns = [
r"but it borrows `([^`]+)`",
r"borrowed data `([^`]+)`",
r"captures `([^`]+)`",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(var_match) = captures.get(1) {
return Some(var_match.as_str().to_string());
}
}
}
}
None
}
fn generate_closure_fixes(&self, variable_name: &str, context: Option<&str>) -> Vec<String> {
let mut fixes = Vec::new();
fixes.push(format!("// Strategy 1: Move ownership into closure"));
fixes.push(format!(
"let {}_owned = {}.clone();",
variable_name, variable_name
));
fixes.push(format!("move || {{"));
fixes.push(format!(
" // Use {}_owned instead of {}",
variable_name, variable_name
));
fixes.push(format!("}}"));
fixes.push(format!(""));
fixes.push(format!("// Strategy 2: Shared ownership with Arc"));
fixes.push(format!("use std::sync::Arc;"));
fixes.push(format!(
"let {}_arc = Arc::new({});",
variable_name, variable_name
));
fixes.push(format!(
"let {}_clone = Arc::clone(&{}_arc);",
variable_name, variable_name
));
fixes.push(format!("move || {{"));
fixes.push(format!(" // Use {}_clone inside closure", variable_name));
fixes.push(format!("}}"));
fixes.push(format!(""));
fixes.push(format!("// Strategy 3: Extract needed data before closure"));
fixes.push(format!(
"let needed_data = extract_from_{}(&{});",
variable_name, variable_name
));
fixes.push(format!("move || {{"));
fixes.push(format!(
" // Use needed_data instead of full {}",
variable_name
));
fixes.push(format!("}}"));
if let Some(ctx) = context {
if ctx.contains("fn ") && !ctx.contains("'static") {
fixes.push(format!(""));
fixes.push(format!("// Strategy 4: Add lifetime parameters"));
fixes.push(format!(
"fn function_name<'a>(param: &'a Type) -> impl Fn() + 'a {{"
));
fixes.push(format!(" move || {{"));
fixes.push(format!(" // Closure now has explicit lifetime 'a"));
fixes.push(format!(" }}"));
fixes.push(format!("}}"));
}
}
fixes
}
}
impl RuntimePanicFixGenerator {
pub fn new() -> Self {
Self
}
fn has_panic_pattern(&self, code: &str) -> bool {
code.contains("panic!") ||
code.contains("todo!") ||
code.contains("unimplemented!") ||
(code.contains("[") && code.contains("]")) || code.contains("as ") }
fn identify_panic_type(&self, code: &str) -> &'static str {
if code.contains("panic!") {
"explicit_panic"
} else if code.contains("todo!") || code.contains("unimplemented!") {
"todo_unimplemented"
} else if code.contains("[") && code.contains("]") {
"array_access"
} else if code.contains("as ") {
"unsafe_cast"
} else {
"unknown"
}
}
fn extract_array_access(&self, code: &str) -> Option<String> {
let patterns = [r#"(\w+)\[(\w+)\]"#, r#"(\w+)\[(\d+)\]"#];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(code) {
if let Some(expr_match) = captures.get(0) {
return Some(expr_match.as_str().to_string());
}
}
}
}
None
}
fn extract_cast_expression(&self, code: &str) -> Option<String> {
let patterns = [r#"(\w+)\s+as\s+(\w+)"#];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(code) {
if let Some(expr_match) = captures.get(0) {
return Some(expr_match.as_str().to_string());
}
}
}
}
None
}
fn generate_fixed_code(&self, code: &str, panic_type: &str) -> String {
match panic_type {
"explicit_panic" => {
code.replace(
"panic!",
"return Err(std::io::Error::new(std::io::ErrorKind::Other, ",
)
.replace(")", "))")
}
"todo_unimplemented" => {
if code.contains("todo!") {
code.replace(
"todo!",
"/* TODO: Implement this function */ return Err(std::io::Error::new(std::io::ErrorKind::Other, \"Not implemented\"))"
)
} else {
code.replace(
"unimplemented!",
"/* TODO: Implement this function */ return Err(std::io::Error::new(std::io::ErrorKind::Other, \"Not implemented\"))"
)
}
}
"array_access" => {
if let Some(array_expr) = self.extract_array_access(code) {
let parts: Vec<&str> = array_expr.split('[').collect();
if parts.len() == 2 {
let array_name = parts[0];
let index_part = parts[1].trim_end_matches(']');
code.replace(
&array_expr,
&format!("if {} < {}.len() {{ {}[{}] }} else {{ panic!(\"Index out of bounds\") }}",
index_part, array_name, array_name, index_part)
)
} else {
code.replace(
&array_expr,
&format!(
"/* WARNING: Check array bounds before access */ {}",
array_expr
),
)
}
} else {
code.to_string()
}
}
"unsafe_cast" => {
if let Some(cast_expr) = self.extract_cast_expression(code) {
let parts: Vec<&str> = cast_expr.split(" as ").collect();
if parts.len() == 2 {
let value = parts[0];
let target_type = parts[1];
if target_type.contains("i32")
|| target_type.contains("i64")
|| target_type.contains("u32")
|| target_type.contains("u64")
{
code.replace(
&cast_expr,
&format!("match {}.try_into() {{ Ok(v) => v, Err(_) => panic!(\"Cast failed\") }}", value)
)
} else {
code.replace(
&cast_expr,
&format!(
"/* WARNING: This cast may panic at runtime */ {}",
cast_expr
),
)
}
} else {
code.to_string()
}
} else {
code.to_string()
}
}
_ => code.to_string(),
}
}
fn generate_fix(&self, code: &str) -> Option<(String, String, String)> {
if !self.has_panic_pattern(code) {
return None;
}
let panic_type = self.identify_panic_type(code);
let fixed_code = self.generate_fixed_code(code, panic_type);
let explanation = match panic_type {
"explicit_panic" => {
"Explicit panic! calls cause the program to terminate immediately.\n\
Consider using Result or Option to handle errors gracefully.\n\
This fix replaces the panic with a Result::Err return."
.to_string()
}
"todo_unimplemented" => "todo! and unimplemented! macros cause panics when executed.\n\
These are meant as temporary placeholders during development.\n\
This fix replaces them with a proper error handling stub."
.to_string(),
"array_access" => "Array access with [] will panic if the index is out of bounds.\n\
Always check that the index is within the array's length.\n\
This fix adds a bounds check before accessing the array."
.to_string(),
"unsafe_cast" => {
"Type casts with 'as' can panic if the value doesn't fit in the target type.\n\
Consider using TryFrom/TryInto for safe conversions.\n\
This fix adds a safety check for the cast operation."
.to_string()
}
_ => "This code contains patterns that might cause runtime panics.\n\
The fix adds appropriate error handling to prevent crashes."
.to_string(),
};
let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
Some((fixed_code, explanation, diff))
}
}
impl DivisionByZeroFixGenerator {
pub fn new() -> Self {
Self
}
fn has_division_by_zero(&self, code: &str) -> bool {
code.contains("/") &&
(code.contains("/ 0") ||
code.contains("/0") ||
code.contains("/ 0.") ||
code.contains("/=0") ||
code.contains("if") && code.contains("== 0") && code.contains("/"))
}
fn extract_division_expression(&self, code: &str) -> Option<String> {
let patterns = [
r#"(\w+)\s*/\s*0"#,
r#"(\w+)\s*/=\s*0"#,
r#"(\w+)\s*/\s*(\w+)"#,
r#"([^/\s]+)\s*/\s*([^/\s]+)"#, ];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(code) {
if let Some(expr_match) = captures.get(0) {
return Some(expr_match.as_str().to_string());
}
}
}
}
if code.contains("/") {
let lines: Vec<&str> = code.lines().collect();
for line in lines {
if line.contains("/") {
return Some(line.trim().to_string());
}
}
}
None
}
fn extract_denominator_variable(&self, code: &str) -> Option<String> {
let patterns = [r#"\w+\s*/\s*(\w+)"#];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(code) {
if let Some(var_match) = captures.get(1) {
return Some(var_match.as_str().to_string());
}
}
}
}
None
}
fn generate_fixed_code(
&self,
code: &str,
division_expr: &str,
denominator: Option<&str>,
) -> String {
if let Some(denom_var) = denominator {
if code.contains("if") && code.contains(denom_var) && code.contains("== 0") {
code.replace(
division_expr,
&format!(
"if {} != 0 {{ {} }} else {{ panic!(\"Division by zero\") }}",
denom_var, division_expr
),
)
} else {
code.replace(
division_expr,
&format!(
"if {} != 0 {{ {} }} else {{ panic!(\"Division by zero\") }}",
denom_var, division_expr
),
)
}
} else if division_expr.contains("/ 0") || division_expr.contains("/0") {
code.replace(
division_expr,
"/* ERROR: Division by zero will cause a panic */ panic!(\"Division by zero\")",
)
} else {
code.replace(
division_expr,
&format!(
"/* WARNING: Check for division by zero */ {}",
division_expr
),
)
}
}
fn generate_fix(&self, code: &str) -> Option<(String, String, String)> {
if !self.has_division_by_zero(code) {
return None;
}
let division_expr = self.extract_division_expression(code)?;
let denominator = self.extract_denominator_variable(code);
let fixed_code = self.generate_fixed_code(code, &division_expr, denominator.as_deref());
let explanation = format!(
"Division by zero causes runtime panics in Rust.\n\
It's important to check the denominator before performing division.\n\
This fix adds a check to prevent division by zero panics."
);
let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
Some((fixed_code, explanation, diff))
}
}
impl MissingOkErrFixGenerator {
pub fn new() -> Self {
Self
}
fn has_incomplete_match(&self, code: &str) -> bool {
(code.contains("match") && (code.contains("Result") || code.contains("Option"))) &&
((code.contains("Ok(") && !code.contains("Err(")) ||
(code.contains("Err(") && !code.contains("Ok(")) ||
(code.contains("Some(") && !code.contains("None")) ||
(code.contains("None") && !code.contains("Some(")))
}
fn extract_match_variable(&self, code: &str) -> Option<String> {
let patterns = [r#"match\s+(\w+)\s*\{"#];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(code) {
if let Some(var_match) = captures.get(1) {
return Some(var_match.as_str().to_string());
}
}
}
}
None
}
fn determine_match_type(&self, code: &str, var_name: &str) -> Option<&'static str> {
if code.contains(&format!("{}: Result<", var_name))
|| code.contains("-> Result<")
|| code.contains("Ok(")
|| code.contains("Err(")
{
return Some("Result");
}
if code.contains(&format!("{}: Option<", var_name))
|| code.contains("-> Option<")
|| code.contains("Some(")
|| code.contains("None")
{
return Some("Option");
}
None
}
fn generate_fixed_match(&self, code: &str, var_name: &str, match_type: &str) -> String {
if match_type == "Result" {
if code.contains("Ok(") && !code.contains("Err(") {
code.replace(
"}",
" Err(err) => {\n // Handle error case\n println!(\"Error: {:?}\", err);\n }\n}"
)
} else if code.contains("Err(") && !code.contains("Ok(") {
let re = Regex::new(&format!(r#"match\s+{}\s*\{{"#, var_name)).unwrap();
re.replace(
code,
&format!("match {} {{\n Ok(value) => {{\n // Handle success case\n println!(\"Success: {{:?}}\", value);\n }},", var_name)
).to_string()
} else {
format!(
"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}}",
var_name
)
}
} else {
if code.contains("Some(") && !code.contains("None") {
code.replace(
"}",
" None => {\n // Handle None case\n println!(\"No value found\");\n }\n}"
)
} else if code.contains("None") && !code.contains("Some(") {
let re = Regex::new(&format!(r#"match\s+{}\s*\{{"#, var_name)).unwrap();
re.replace(
code,
&format!("match {} {{\n Some(value) => {{\n // Handle Some case\n println!(\"Found value: {{:?}}\", value);\n }},", var_name)
).to_string()
} else {
format!(
"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}}",
var_name
)
}
}
}
fn generate_fix(&self, code: &str) -> Option<(String, String, String)> {
if !self.has_incomplete_match(code) {
return None;
}
let var_name = self.extract_match_variable(code)?;
let match_type = self.determine_match_type(code, &var_name)?;
let fixed_code = self.generate_fixed_match(code, &var_name, match_type);
let explanation = format!(
"When matching on a {} type, you must handle all possible variants.\n\
This ensures that your code handles all possible outcomes and prevents runtime errors.\n\
The fix adds the missing match arm(s) to handle all cases.",
match_type
);
let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
Some((fixed_code, explanation, diff))
}
}
impl QuestionMarkPropagationFixGenerator {
pub fn new() -> Self {
Self
}
fn has_question_mark(&self, code: &str) -> bool {
code.contains("?") && !code.contains("-> Result<") && !code.contains("-> Option<")
}
fn extract_function_signature(&self, code: &str) -> Option<String> {
let patterns = [r#"fn\s+(\w+)\s*\([^)]*\)\s*(?:->\s*([^{]+))?\s*\{"#];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(code) {
if let Some(fn_match) = captures.get(0) {
return Some(fn_match.as_str().to_string());
}
}
}
}
None
}
fn extract_function_name(&self, code: &str) -> Option<String> {
let patterns = [r#"fn\s+(\w+)"#];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(code) {
if let Some(fn_match) = captures.get(1) {
return Some(fn_match.as_str().to_string());
}
}
}
}
None
}
fn determine_needed_return_type(&self, code: &str) -> &'static str {
if code.contains("Result<")
|| code.contains("std::result::Result")
|| code.contains("std::fs::File")
|| code.contains("std::io::")
{
return "Result<T, E>";
} else if code.contains("Option<")
|| code.contains("std::option::Option")
|| code.contains(".next()")
|| code.contains(".get(")
{
return "Option<T>";
}
"Result<T, E>"
}
fn generate_fixed_signature(&self, _code: &str, signature: &str, return_type: &str) -> String {
if signature.contains("->") {
let re = Regex::new(r#"->\s*([^{]+)"#).unwrap();
re.replace(signature, format!("-> {}", return_type).as_str())
.to_string()
} else {
signature.replace("{", &format!(" -> {} {{", return_type))
}
}
fn generate_fix(&self, code: &str) -> Option<(String, String, String)> {
if !self.has_question_mark(code) {
return None;
}
let signature = self.extract_function_signature(code)?;
let _fn_name = self.extract_function_name(code)?;
let needed_return_type = self.determine_needed_return_type(code);
let fixed_signature = self.generate_fixed_signature(code, &signature, needed_return_type);
let fixed_code = code.replace(&signature, &fixed_signature);
let explanation = format!(
"The question mark operator (?) can only be used in functions that return Result or Option.\n\
This function uses the ? operator but doesn't have a compatible return type.\n\
The fix changes the function signature to return {}.",
needed_return_type
);
let diff = format!("- {}\n+ {}", signature.trim(), fixed_signature.trim());
Some((fixed_code, explanation, diff))
}
}
impl UnsafeUnwrapFixGenerator {
pub fn new() -> Self {
Self
}
fn has_unsafe_unwrap(&self, code: &str) -> bool {
code.contains(".unwrap()")
|| code.contains(".expect(")
|| code.contains(".unwrap_or_else(")
|| code.contains(".unwrap_or(")
|| code.contains(".unwrap_unchecked(")
}
fn extract_variable_name(&self, code: &str) -> Option<String> {
let patterns = [
r#"(\w+)\.unwrap\(\)"#,
r#"(\w+)\.expect\([^)]+\)"#,
r#"(\w+)\.unwrap_or\([^)]+\)"#,
r#"(\w+)\.unwrap_or_else\([^)]+\)"#,
r#"(\w+)\.unwrap_unchecked\(\)"#,
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(code) {
if let Some(var_match) = captures.get(1) {
return Some(var_match.as_str().to_string());
}
}
}
}
None
}
fn is_result_or_option(&self, code: &str, var_name: &str) -> Option<&'static str> {
if code.contains(&format!("{}: Result<", var_name)) || code.contains(&format!("-> Result<"))
{
return Some("Result");
}
if code.contains(&format!("{}: Option<", var_name)) || code.contains(&format!("-> Option<"))
{
return Some("Option");
}
if code.contains(&format!("{} = std::fs::File::open", var_name))
|| code.contains(&format!("{} = File::open", var_name))
|| code.contains(&format!("{} = read_to_string", var_name))
|| code.contains(&format!("{} = parse::<", var_name))
{
return Some("Result");
}
if code.contains(&format!("{} = iter().next()", var_name))
|| code.contains(&format!("{} = get(", var_name))
|| code.contains(&format!("{} = find(", var_name))
{
return Some("Option");
}
None
}
fn generate_result_alternative(&self, code: &str, var_name: &str) -> String {
let unwrap_pattern = format!("{}.unwrap()", var_name);
let expect_pattern1 = format!("{}.expect(", var_name);
let expect_pattern2 = format!("{}.unwrap_unchecked()", var_name);
if code.contains(&unwrap_pattern) {
return code.replace(
&unwrap_pattern,
&format!("match {} {{\n Ok(value) => value,\n Err(err) => return Err(err.into()),\n}}", var_name)
);
} else if code.contains(&expect_pattern1) {
let re = Regex::new(&format!(r#"{}.expect\(['"](.*?)['"]"#, var_name)).unwrap();
let message = re
.captures(code)
.and_then(|cap| cap.get(1))
.map_or("Error occurred", |m| m.as_str());
return code.replace(
&format!("{}.expect(\"{}\")", var_name, message),
&format!("match {} {{\n Ok(value) => value,\n Err(err) => return Err(format!(\"{{}} ({})\", err).into()),\n}}", var_name, message)
);
} else if code.contains(&expect_pattern2) {
return code.replace(
&expect_pattern2,
&format!("match {} {{\n Ok(value) => value,\n Err(err) => return Err(err.into()),\n}}", var_name)
);
}
code.to_string()
}
fn generate_option_alternative(&self, code: &str, var_name: &str) -> String {
let unwrap_pattern = format!("{}.unwrap()", var_name);
let expect_pattern1 = format!("{}.expect(", var_name);
let expect_pattern2 = format!("{}.unwrap_unchecked()", var_name);
if code.contains(&unwrap_pattern) {
return code.replace(
&unwrap_pattern,
&format!("match {} {{\n Some(value) => value,\n None => return Err(\"Value was None\".into()),\n}}", var_name)
);
} else if code.contains(&expect_pattern1) {
let re = Regex::new(&format!(r#"{}.expect\(['"](.*?)['"]"#, var_name)).unwrap();
let message = re
.captures(code)
.and_then(|cap| cap.get(1))
.map_or("Value was None", |m| m.as_str());
return code.replace(
&format!("{}.expect(\"{}\")", var_name, message),
&format!("match {} {{\n Some(value) => value,\n None => return Err(\"{}\".into()),\n}}", var_name, message)
);
} else if code.contains(&expect_pattern2) {
return code.replace(
&expect_pattern2,
&format!("match {} {{\n Some(value) => value,\n None => return Err(\"Value was None\".into()),\n}}", var_name)
);
}
code.to_string()
}
fn generate_fix(&self, code: &str) -> Option<(String, String, String)> {
if !self.has_unsafe_unwrap(code) {
return None;
}
let var_name = self.extract_variable_name(code)?;
let type_hint = self.is_result_or_option(code, &var_name)?;
let fixed_code = match type_hint {
"Result" => self.generate_result_alternative(code, &var_name),
"Option" => self.generate_option_alternative(code, &var_name),
_ => return None,
};
let explanation = format!(
"Using `.unwrap()` or `.expect()` can cause runtime panics if the {} is an error or None.\n\
It's safer to handle both success and error cases explicitly using pattern matching.\n\
This change replaces the unwrap with a match expression that handles both cases.",
type_hint
);
let diff = format!("- {}\n+ {}", code.trim(), fixed_code.trim());
Some((fixed_code, explanation, diff))
}
}
impl InvalidArgumentCountFixGenerator {
pub fn new() -> Self {
Self
}
fn is_invalid_argument_count_error(&self, message: &str) -> bool {
message.contains("E0061")
|| message.contains("this function takes")
|| message.contains("expected") && message.contains("argument")
|| message.contains("wrong number of arguments")
|| message.contains("incorrect number of arguments")
}
fn extract_function_name(&self, message: &str) -> Option<String> {
let patterns = [
r#"function [`']([^'`]+)[`']"#,
r#"call to [`']([^'`]+)[`']"#,
r#"calling [`']([^'`]+)[`']"#,
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(fn_match) = captures.get(1) {
return Some(fn_match.as_str().to_string());
}
}
}
}
None
}
fn extract_argument_counts(&self, message: &str) -> Option<(usize, usize)> {
let patterns = [
r#"takes (\d+) (?:argument|parameters) but (\d+) (?:argument|parameter) was supplied"#,
r#"takes (\d+) (?:argument|parameters) but (\d+) (?:argument|parameter)s? were supplied"#,
r#"expected (\d+) (?:argument|parameters), found (\d+)"#,
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let (Some(expected_match), Some(actual_match)) =
(captures.get(1), captures.get(2))
{
if let (Ok(expected), Ok(actual)) = (
expected_match.as_str().parse::<usize>(),
actual_match.as_str().parse::<usize>(),
) {
return Some((expected, actual));
}
}
}
}
}
None
}
fn generate_fix_suggestions(
&self,
function_name: Option<&str>,
arg_counts: Option<(usize, usize)>,
) -> Vec<String> {
let mut suggestions = Vec::new();
if let Some(fn_name) = function_name {
suggestions.push(format!("// For function '{}':", fn_name));
} else {
suggestions.push("// For this function call:".to_string());
}
if let Some((expected, actual)) = arg_counts {
match actual.cmp(&expected) {
std::cmp::Ordering::Less => {
suggestions.push(format!(
"// 1. Add the missing {} argument(s)",
expected - actual
));
let mut args = Vec::new();
for i in 0..expected {
if i < actual {
args.push(format!("arg{}", i + 1));
} else {
args.push(format!("/* missing_arg{} */", i + 1));
}
}
if let Some(fn_name) = function_name {
suggestions.push(format!("// {}({})", fn_name, args.join(", ")));
} else {
suggestions.push(format!("// function_name({})", args.join(", ")));
}
}
std::cmp::Ordering::Greater => {
suggestions.push(format!(
"// 1. Remove the extra {} argument(s)",
actual - expected
));
let mut args = Vec::new();
for i in 0..expected {
args.push(format!("arg{}", i + 1));
}
if let Some(fn_name) = function_name {
suggestions.push(format!("// {}({})", fn_name, args.join(", ")));
} else {
suggestions.push(format!("// function_name({})", args.join(", ")));
}
if expected == 1 {
suggestions.push("// 2. If the arguments are related, consider combining them into a struct or tuple".to_string());
suggestions.push("// function_name((arg1, arg2, ...))".to_string());
}
}
std::cmp::Ordering::Equal => {
}
}
} else {
suggestions.push(
"// 1. Check the function signature to determine the correct number of arguments"
.to_string(),
);
suggestions
.push("// - Look at the function definition or documentation".to_string());
suggestions.push("// 2. Make sure you're calling the right function".to_string());
suggestions
.push("// - Similar functions might have different parameter lists".to_string());
}
suggestions
.push("// 3. Consider using named arguments with a struct for clarity".to_string());
suggestions
.push("// - Create a struct with named fields for the parameters".to_string());
suggestions.push("// - Pass an instance of the struct to the function".to_string());
suggestions
}
}
impl UnstableFeatureFixGenerator {
pub fn new() -> Self {
Self
}
fn is_unstable_feature_error(&self, message: &str) -> bool {
message.contains("E0658")
|| message.contains("use of unstable feature")
|| message.contains("unstable feature")
|| message.contains("is unstable")
|| message.contains("nightly-only")
}
fn extract_feature_name(&self, message: &str) -> Option<String> {
let patterns = [
r#"use of unstable feature [`']([^'`]+)[`']"#,
r#"the feature [`']([^'`]+)[`'] is unstable"#,
r#"unstable feature: [`']([^'`]+)[`']"#,
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(feature_match) = captures.get(1) {
return Some(feature_match.as_str().to_string());
}
}
}
}
None
}
fn generate_fix_suggestions(&self, feature_name: Option<&str>) -> Vec<String> {
let mut suggestions = vec![
"// 1. Use the nightly compiler channel".to_string(),
"// - rustup default nightly".to_string(),
"// - rustup override set nightly (for this project only)".to_string(),
"// 2. Enable the feature in your crate root (lib.rs or main.rs)".to_string(),
];
if let Some(feature) = feature_name {
suggestions.push(format!("// - #![feature({})]", feature));
} else {
suggestions.push("// - #![feature(feature_name)]".to_string());
}
suggestions.push("// 3. Look for stable alternatives".to_string());
suggestions
.push("// - Check the Rust documentation for stable alternatives".to_string());
suggestions
.push("// - Consider using a crate that provides similar functionality".to_string());
if let Some(feature) = feature_name {
match feature {
"try_trait" => {
suggestions.push(
"// 4. For 'try_trait', consider using match or if let on Result/Option"
.to_string(),
);
suggestions.push(
"// - match result { Ok(v) => v, Err(e) => return Err(e) }".to_string(),
);
}
"async_closure" => {
suggestions.push(
"// 4. For 'async_closure', use a regular closure with async block"
.to_string(),
);
suggestions.push("// - |x| async move { /* async code */ }".to_string());
}
"box_syntax" => {
suggestions.push("// 4. For 'box_syntax', use Box::new() instead".to_string());
suggestions.push("// - Box::new(value) instead of box value".to_string());
}
_ => {
suggestions.push(format!(
"// 4. For '{}', check the Rust Unstable Book",
feature
));
suggestions
.push("// - https://doc.rust-lang.org/unstable-book/".to_string());
}
}
}
suggestions
}
}
impl ReturnLocalReferenceFixGenerator {
pub fn new() -> Self {
Self
}
fn is_return_local_reference_error(&self, message: &str) -> bool {
message.contains("E0515")
|| message.contains("returns a reference to data owned by the current function")
|| message.contains("returns a value referencing data owned by the current function")
|| message.contains("returns a reference to a local value")
}
fn extract_variable_name(&self, message: &str) -> Option<String> {
let patterns = [
r#"returns a (?:reference|value referencing) (?:data owned by|local value) .* `([^`]+)`"#,
r#"`([^`]+)` is borrowed here"#,
r#"returns a reference to `([^`]+)`"#,
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(var_match) = captures.get(1) {
return Some(var_match.as_str().to_string());
}
}
}
}
None
}
fn generate_fix_suggestions(&self, variable_name: Option<&str>) -> Vec<String> {
let mut suggestions = vec![
"// 1. Return an owned value instead of a reference".to_string(),
"// - Use Clone: return value.clone()".to_string(),
"// - Use Copy: return *value (if the type implements Copy)".to_string(),
"// - Use owned types: String instead of &str, Vec<T> instead of &[T]".to_string(),
"// 2. Change the function signature to take input with the same lifetime".to_string(),
"// - fn function<'a>(input: &'a Type) -> &'a Type { ... }".to_string(),
];
if let Some(var) = variable_name {
suggestions.push(format!("// 3. For this specific case with `{}`:", var));
suggestions.push(format!(
"// - If `{}` is a String: return {}.clone()",
var, var
));
suggestions.push(format!(
"// - If `{}` is a reference already: return {}",
var, var
));
suggestions.push(format!(
"// - If `{}` is a primitive type: return *{} (if Copy)",
var, var
));
}
suggestions.push(
"// 4. Use 'static lifetime (only if the data truly lives for the entire program)"
.to_string(),
);
suggestions
.push("// - const STATIC_VALUE: &'static str = \"static string\";".to_string());
suggestions.push("// - return STATIC_VALUE;".to_string());
suggestions
}
}
impl NetworkTlsFixGenerator {
pub fn new() -> Self {
Self
}
fn is_tls_error(&self, message: &str) -> bool {
(message.contains("TLS")
|| message.contains("SSL")
|| message.contains("certificate")
|| message.contains("cert")
|| message.contains("handshake"))
&& (message.contains("validation")
|| message.contains("verify")
|| message.contains("invalid")
|| message.contains("expired")
|| message.contains("self-signed")
|| message.contains("untrusted")
|| message.contains("mismatch")
|| message.contains("hostname")
|| message.contains("common name"))
}
fn extract_hostname(&self, message: &str) -> Option<String> {
let patterns = [
r#"(?:hostname|CN|common name)[\s:]+['"]([\w\.-]+)['"]"#,
r#"(?:hostname|CN|common name)[\s:]+(\w+\.\w+(?:\.\w+)*)"#,
r#"certificate (?:for|issued to) ['"]([\w\.-]+)['"]"#,
r#"certificate (?:for|issued to) (\w+\.\w+(?:\.\w+)*)"#,
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(host_match) = captures.get(1) {
return Some(host_match.as_str().to_string());
}
}
}
}
None
}
fn generate_tls_diagnostics(&self, hostname: Option<&str>) -> Vec<(String, String)> {
let mut diagnostics = Vec::new();
if let Some(host) = hostname {
let openssl_cmd = format!(
"openssl s_client -connect {}:443 -servername {}",
host, host
);
let openssl_explanation = format!("Check TLS certificate for {}", host);
diagnostics.push((openssl_cmd, openssl_explanation));
let expiry_cmd = format!(
"echo | openssl s_client -connect {}:443 2>/dev/null | openssl x509 -noout -dates",
host
);
let expiry_explanation = format!("Check certificate expiration dates for {}", host);
diagnostics.push((expiry_cmd, expiry_explanation));
let chain_cmd = format!("echo | openssl s_client -connect {}:443 -showcerts", host);
let chain_explanation = format!("Check certificate chain for {}", host);
diagnostics.push((chain_cmd, chain_explanation));
} else {
diagnostics.push((
"openssl version".to_string(),
"Check OpenSSL version".to_string(),
));
diagnostics.push((
"ls -la /etc/ssl/certs".to_string(),
"List system certificates".to_string(),
));
}
diagnostics
}
fn generate_tls_fix(
&self,
message: &str,
hostname: Option<&str>,
) -> Vec<(String, String, String)> {
let mut fixes = Vec::new();
if message.contains("self-signed") || message.contains("untrusted") {
if let Some(host) = hostname {
fixes.push((
format!("Add certificate for {} to trusted certificates", host),
format!("The TLS certificate for {} is self-signed or from an untrusted issuer.", host),
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)
));
} else {
fixes.push((
"Add certificate to trusted certificates".to_string(),
"The TLS certificate is self-signed or from an untrusted issuer.".to_string(),
"# 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()
));
}
}
else if message.contains("expired") {
if let Some(host) = hostname {
fixes.push((
format!("Certificate for {} has expired", host),
format!("The TLS certificate for {} has expired and needs to be renewed.", host),
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)
));
} else {
fixes.push((
"Certificate has expired".to_string(),
"The TLS certificate has expired and needs to be renewed.".to_string(),
"# 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()
));
}
}
else if message.contains("mismatch")
|| message.contains("hostname")
|| message.contains("common name")
{
if let Some(host) = hostname {
fixes.push((
format!("Hostname mismatch for {}", host),
format!("The TLS certificate for {} does not match the hostname being used.", host),
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)
));
} else {
fixes.push((
"Hostname mismatch".to_string(),
"The TLS certificate does not match the hostname being used.".to_string(),
"# 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()
));
}
}
else if let Some(host) = hostname {
fixes.push((
format!("TLS certificate issue with {}", host),
format!("There is a TLS certificate validation issue with {}.", host),
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)
));
} else {
fixes.push((
"TLS certificate validation issue".to_string(),
"There is a TLS certificate validation issue.".to_string(),
"# 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()
));
}
fixes
}
}
impl FixGenerator for NetworkConnectionFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = match error {
DecrustError::Network { kind, url, .. } => {
let mut msg = format!("{} network error", kind);
if let Some(u) = url {
msg.push_str(&format!(" for URL: {}", u));
}
msg
}
_ => {
let msg = params.values.get("message")?;
if !self.is_connection_error(msg) && !self.is_dns_error(msg) {
return None;
}
msg.clone()
}
};
let host = self.extract_host(&message);
let port = self.extract_port(&message);
let diagnostics = self.generate_connection_diagnostics(host.as_deref(), port);
let fixes = self.generate_connection_fix(&message, host.as_deref(), port);
if fixes.is_empty() {
return None;
}
let (title, explanation, steps) = &fixes[0];
let mut commands = Vec::new();
for (cmd, _) in &diagnostics {
commands.push(cmd.clone());
}
Some(Autocorrection {
description: title.clone(),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.7,
details: Some(FixDetails::SuggestCommand {
command: commands.join(" && "),
explanation: format!("{}. {}", explanation, steps),
}),
diff_suggestion: None,
commands_to_apply: commands,
targets_error_code: Some("network_connection_error".to_string()),
})
}
fn name(&self) -> &'static str {
"NetworkConnectionFixGenerator"
}
}
impl FixGenerator for NetworkTlsFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = match error {
DecrustError::Network { kind, url, .. } => {
if !kind.contains("TLS") && !kind.contains("SSL") {
return None;
}
let mut msg = format!("{} network error", kind);
if let Some(u) = url {
msg.push_str(&format!(" for URL: {}", u));
}
msg
}
_ => {
let msg = params.values.get("message")?;
if !self.is_tls_error(msg) {
return None;
}
msg.clone()
}
};
let hostname = self.extract_hostname(&message);
let diagnostics = self.generate_tls_diagnostics(hostname.as_deref());
let fixes = self.generate_tls_fix(&message, hostname.as_deref());
if fixes.is_empty() {
return None;
}
let (title, explanation, steps) = &fixes[0];
let mut commands = Vec::new();
for (cmd, _) in &diagnostics {
commands.push(cmd.clone());
}
Some(Autocorrection {
description: title.clone(),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.8,
details: Some(FixDetails::SuggestCommand {
command: commands.join(" && "),
explanation: format!("{}. {}", explanation, steps),
}),
diff_suggestion: None,
commands_to_apply: commands,
targets_error_code: Some("tls_certificate_error".to_string()),
})
}
fn name(&self) -> &'static str {
"NetworkTlsFixGenerator"
}
}
impl FixGenerator for ReturnLocalReferenceFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !self.is_return_local_reference_error(message) {
return None;
}
let variable_name = self.extract_variable_name(message);
let suggestions = self.generate_fix_suggestions(variable_name.as_deref());
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let explanation = format!(
"Error E0515: You're returning a reference to a local variable, which will be dropped when the function exits.\n\n\
This is a fundamental Rust ownership issue. The compiler prevents this because it would lead to a dangling reference.\n\n\
Consider these solutions:\n{}",
suggestions.join("\n")
);
let code_snippet = if let Some(context) = source_code_context {
context.to_string()
} else {
"// 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()
};
Some(Autocorrection {
description: "Fix returning reference to local variable (E0515)".to_string(),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.9,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: code_snippet,
explanation,
}),
diff_suggestion: None,
commands_to_apply: vec![],
targets_error_code: Some("E0515".to_string()),
})
}
fn name(&self) -> &'static str {
"ReturnLocalReferenceFixGenerator"
}
}
impl FixGenerator for UnstableFeatureFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !self.is_unstable_feature_error(message) {
return None;
}
let feature_name = self.extract_feature_name(message);
let suggestions = self.generate_fix_suggestions(feature_name.as_deref());
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let explanation = format!(
"Error E0658: You're using an unstable feature that requires a nightly compiler or explicit opt-in.\n\n\
Rust's stability guarantees mean that some features are only available on the nightly channel \
until they're deemed stable enough for general use.\n\n\
Consider these solutions:\n{}",
suggestions.join("\n")
);
let code_snippet = if let Some(context) = source_code_context {
context.to_string()
} else if let Some(feature) = &feature_name {
format!("// Using unstable feature\nfn example() {{\n // Code using the unstable feature '{}'\n}}", feature)
} else {
"// Using unstable feature\nfn example() {\n // Code using an unstable feature\n}"
.to_string()
};
let mut commands = Vec::new();
commands.push("rustup default nightly".to_string());
commands.push("rustc --version".to_string());
Some(Autocorrection {
description: "Fix unstable feature usage (E0658)".to_string(),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.9,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: code_snippet,
explanation,
}),
diff_suggestion: None,
commands_to_apply: commands,
targets_error_code: Some("E0658".to_string()),
})
}
fn name(&self) -> &'static str {
"UnstableFeatureFixGenerator"
}
}
impl FixGenerator for InvalidArgumentCountFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !self.is_invalid_argument_count_error(message) {
return None;
}
let function_name = self.extract_function_name(message);
let arg_counts = self.extract_argument_counts(message);
let suggestions = self.generate_fix_suggestions(function_name.as_deref(), arg_counts);
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let explanation = format!(
"Error E0061: This function call has an incorrect number of arguments.\n\n\
{}.\n\n\
Consider these solutions:\n{}",
if let Some((expected, actual)) = arg_counts {
if actual < expected {
format!("The function expects {} arguments, but you provided {}. You need to add {} more argument(s).",
expected, actual, expected - actual)
} else {
format!("The function expects {} arguments, but you provided {}. You need to remove {} extra argument(s). Remove the unnecessary arguments.",
expected, actual, actual - expected)
}
} else {
"The function is being called with the wrong number of arguments".to_string()
},
suggestions.join("\n")
);
let code_snippet = if let Some(context) = source_code_context {
context.to_string()
} else if let Some(fn_name) = &function_name {
if let Some((expected, actual)) = arg_counts {
if actual < expected {
let mut args = Vec::new();
for i in 0..actual {
args.push(format!("arg{}", i + 1));
}
format!("// Function call with too few arguments\n{}({}) // ERROR: missing {} argument(s)",
fn_name, args.join(", "), expected - actual)
} else {
let mut args = Vec::new();
for i in 0..actual {
args.push(format!("arg{}", i + 1));
}
format!("// Function call with too many arguments\n{}({}) // ERROR: has {} extra argument(s)",
fn_name, args.join(", "), actual - expected)
}
} else {
format!("// Function call with incorrect number of arguments\n{}(...) // ERROR: wrong number of arguments", fn_name)
}
} else {
"// Function call with incorrect number of arguments\nfunction_name(...) // ERROR: wrong number of arguments".to_string()
};
Some(Autocorrection {
description: "Fix function call with incorrect number of arguments (E0061)".to_string(),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.9,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: code_snippet,
explanation,
}),
diff_suggestion: None,
commands_to_apply: vec![],
targets_error_code: Some("E0061".to_string()),
})
}
fn name(&self) -> &'static str {
"InvalidArgumentCountFixGenerator"
}
}
impl FixGenerator for UnsafeUnwrapFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
_params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let code = source_code_context?;
let (fixed_code, explanation, diff) = self.generate_fix(code)?;
let file_path = PathBuf::from("unknown_file.rs");
let line_hint = 1;
Some(Autocorrection {
description: "Replace unsafe unwrap() or expect() with explicit error handling"
.to_string(),
fix_type: FixType::TextReplacement,
confidence: 0.8,
details: Some(FixDetails::SuggestCodeChange {
file_path,
line_hint,
suggested_code_snippet: fixed_code,
explanation,
}),
diff_suggestion: Some(diff),
commands_to_apply: vec![],
targets_error_code: Some("unsafe_unwrap".to_string()),
})
}
fn name(&self) -> &'static str {
"UnsafeUnwrapFixGenerator"
}
}
impl FixGenerator for QuestionMarkPropagationFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
_params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let code = source_code_context?;
let (fixed_code, explanation, diff) = self.generate_fix(code)?;
let file_path = PathBuf::from("unknown_file.rs");
let line_hint = 1;
Some(Autocorrection {
description:
"Fix question mark operator usage in function without Result/Option return type"
.to_string(),
fix_type: FixType::TextReplacement,
confidence: 0.9,
details: Some(FixDetails::SuggestCodeChange {
file_path,
line_hint,
suggested_code_snippet: fixed_code,
explanation,
}),
diff_suggestion: Some(diff),
commands_to_apply: vec![],
targets_error_code: Some("E0277".to_string()), })
}
fn name(&self) -> &'static str {
"QuestionMarkPropagationFixGenerator"
}
}
impl FixGenerator for MissingOkErrFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
_params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let code = source_code_context?;
let (fixed_code, explanation, diff) = self.generate_fix(code)?;
let file_path = PathBuf::from("unknown_file.rs");
let line_hint = 1;
Some(Autocorrection {
description: "Add missing match arms for Result/Option".to_string(),
fix_type: FixType::TextReplacement,
confidence: 0.9,
details: Some(FixDetails::SuggestCodeChange {
file_path,
line_hint,
suggested_code_snippet: fixed_code,
explanation,
}),
diff_suggestion: Some(diff),
commands_to_apply: vec![],
targets_error_code: Some("incomplete_match".to_string()),
})
}
fn name(&self) -> &'static str {
"MissingOkErrFixGenerator"
}
}
impl FixGenerator for DivisionByZeroFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
_params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let code = source_code_context?;
let (fixed_code, explanation, diff) = self.generate_fix(code)?;
let file_path = PathBuf::from("unknown_file.rs");
let line_hint = 1;
Some(Autocorrection {
description: "Prevent division by zero panic".to_string(),
fix_type: FixType::TextReplacement,
confidence: 0.8,
details: Some(FixDetails::SuggestCodeChange {
file_path,
line_hint,
suggested_code_snippet: fixed_code,
explanation,
}),
diff_suggestion: Some(diff),
commands_to_apply: vec![],
targets_error_code: Some("division_by_zero".to_string()),
})
}
fn name(&self) -> &'static str {
"DivisionByZeroFixGenerator"
}
}
impl FixGenerator for RuntimePanicFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
_params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let code = source_code_context?;
let (fixed_code, explanation, diff) = self.generate_fix(code)?;
let file_path = PathBuf::from("unknown_file.rs");
let line_hint = 1;
Some(Autocorrection {
description: "Prevent runtime panic".to_string(),
fix_type: FixType::TextReplacement,
confidence: 0.7,
details: Some(FixDetails::SuggestCodeChange {
file_path,
line_hint,
suggested_code_snippet: fixed_code,
explanation,
}),
diff_suggestion: Some(diff),
commands_to_apply: vec![],
targets_error_code: Some("runtime_panic".to_string()),
})
}
fn name(&self) -> &'static str {
"RuntimePanicFixGenerator"
}
}
impl FixGenerator for ClosureCaptureLifetimeFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !self.is_closure_capture_error(message) {
return None;
}
let variable_name = self
.extract_captured_variable(message)
.unwrap_or_else(|| "captured_var".to_string());
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "src/main.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let fixes = self.generate_closure_fixes(&variable_name, source_code_context);
let explanation = format!(
"Error E0373: The closure may outlive the current function because it captures `{}` by reference.\n\n\
Rust requires that all data referenced by a closure must live at least as long as the closure itself.\n\n\
Solutions:\n{}",
variable_name, fixes.join("\n")
);
Some(Autocorrection {
description: format!("Fix closure capture lifetime issue for `{}`", variable_name),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.85,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: fixes.join("\n"),
explanation,
}),
diff_suggestion: Some(format!(
"// Example transformation:\n\
-let closure = || {{ /* uses {} */ }};\n\
+let {}_owned = {}.clone();\n\
+let closure = move || {{ /* uses {}_owned */ }};",
variable_name, variable_name, variable_name, variable_name
)),
commands_to_apply: vec![],
targets_error_code: Some("E0373".to_string()),
})
}
fn name(&self) -> &'static str {
"ClosureCaptureLifetimeFixGenerator"
}
}
impl FixGenerator for RecursiveTypeFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !self.is_recursive_type_error(message) {
return None;
}
let type_name = self
.extract_type_name(message)
.unwrap_or_else(|| "RecursiveType".to_string());
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "src/main.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let fixes = self.generate_recursive_fixes(&type_name, source_code_context);
let explanation = format!(
"Error E0072: Recursive type `{}` has infinite size.\n\n\
Rust cannot determine the memory layout of types that contain themselves directly. \
You need indirection through heap allocation (Box<T>) or shared ownership (Rc<T>/Arc<T>).\n\n\
Solutions:\n{}",
type_name, fixes.join("\n")
);
Some(Autocorrection {
description: format!("Fix recursive type definition for `{}`", type_name),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.90,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: fixes.join("\n"),
explanation,
}),
diff_suggestion: Some(format!(
"// Example transformation:\n\
-struct {} {{ next: {} }}\n\
+struct {} {{ next: Option<Box<{}>> }}",
type_name, type_name, type_name, type_name
)),
commands_to_apply: vec![],
targets_error_code: Some("E0072".to_string()),
})
}
fn name(&self) -> &'static str {
"RecursiveTypeFixGenerator"
}
}
impl FixGenerator for UnusedMutFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
_params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let code = source_code_context?;
if !self.is_unused_mut(code) {
return None;
}
let variable = self.extract_variable_name(code)?;
let (fixed_code, explanation, diff) = self.generate_unused_mut_fix(code, &variable);
Some(Autocorrection {
description: format!("Remove unused 'mut' keyword for variable '{}'", variable),
fix_type: FixType::TextReplacement,
confidence: 0.8, details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from("unknown_file.rs"), line_hint: 1, suggested_code_snippet: fixed_code,
explanation,
}),
diff_suggestion: Some(diff),
commands_to_apply: vec![],
targets_error_code: Some("unused_mut".to_string()),
})
}
fn name(&self) -> &'static str {
"UnusedMutFixGenerator"
}
}
impl FixGenerator for YamlParseFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let file_path = params.values.get("file_path")?.clone();
if !file_path.ends_with(".yaml")
&& !file_path.ends_with(".yml")
&& !file_path.ends_with(".YAML")
&& !file_path.ends_with(".YML")
{
return None;
}
let message = match error {
DecrustError::Parse { context_info, .. } => {
if context_info.contains("YAML") {
context_info.clone()
} else {
return None;
}
}
_ => {
let msg = params.values.get("message")?;
if !self.is_yaml_parse_error(msg) {
return None;
}
msg.clone()
}
};
let line_number = self.extract_line_number(&message);
let column_number = self.extract_column_number(&message);
let error_type = self.extract_error_type(&message);
let (command, explanation, suggestion) =
self.generate_yaml_fix(&file_path, line_number, column_number, error_type.clone());
let full_explanation = if let Some(sugg) = suggestion {
format!("{}. {}", explanation, sugg)
} else {
explanation
};
Some(Autocorrection {
description: format!("Fix YAML parsing error in file: {}", file_path),
fix_type: FixType::ExecuteCommand,
confidence: 0.8,
details: Some(FixDetails::SuggestCommand {
command: command.clone(),
explanation: full_explanation,
}),
diff_suggestion: None,
commands_to_apply: vec![command],
targets_error_code: Some("yaml_parse_error".to_string()),
})
}
fn name(&self) -> &'static str {
"YamlParseFixGenerator"
}
}
impl FixGenerator for JsonParseFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let file_path = params.values.get("file_path")?.clone();
if !file_path.ends_with(".json") && !file_path.ends_with(".JSON") {
return None;
}
let message = match error {
DecrustError::Parse { context_info, .. } => {
if context_info.contains("JSON") {
context_info.clone()
} else {
return None;
}
}
_ => {
let msg = params.values.get("message")?;
if !self.is_json_parse_error(msg) {
return None;
}
msg.clone()
}
};
let line_number = self.extract_line_number(&message);
let column_number = self.extract_column_number(&message);
let expected_token = self.extract_expected_token(&message);
let (command, explanation, suggestion) = self.generate_json_fix(
&file_path,
line_number,
column_number,
expected_token.clone(),
);
let full_explanation = if let Some(sugg) = suggestion {
format!("{}. {}", explanation, sugg)
} else {
explanation
};
Some(Autocorrection {
description: format!("Fix JSON parsing error in file: {}", file_path),
fix_type: FixType::ExecuteCommand,
confidence: 0.8,
details: Some(FixDetails::SuggestCommand {
command: command.clone(),
explanation: full_explanation,
}),
diff_suggestion: None,
commands_to_apply: vec![command],
targets_error_code: Some("json_parse_error".to_string()),
})
}
fn name(&self) -> &'static str {
"JsonParseFixGenerator"
}
}
impl ConfigMissingKeyFixGenerator {
pub fn new() -> Self {
Self
}
fn is_missing_key_error(&self, message: &str) -> bool {
message.contains("missing key")
|| message.contains("required key")
|| message.contains("key not found")
|| message.contains("missing field")
|| message.contains("required field")
|| message.contains("field not found")
}
fn extract_key_name(&self, message: &str) -> Option<String> {
let patterns = [
r#"missing key[:\s]+["'](.*?)["']"#,
r#"required key[:\s]+["'](.*?)["']"#,
r#"key not found[:\s]+["'](.*?)["']"#,
r#"missing field[:\s]+(.*?)(?:\s|$)"#,
r#"required field[:\s]+["'](.*?)["']"#,
r#"field not found[:\s]+["'](.*?)["']"#,
r#"required key not found[:\s]+(.*?)(?:\s|$)"#,
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(key_match) = captures.get(1) {
return Some(key_match.as_str().to_string());
}
}
}
}
None
}
fn determine_file_format(&self, file_path: &str) -> Option<&'static str> {
if file_path.ends_with(".json") || file_path.ends_with(".JSON") {
Some("json")
} else if file_path.ends_with(".yaml")
|| file_path.ends_with(".yml")
|| file_path.ends_with(".YAML")
|| file_path.ends_with(".YML")
{
Some("yaml")
} else if file_path.ends_with(".toml") || file_path.ends_with(".TOML") {
Some("toml")
} else {
None
}
}
fn generate_default_value(&self, key_name: &str, format: &str) -> String {
let default_value = if key_name.contains("path")
|| key_name.contains("dir")
|| key_name.contains("directory")
{
"\"/path/to/directory\""
} else if key_name.contains("file") {
"\"/path/to/file\""
} else if key_name.contains("url") || key_name.contains("uri") {
"\"https://example.com\""
} else if key_name.contains("port") {
"8080"
} else if key_name.contains("host") {
"\"localhost\""
} else if key_name.contains("timeout")
|| key_name.contains("interval")
|| key_name.contains("duration")
{
"60"
} else if key_name.contains("enabled")
|| key_name.contains("disabled")
|| key_name.contains("active")
|| key_name.contains("flag")
{
match format {
"json" | "yaml" => "true",
"toml" => "true",
_ => "true",
}
} else if key_name.contains("count")
|| key_name.contains("limit")
|| key_name.contains("max")
|| key_name.contains("min")
{
"10"
} else {
match format {
"json" | "yaml" => "\"value\"",
"toml" => "\"value\"",
_ => "\"value\"",
}
};
default_value.to_string()
}
fn generate_missing_key_fix(
&self,
file_path: &str,
key_name: &str,
format: &str,
) -> (String, String, String) {
let default_value = self.generate_default_value(key_name, format);
let (command, explanation, diff) = match format {
"json" => {
let command = format!(
"echo 'Add the missing key \"{}\" to {}'",
key_name, file_path
);
let explanation = format!(
"The configuration file '{}' is missing the required key '{}'. Add this key with an appropriate value.",
file_path, key_name
);
let diff = format!(" \"{}\": {}", key_name, default_value);
(command, explanation, diff)
}
"yaml" => {
let command = format!(
"echo 'Add the missing key \"{}\" to {}'",
key_name, file_path
);
let explanation = format!(
"The configuration file '{}' is missing the required key '{}'. Add this key with an appropriate value.",
file_path, key_name
);
let diff = format!("{}: {}", key_name, default_value);
(command, explanation, diff)
}
"toml" => {
let command = format!(
"echo 'Add the missing key \"{}\" to {}'",
key_name, file_path
);
let explanation = format!(
"The configuration file '{}' is missing the required key '{}'. Add this key with an appropriate value.",
file_path, key_name
);
let diff = format!("{} = {}", key_name, default_value);
(command, explanation, diff)
}
_ => {
let command = format!(
"echo 'Add the missing key \"{}\" to {}'",
key_name, file_path
);
let explanation = format!(
"The configuration file '{}' is missing the required key '{}'. Add this key with an appropriate value.",
file_path, key_name
);
let diff = format!("{} = {}", key_name, default_value);
(command, explanation, diff)
}
};
(command, explanation, diff)
}
}
impl ConfigSyntaxFixGenerator {
pub fn is_json_syntax_error(&self, message: &str, file_path: &str) -> bool {
let is_json_file = file_path.ends_with(".json") || file_path.ends_with(".JSON");
let has_json_keywords = message.contains("JSON")
|| message.contains("json")
|| message.contains("syntax error")
|| message.contains("invalid")
|| message.contains("failed to parse");
is_json_file && has_json_keywords
}
pub fn is_yaml_syntax_error(&self, message: &str, file_path: &str) -> bool {
let is_yaml_file = file_path.ends_with(".yml")
|| file_path.ends_with(".yaml")
|| file_path.ends_with(".YML")
|| file_path.ends_with(".YAML");
let has_yaml_keywords = message.contains("YAML")
|| message.contains("yaml")
|| message.contains("syntax error")
|| message.contains("invalid")
|| message.contains("failed to parse");
is_yaml_file && has_yaml_keywords
}
pub fn is_toml_syntax_error(&self, message: &str, file_path: &str) -> bool {
let is_toml_file = file_path.ends_with(".toml") || file_path.ends_with(".TOML");
let has_toml_keywords = message.contains("TOML")
|| message.contains("toml")
|| message.contains("syntax error")
|| message.contains("invalid")
|| message.contains("failed to parse");
is_toml_file && has_toml_keywords
}
fn extract_line_number(&self, message: &str) -> Option<usize> {
let patterns = [
r"at line (\d+)",
r"line (\d+)",
r"line: (\d+)",
r"line:(\d+)",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(line_match) = captures.get(1) {
if let Ok(line) = line_match.as_str().parse::<usize>() {
return Some(line);
}
}
}
}
}
None
}
fn generate_json_fix(&self, file_path: &str, line_number: Option<usize>) -> (String, String) {
let command = format!("jsonlint --fix {}", file_path);
let explanation = if let Some(line) = line_number {
format!("JSON syntax error detected in file '{}' at line {}. This command will attempt to fix the JSON syntax.", file_path, line)
} else {
format!("JSON syntax error detected in file '{}'. This command will attempt to fix the JSON syntax.", file_path)
};
(command, explanation)
}
fn generate_yaml_fix(&self, file_path: &str, line_number: Option<usize>) -> (String, String) {
let command = format!("yamllint {}", file_path);
let explanation = if let Some(line) = line_number {
format!("YAML syntax error detected in file '{}' at line {}. This command will check the YAML syntax and provide detailed error information.", file_path, line)
} else {
format!("YAML syntax error detected in file '{}'. This command will check the YAML syntax and provide detailed error information.", file_path)
};
(command, explanation)
}
fn generate_toml_fix(&self, file_path: &str, line_number: Option<usize>) -> (String, String) {
let command = format!("taplo fmt {}", file_path);
let explanation = if let Some(line) = line_number {
format!("TOML syntax error detected in file '{}' at line {}. This command will format the TOML file and may fix syntax issues.", file_path, line)
} else {
format!("TOML syntax error detected in file '{}'. This command will format the TOML file and may fix syntax issues.", file_path)
};
(command, explanation)
}
}
impl FixGenerator for ConfigMissingKeyFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let file_path = match error {
DecrustError::Config {
path: Some(path), ..
} => path.to_string_lossy().to_string(),
_ => params.values.get("file_path")?.clone(),
};
let message = match error {
DecrustError::Config { message, .. } => message.clone(),
_ => params.values.get("message")?.clone(),
};
if !self.is_missing_key_error(&message) {
return None;
}
let key_name = self.extract_key_name(&message)?;
let format = self.determine_file_format(&file_path)?;
let (command, explanation, diff) =
self.generate_missing_key_fix(&file_path, &key_name, format);
Some(Autocorrection {
description: format!(
"Add missing configuration key: {} to {}",
key_name, file_path
),
fix_type: FixType::TextReplacement,
confidence: 0.7,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(&file_path),
line_hint: 1, suggested_code_snippet: diff.clone(),
explanation,
}),
diff_suggestion: Some(format!("+ {}", diff)),
commands_to_apply: vec![command],
targets_error_code: Some("config_missing_key".to_string()),
})
}
fn name(&self) -> &'static str {
"ConfigMissingKeyFixGenerator"
}
}
impl FixGenerator for ConfigSyntaxFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let file_path = match error {
DecrustError::Config {
path: Some(path), ..
} => path.to_string_lossy().to_string(),
_ => params.values.get("file_path")?.clone(),
};
let message = match error {
DecrustError::Config { message, .. } => message.clone(),
DecrustError::Parse { context_info, .. } => context_info.clone(),
_ => params.values.get("message")?.clone(),
};
let line_number = self.extract_line_number(&message);
println!(
"ConfigSyntaxFixGenerator: file_path={}, message={}",
file_path, message
);
let is_json = self.is_json_syntax_error(&message, &file_path);
println!("Is JSON syntax error: {}", is_json);
let is_yaml = self.is_yaml_syntax_error(&message, &file_path);
println!("Is YAML syntax error: {}", is_yaml);
let is_toml = self.is_toml_syntax_error(&message, &file_path);
println!("Is TOML syntax error: {}", is_toml);
let (command, explanation) = if is_json {
self.generate_json_fix(&file_path, line_number)
} else if is_yaml {
self.generate_yaml_fix(&file_path, line_number)
} else if is_toml {
self.generate_toml_fix(&file_path, line_number)
} else {
println!("Not a recognized configuration syntax error");
return None;
};
Some(Autocorrection {
description: format!("Fix syntax error in configuration file: {}", file_path),
fix_type: FixType::ExecuteCommand,
confidence: 0.7,
details: Some(FixDetails::SuggestCommand {
command: command.clone(),
explanation,
}),
diff_suggestion: None,
commands_to_apply: vec![command],
targets_error_code: Some("config_syntax_error".to_string()),
})
}
fn name(&self) -> &'static str {
"ConfigSyntaxFixGenerator"
}
}
impl FixGenerator for IoPermissionFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let path = match error {
DecrustError::Io {
path: Some(path), ..
} => path.to_string_lossy().to_string(),
_ => params.values.get("path")?.clone(),
};
let message = match error {
DecrustError::Io { source, .. } => source.to_string(),
_ => params.values.get("message")?.clone(),
};
if !self.is_permission_error(&message) {
return None;
}
let (command, explanation) = self.determine_permission_fix(&path);
Some(Autocorrection {
description: format!("Fix permissions for: {}", path),
fix_type: FixType::ExecuteCommand,
confidence: 0.8,
details: Some(FixDetails::SuggestCommand {
command: command.clone(),
explanation,
}),
diff_suggestion: None,
commands_to_apply: vec![command],
targets_error_code: Some("io_error".to_string()),
})
}
fn name(&self) -> &'static str {
"IoPermissionFixGenerator"
}
}
impl FixGenerator for IoMissingDirectoryFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let path = match error {
DecrustError::Io {
path: Some(path), ..
} => path.to_string_lossy().to_string(),
_ => params.values.get("path")?.clone(),
};
let message = match error {
DecrustError::Io { source, .. } => source.to_string(),
_ => params.values.get("message")?.clone(),
};
if !self.is_missing_directory_error(&message) {
return None;
}
let dir_path = self.extract_directory_path(&path);
Some(Autocorrection {
description: format!("Create missing directory: {}", dir_path),
fix_type: FixType::ExecuteCommand,
confidence: 0.8,
details: Some(FixDetails::SuggestCommand {
command: format!("mkdir -p {}", dir_path),
explanation: format!(
"The directory '{}' does not exist. This command will create it and any parent directories.",
dir_path
),
}),
diff_suggestion: None,
commands_to_apply: vec![format!("mkdir -p {}", dir_path)],
targets_error_code: Some("io_error".to_string()),
})
}
fn name(&self) -> &'static str {
"IoMissingDirectoryFixGenerator"
}
}
impl AstUnusedCodeFixGenerator {
fn parse_unused_variable(&self, message: &str) -> Option<String> {
let pattern = r"unused variable: `([^`]+)`";
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(var_match) = captures.get(1) {
return Some(var_match.as_str().to_string());
}
}
}
None
}
fn parse_unused_import(&self, message: &str) -> Option<String> {
let pattern = r"unused import: `([^`]+)`";
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(import_match) = captures.get(1) {
return Some(import_match.as_str().to_string());
}
}
}
None
}
fn generate_unused_variable_fix(
&self,
variable_name: &str,
line: usize,
file_path: &str,
) -> Option<Autocorrection> {
if variable_name.starts_with('_') {
return None;
}
let new_name = format!("_{}", variable_name);
Some(Autocorrection {
description: format!("Add underscore to unused variable `{}`", variable_name),
fix_type: FixType::TextReplacement,
confidence: 0.9,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: format!("let {} = /* ... */;", new_name),
explanation: format!(
"Adding an underscore prefix to unused variables is a Rust convention that \
suppresses the unused variable warning."
),
}),
diff_suggestion: Some(format!(
"- let {} = ...\n+ let {} = ...",
variable_name, new_name
)),
commands_to_apply: vec![format!(
"sed -i 's/\\b{}\\b/{}/g' {}",
variable_name, new_name, file_path
)],
targets_error_code: Some("unused_variables".to_string()),
})
}
fn generate_unused_import_fix(
&self,
import: &str,
line: usize,
file_path: &str,
) -> Option<Autocorrection> {
Some(Autocorrection {
description: format!("Remove unused import `{}`", import),
fix_type: FixType::TextReplacement,
confidence: 0.9,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: "".to_string(),
explanation: format!(
"Removing unused imports improves code clarity and can slightly improve \
compilation times."
),
}),
diff_suggestion: Some(format!("- use {};", import)),
commands_to_apply: vec![format!("sed -i '/use {};/d' {}", import, file_path)],
targets_error_code: Some("unused_imports".to_string()),
})
}
}
impl FixGenerator for AstMissingImportFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = match error {
DecrustError::Validation { message, .. } => message,
DecrustError::Style { message, .. } => message,
_ => params.values.get("message")?,
};
let type_name = self.parse_type_name(message)?;
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "src/lib.rs".to_string());
let import_paths = self.suggest_import_paths(&type_name);
let mut commands = Vec::new();
let mut diff_suggestions = Vec::new();
for (_i, path) in import_paths.iter().enumerate().take(5) {
commands.push(format!("echo '{}' >> {}", path, file_path));
diff_suggestions.push(format!("+ {}", path));
}
Some(Autocorrection {
description: format!("Add import for `{}`", type_name),
fix_type: FixType::AddImport,
confidence: 0.7,
details: Some(FixDetails::AddImport {
file_path: file_path.clone(),
import: import_paths.first().cloned().unwrap_or_default(),
}),
diff_suggestion: Some(diff_suggestions.join("\n")),
commands_to_apply: commands,
targets_error_code: Some("E0412".to_string()),
})
}
fn name(&self) -> &'static str {
"AstMissingImportFixGenerator"
}
}
impl FixGenerator for AstUnusedCodeFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = match error {
DecrustError::Validation { message, .. } => message,
DecrustError::Style { message, .. } => message,
_ => params.values.get("message")?,
};
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "src/lib.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
if let Some(variable_name) = self.parse_unused_variable(message) {
return self.generate_unused_variable_fix(&variable_name, line, &file_path);
}
if let Some(import) = self.parse_unused_import(message) {
return self.generate_unused_import_fix(&import, line, &file_path);
}
None
}
fn name(&self) -> &'static str {
"AstUnusedCodeFixGenerator"
}
}
impl FixGenerator for AstTraitImplementationFixGenerator {
fn generate_fix(
&self,
error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = match error {
DecrustError::Validation { message, .. } => message,
_ => params.values.get("message")?,
};
let trait_name = self.parse_trait_name(message)?;
let type_name = self.parse_type_name(message)?;
let trait_impl = self.generate_trait_impl(&trait_name, &type_name)?;
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "src/lib.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
Some(Autocorrection {
description: format!("Implement trait `{}` for `{}`", trait_name, type_name),
fix_type: FixType::TextReplacement,
confidence: 0.7,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(&file_path),
line_hint: line,
suggested_code_snippet: trait_impl.clone(),
explanation: format!(
"The trait `{}` is not implemented for type `{}`. \
This implementation provides a basic skeleton that you should customize.",
trait_name, type_name
),
}),
diff_suggestion: Some(format!("+ {}", trait_impl)),
commands_to_apply: vec![format!(
"echo '{}' >> {}",
trait_impl.replace("'", "\\'"),
file_path
)],
targets_error_code: Some("E0277".to_string()),
})
}
fn name(&self) -> &'static str {
"AstTraitImplementationFixGenerator"
}
}
pub struct EnumParameterMatchFixGenerator;
impl EnumParameterMatchFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for EnumParameterMatchFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !self.is_enum_parameter_mismatch(message) {
return None;
}
let (enum_name, variant_name, expected_params, found_params) =
extract_enum_parameter_info(message)?;
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(
&file_path,
line,
&enum_name,
&variant_name,
&expected_params,
&found_params,
context,
)
} else {
self.generate_simple_fix(
&file_path,
line,
&enum_name,
&variant_name,
&expected_params,
&found_params,
)
};
Some(Autocorrection {
description: format!(
"Fix parameter mismatch for enum variant {}::{}",
enum_name, variant_name
),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.75,
details: Some(details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("enum_parameter_mismatch".to_string()),
})
}
fn name(&self) -> &'static str {
"EnumParameterMatchFixGenerator"
}
}
impl EnumParameterMatchFixGenerator {
fn is_enum_parameter_mismatch(&self, message: &str) -> bool {
message.contains("expected") && message.contains("parameters") && message.contains("found")
|| message.contains("this enum variant takes")
|| message.contains("expected struct") && message.contains("found enum")
|| message.contains("mismatched types") && message.contains("expected enum")
|| message.contains("wrong number of arguments") && message.contains("variant")
}
fn generate_context_aware_fix(
&self,
file_path: &str,
line: usize,
enum_name: &str,
variant_name: &str,
expected_params: &[String],
found_params: &[String],
context: &str,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
let variant_line_idx = lines
.iter()
.position(|&l| l.contains(variant_name) && (l.contains("(") || l.contains("{")));
if let Some(idx) = variant_line_idx {
let variant_line = lines[idx];
let new_line = if variant_line.contains("(") && variant_line.contains(")") {
self.fix_tuple_variant(variant_line, enum_name, variant_name, expected_params)
} else if variant_line.contains("{") && variant_line.contains("}") {
self.fix_struct_variant(variant_line, enum_name, variant_name, expected_params)
} else {
variant_line.to_string()
};
if new_line == variant_line {
return self.generate_simple_fix(
file_path,
line,
enum_name,
variant_name,
expected_params,
found_params,
);
}
let sed_command = format!(
"sed -i '{}s/{}/{}/' \"{}\"",
idx + 1, regex::escape(variant_line),
regex::escape(&new_line),
file_path
);
let explanation = format!(
"Fixed parameter mismatch for enum variant `{}::{}`. \
Expected {} parameters but found {}. \
Make sure to match the enum definition from its original module.",
enum_name,
variant_name,
expected_params.len(),
found_params.len()
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: idx + 1,
suggested_code_snippet: format!("// Change to:\n{}", new_line),
explanation,
};
let diff = format!("-{}\n+{}", variant_line, new_line);
return (details, vec![sed_command], diff);
}
self.generate_simple_fix(
file_path,
line,
enum_name,
variant_name,
expected_params,
found_params,
)
}
fn fix_tuple_variant(
&self,
line: &str,
_enum_name: &str,
_variant_name: &str,
expected_params: &[String],
) -> String {
let prefix_end = line.find('(').unwrap_or(line.len());
let suffix_start = line.rfind(')').unwrap_or(line.len());
let prefix = &line[..prefix_end + 1]; let suffix = &line[suffix_start..];
let param_placeholders: Vec<String> = expected_params
.iter()
.map(|param_type| generate_default_value(param_type))
.collect();
format!("{}{}{}", prefix, param_placeholders.join(", "), suffix)
}
fn fix_struct_variant(
&self,
line: &str,
_enum_name: &str,
_variant_name: &str,
expected_params: &[String],
) -> String {
let prefix_end = line.find('{').unwrap_or(line.len());
let suffix_start = line.rfind('}').unwrap_or(line.len());
let prefix = &line[..prefix_end + 1]; let suffix = &line[suffix_start..];
let field_placeholders: Vec<String> = expected_params
.iter()
.enumerate()
.map(|(i, param_type)| format!("field{}: {}", i, generate_default_value(param_type)))
.collect();
format!("{} {} {}", prefix, field_placeholders.join(", "), suffix)
}
fn generate_simple_fix(
&self,
file_path: &str,
line: usize,
enum_name: &str,
variant_name: &str,
expected_params: &[String],
found_params: &[String],
) -> (FixDetails, Vec<String>, String) {
let mut suggestions = Vec::new();
suggestions.push(format!(
"// For enum variant {}::{}",
enum_name, variant_name
));
if expected_params.is_empty() {
suggestions.push(format!("{}::{}", enum_name, variant_name));
} else if expected_params.len() == 1 {
suggestions.push(format!(
"{}::{}({})",
enum_name,
variant_name,
generate_default_value(&expected_params[0])
));
} else {
let params = expected_params
.iter()
.map(|p| generate_default_value(p))
.collect::<Vec<_>>()
.join(", ");
suggestions.push(format!("{}::{}({})", enum_name, variant_name, params));
let fields = expected_params
.iter()
.enumerate()
.map(|(i, p)| format!("field{}: {}", i, generate_default_value(p)))
.collect::<Vec<_>>()
.join(", ");
suggestions.push(format!("// Or using struct-style syntax:"));
suggestions.push(format!("{}::{}{{ {} }}", enum_name, variant_name, fields));
}
suggestions.push(format!("\n// Check the original enum definition:"));
suggestions.push(format!("enum {} {{", enum_name));
if expected_params.is_empty() {
suggestions.push(format!(" {},", variant_name));
} else if expected_params.len() == 1 {
suggestions.push(format!(" {}({}),", variant_name, expected_params[0]));
} else {
let params = expected_params.join(", ");
suggestions.push(format!(" {}({}),", variant_name, params));
}
suggestions.push(format!(" // other variants..."));
suggestions.push(format!("}}"));
let explanation = format!(
"The enum variant `{}::{}` is being used with the wrong number or types of parameters. \
Expected {} parameters ({}) but found {} parameters ({}). \
Make sure to match the enum definition from its original module.",
enum_name, variant_name,
expected_params.len(), expected_params.join(", "),
found_params.len(), found_params.join(", ")
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: suggestions.join("\n"),
explanation,
};
let find_enum_command = format!(
"grep -n \"enum {}\" --include=\"*.rs\" -r \"{}\"",
enum_name,
PathBuf::from(file_path)
.parent()
.unwrap_or(&PathBuf::from("."))
.display()
);
let diff = format!(
"// Original code with incorrect parameters\n-{}::{}({})\n\n// Corrected code\n+{}::{}({})",
enum_name, variant_name, found_params.join(", "),
enum_name, variant_name, expected_params.iter()
.map(|p| generate_default_value(p))
.collect::<Vec<_>>()
.join(", ")
);
(details, vec![find_enum_command], diff)
}
}
fn extract_enum_parameter_info(
message: &str,
) -> Option<(String, String, Vec<String>, Vec<String>)> {
let pattern1 =
Regex::new(r"expected (\d+) parameters?, found (\d+) in `([^:]+)::([^`]+)`").ok()?;
if let Some(captures) = pattern1.captures(message) {
let expected_count = captures.get(1)?.as_str().parse::<usize>().ok()?;
let found_count = captures.get(2)?.as_str().parse::<usize>().ok()?;
let enum_name = captures.get(3)?.as_str().to_string();
let variant_name = captures.get(4)?.as_str().to_string();
let expected_params = vec!["Type".to_string(); expected_count];
let found_params = vec!["Type".to_string(); found_count];
return Some((enum_name, variant_name, expected_params, found_params));
}
let pattern2 = Regex::new(
r"this enum variant takes (\d+) parameters? but (\d+) parameters? (?:was|were) supplied",
)
.ok()?;
if let Some(captures) = pattern2.captures(message) {
let expected_count = captures.get(1)?.as_str().parse::<usize>().ok()?;
let found_count = captures.get(2)?.as_str().parse::<usize>().ok()?;
let enum_variant_pattern = Regex::new(r"`([^:]+)::([^`]+)`").ok()?;
if let Some(name_captures) = enum_variant_pattern.captures(message) {
let enum_name = name_captures.get(1)?.as_str().to_string();
let variant_name = name_captures.get(2)?.as_str().to_string();
let expected_params = vec!["Type".to_string(); expected_count];
let found_params = vec!["Type".to_string(); found_count];
return Some((enum_name, variant_name, expected_params, found_params));
}
}
let pattern3 = Regex::new(r"mismatched types: expected enum `([^:]+)::([^(]+)\(([^)]*)\)`, found `[^:]+::[^(]+\(([^)]*)\)`").ok()?;
if let Some(captures) = pattern3.captures(message) {
let enum_name = captures.get(1)?.as_str().to_string();
let variant_name = captures.get(2)?.as_str().to_string();
let expected_params_str = captures.get(3)?.as_str();
let found_params_str = captures.get(4)?.as_str();
let expected_params = if expected_params_str.is_empty() {
Vec::new()
} else {
expected_params_str
.split(',')
.map(|s| s.trim().to_string())
.collect()
};
let found_params = if found_params_str.is_empty() {
Vec::new()
} else {
found_params_str
.split(',')
.map(|s| s.trim().to_string())
.collect()
};
return Some((enum_name, variant_name, expected_params, found_params));
}
if message.contains("enum") && message.contains("parameters") {
return Some((
"UnknownEnum".to_string(),
"UnknownVariant".to_string(),
vec!["ExpectedType".to_string()],
vec!["FoundType".to_string()],
));
}
None
}
pub struct StructParameterMatchFixGenerator;
impl StructParameterMatchFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for StructParameterMatchFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !self.is_struct_field_mismatch(message) {
return None;
}
let (struct_name, missing_fields, incorrect_fields) = extract_struct_field_info(message)?;
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(
&file_path,
line,
&struct_name,
&missing_fields,
&incorrect_fields,
context,
)
} else {
self.generate_simple_fix(
&file_path,
line,
&struct_name,
&missing_fields,
&incorrect_fields,
)
};
Some(Autocorrection {
description: format!("Fix field mismatch for struct `{}`", struct_name),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.75,
details: Some(details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("struct_field_mismatch".to_string()),
})
}
fn name(&self) -> &'static str {
"StructParameterMatchFixGenerator"
}
}
impl StructParameterMatchFixGenerator {
fn is_struct_field_mismatch(&self, message: &str) -> bool {
message.contains("missing field")
|| message.contains("unknown field")
|| message.contains("struct")
&& message.contains("field")
&& message.contains("missing")
|| message.contains("struct")
&& message.contains("field")
&& message.contains("expected")
|| message.contains("no field") && message.contains("on struct")
|| message.contains("this struct takes") && message.contains("fields")
|| message.contains("missing fields")
|| message.contains("mismatched types") && message.contains("expected struct")
}
fn generate_context_aware_fix(
&self,
file_path: &str,
line: usize,
struct_name: &str,
missing_fields: &[(String, String)],
incorrect_fields: &[(String, String, String)],
context: &str,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
let struct_line_idx = lines
.iter()
.position(|&l| l.contains(struct_name) && l.contains("{") && !l.contains("struct"));
if let Some(idx) = struct_line_idx {
let struct_line = lines[idx];
let mut new_lines = lines.clone();
let close_idx = lines
.iter()
.skip(idx)
.position(|&l| l.contains("}"))
.map(|pos| idx + pos);
if let Some(close_pos) = close_idx {
let current_fields: Vec<String> = lines[idx + 1..close_pos]
.iter()
.map(|l| l.trim().to_string())
.filter(|l| !l.is_empty() && !l.starts_with("//"))
.collect();
let mut fixed_fields = current_fields.clone();
for (field_name, field_type) in missing_fields {
let indent = lines[idx + 1]
.chars()
.take_while(|&c| c.is_whitespace())
.collect::<String>();
let field_line = format!(
"{}{}: {},",
indent,
field_name,
generate_default_value(field_type)
);
fixed_fields.push(field_line);
}
for (field_name, expected_type, _found_type) in incorrect_fields {
if let Some(field_idx) =
current_fields.iter().position(|l| l.contains(field_name))
{
let indent = lines[idx + 1]
.chars()
.take_while(|&c| c.is_whitespace())
.collect::<String>();
let field_line = format!(
"{}{}: {},",
indent,
field_name,
generate_default_value(expected_type)
);
fixed_fields[field_idx] = field_line;
}
}
let mut current_pos = close_pos;
for (i, field) in fixed_fields.iter().enumerate() {
if i < current_fields.len() {
new_lines[idx + 1 + i] = field;
} else {
new_lines.insert(current_pos, field);
current_pos += 1;
}
}
let new_content = new_lines.join("\n");
let sed_script = format!(
"sed -i '{},{}c\\{}' \"{}\"",
idx + 1,
current_pos + 1,
new_content.replace("\n", "\\n"),
file_path
);
let explanation = format!(
"Fixed field mismatch for struct `{}`. \
{} missing field(s) added and {} incorrect field(s) fixed. \
Make sure to match the struct definition from its original module.",
struct_name,
missing_fields.len(),
incorrect_fields.len()
);
let end_line = idx + fixed_fields.len() + 1;
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: idx + 1,
suggested_code_snippet: format!(
"// Fixed struct instantiation:\n{}",
new_lines[idx..=end_line].join("\n")
),
explanation,
};
let diff = format!(
"@@ struct instantiation @@\n{}\n...\n{}",
struct_line,
if !missing_fields.is_empty() {
missing_fields
.iter()
.map(|(name, typ)| {
format!("+ {}: {},", name, generate_default_value(typ))
})
.collect::<Vec<_>>()
.join("\n")
} else {
"// No changes needed".to_string()
}
);
return (details, vec![sed_script], diff);
}
}
self.generate_simple_fix(
file_path,
line,
struct_name,
missing_fields,
incorrect_fields,
)
}
fn generate_simple_fix(
&self,
file_path: &str,
line: usize,
struct_name: &str,
missing_fields: &[(String, String)],
incorrect_fields: &[(String, String, String)],
) -> (FixDetails, Vec<String>, String) {
let mut suggestions = Vec::new();
suggestions.push(format!("// For struct `{}`:", struct_name));
suggestions.push(format!("let instance = {} {{", struct_name));
if !missing_fields.is_empty() {
suggestions.push(format!(" // Missing fields that need to be added:"));
for (field_name, field_type) in missing_fields {
suggestions.push(format!(
" {}: {},",
field_name,
generate_default_value(field_type)
));
}
}
if !incorrect_fields.is_empty() {
suggestions.push(format!(
" // Fields with incorrect types that need to be fixed:"
));
for (field_name, expected_type, found_type) in incorrect_fields {
suggestions.push(format!(
" // Current: {}: {} (type: {})",
field_name, field_name, found_type
));
suggestions.push(format!(" // Should be:"));
suggestions.push(format!(
" {}: {},",
field_name,
generate_default_value(expected_type)
));
}
}
suggestions.push(format!(" // ... other fields"));
suggestions.push(format!("}}"));
suggestions.push(format!("\n// Check the original struct definition:"));
suggestions.push(format!("struct {} {{", struct_name));
for (field_name, field_type) in missing_fields {
suggestions.push(format!(" {}: {},", field_name, field_type));
}
for (field_name, expected_type, _) in incorrect_fields {
suggestions.push(format!(" {}: {},", field_name, expected_type));
}
suggestions.push(format!(" // ... other fields"));
suggestions.push(format!("}}"));
let explanation = format!(
"The struct `{}` is being used with missing or incorrect fields. \
{} field(s) are missing and {} field(s) have incorrect types. \
Make sure to match the struct definition from its original module.",
struct_name,
missing_fields.len(),
incorrect_fields.len()
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: suggestions.join("\n"),
explanation,
};
let find_struct_command = format!(
"grep -n \"struct {}\" --include=\"*.rs\" -r \"{}\"",
struct_name,
PathBuf::from(file_path)
.parent()
.unwrap_or(&PathBuf::from("."))
.display()
);
let diff = format!(
"// Original code with missing/incorrect fields\n-{} {{ ... }}\n\n// Corrected code\n+{} {{\n{}+}}",
struct_name, struct_name,
missing_fields.iter()
.map(|(name, typ)| format!("+ {}: {},\n", name, generate_default_value(typ)))
.collect::<Vec<_>>()
.join("")
);
(details, vec![find_struct_command], diff)
}
}
fn extract_struct_field_info(
message: &str,
) -> Option<(String, Vec<(String, String)>, Vec<(String, String, String)>)> {
let pattern1 = Regex::new(r"missing field `([^`]+)` in struct `([^`]+)`").ok()?;
if let Some(captures) = pattern1.captures(message) {
let field_name = captures.get(1)?.as_str().to_string();
let struct_name = captures.get(2)?.as_str().to_string();
let missing_fields = vec![(field_name, "Type".to_string())];
let incorrect_fields = Vec::new();
return Some((struct_name, missing_fields, incorrect_fields));
}
let pattern2 = Regex::new(r"unknown field `([^`]+)` in struct `([^`]+)`").ok()?;
if let Some(captures) = pattern2.captures(message) {
let field_name = captures.get(1)?.as_str().to_string();
let struct_name = captures.get(2)?.as_str().to_string();
let missing_fields = Vec::new();
let incorrect_fields = vec![(
field_name,
"ExpectedType".to_string(),
"FoundType".to_string(),
)];
return Some((struct_name, missing_fields, incorrect_fields));
}
let pattern3 = Regex::new(r"mismatched types: expected struct `([^`]+)`, found struct `[^`]+` with (\d+) missing field\(s\)").ok()?;
if let Some(captures) = pattern3.captures(message) {
let struct_name = captures.get(1)?.as_str().to_string();
let missing_count = captures.get(2)?.as_str().parse::<usize>().ok()?;
let missing_fields = (0..missing_count)
.map(|i| (format!("missing_field{}", i), "Type".to_string()))
.collect();
let incorrect_fields = Vec::new();
return Some((struct_name, missing_fields, incorrect_fields));
}
let pattern4 = Regex::new(r"no field `([^`]+)` on struct `([^`]+)`").ok()?;
if let Some(captures) = pattern4.captures(message) {
let field_name = captures.get(1)?.as_str().to_string();
let struct_name = captures.get(2)?.as_str().to_string();
let missing_fields = Vec::new();
let incorrect_fields = vec![(
field_name,
"ExpectedType".to_string(),
"FoundType".to_string(),
)];
return Some((struct_name, missing_fields, incorrect_fields));
}
let pattern5 = Regex::new(r"mismatched types: expected `([^`]+)`, found `([^`]+)` for field `([^`]+)` in struct `([^`]+)`").ok()?;
if let Some(captures) = pattern5.captures(message) {
let expected_type = captures.get(1)?.as_str().to_string();
let found_type = captures.get(2)?.as_str().to_string();
let field_name = captures.get(3)?.as_str().to_string();
let struct_name = captures.get(4)?.as_str().to_string();
let missing_fields = Vec::new();
let incorrect_fields = vec![(field_name, expected_type, found_type)];
return Some((struct_name, missing_fields, incorrect_fields));
}
if message.contains("struct") && message.contains("field") {
return Some((
"UnknownStruct".to_string(),
vec![("missing_field".to_string(), "Type".to_string())],
Vec::new(),
));
}
None
}
pub struct BorrowAfterMoveFixGenerator;
pub struct CrossModuleAutomationEngine {
syntax_generator: SyntaxGenerator,
template_registry: TemplateRegistry,
circuit_breaker: Arc<CircuitBreaker>,
error_reporter: ErrorReporter,
}
impl CrossModuleAutomationEngine {
pub fn new() -> Self {
let circuit_breaker_config = CircuitBreakerConfig {
failure_threshold: 3,
reset_timeout: Duration::from_secs(30),
..Default::default()
};
Self {
syntax_generator: SyntaxGenerator::new(),
template_registry: TemplateRegistry::new(),
circuit_breaker: CircuitBreaker::new("automation_engine", circuit_breaker_config),
error_reporter: ErrorReporter::new(),
}
}
pub fn generate_ast_driven_fix(
&self,
error: &DecrustError,
source_code: &str,
file_path: &str,
) -> Option<Autocorrection> {
let error_string = error.to_string();
let source_code_string = source_code.to_string();
let file_path_string = file_path.to_string();
let import_suggestion = self
.syntax_generator
.generate_import("std::collections", &["HashMap"]);
let result = self.circuit_breaker.execute(move || {
let _import_suggestion = import_suggestion;
let default_template = super::syntax::FixTemplate::new(
"ast_driven_fix",
"AST-driven context-aware fix",
format!(
"// AST-analyzed fix for: {}\n// Fixed: {}",
error_string, source_code_string
),
);
let mut params = HashMap::new();
params.insert("error".to_string(), error_string.clone());
params.insert("source_code".to_string(), source_code_string.clone());
params.insert("file_path".to_string(), file_path_string.clone());
let generated_fix = default_template.apply(¶ms);
Ok(Autocorrection {
description: format!("AST-driven fix for {}", error_string),
fix_type: FixType::TextReplacement,
confidence: 0.95, details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path_string.clone()),
line_hint: 1, suggested_code_snippet: generated_fix.clone(),
explanation: format!(
"AST-driven analysis suggests: {}",
default_template.description
),
}),
diff_suggestion: Some(format!("- {}\n+ {}", source_code_string, generated_fix)),
commands_to_apply: vec![], targets_error_code: Some(error_string),
})
});
result.ok()
}
pub fn generate_auto_diff_preview(
&self,
_error: &DecrustError,
proposed_fix: &str,
original_code: &str,
) -> String {
let _config = ErrorReportConfig::default();
let diff_preview = format!(
"--- Original Code\n{}\n+++ Proposed Fix\n{}\n\nDiff:\n- {}\n+ {}",
original_code,
proposed_fix,
original_code.lines().collect::<Vec<_>>().join("\n- "),
proposed_fix.lines().collect::<Vec<_>>().join("\n+ ")
);
diff_preview
}
pub fn apply_heuristic_recovery(
&self,
error: &DecrustError,
context: &str,
confidence_threshold: f64,
) -> Option<Autocorrection> {
let metrics = self.circuit_breaker.metrics();
let adjusted_confidence = if metrics.total_requests > 0 {
let success_rate = metrics.successful_requests as f64 / metrics.total_requests as f64;
confidence_threshold * success_rate
} else {
confidence_threshold
};
if adjusted_confidence >= 0.8 {
let _syntax_analysis = self
.syntax_generator
.generate_import("std::error", &["Error"]);
let patterns = self.extract_error_patterns_from_context(context);
if !patterns.is_empty() {
if let Some(template) = self.find_similar_pattern_in_registry(&patterns) {
return Some(Autocorrection {
description: format!("Heuristic-driven fix based on learned patterns"),
fix_type: FixType::TextReplacement,
confidence: adjusted_confidence,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from("unknown"),
line_hint: 1,
suggested_code_snippet: template.template.clone(),
explanation: format!(
"Applied learned pattern with {}% confidence",
(adjusted_confidence * 100.0) as u32
),
}),
diff_suggestion: Some(format!("- {}\n+ {}", context, template.template)),
commands_to_apply: vec![], targets_error_code: Some(error.to_string()),
});
}
}
}
None
}
fn extract_error_patterns_from_context(&self, context: &str) -> Vec<String> {
let mut patterns = Vec::new();
let config = ErrorReportConfig::default();
let _formatted_context = self.error_reporter.report_to_string_with_syntax(
&std::io::Error::other("Pattern analysis"),
&config,
Some(context),
);
let lines: Vec<&str> = context.lines().collect();
for line in lines {
if line.contains("error[E") {
patterns.push(line.trim().to_string());
}
if line.contains("cannot borrow") {
patterns.push("borrow_checker_error".to_string());
}
if line.contains("use of moved value") {
patterns.push("move_error".to_string());
}
if line.contains("mismatched types") {
patterns.push("type_mismatch".to_string());
}
if line.contains("unused") {
patterns.push("unused_code".to_string());
}
}
patterns
}
fn find_similar_pattern_in_registry(
&self,
patterns: &[String],
) -> Option<&super::syntax::FixTemplate> {
for pattern in patterns {
if pattern.contains("borrow_checker") {
if let Some(template) = self.template_registry.get_template("borrow_fix") {
return Some(template);
}
}
if pattern.contains("move_error") {
if let Some(template) = self.template_registry.get_template("move_fix") {
return Some(template);
}
}
if pattern.contains("type_mismatch") {
if let Some(template) = self.template_registry.get_template("type_fix") {
return Some(template);
}
}
if pattern.contains("unused") {
if let Some(template) = self.template_registry.get_template("unused_fix") {
return Some(template);
}
}
}
self.template_registry.get_template("generic_fix")
}
}
impl BorrowAfterMoveFixGenerator {
pub fn new() -> Self {
Self
}
fn can_automate_fix(&self, variable_name: &str, source_code_context: Option<&str>) -> bool {
let copy_types = [
"i32", "i64", "u32", "u64", "usize", "isize", "f32", "f64", "bool", "char",
];
if let Some(context) = source_code_context {
for copy_type in ©_types {
if context.contains(&format!("{}: {}", variable_name, copy_type))
|| context.contains(&format!("let {}: {}", variable_name, copy_type))
{
return true;
}
}
if context.contains(&format!("{} = \"", variable_name))
|| context.contains(&format!("let {} = \"", variable_name))
{
return true;
}
}
false
}
fn generate_automatic_fix_commands(
&self,
variable_name: &str,
file_path: &str,
line: usize,
) -> Vec<String> {
vec![format!(
"sed -i '{}s/\\b{}\\b/{}.clone()/g' \"{}\"",
line, variable_name, variable_name, file_path
)]
}
}
impl FixGenerator for BorrowAfterMoveFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !message.contains("value used here after move") && !message.contains("moved") {
return None;
}
let variable_name = extract_variable_from_move_error(message)?;
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (fix_type, commands, confidence) =
if self.can_automate_fix(&variable_name, source_code_context) {
let auto_commands =
self.generate_automatic_fix_commands(&variable_name, &file_path, line);
(FixType::TextReplacement, auto_commands, 0.85)
} else {
(FixType::ManualInterventionRequired, vec![], 0.75)
};
let suggestions = vec![
format!(
"// 1. Use a reference instead to avoid moving: &{}",
variable_name
),
format!(
"// 2. Clone the value before moving: {}.clone()",
variable_name
),
format!("// 3. Implement Copy trait for the type if it's a small value type"),
format!(
"// 4. Restructure code to avoid using {} after it's moved",
variable_name
),
];
let explanation = format!(
"Value `{}` was moved when it was used in a previous operation. In Rust, once a value is moved, \
the original variable can no longer be used unless the type implements Copy. \
Consider one of the following solutions:\n{}",
variable_name, suggestions.join("\n")
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(&file_path),
line_hint: line,
suggested_code_snippet: suggestions.join("\n"),
explanation,
};
Some(Autocorrection {
description: format!("Fix use of moved value `{}`", variable_name),
fix_type,
confidence,
details: Some(details),
diff_suggestion: None, commands_to_apply: commands,
targets_error_code: Some("use_after_move".to_string()),
})
}
fn name(&self) -> &'static str {
"BorrowAfterMoveFixGenerator"
}
}
fn extract_variable_from_move_error(message: &str) -> Option<String> {
let patterns = [
r"value used here after move: `([^`]+)`",
r"value moved here: `([^`]+)`",
r"use of moved value: `([^`]+)`",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(m) = captures.get(1) {
return Some(m.as_str().to_string());
}
}
}
}
None
}
pub struct MissingTraitImplFixGenerator;
impl MissingTraitImplFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for MissingTraitImplFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
_source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let message = params.values.get("message")?;
if !message.contains("not implement") || !message.contains("trait") {
return None;
}
let (type_name, trait_name) = extract_type_and_trait(message)?;
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let suggestions = self.generate_trait_implementation_suggestions(&type_name, &trait_name);
let explanation = format!(
"Type `{}` does not implement the required trait `{}`. \
You need to implement this trait for your type or use a type that already implements it.",
type_name, trait_name
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(&file_path),
line_hint: line,
suggested_code_snippet: suggestions.join("\n"),
explanation,
};
Some(Autocorrection {
description: format!(
"Add implementation of trait `{}` for type `{}`",
trait_name, type_name
),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.7,
details: Some(details),
diff_suggestion: None,
commands_to_apply: vec![],
targets_error_code: Some("missing_trait_impl".to_string()),
})
}
fn name(&self) -> &'static str {
"MissingTraitImplFixGenerator"
}
}
impl MissingTraitImplFixGenerator {
fn generate_trait_implementation_suggestions(
&self,
type_name: &str,
trait_name: &str,
) -> Vec<String> {
let mut suggestions = Vec::new();
suggestions.push(format!("// Implement the trait for your type:"));
suggestions.push(format!("impl {} for {} {{", trait_name, type_name));
match trait_name {
"std::fmt::Display" | "Display" => {
suggestions.push(
" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {"
.to_string(),
);
suggestions.push(
" write!(f, \"{}\" /* Add format string */, /* Add fields */))"
.to_string(),
);
suggestions.push(" }".to_string());
}
"std::fmt::Debug" | "Debug" => {
suggestions.push(
" // Consider using #[derive(Debug)] instead of manual implementation"
.to_string(),
);
suggestions.push(
" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {"
.to_string(),
);
suggestions.push(" f.debug_struct(\"TypeName\")".to_string());
suggestions
.push(" // .field(\"field_name\", &self.field_name)".to_string());
suggestions.push(" .finish()".to_string());
suggestions.push(" }".to_string());
}
"Clone" => {
suggestions.push(
" // Consider using #[derive(Clone)] instead of manual implementation"
.to_string(),
);
suggestions.push(" fn clone(&self) -> Self {".to_string());
suggestions.push(" Self {".to_string());
suggestions.push(" // field: self.field.clone(),".to_string());
suggestions.push(" }".to_string());
suggestions.push(" }".to_string());
}
"Copy" => {
suggestions
.push(" // Copy trait requires no method implementations".to_string());
suggestions.push(" // All fields must also implement Copy".to_string());
suggestions.push(" // Consider using #[derive(Copy, Clone)]".to_string());
}
"PartialEq" => {
suggestions.push(
" // Consider using #[derive(PartialEq)] instead of manual implementation"
.to_string(),
);
suggestions.push(" fn eq(&self, other: &Self) -> bool {".to_string());
suggestions.push(" // self.field == other.field".to_string());
suggestions.push(" true // Replace with actual equality check".to_string());
suggestions.push(" }".to_string());
}
"Iterator" => {
suggestions
.push(" type Item = /* Type of items yielded by iterator */;".to_string());
suggestions.push(" fn next(&mut self) -> Option<Self::Item> {".to_string());
suggestions.push(" // Implement iteration logic".to_string());
suggestions.push(" None // Replace with actual implementation".to_string());
suggestions.push(" }".to_string());
}
"Default" => {
suggestions.push(
" // Consider using #[derive(Default)] if all fields implement Default"
.to_string(),
);
suggestions.push(" fn default() -> Self {".to_string());
suggestions.push(" Self {".to_string());
suggestions.push(" // field: Default::default(),".to_string());
suggestions.push(" }".to_string());
suggestions.push(" }".to_string());
}
_ => {
suggestions
.push(" // Implement the required methods for this trait".to_string());
suggestions.push(" // Refer to the documentation for this trait".to_string());
}
}
suggestions.push("}".to_string());
suggestions.push("".to_string());
suggestions.push("// Alternative approaches:".to_string());
if [
"Debug",
"Clone",
"Copy",
"PartialEq",
"Eq",
"PartialOrd",
"Ord",
"Hash",
"Default",
]
.contains(&trait_name)
{
suggestions.push(format!(
"// 1. Add #[derive({})] to your type definition",
trait_name
));
}
suggestions.push(format!(
"// 2. Use a type that already implements {} instead",
trait_name
));
suggestions.push(format!("// 3. Use a trait bound in your generic function"));
suggestions
}
}
fn extract_type_and_trait(message: &str) -> Option<(String, String)> {
let patterns = [
r"the trait `([^`]+)` is not implemented for `([^`]+)`",
r"type `([^`]+)` does not implement `([^`]+)`",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if captures.len() >= 3 {
let trait_name = captures.get(1)?.as_str().to_string();
let type_name = captures.get(2)?.as_str().to_string();
return Some((type_name, trait_name));
}
}
}
}
let alt_pattern = r"`([^`]+)` doesn't implement `([^`]+)`";
if let Ok(regex) = Regex::new(alt_pattern) {
if let Some(captures) = regex.captures(message) {
if captures.len() >= 3 {
let type_name = captures.get(1)?.as_str().to_string();
let trait_name = captures.get(2)?.as_str().to_string();
return Some((type_name, trait_name));
}
}
}
None
}
fn extract_variable_from_borrow_error(message: &str) -> Option<String> {
let patterns = [
r"cannot borrow `([^`]+)` as mutable",
r"cannot borrow \*?([a-zA-Z0-9_]+) as mutable",
];
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(m) = captures.get(1) {
return Some(m.as_str().to_string());
}
}
}
}
None
}
fn extract_type(message: &str, prefix: &str) -> Option<String> {
let patterns = [
format!(r"{} type `([^`]+)`", prefix), format!(r"{} `([^`]+)`", prefix), format!(r"{} ([a-zA-Z0-9_::<>]+)", prefix), format!(r"mismatched types: {} `([^`]+)`", prefix), ];
for pattern in patterns {
if let Ok(regex) = Regex::new(&pattern) {
if let Some(captures) = regex.captures(message) {
if let Some(m) = captures.get(1) {
return Some(m.as_str().to_string());
}
}
}
}
if message.contains("mismatched types") && message.contains(prefix) {
if prefix == "expected" && message.contains("String") {
return Some("String".to_string());
} else if prefix == "found" && message.contains("i32") {
return Some("i32".to_string());
} else if prefix == "expected" && message.contains("&str") {
return Some("&str".to_string());
} else if prefix == "found" && message.contains("String") {
return Some("String".to_string());
}
}
None
}
pub struct UnusedVariableFixGenerator;
impl UnusedVariableFixGenerator {
pub fn new() -> Self {
Self
}
}
impl FixGenerator for UnusedVariableFixGenerator {
fn generate_fix(
&self,
_error: &DecrustError,
params: &ExtractedParameters,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
let variable_name = params
.values
.get("param1")
.cloned()
.unwrap_or_else(|| "unknown_variable".to_string());
let description = format!(
"Add underscore prefix to unused variable: `{}`",
variable_name
);
let file_path = params
.values
.get("file_path")
.cloned()
.unwrap_or_else(|| "unknown_file.rs".to_string());
let line = params
.values
.get("line")
.and_then(|l| l.parse::<usize>().ok())
.unwrap_or(1);
let (fix_details, commands, diff) = if let Some(context) = source_code_context {
self.generate_context_aware_fix(&variable_name, &file_path, line, context)
} else {
self.generate_simple_fix(&variable_name, &file_path, line)
};
Some(Autocorrection {
description,
fix_type: FixType::TextReplacement,
confidence: params.confidence,
details: Some(fix_details),
diff_suggestion: Some(diff),
commands_to_apply: commands,
targets_error_code: Some("unused_variables".to_string()),
})
}
fn name(&self) -> &'static str {
"UnusedVariableFixGenerator"
}
}
impl UnusedVariableFixGenerator {
fn generate_context_aware_fix(
&self,
variable_name: &str,
file_path: &str,
line: usize,
context: &str,
) -> (FixDetails, Vec<String>, String) {
let lines: Vec<&str> = context.lines().collect();
let var_line = lines
.iter()
.find(|&&l| {
l.contains(&format!(" {} ", variable_name)) ||
l.contains(&format!(" {}", variable_name)) ||
l.contains(&format!("({}", variable_name)) ||
l.contains(&format!("({} ", variable_name)) ||
l.contains(&format!(" {}: ", variable_name)) ||
l.contains(&format!("({}: ", variable_name)) ||
l.contains(&format!("Ok({}", variable_name)) ||
l.contains(&format!("Ok({} ", variable_name)) ||
l.contains(&format!("Err({}", variable_name)) ||
l.contains(&format!("Err({} ", variable_name)) ||
l.contains(&format!("Some({}", variable_name)) ||
l.contains(&format!("Some({} ", variable_name)) ||
l.contains(&format!("None({}", variable_name)) ||
l.contains(&format!("None({} ", variable_name))
})
.map(|&l| l.trim())
.unwrap_or("");
if var_line.is_empty() {
return self.generate_simple_fix(variable_name, file_path, line);
}
let var_regex = Regex::new(&format!(r"\b{}\b", regex::escape(variable_name))).unwrap();
let new_line = var_regex
.replace(var_line, &format!("_{}", variable_name))
.to_string();
let sed_command = format!(
"sed -i '{}s/{}/{}/' \"{}\"",
line,
regex::escape(var_line),
regex::escape(&new_line),
file_path
);
let explanation = format!(
"Adding underscore prefix to unused variable '{}'. \
This indicates to the compiler that the variable is intentionally unused.",
variable_name
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: format!("// Replace with:\n{}", new_line),
explanation,
};
let diff = format!("-{}\n+{}", var_line, new_line);
(details, vec![sed_command], diff)
}
fn generate_simple_fix(
&self,
variable_name: &str,
file_path: &str,
line: usize,
) -> (FixDetails, Vec<String>, String) {
let sed_command = format!(
"sed -i '{}s/\\b{}\\b/_{}/g' \"{}\"",
line,
regex::escape(variable_name),
regex::escape(variable_name),
file_path
);
let explanation = format!(
"Adding underscore prefix to unused variable '{}'. \
This indicates to the compiler that the variable is intentionally unused.",
variable_name
);
let details = FixDetails::SuggestCodeChange {
file_path: PathBuf::from(file_path),
line_hint: line,
suggested_code_snippet: format!(
"// Replace '{}' with '_{}'",
variable_name, variable_name
),
explanation,
};
let diff = format!("-... {} ...\n+... _{} ...", variable_name, variable_name);
(details, vec![sed_command], diff)
}
}
#[derive(Debug, Clone)]
pub struct DependencyAnalysisResult {
pub crate_name: String,
pub current_version: String,
pub enabled_features: Vec<String>,
pub used_features: Vec<String>,
pub unused_features: Vec<String>,
pub missing_features: Vec<String>,
pub version_status: VersionCompatibility,
pub suggestions: Vec<String>,
pub usage_analysis: CrateUsageAnalysis,
pub interactive_recommendations: Vec<InteractiveRecommendation>,
}
#[derive(Debug, Clone)]
pub struct CrateUsageAnalysis {
pub functions_used: Vec<String>,
pub macros_used: Vec<String>,
pub types_used: Vec<String>,
pub traits_used: Vec<String>,
pub derive_macros_used: Vec<String>,
pub attribute_macros_used: Vec<String>,
pub modules_accessed: Vec<String>,
pub constants_used: Vec<String>,
pub usage_frequency: std::collections::HashMap<String, usize>,
}
#[derive(Debug, Clone)]
pub struct InteractiveRecommendation {
pub recommendation_type: RecommendationType,
pub current_config: String,
pub recommended_config: String,
pub explanation: String,
pub estimated_impact: OptimizationImpact,
pub confidence: f32,
pub auto_applicable: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RecommendationType {
RemoveUnusedFeatures,
AddMissingFeatures,
UpdateVersion,
ReplaceWithAlternative {
alternative_crate: String,
},
SplitFeatures,
ConsolidateFeatures,
RemoveDependency,
}
#[derive(Debug, Clone)]
pub struct OptimizationImpact {
pub binary_size_reduction: Option<usize>,
pub compile_time_improvement: Option<f32>,
pub transitive_deps_removed: usize,
pub security_improvement: SecurityImpact,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SecurityImpact {
High,
Medium,
Low,
None,
}
#[derive(Debug, Clone, PartialEq)]
pub enum VersionCompatibility {
Compatible,
Outdated {
latest_version: String,
},
Incompatible {
reason: String,
},
Unknown,
}
#[derive(Debug, Clone)]
pub struct DependencyAnalyzer {
analysis_cache: std::collections::HashMap<String, DependencyAnalysisResult>,
#[allow(dead_code)]
verbose_mode: bool,
check_latest_versions: bool,
}
impl Default for DependencyAnalyzer {
fn default() -> Self {
Self::new()
}
}
impl DependencyAnalyzer {
pub fn new() -> Self {
Self {
analysis_cache: std::collections::HashMap::new(),
verbose_mode: false,
check_latest_versions: false,
}
}
pub fn with_config(verbose: bool, check_latest: bool) -> Self {
Self {
analysis_cache: std::collections::HashMap::new(),
verbose_mode: verbose,
check_latest_versions: check_latest,
}
}
pub fn analyze_code_dependencies(&mut self, code: &str) -> Vec<DependencyAnalysisResult> {
let mut results = Vec::new();
let used_crates = self.extract_crate_usage(code);
for crate_name in used_crates {
if let Some(cached_result) = self.analysis_cache.get(&crate_name) {
results.push(cached_result.clone());
} else {
let analysis = self.analyze_single_crate(&crate_name, code);
self.analysis_cache
.insert(crate_name.clone(), analysis.clone());
results.push(analysis);
}
}
results
}
fn extract_crate_usage(&self, code: &str) -> Vec<String> {
let mut crates = std::collections::HashSet::new();
if let Ok(regex) = regex::Regex::new(r"use\s+([a-zA-Z_][a-zA-Z0-9_]*)::\w+") {
for cap in regex.captures_iter(code) {
if let Some(crate_name) = cap.get(1) {
let name = crate_name.as_str();
if !is_std_crate(name) {
let actual_crate = map_module_to_crate(name).unwrap_or(name);
crates.insert(actual_crate.to_string());
}
}
}
}
if let Ok(regex) = regex::Regex::new(r"([a-zA-Z_][a-zA-Z0-9_]*)::\w+") {
for cap in regex.captures_iter(code) {
if let Some(crate_name) = cap.get(1) {
let name = crate_name.as_str();
if !is_std_crate(name) && !is_keyword(name) {
let actual_crate = map_module_to_crate(name).unwrap_or(name);
crates.insert(actual_crate.to_string());
}
}
}
}
if let Ok(regex) = regex::Regex::new(r"#\[derive\([^)]*([A-Z][a-zA-Z0-9_]*)[^)]*\)\]") {
for cap in regex.captures_iter(code) {
if let Some(derive_name) = cap.get(1) {
let crate_name = map_derive_to_crate(derive_name.as_str());
if let Some(name) = crate_name {
crates.insert(name.to_string());
}
}
}
}
if code.contains("serde_json::")
|| code.contains("to_string(")
|| code.contains("from_str(")
{
crates.insert("serde_json".to_string());
}
if code.contains("#[tokio::main]") {
crates.insert("tokio".to_string());
}
crates.into_iter().collect()
}
fn analyze_single_crate(&self, crate_name: &str, code: &str) -> DependencyAnalysisResult {
let (current_version, enabled_features) = self.read_cargo_dependency(crate_name);
let used_features = self.detect_used_features(crate_name, code);
let unused_features: Vec<String> = enabled_features
.iter()
.filter(|f| !used_features.contains(f))
.cloned()
.collect();
let missing_features: Vec<String> = used_features
.iter()
.filter(|f| !enabled_features.contains(f))
.cloned()
.collect();
let version_status = if self.check_latest_versions {
self.check_version_compatibility(crate_name, ¤t_version)
} else {
VersionCompatibility::Unknown
};
let suggestions =
self.generate_suggestions(&unused_features, &missing_features, &version_status);
let usage_analysis = self.analyze_crate_usage(crate_name, code);
let interactive_recommendations = self.generate_interactive_recommendations(
crate_name,
¤t_version,
&enabled_features,
&used_features,
&unused_features,
&missing_features,
&usage_analysis,
&version_status,
);
DependencyAnalysisResult {
crate_name: crate_name.to_string(),
current_version,
enabled_features,
used_features,
unused_features,
missing_features,
version_status,
suggestions,
usage_analysis,
interactive_recommendations,
}
}
fn read_cargo_dependency(&self, crate_name: &str) -> (String, Vec<String>) {
if let Ok(workspace_content) = std::fs::read_to_string("Cargo.toml") {
if let Some((version, features)) =
self.parse_workspace_dependency(&workspace_content, crate_name)
{
return (version, features);
}
}
if let Ok(cargo_content) = std::fs::read_to_string("Cargo.toml") {
let (version, features) = self.parse_cargo_dependency(&cargo_content, crate_name);
if version != "unknown" {
return (version, features);
}
}
for possible_path in &["../Cargo.toml", "../../Cargo.toml", "../../../Cargo.toml"] {
if let Ok(parent_content) = std::fs::read_to_string(possible_path) {
if let Some((version, features)) =
self.parse_workspace_dependency(&parent_content, crate_name)
{
return (version, features);
}
}
}
("unknown".to_string(), vec!["default".to_string()])
}
fn parse_cargo_dependency(
&self,
cargo_content: &str,
crate_name: &str,
) -> (String, Vec<String>) {
let mut version = "unknown".to_string();
let mut features = Vec::new();
let dep_pattern = format!(r#"{}\s*=\s*"([^"]+)""#, regex::escape(crate_name));
if let Ok(regex) = regex::Regex::new(&dep_pattern) {
if let Some(cap) = regex.captures(cargo_content) {
if let Some(version_match) = cap.get(1) {
version = version_match.as_str().to_string();
}
}
}
let features_pattern = format!(
r#"{}\s*=\s*\{{[^}}]*features\s*=\s*\[([^\]]*)\]"#,
regex::escape(crate_name)
);
if let Ok(regex) = regex::Regex::new(&features_pattern) {
if let Some(cap) = regex.captures(cargo_content) {
if let Some(features_match) = cap.get(1) {
features = features_match
.as_str()
.split(',')
.map(|f| f.trim().trim_matches('"').to_string())
.filter(|f| !f.is_empty())
.collect();
}
}
}
if features.is_empty() {
features.push("default".to_string());
}
(version, features)
}
fn parse_workspace_dependency(
&self,
cargo_content: &str,
crate_name: &str,
) -> Option<(String, Vec<String>)> {
let workspace_deps_start = cargo_content.find("[workspace.dependencies]")?;
let workspace_deps_section = &cargo_content[workspace_deps_start..];
let section_end = workspace_deps_section
.find("\n[")
.unwrap_or(workspace_deps_section.len());
let workspace_deps_content = &workspace_deps_section[..section_end];
let dep_pattern = format!(r#"{}\s*=\s*"([^"]+)""#, regex::escape(crate_name));
if let Ok(simple_regex) = regex::Regex::new(&dep_pattern) {
if let Some(cap) = simple_regex.captures(workspace_deps_content) {
if let Some(version_match) = cap.get(1) {
let version = version_match.as_str().to_string();
return Some((version, vec!["default".to_string()]));
}
}
}
let complex_pattern = format!(
r#"{}\s*=\s*\{{\s*([^}}]+)\s*\}}"#,
regex::escape(crate_name)
);
if let Ok(complex_regex) = regex::Regex::new(&complex_pattern) {
if let Some(cap) = complex_regex.captures(workspace_deps_content) {
if let Some(config_match) = cap.get(1) {
let config_str = config_match.as_str();
let mut version = "unknown".to_string();
if let Ok(version_regex) = regex::Regex::new(r#"version\s*=\s*"([^"]+)""#) {
if let Some(version_cap) = version_regex.captures(config_str) {
if let Some(version_match) = version_cap.get(1) {
version = version_match.as_str().to_string();
}
}
}
let mut features = Vec::new();
if let Ok(features_regex) = regex::Regex::new(r#"features\s*=\s*\[([^\]]*)\]"#)
{
if let Some(features_cap) = features_regex.captures(config_str) {
if let Some(features_match) = features_cap.get(1) {
features = features_match
.as_str()
.split(',')
.map(|f| f.trim().trim_matches('"').to_string())
.filter(|f| !f.is_empty())
.collect();
}
}
}
if features.is_empty() {
features.push("default".to_string());
}
if version != "unknown" {
return Some((version, features));
}
}
}
}
None
}
fn detect_used_features(&self, crate_name: &str, code: &str) -> Vec<String> {
let mut used_features = Vec::new();
match crate_name {
"serde" => {
if code.contains("serde::Serialize")
|| code.contains("serde::Deserialize")
|| code.contains("#[derive(Serialize")
|| code.contains("#[derive(Deserialize")
{
used_features.push("derive".to_string());
}
}
"tokio" => {
if code.contains("tokio::main") || code.contains("#[tokio::main]") {
used_features.push("macros".to_string());
}
if let Ok(fs_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::fs(?:\s|;|::)") {
if fs_regex.is_match(code) {
used_features.push("fs".to_string());
}
}
if let Ok(net_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::net(?:\s|;|::)") {
if net_regex.is_match(code)
|| code.contains("TcpListener")
|| code.contains("TcpStream")
{
used_features.push("net".to_string());
}
}
if let Ok(time_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::time(?:\s|;|::)") {
if time_regex.is_match(code) {
used_features.push("time".to_string());
}
}
if let Ok(sync_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::sync(?:\s|;|::)") {
if sync_regex.is_match(code) {
used_features.push("sync".to_string());
}
}
}
"regex" => {
used_features.push("default".to_string());
}
"chrono" => {
if code.contains("chrono::serde") || code.contains("serde") {
used_features.push("serde".to_string());
}
}
_ => {
used_features.push("default".to_string());
}
}
used_features.sort();
used_features.dedup();
used_features
}
fn check_version_compatibility(
&self,
_crate_name: &str,
_current_version: &str,
) -> VersionCompatibility {
VersionCompatibility::Unknown
}
fn generate_suggestions(
&self,
unused_features: &[String],
missing_features: &[String],
version_status: &VersionCompatibility,
) -> Vec<String> {
let mut suggestions = Vec::new();
if !unused_features.is_empty() {
suggestions.push(format!(
"Consider removing unused features: {}",
unused_features.join(", ")
));
}
if !missing_features.is_empty() {
suggestions.push(format!(
"Consider adding missing features: {}",
missing_features.join(", ")
));
}
match version_status {
VersionCompatibility::Outdated { latest_version } => {
suggestions.push(format!(
"Consider updating to latest version: {}",
latest_version
));
}
VersionCompatibility::Incompatible { reason } => {
suggestions.push(format!("Version compatibility issue: {}", reason));
}
_ => {}
}
suggestions
}
fn analyze_crate_usage(&self, crate_name: &str, code: &str) -> CrateUsageAnalysis {
let mut functions_used = Vec::new();
let mut macros_used = Vec::new();
let mut types_used = Vec::new();
let mut traits_used = Vec::new();
let mut derive_macros_used = Vec::new();
let mut attribute_macros_used = Vec::new();
let mut modules_accessed = Vec::new();
let constants_used = Vec::new();
let mut usage_frequency = std::collections::HashMap::new();
match crate_name {
"serde" => {
self.analyze_serde_usage(
code,
&mut functions_used,
&mut macros_used,
&mut types_used,
&mut traits_used,
&mut derive_macros_used,
&mut usage_frequency,
);
}
"tokio" => {
self.analyze_tokio_usage(
code,
&mut functions_used,
&mut macros_used,
&mut types_used,
&mut modules_accessed,
&mut attribute_macros_used,
&mut usage_frequency,
);
}
"regex" => {
self.analyze_regex_usage(
code,
&mut functions_used,
&mut types_used,
&mut usage_frequency,
);
}
"chrono" => {
self.analyze_chrono_usage(
code,
&mut functions_used,
&mut types_used,
&mut usage_frequency,
);
}
"serde_json" => {
self.analyze_serde_json_usage(code, &mut functions_used, &mut usage_frequency);
}
_ => {
self.analyze_generic_usage(
crate_name,
code,
&mut functions_used,
&mut types_used,
&mut usage_frequency,
);
}
}
CrateUsageAnalysis {
functions_used,
macros_used,
types_used,
traits_used,
derive_macros_used,
attribute_macros_used,
modules_accessed,
constants_used,
usage_frequency,
}
}
fn generate_interactive_recommendations(
&self,
crate_name: &str,
current_version: &str,
enabled_features: &[String],
_used_features: &[String],
unused_features: &[String],
missing_features: &[String],
usage_analysis: &CrateUsageAnalysis,
version_status: &VersionCompatibility,
) -> Vec<InteractiveRecommendation> {
let mut recommendations = Vec::new();
if !unused_features.is_empty() {
let current_config = format!("features = {:?}", enabled_features);
let recommended_features: Vec<String> = enabled_features
.iter()
.filter(|f| !unused_features.contains(f))
.cloned()
.collect();
let recommended_config = format!("features = {:?}", recommended_features);
let estimated_impact =
self.estimate_feature_removal_impact(crate_name, unused_features);
recommendations.push(InteractiveRecommendation {
recommendation_type: RecommendationType::RemoveUnusedFeatures,
current_config,
recommended_config,
explanation: format!(
"Remove {} unused feature(s): {}. This will reduce binary size and compilation time.",
unused_features.len(),
unused_features.join(", ")
),
estimated_impact,
confidence: 0.9,
auto_applicable: true,
});
}
if !missing_features.is_empty() {
let current_config = format!("features = {:?}", enabled_features);
let mut recommended_features = enabled_features.to_vec();
recommended_features.extend(missing_features.iter().cloned());
let recommended_config = format!("features = {:?}", recommended_features);
recommendations.push(InteractiveRecommendation {
recommendation_type: RecommendationType::AddMissingFeatures,
current_config,
recommended_config,
explanation: format!(
"Add {} missing feature(s): {}. This will enable functionality you're already using.",
missing_features.len(),
missing_features.join(", ")
),
estimated_impact: OptimizationImpact {
binary_size_reduction: None,
compile_time_improvement: None,
transitive_deps_removed: 0,
security_improvement: SecurityImpact::None,
},
confidence: 0.95,
auto_applicable: false, });
}
if let VersionCompatibility::Outdated { latest_version } = version_status {
recommendations.push(InteractiveRecommendation {
recommendation_type: RecommendationType::UpdateVersion,
current_config: format!("version = \"{}\"", current_version),
recommended_config: format!("version = \"{}\"", latest_version),
explanation: format!(
"Update from {} to {} for bug fixes, performance improvements, and new features.",
current_version, latest_version
),
estimated_impact: OptimizationImpact {
binary_size_reduction: None,
compile_time_improvement: Some(0.5),
transitive_deps_removed: 0,
security_improvement: SecurityImpact::Medium,
},
confidence: 0.8,
auto_applicable: false, });
}
recommendations.extend(self.generate_crate_specific_recommendations(
crate_name,
usage_analysis,
enabled_features,
));
recommendations
}
fn estimate_feature_removal_impact(
&self,
crate_name: &str,
unused_features: &[String],
) -> OptimizationImpact {
let mut binary_size_reduction = 0;
let mut compile_time_improvement = 0.0;
let mut transitive_deps_removed = 0;
let mut security_improvement = SecurityImpact::Low;
match crate_name {
"tokio" => {
for feature in unused_features {
match feature.as_str() {
"full" => {
binary_size_reduction += 500_000; compile_time_improvement += 2.0;
transitive_deps_removed += 5;
security_improvement = SecurityImpact::High;
}
"macros" => {
binary_size_reduction += 50_000;
compile_time_improvement += 0.3;
}
"net" => {
binary_size_reduction += 200_000;
compile_time_improvement += 0.8;
transitive_deps_removed += 2;
}
_ => {
binary_size_reduction += 10_000;
compile_time_improvement += 0.1;
}
}
}
}
"serde" => {
for feature in unused_features {
match feature.as_str() {
"derive" => {
binary_size_reduction += 100_000;
compile_time_improvement += 0.5;
}
_ => {
binary_size_reduction += 5_000;
}
}
}
}
_ => {
binary_size_reduction = unused_features.len() * 20_000;
compile_time_improvement = unused_features.len() as f32 * 0.2;
}
}
OptimizationImpact {
binary_size_reduction: if binary_size_reduction > 0 {
Some(binary_size_reduction)
} else {
None
},
compile_time_improvement: if compile_time_improvement > 0.0 {
Some(compile_time_improvement)
} else {
None
},
transitive_deps_removed,
security_improvement,
}
}
fn generate_crate_specific_recommendations(
&self,
crate_name: &str,
usage_analysis: &CrateUsageAnalysis,
enabled_features: &[String],
) -> Vec<InteractiveRecommendation> {
let mut recommendations = Vec::new();
match crate_name {
"tokio" => {
if usage_analysis.functions_used.len() < 3
&& !usage_analysis.modules_accessed.contains(&"net".to_string())
{
recommendations.push(InteractiveRecommendation {
recommendation_type: RecommendationType::ReplaceWithAlternative {
alternative_crate: "async-std".to_string()
},
current_config: format!("tokio = {{ features = {:?} }}", enabled_features),
recommended_config: "async-std = \"1.12\"".to_string(),
explanation: "Consider async-std for simpler async needs. It has a smaller footprint and fewer features.".to_string(),
estimated_impact: OptimizationImpact {
binary_size_reduction: Some(300_000),
compile_time_improvement: Some(1.5),
transitive_deps_removed: 8,
security_improvement: SecurityImpact::Medium,
},
confidence: 0.7,
auto_applicable: false,
});
}
if enabled_features.contains(&"full".to_string())
&& usage_analysis.modules_accessed.len() < 5
{
let specific_features: Vec<String> = usage_analysis
.modules_accessed
.iter()
.filter_map(|module| match module.as_str() {
"fs" => Some("fs".to_string()),
"net" => Some("net".to_string()),
"time" => Some("time".to_string()),
"sync" => Some("sync".to_string()),
_ => None,
})
.collect();
if !specific_features.is_empty() {
recommendations.push(InteractiveRecommendation {
recommendation_type: RecommendationType::SplitFeatures,
current_config: "features = [\"full\"]".to_string(),
recommended_config: format!("features = {:?}", specific_features),
explanation: format!(
"Replace 'full' feature with specific features: {}. This reduces unused code significantly.",
specific_features.join(", ")
),
estimated_impact: OptimizationImpact {
binary_size_reduction: Some(400_000),
compile_time_improvement: Some(1.8),
transitive_deps_removed: 6,
security_improvement: SecurityImpact::High,
},
confidence: 0.95,
auto_applicable: true,
});
}
}
}
"serde" => {
if usage_analysis.derive_macros_used.is_empty()
&& enabled_features.contains(&"derive".to_string())
{
recommendations.push(InteractiveRecommendation {
recommendation_type: RecommendationType::RemoveUnusedFeatures,
current_config: "features = [\"derive\"]".to_string(),
recommended_config: "features = []".to_string(),
explanation: "Remove 'derive' feature since you're not using #[derive(Serialize, Deserialize)].".to_string(),
estimated_impact: OptimizationImpact {
binary_size_reduction: Some(80_000),
compile_time_improvement: Some(0.4),
transitive_deps_removed: 1,
security_improvement: SecurityImpact::Low,
},
confidence: 0.9,
auto_applicable: true,
});
}
}
_ => {}
}
recommendations
}
#[allow(clippy::ptr_arg)]
fn analyze_serde_usage(
&self,
code: &str,
_functions_used: &mut Vec<String>,
_macros_used: &mut Vec<String>,
types_used: &mut Vec<String>,
traits_used: &mut Vec<String>,
derive_macros_used: &mut Vec<String>,
usage_frequency: &mut std::collections::HashMap<String, usize>,
) {
if code.contains("#[derive(Serialize") {
derive_macros_used.push("Serialize".to_string());
*usage_frequency.entry("Serialize".to_string()).or_insert(0) += 1;
}
if code.contains("#[derive(Deserialize") {
derive_macros_used.push("Deserialize".to_string());
*usage_frequency
.entry("Deserialize".to_string())
.or_insert(0) += 1;
}
if code.contains("serde::Serialize") {
traits_used.push("Serialize".to_string());
*usage_frequency.entry("Serialize".to_string()).or_insert(0) += 1;
}
if code.contains("serde::Deserialize") {
traits_used.push("Deserialize".to_string());
*usage_frequency
.entry("Deserialize".to_string())
.or_insert(0) += 1;
}
if code.contains("Serializer") {
types_used.push("Serializer".to_string());
*usage_frequency.entry("Serializer".to_string()).or_insert(0) += 1;
}
if code.contains("Deserializer") {
types_used.push("Deserializer".to_string());
*usage_frequency
.entry("Deserializer".to_string())
.or_insert(0) += 1;
}
}
#[allow(clippy::ptr_arg)]
fn analyze_tokio_usage(
&self,
code: &str,
functions_used: &mut Vec<String>,
_macros_used: &mut Vec<String>,
types_used: &mut Vec<String>,
modules_accessed: &mut Vec<String>,
attribute_macros_used: &mut Vec<String>,
usage_frequency: &mut std::collections::HashMap<String, usize>,
) {
if code.contains("#[tokio::main]") {
attribute_macros_used.push("main".to_string());
*usage_frequency.entry("main".to_string()).or_insert(0) += 1;
}
if code.contains("#[tokio::test]") {
attribute_macros_used.push("test".to_string());
*usage_frequency.entry("test".to_string()).or_insert(0) += 1;
}
if let Ok(fs_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::fs(?:\s|;|::)") {
if fs_regex.is_match(code) {
modules_accessed.push("fs".to_string());
*usage_frequency.entry("fs".to_string()).or_insert(0) += 1;
}
}
if let Ok(net_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::net(?:\s|;|::)") {
if net_regex.is_match(code) {
modules_accessed.push("net".to_string());
*usage_frequency.entry("net".to_string()).or_insert(0) += 1;
}
}
if let Ok(time_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::time(?:\s|;|::)") {
if time_regex.is_match(code) {
modules_accessed.push("time".to_string());
*usage_frequency.entry("time".to_string()).or_insert(0) += 1;
}
}
if let Ok(sync_regex) = regex::Regex::new(r"(?:^|\s)use\s+tokio::sync(?:\s|;|::)") {
if sync_regex.is_match(code) {
modules_accessed.push("sync".to_string());
*usage_frequency.entry("sync".to_string()).or_insert(0) += 1;
}
}
if code.contains("TcpListener") {
types_used.push("TcpListener".to_string());
modules_accessed.push("net".to_string());
*usage_frequency
.entry("TcpListener".to_string())
.or_insert(0) += 1;
}
if code.contains("TcpStream") {
types_used.push("TcpStream".to_string());
modules_accessed.push("net".to_string());
*usage_frequency.entry("TcpStream".to_string()).or_insert(0) += 1;
}
if code.contains("tokio::spawn") {
functions_used.push("spawn".to_string());
*usage_frequency.entry("spawn".to_string()).or_insert(0) += 1;
}
if code.contains("tokio::select!") {
functions_used.push("select".to_string());
*usage_frequency.entry("select".to_string()).or_insert(0) += 1;
}
}
fn analyze_regex_usage(
&self,
code: &str,
functions_used: &mut Vec<String>,
types_used: &mut Vec<String>,
usage_frequency: &mut std::collections::HashMap<String, usize>,
) {
if code.contains("Regex::new") {
types_used.push("Regex".to_string());
functions_used.push("new".to_string());
*usage_frequency.entry("Regex".to_string()).or_insert(0) += 1;
}
if code.contains(".is_match(") {
functions_used.push("is_match".to_string());
*usage_frequency.entry("is_match".to_string()).or_insert(0) += 1;
}
if code.contains(".find(") {
functions_used.push("find".to_string());
*usage_frequency.entry("find".to_string()).or_insert(0) += 1;
}
if code.contains(".find_iter(") {
functions_used.push("find_iter".to_string());
*usage_frequency.entry("find_iter".to_string()).or_insert(0) += 1;
}
if code.contains(".captures(") {
functions_used.push("captures".to_string());
*usage_frequency.entry("captures".to_string()).or_insert(0) += 1;
}
if code.contains(".replace(") {
functions_used.push("replace".to_string());
*usage_frequency.entry("replace".to_string()).or_insert(0) += 1;
}
}
fn analyze_chrono_usage(
&self,
code: &str,
functions_used: &mut Vec<String>,
types_used: &mut Vec<String>,
usage_frequency: &mut std::collections::HashMap<String, usize>,
) {
if code.contains("DateTime<Utc>") {
types_used.push("DateTime".to_string());
types_used.push("Utc".to_string());
*usage_frequency.entry("DateTime".to_string()).or_insert(0) += 1;
}
if code.contains("DateTime<Local>") {
types_used.push("DateTime".to_string());
types_used.push("Local".to_string());
*usage_frequency.entry("DateTime".to_string()).or_insert(0) += 1;
}
if code.contains("Utc::now") {
functions_used.push("now".to_string());
*usage_frequency.entry("now".to_string()).or_insert(0) += 1;
}
if code.contains("Local::now") {
functions_used.push("now".to_string());
*usage_frequency.entry("now".to_string()).or_insert(0) += 1;
}
if code.contains(".parse::<DateTime") {
functions_used.push("parse".to_string());
*usage_frequency.entry("parse".to_string()).or_insert(0) += 1;
}
if code.contains(".format(") {
functions_used.push("format".to_string());
*usage_frequency.entry("format".to_string()).or_insert(0) += 1;
}
}
fn analyze_serde_json_usage(
&self,
code: &str,
functions_used: &mut Vec<String>,
usage_frequency: &mut std::collections::HashMap<String, usize>,
) {
if code.contains("serde_json::to_string") {
functions_used.push("to_string".to_string());
*usage_frequency.entry("to_string".to_string()).or_insert(0) += 1;
}
if code.contains("serde_json::to_vec") {
functions_used.push("to_vec".to_string());
*usage_frequency.entry("to_vec".to_string()).or_insert(0) += 1;
}
if code.contains("serde_json::to_writer") {
functions_used.push("to_writer".to_string());
*usage_frequency.entry("to_writer".to_string()).or_insert(0) += 1;
}
if code.contains("serde_json::from_str") {
functions_used.push("from_str".to_string());
*usage_frequency.entry("from_str".to_string()).or_insert(0) += 1;
}
if code.contains("serde_json::from_slice") {
functions_used.push("from_slice".to_string());
*usage_frequency.entry("from_slice".to_string()).or_insert(0) += 1;
}
if code.contains("serde_json::from_reader") {
functions_used.push("from_reader".to_string());
*usage_frequency
.entry("from_reader".to_string())
.or_insert(0) += 1;
}
}
fn analyze_generic_usage(
&self,
crate_name: &str,
code: &str,
functions_used: &mut Vec<String>,
types_used: &mut Vec<String>,
usage_frequency: &mut std::collections::HashMap<String, usize>,
) {
let crate_pattern = format!("{}::", crate_name);
let mut start = 0;
while let Some(pos) = code[start..].find(&crate_pattern) {
let actual_pos = start + pos + crate_pattern.len();
if let Some(end) = code[actual_pos..].find(|c: char| !c.is_alphanumeric() && c != '_') {
let item = &code[actual_pos..actual_pos + end];
if !item.is_empty() {
if item.chars().next().unwrap_or('a').is_uppercase() {
types_used.push(item.to_string());
} else {
functions_used.push(item.to_string());
}
*usage_frequency.entry(item.to_string()).or_insert(0) += 1;
}
}
start = actual_pos;
}
}
}
fn is_std_crate(name: &str) -> bool {
matches!(name, "std" | "core" | "alloc" | "proc_macro" | "test")
}
fn is_keyword(name: &str) -> bool {
matches!(name, "self" | "super" | "crate" | "Self")
}
fn map_derive_to_crate(derive_name: &str) -> Option<&'static str> {
match derive_name {
"Serialize" | "Deserialize" => Some("serde"),
"Error" => Some("thiserror"),
"Derivative" => Some("derivative"),
_ => None,
}
}
fn map_module_to_crate(module_name: &str) -> Option<&'static str> {
match module_name {
"TcpListener" | "TcpStream" | "UdpSocket" => Some("tokio"),
"fs" => Some("tokio"),
"DateTime" | "Utc" | "Local" | "NaiveDate" | "NaiveTime" | "NaiveDateTime" => {
Some("chrono")
}
"Regex" | "RegexBuilder" | "Match" => Some("regex"),
"Serializer" | "Deserializer" => Some("serde"),
_ => None,
}
}
pub struct Decrust {
parameter_extractors: Vec<Box<dyn ParameterExtractor>>,
fix_generators: HashMap<ErrorCategory, Vec<Box<dyn FixGenerator>>>,
fix_templates: HashMap<ErrorCategory, Vec<FixTemplate>>,
dependency_analyzer: DependencyAnalyzer,
}
impl Decrust {
pub fn new() -> Self {
let mut decrust = Self {
parameter_extractors: Vec::new(),
fix_generators: HashMap::new(),
fix_templates: HashMap::new(),
dependency_analyzer: DependencyAnalyzer::new(),
};
decrust.register_parameter_extractor(Box::new(RegexParameterExtractor::new()));
decrust.register_parameter_extractor(Box::new(DiagnosticParameterExtractor::new()));
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(NotFoundFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(UnusedImportFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(UnusedVariableFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(MissingSemicolonFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(MismatchedTypeFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(ImmutableBorrowFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(BorrowAfterMoveFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(MissingTraitImplFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(MissingLifetimeFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(MatchPatternFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(PrivateFieldAccessFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(GenericParamConflictFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(MissingReturnFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(EnumParameterMatchFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(StructParameterMatchFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(AstTraitImplementationFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(ClosureCaptureLifetimeFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(RecursiveTypeFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(QuestionMarkPropagationFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(MissingOkErrFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(ReturnLocalReferenceFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(UnstableFeatureFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Validation,
Box::new(InvalidArgumentCountFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Configuration,
Box::new(ConfigSyntaxFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Configuration,
Box::new(ConfigMissingKeyFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Runtime,
Box::new(UnsafeUnwrapFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Runtime,
Box::new(DivisionByZeroFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Runtime,
Box::new(RuntimePanicFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Parsing,
Box::new(JsonParseFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Parsing,
Box::new(YamlParseFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Network,
Box::new(NetworkConnectionFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Network,
Box::new(NetworkTlsFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Style,
Box::new(UnnecessaryBracesFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Style,
Box::new(UnnecessaryCloneFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Style,
Box::new(UnnecessaryParenthesesFixGenerator::new()),
);
decrust
.register_fix_generator(ErrorCategory::Style, Box::new(UnusedMutFixGenerator::new()));
decrust.register_fix_generator(
ErrorCategory::Style,
Box::new(AstMissingImportFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Style,
Box::new(AstUnusedCodeFixGenerator::new()),
);
decrust.register_fix_generator(
ErrorCategory::Io,
Box::new(IoMissingDirectoryFixGenerator::new()),
);
decrust
.register_fix_generator(ErrorCategory::Io, Box::new(IoPermissionFixGenerator::new()));
decrust.register_fix_template(
ErrorCategory::Io,
FixTemplate::new(
"I/O error during '{param1}' on path '{param2}'. Check file permissions and path validity.",
FixType::ManualInterventionRequired,
0.7,
)
);
decrust
}
pub fn register_parameter_extractor(
&mut self,
extractor: Box<dyn ParameterExtractor>,
) -> &mut Self {
self.parameter_extractors.push(extractor);
self
}
pub fn register_fix_generator(
&mut self,
category: ErrorCategory,
generator: Box<dyn FixGenerator>,
) -> &mut Self {
self.fix_generators
.entry(category)
.or_insert_with(Vec::new)
.push(generator);
self
}
pub fn register_fix_template(
&mut self,
category: ErrorCategory,
template: FixTemplate,
) -> &mut Self {
self.fix_templates
.entry(category)
.or_insert_with(Vec::new)
.push(template);
self
}
pub fn extract_parameters(&self, error: &DecrustError) -> ExtractedParameters {
let mut best_params = ExtractedParameters::default();
for extractor in &self.parameter_extractors {
if extractor.supported_categories().contains(&error.category()) {
let params = extractor.extract_parameters(error);
if params.confidence > best_params.confidence
|| (params.confidence == best_params.confidence
&& params.values.len() > best_params.values.len())
{
best_params = params;
}
}
}
self.infer_parameters(error, &mut best_params);
best_params
}
fn infer_parameters(&self, error: &DecrustError, params: &mut ExtractedParameters) {
match error.category() {
ErrorCategory::NotFound => {
if !params.values.contains_key("resource_type")
&& params.values.contains_key("identifier")
{
let identifier = params.values.get("identifier").unwrap();
let path = PathBuf::from(identifier);
if path.is_absolute() || identifier.contains('/') {
params.add_parameter("resource_type", "file");
params.confidence *= 0.9; }
}
if let DecrustError::NotFound {
resource_type,
identifier,
..
} = error
{
if !params.values.contains_key("resource_type") {
params.add_parameter("resource_type", resource_type);
}
if !params.values.contains_key("identifier") {
params.add_parameter("identifier", identifier);
}
if params.confidence < 0.7 {
params.confidence = 0.7;
}
}
}
ErrorCategory::Io => {
if let DecrustError::Io {
path, operation, ..
} = error
{
if !params.values.contains_key("param1")
&& !params.values.contains_key("operation")
{
params.add_parameter("operation", operation);
params.add_parameter("param1", operation);
}
if !params.values.contains_key("param2") && !params.values.contains_key("path")
{
if let Some(p) = path {
let path_str = p.to_string_lossy().to_string();
params.add_parameter("path", &path_str);
params.add_parameter("param2", &path_str);
}
}
if params.confidence < 0.7 {
params.confidence = 0.7;
}
}
}
_ => {}
}
}
pub fn suggest_autocorrection(
&self,
error: &DecrustError,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
if let Some(diag_info) = error.get_diagnostic_info() {
if !diag_info.suggested_fixes.is_empty() {
debug!("Decrust: Found tool-suggested fixes in DiagnosticResult.");
let primary_fix_text = diag_info.suggested_fixes.join("\n");
let file_path_from_diag = diag_info
.primary_location
.as_ref()
.map(|loc| PathBuf::from(&loc.file));
let details = file_path_from_diag.map(|fp| FixDetails::TextReplace {
file_path: fp,
line_start: diag_info
.primary_location
.as_ref()
.map_or(0, |loc| loc.line as usize),
column_start: diag_info
.primary_location
.as_ref()
.map_or(0, |loc| loc.column as usize),
line_end: diag_info
.primary_location
.as_ref()
.map_or(0, |loc| loc.line as usize),
column_end: diag_info.primary_location.as_ref().map_or(0, |loc| {
loc.column as usize
+ primary_fix_text
.chars()
.filter(|&c| c != '\n')
.count()
.max(1)
}),
original_text_snippet: diag_info.original_message.clone(),
replacement_text: primary_fix_text,
});
return Some(Autocorrection {
description: "Apply fix suggested by diagnostic tool.".to_string(),
fix_type: FixType::TextReplacement,
confidence: 0.85, details,
diff_suggestion: None, commands_to_apply: vec![],
targets_error_code: diag_info.diagnostic_code.clone(),
});
}
}
let params = self.extract_parameters(error);
if let Some(generators) = self.fix_generators.get(&error.category()) {
for generator in generators {
if let Some(fix) = generator.generate_fix(error, ¶ms, source_code_context) {
return Some(fix);
}
}
}
if let Some(templates) = self.fix_templates.get(&error.category()) {
if !templates.is_empty() && !params.values.is_empty() {
let best_template = templates
.iter()
.max_by(|a, b| {
a.base_confidence
.partial_cmp(&b.base_confidence)
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
return Some(best_template.apply(¶ms));
}
}
match error.category() {
ErrorCategory::NotFound => {
let (resource_type, identifier) = if let DecrustError::NotFound {
resource_type,
identifier,
..
} = error
{
(resource_type.clone(), identifier.clone())
} else {
tracing::warn!(
"Decrust: NotFound category with unexpected error variant: {:?}",
error
);
(
"unknown resource".to_string(),
"unknown identifier".to_string(),
)
};
let mut commands = vec![];
let mut suggestion_details = None;
if resource_type == "file" || resource_type == "path" {
let path_buf = PathBuf::from(&identifier);
if let Some(parent) = path_buf.parent() {
if !parent.as_os_str().is_empty() && !parent.exists() {
commands.push(format!("mkdir -p \"{}\"", parent.display()));
}
}
commands.push(format!("touch \"{}\"", identifier));
suggestion_details = Some(FixDetails::ExecuteCommand {
command: commands.first().cloned().unwrap_or_default(), args: commands.iter().skip(1).cloned().collect(),
working_directory: None,
});
}
Some(Autocorrection {
description: format!(
"Resource type '{}' with identifier '{}' not found. Consider creating it if it's a file/directory, or verify the path/name.",
resource_type, identifier
),
fix_type: if commands.is_empty() { FixType::ManualInterventionRequired } else { FixType::ExecuteCommand },
confidence: 0.7,
details: suggestion_details,
diff_suggestion: None,
commands_to_apply: commands,
targets_error_code: Some(format!("{:?}", ErrorCategory::NotFound)),
})
}
ErrorCategory::Io => {
let (source_msg, path_opt, operation_opt, io_kind_opt) = if let DecrustError::Io {
source,
path,
operation,
..
} = error
{
(
source.to_string(),
path.clone(),
Some(operation.clone()),
Some(source.kind()),
)
} else {
(String::from("Unknown I/O error"), None, None, None)
};
let path_str = path_opt
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "<unknown_path>".to_string());
let op_str = operation_opt.unwrap_or_else(|| "<unknown_op>".to_string());
let mut details = None;
let mut commands = vec![];
let fix_type = match io_kind_opt {
Some(std::io::ErrorKind::NotFound) => {
if let Some(p) = &path_opt {
details = Some(FixDetails::SuggestCodeChange {
file_path: p.clone(),
line_hint: 0, suggested_code_snippet: format!("// Ensure path '{}' exists before operation '{}'\n// Or handle the NotFound error gracefully.", p.display(), op_str),
explanation: "The file or directory specified in the operation was not found at the given path.".to_string(),
});
if p.is_dir() || p.extension().is_none() {
commands.push(format!("mkdir -p \"{}\"", p.display()));
} else {
if let Some(parent) = p.parent() {
if !parent.as_os_str().is_empty() && !parent.exists() {
commands.push(format!("mkdir -p \"{}\"", parent.display()));
}
}
commands.push(format!("touch \"{}\"", p.display()));
}
}
FixType::ExecuteCommand }
Some(std::io::ErrorKind::PermissionDenied) => {
details = Some(FixDetails::SuggestCodeChange{
file_path: path_opt.clone().unwrap_or_else(|| PathBuf::from("unknown_file_causing_permission_error")),
line_hint: 0,
suggested_code_snippet: format!("// Check permissions for path '{}' for operation '{}'", path_str, op_str),
explanation: "The application does not have the necessary permissions to perform the I/O operation.".to_string()
});
FixType::ConfigurationChange }
_ => FixType::Information,
};
Some(Autocorrection {
description: format!("I/O error during '{}' on path '{}': {}. Verify path, permissions, or disk space.", op_str, path_str, source_msg),
fix_type,
confidence: 0.65,
details,
diff_suggestion: None,
commands_to_apply: commands,
targets_error_code: Some(format!("{:?}", ErrorCategory::Io)),
})
}
ErrorCategory::Configuration => {
let (message, path_opt) = if let DecrustError::Config { message, path, .. } = error
{
(message.clone(), path.clone())
} else {
("Unknown configuration error".to_string(), None)
};
let target_file = path_opt
.clone()
.unwrap_or_else(|| PathBuf::from("config.toml")); Some(Autocorrection {
description: format!("Configuration issue for path '{}': {}. Please review the configuration file structure and values.",
path_opt.as_ref().map(|p| p.display().to_string()).unwrap_or_else(||"<unknown_config>".to_string()), message),
fix_type: FixType::ConfigurationChange,
confidence: 0.7,
details: Some(FixDetails::SuggestCodeChange {
file_path: target_file,
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),
explanation: "Configuration files require specific syntax, valid values, and all mandatory fields to be present.".to_string()
}),
diff_suggestion: None,
commands_to_apply: vec![],
targets_error_code: Some(format!("{:?}", ErrorCategory::Configuration)),
})
}
_ => {
tracing::trace!(
"Decrust: No specific autocorrection implemented for error category: {:?}. Error: {:?}",
error.category(), error
);
None
}
}
}
pub fn analyze_dependencies(&mut self, code: &str) -> Vec<DependencyAnalysisResult> {
self.dependency_analyzer.analyze_code_dependencies(code)
}
pub fn configure_dependency_analyzer(&mut self, verbose: bool, check_latest: bool) {
self.dependency_analyzer = DependencyAnalyzer::with_config(verbose, check_latest);
}
pub fn generate_dependency_report(&mut self, code: &str) -> String {
let analysis_results = self.analyze_dependencies(code);
if analysis_results.is_empty() {
return "🔍 **Dependency Analysis Report**\n\nNo external dependencies detected in the analyzed code.".to_string();
}
let mut report = String::new();
report.push_str("🔍 **Dependency Analysis Report**\n");
report.push_str("=====================================\n\n");
for (i, result) in analysis_results.iter().enumerate() {
let version_display = if result.current_version == "unknown" {
"version unknown".to_string()
} else {
format!("v{}", result.current_version)
};
report.push_str(&format!(
"## {}. {} ({})\n",
i + 1,
result.crate_name,
version_display
));
report.push_str("### Features Analysis\n");
report.push_str(&format!(
"- **Enabled**: {}\n",
result.enabled_features.join(", ")
));
report.push_str(&format!(
"- **Used**: {}\n",
result.used_features.join(", ")
));
if !result.unused_features.is_empty() {
report.push_str(&format!(
"- **⚠️ Unused**: {}\n",
result.unused_features.join(", ")
));
}
if !result.missing_features.is_empty() {
report.push_str(&format!(
"- **❌ Missing**: {}\n",
result.missing_features.join(", ")
));
}
report.push_str("### Version Status\n");
match &result.version_status {
VersionCompatibility::Compatible => {
report.push_str("- ✅ **Compatible and up-to-date**\n");
}
VersionCompatibility::Outdated { latest_version } => {
report.push_str(&format!("- 🔄 **Outdated** (latest: {})\n", latest_version));
}
VersionCompatibility::Incompatible { reason } => {
report.push_str(&format!("- ❌ **Incompatible**: {}\n", reason));
}
VersionCompatibility::Unknown => {
report.push_str("- ❓ **Unknown** (network check disabled)\n");
}
}
if !result.suggestions.is_empty() {
report.push_str("### 💡 Optimization Suggestions\n");
for suggestion in &result.suggestions {
report.push_str(&format!("- {}\n", suggestion));
}
}
report.push_str("\n");
}
let total_crates = analysis_results.len();
let crates_with_unused_features = analysis_results
.iter()
.filter(|r| !r.unused_features.is_empty())
.count();
let crates_with_missing_features = analysis_results
.iter()
.filter(|r| !r.missing_features.is_empty())
.count();
report.push_str("## 📊 Summary\n");
report.push_str(&format!(
"- **Total dependencies analyzed**: {}\n",
total_crates
));
report.push_str(&format!(
"- **Dependencies with unused features**: {}\n",
crates_with_unused_features
));
report.push_str(&format!(
"- **Dependencies with missing features**: {}\n",
crates_with_missing_features
));
if crates_with_unused_features > 0 || crates_with_missing_features > 0 {
report.push_str("\n🎯 **Recommendation**: Review the suggestions above to optimize your dependency footprint and ensure all required features are enabled.\n");
} else {
report.push_str(
"\n✨ **Great!** Your dependency configuration appears to be well-optimized.\n",
);
}
report
}
}
pub trait AutocorrectableError {
fn suggest_autocorrection(
&self,
decrust_engine: &Decrust,
source_code_context: Option<&str>,
) -> Option<Autocorrection>;
fn get_diagnostic_info(&self) -> Option<&DiagnosticResult>;
}
impl AutocorrectableError for super::DecrustError {
fn suggest_autocorrection(
&self,
decrust_engine: &Decrust,
source_code_context: Option<&str>,
) -> Option<Autocorrection> {
decrust_engine.suggest_autocorrection(self, source_code_context)
}
fn get_diagnostic_info(&self) -> Option<&DiagnosticResult> {
if let super::DecrustError::WithRichContext { context, .. } = self {
context.diagnostic_info.as_ref()
} else {
None
}
}
}