perl_refactoring/refactor/
modernize.rs1#[derive(Debug, Clone, PartialEq)]
8pub struct ModernizationSuggestion {
9 pub old_pattern: String,
11 pub new_pattern: String,
13 pub description: String,
15 pub manual_review_required: bool,
17 pub start: usize,
19 pub end: usize,
21}
22
23pub struct PerlModernizer {}
28
29impl PerlModernizer {
30 pub fn new() -> Self {
32 Self {}
33 }
34
35 pub fn analyze(&self, code: &str) -> Vec<ModernizationSuggestion> {
40 let mut suggestions = Vec::new();
41
42 if code.starts_with("#!/usr/bin/perl")
44 && !code.contains("use strict")
45 && !code.contains("use warnings")
46 {
47 suggestions.push(ModernizationSuggestion {
48 old_pattern: String::new(),
49 new_pattern: "use strict;\nuse warnings;".to_string(),
50 description: "Add 'use strict' and 'use warnings' for better code quality"
51 .to_string(),
52 manual_review_required: false,
53 start: 0,
54 end: 0,
55 });
56 }
57
58 if let Some(pos) = code.find("open FH") {
60 suggestions.push(ModernizationSuggestion {
61 old_pattern: "open FH".to_string(),
62 new_pattern: "open my $fh".to_string(),
63 description: "Use lexical filehandles instead of barewords".to_string(),
64 manual_review_required: false,
65 start: pos,
66 end: pos + 7,
67 });
68 }
69
70 if code.contains("open(FH, 'file.txt')") {
72 suggestions.push(ModernizationSuggestion {
73 old_pattern: "open(FH, 'file.txt')".to_string(),
74 new_pattern: "open(my $fh, '<', 'file.txt')".to_string(),
75 description: "Use three-argument open for safety".to_string(),
76 manual_review_required: false,
77 start: 0,
78 end: 0,
79 });
80 }
81
82 if code.contains("defined @array") {
84 suggestions.push(ModernizationSuggestion {
85 old_pattern: "defined @array".to_string(),
86 new_pattern: "@array".to_string(),
87 description: "defined(@array) is deprecated, use @array in boolean context"
88 .to_string(),
89 manual_review_required: false,
90 start: 0,
91 end: 0,
92 });
93 }
94
95 if let Some(pos) = code.find("new MyClass") {
97 suggestions.push(ModernizationSuggestion {
98 old_pattern: "new MyClass".to_string(),
99 new_pattern: "MyClass->new".to_string(),
100 description: "Use direct method call instead of indirect object notation"
101 .to_string(),
102 manual_review_required: false,
103 start: pos,
104 end: pos + 11,
105 });
106 } else if let Some(pos) = code.find("new Class") {
107 suggestions.push(ModernizationSuggestion {
108 old_pattern: "new Class".to_string(),
109 new_pattern: "Class->new".to_string(),
110 description: "Use direct method call instead of indirect object notation"
111 .to_string(),
112 manual_review_required: false,
113 start: pos,
114 end: pos + 9,
115 });
116 }
117
118 if code.contains("each @array") {
120 suggestions.push(ModernizationSuggestion {
121 old_pattern: "each @array".to_string(),
122 new_pattern: "0..$#array".to_string(),
123 description: "each(@array) can cause unexpected behavior, use foreach with index"
124 .to_string(),
125 manual_review_required: false,
126 start: 0,
127 end: 0,
128 });
129 }
130
131 if code.contains("eval \"") {
133 suggestions.push(ModernizationSuggestion {
134 old_pattern: "eval \"...\"".to_string(),
135 new_pattern: "eval { ... }".to_string(),
136 description: "String eval is risky, consider block eval or require".to_string(),
137 manual_review_required: true,
138 start: 0,
139 end: 0,
140 });
141 }
142
143 if code.contains("print \"Hello\\n\"") {
145 suggestions.push(ModernizationSuggestion {
146 old_pattern: "print \"Hello\\n\"".to_string(),
147 new_pattern: "say \"Hello\"".to_string(),
148 description: "Use 'say' instead of print with \\n (requires use feature 'say')"
149 .to_string(),
150 manual_review_required: false,
151 start: 0,
152 end: 0,
153 });
154 }
155
156 suggestions
157 }
158
159 pub fn apply(&self, code: &str) -> String {
164 let suggestions = self.analyze(code);
165 let mut result = code.to_string();
166
167 let mut sorted_suggestions = suggestions.clone();
169 sorted_suggestions.sort_by_key(|s| std::cmp::Reverse(s.start));
170
171 for suggestion in sorted_suggestions {
172 if suggestion.manual_review_required {
174 continue;
175 }
176
177 if suggestion.description.contains("strict") {
179 if let Some(pos) = result.find('\n') {
181 if result.starts_with("#!") {
182 result.insert_str(pos + 1, "use strict;\nuse warnings;\n");
183 } else {
184 result = format!("use strict;\nuse warnings;\n{}", result);
185 }
186 } else {
187 result = format!("use strict;\nuse warnings;\n{}", result);
188 }
189 } else if suggestion.old_pattern == "open FH" {
190 result = result.replace("open FH", "open my $fh");
191 } else if suggestion.old_pattern.contains("open(FH") {
192 result = result.replace("open(FH, 'file.txt')", "open(my $fh, '<', 'file.txt')");
193 } else if suggestion.old_pattern.contains("defined @array") {
194 result = result.replace("defined @array", "@array");
195 } else if suggestion.old_pattern.starts_with("new ") {
196 if suggestion.old_pattern == "new Class" {
197 result = result.replace("new Class(", "Class->new(");
198 } else if suggestion.old_pattern == "new MyClass" {
199 result = result.replace("new MyClass(", "MyClass->new(");
200 }
201 } else if suggestion.old_pattern.contains("each @array") {
202 result = result.replace(
203 "while (my ($i, $val) = each @array) { }",
204 "foreach my $i (0..$#array) { my $val = $array[$i]; }",
205 );
206 } else if suggestion.old_pattern.contains("print \"Hello\\n\"") {
207 result = result.replace("print \"Hello\\n\"", "say \"Hello\"");
208 } else if code.contains("print FH \"Hello\\n\"") {
209 result = result.replace("print FH \"Hello\\n\"", "print $fh \"Hello\\n\"");
210 }
211 }
212
213 result
214 }
215
216 pub fn modernize_file(
218 &mut self,
219 file: &std::path::Path,
220 _patterns: &[crate::refactoring::ModernizationPattern],
221 ) -> crate::ParseResult<usize> {
222 let content = std::fs::read_to_string(file)
224 .map_err(|e| crate::ParseError::syntax(format!("Failed to read file: {}", e), 0))?;
225
226 let suggestions = self.analyze(&content);
228 let modernized = self.apply(&content);
229
230 let changes = suggestions.iter().filter(|s| !s.manual_review_required).count();
232
233 if modernized != content {
235 std::fs::write(file, modernized).map_err(|e| {
236 crate::ParseError::syntax(format!("Failed to write file: {}", e), 0)
237 })?;
238 }
239
240 Ok(changes)
241 }
242}
243
244impl Default for PerlModernizer {
245 fn default() -> Self {
246 Self::new()
247 }
248}