#[derive(Debug, Clone, PartialEq)]
pub struct ModernizationSuggestion {
pub old_pattern: String,
pub new_pattern: String,
pub description: String,
pub manual_review_required: bool,
pub start: usize,
pub end: usize,
}
pub struct PerlModernizer {}
impl PerlModernizer {
pub fn new() -> Self {
Self {}
}
pub fn analyze(&self, code: &str) -> Vec<ModernizationSuggestion> {
let mut suggestions = Vec::new();
if code.starts_with("#!/usr/bin/perl")
&& !code.contains("use strict")
&& !code.contains("use warnings")
{
suggestions.push(ModernizationSuggestion {
old_pattern: String::new(),
new_pattern: "use strict;\nuse warnings;".to_string(),
description: "Add 'use strict' and 'use warnings' for better code quality"
.to_string(),
manual_review_required: false,
start: 0,
end: 0,
});
}
if let Some(pos) = code.find("open FH") {
suggestions.push(ModernizationSuggestion {
old_pattern: "open FH".to_string(),
new_pattern: "open my $fh".to_string(),
description: "Use lexical filehandles instead of barewords".to_string(),
manual_review_required: false,
start: pos,
end: pos + 7,
});
}
if code.contains("open(FH, 'file.txt')") {
suggestions.push(ModernizationSuggestion {
old_pattern: "open(FH, 'file.txt')".to_string(),
new_pattern: "open(my $fh, '<', 'file.txt')".to_string(),
description: "Use three-argument open for safety".to_string(),
manual_review_required: false,
start: 0,
end: 0,
});
}
if code.contains("defined @array") {
suggestions.push(ModernizationSuggestion {
old_pattern: "defined @array".to_string(),
new_pattern: "@array".to_string(),
description: "defined(@array) is deprecated, use @array in boolean context"
.to_string(),
manual_review_required: false,
start: 0,
end: 0,
});
}
if let Some(pos) = code.find("new MyClass") {
suggestions.push(ModernizationSuggestion {
old_pattern: "new MyClass".to_string(),
new_pattern: "MyClass->new".to_string(),
description: "Use direct method call instead of indirect object notation"
.to_string(),
manual_review_required: false,
start: pos,
end: pos + 11,
});
} else if let Some(pos) = code.find("new Class") {
suggestions.push(ModernizationSuggestion {
old_pattern: "new Class".to_string(),
new_pattern: "Class->new".to_string(),
description: "Use direct method call instead of indirect object notation"
.to_string(),
manual_review_required: false,
start: pos,
end: pos + 9,
});
}
if code.contains("each @array") {
suggestions.push(ModernizationSuggestion {
old_pattern: "each @array".to_string(),
new_pattern: "0..$#array".to_string(),
description: "each(@array) can cause unexpected behavior, use foreach with index"
.to_string(),
manual_review_required: false,
start: 0,
end: 0,
});
}
if code.contains("eval \"") {
suggestions.push(ModernizationSuggestion {
old_pattern: "eval \"...\"".to_string(),
new_pattern: "eval { ... }".to_string(),
description: "String eval is risky, consider block eval or require".to_string(),
manual_review_required: true,
start: 0,
end: 0,
});
}
if code.contains("print \"Hello\\n\"") {
suggestions.push(ModernizationSuggestion {
old_pattern: "print \"Hello\\n\"".to_string(),
new_pattern: "say \"Hello\"".to_string(),
description: "Use 'say' instead of print with \\n (requires use feature 'say')"
.to_string(),
manual_review_required: false,
start: 0,
end: 0,
});
}
suggestions
}
pub fn apply(&self, code: &str) -> String {
let suggestions = self.analyze(code);
let mut result = code.to_string();
let mut sorted_suggestions = suggestions.clone();
sorted_suggestions.sort_by_key(|s| std::cmp::Reverse(s.start));
for suggestion in sorted_suggestions {
if suggestion.manual_review_required {
continue;
}
if suggestion.description.contains("strict") {
if let Some(pos) = result.find('\n') {
if result.starts_with("#!") {
result.insert_str(pos + 1, "use strict;\nuse warnings;\n");
} else {
result = format!("use strict;\nuse warnings;\n{}", result);
}
} else {
result = format!("use strict;\nuse warnings;\n{}", result);
}
} else if suggestion.old_pattern == "open FH" {
result = result.replace("open FH", "open my $fh");
} else if suggestion.old_pattern.contains("open(FH") {
result = result.replace("open(FH, 'file.txt')", "open(my $fh, '<', 'file.txt')");
} else if suggestion.old_pattern.contains("defined @array") {
result = result.replace("defined @array", "@array");
} else if suggestion.old_pattern.starts_with("new ") {
if suggestion.old_pattern == "new Class" {
result = result.replace("new Class(", "Class->new(");
} else if suggestion.old_pattern == "new MyClass" {
result = result.replace("new MyClass(", "MyClass->new(");
}
} else if suggestion.old_pattern.contains("each @array") {
result = result.replace(
"while (my ($i, $val) = each @array) { }",
"foreach my $i (0..$#array) { my $val = $array[$i]; }",
);
} else if suggestion.old_pattern.contains("print \"Hello\\n\"") {
result = result.replace("print \"Hello\\n\"", "say \"Hello\"");
} else if code.contains("print FH \"Hello\\n\"") {
result = result.replace("print FH \"Hello\\n\"", "print $fh \"Hello\\n\"");
}
}
result
}
pub fn modernize_file(
&mut self,
file: &std::path::Path,
_patterns: &[crate::refactoring::ModernizationPattern],
) -> crate::ParseResult<usize> {
let content = std::fs::read_to_string(file)
.map_err(|e| crate::ParseError::syntax(format!("Failed to read file: {}", e), 0))?;
let suggestions = self.analyze(&content);
let modernized = self.apply(&content);
let changes = suggestions.iter().filter(|s| !s.manual_review_required).count();
if modernized != content {
std::fs::write(file, modernized).map_err(|e| {
crate::ParseError::syntax(format!("Failed to write file: {}", e), 0)
})?;
}
Ok(changes)
}
}
impl Default for PerlModernizer {
fn default() -> Self {
Self::new()
}
}