ricecoder_generation/
code_quality_enforcer.rs1use crate::error::GenerationError;
10use crate::models::GeneratedFile;
11
12#[derive(Debug, Clone)]
14pub struct CodeQualityConfig {
15 pub require_doc_comments: bool,
17 pub generate_tests: bool,
19 pub enforce_naming: bool,
21 pub enforce_error_handling: bool,
23}
24
25impl Default for CodeQualityConfig {
26 fn default() -> Self {
27 Self {
28 require_doc_comments: true,
29 generate_tests: false,
30 enforce_naming: true,
31 enforce_error_handling: true,
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
38pub struct CodeQualityEnforcer {
39 config: CodeQualityConfig,
41}
42
43impl CodeQualityEnforcer {
44 pub fn new() -> Self {
46 Self {
47 config: CodeQualityConfig::default(),
48 }
49 }
50
51 pub fn with_config(config: CodeQualityConfig) -> Self {
53 Self { config }
54 }
55
56 pub fn enforce(
67 &self,
68 files: Vec<GeneratedFile>,
69 ) -> Result<Vec<GeneratedFile>, GenerationError> {
70 let mut enhanced_files = Vec::new();
71
72 for file in files {
73 let enhanced = self.enforce_file(&file)?;
74 enhanced_files.push(enhanced);
75 }
76
77 Ok(enhanced_files)
78 }
79
80 pub fn enforce_file(&self, file: &GeneratedFile) -> Result<GeneratedFile, GenerationError> {
82 let mut content = file.content.clone();
83
84 match file.language.as_str() {
86 "rust" => {
87 content = self.enforce_rust_quality(&content)?;
88 }
89 "typescript" | "ts" => {
90 content = self.enforce_typescript_quality(&content)?;
91 }
92 "python" | "py" => {
93 content = self.enforce_python_quality(&content)?;
94 }
95 _ => {
96 content = self.enforce_generic_quality(&content)?;
98 }
99 }
100
101 Ok(GeneratedFile {
102 path: file.path.clone(),
103 content,
104 language: file.language.clone(),
105 })
106 }
107
108 fn enforce_rust_quality(&self, content: &str) -> Result<String, GenerationError> {
110 let mut enhanced = content.to_string();
111
112 if self.config.require_doc_comments {
113 enhanced = self.add_rust_doc_comments(&enhanced);
114 }
115
116 if self.config.enforce_naming {
117 enhanced = self.enforce_rust_naming(&enhanced);
118 }
119
120 if self.config.enforce_error_handling {
121 enhanced = self.enforce_rust_error_handling(&enhanced);
122 }
123
124 Ok(enhanced)
125 }
126
127 fn add_rust_doc_comments(&self, content: &str) -> String {
129 let mut result = String::new();
130 let lines: Vec<&str> = content.lines().collect();
131 let mut i = 0;
132
133 while i < lines.len() {
134 let line = lines[i];
135
136 if (line.contains("pub fn ")
138 || line.contains("pub struct ")
139 || line.contains("pub enum "))
140 && !line.trim().starts_with("///")
141 {
142 let has_doc = if i > 0 {
144 lines[i - 1].trim().starts_with("///")
145 } else {
146 false
147 };
148
149 if !has_doc {
150 let item_name = if let Some(start) = line.find("pub ") {
152 let after_pub = &line[start + 4..];
153 if let Some(space_idx) = after_pub.find(' ') {
154 after_pub[..space_idx].to_string()
155 } else if let Some(paren_idx) = after_pub.find('(') {
156 after_pub[..paren_idx].to_string()
157 } else if let Some(brace_idx) = after_pub.find('{') {
158 after_pub[..brace_idx].to_string()
159 } else {
160 "item".to_string()
161 }
162 } else {
163 "item".to_string()
164 };
165
166 let indent = line.len() - line.trim_start().len();
168 result.push_str(&" ".repeat(indent));
169 result.push_str(&format!("/// {}\n", item_name));
170 }
171 }
172
173 result.push_str(line);
174 result.push('\n');
175 i += 1;
176 }
177
178 result
179 }
180
181 fn enforce_rust_naming(&self, content: &str) -> String {
183 content.to_string()
186 }
187
188 fn enforce_rust_error_handling(&self, content: &str) -> String {
190 let result = content.to_string();
191
192 if result.contains("fn ") && result.contains("io::") {
194 if !result.contains("Result<") && !result.contains("-> ") {
196 }
198 }
199
200 result
201 }
202
203 fn enforce_typescript_quality(&self, content: &str) -> Result<String, GenerationError> {
205 let mut enhanced = content.to_string();
206
207 if self.config.require_doc_comments {
208 enhanced = self.add_typescript_doc_comments(&enhanced);
209 }
210
211 if self.config.enforce_naming {
212 enhanced = self.enforce_typescript_naming(&enhanced);
213 }
214
215 if self.config.enforce_error_handling {
216 enhanced = self.enforce_typescript_error_handling(&enhanced);
217 }
218
219 Ok(enhanced)
220 }
221
222 fn add_typescript_doc_comments(&self, content: &str) -> String {
224 let mut result = String::new();
225 let lines: Vec<&str> = content.lines().collect();
226 let mut i = 0;
227
228 while i < lines.len() {
229 let line = lines[i];
230
231 if (line.contains("export function ")
233 || line.contains("export class ")
234 || line.contains("export interface "))
235 && !line.trim().starts_with("/**")
236 {
237 let has_doc = if i > 0 {
239 lines[i - 1].trim().starts_with("/**") || lines[i - 1].trim().starts_with("*")
240 } else {
241 false
242 };
243
244 if !has_doc {
245 let item_name = if let Some(start) = line.find("export ") {
247 let after_export = &line[start + 7..];
248 if let Some(space_idx) = after_export.find(' ') {
249 after_export[space_idx + 1..]
250 .split('(')
251 .next()
252 .unwrap_or("item")
253 } else {
254 "item"
255 }
256 } else {
257 "item"
258 };
259
260 let indent = line.len() - line.trim_start().len();
262 result.push_str(&" ".repeat(indent));
263 result.push_str("/**\n");
264 result.push_str(&" ".repeat(indent));
265 result.push_str(&format!(" * {}\n", item_name));
266 result.push_str(&" ".repeat(indent));
267 result.push_str(" */\n");
268 }
269 }
270
271 result.push_str(line);
272 result.push('\n');
273 i += 1;
274 }
275
276 result
277 }
278
279 fn enforce_typescript_naming(&self, content: &str) -> String {
281 content.to_string()
283 }
284
285 fn enforce_typescript_error_handling(&self, content: &str) -> String {
287 content.to_string()
289 }
290
291 fn enforce_python_quality(&self, content: &str) -> Result<String, GenerationError> {
293 let mut enhanced = content.to_string();
294
295 if self.config.require_doc_comments {
296 enhanced = self.add_python_doc_comments(&enhanced);
297 }
298
299 if self.config.enforce_naming {
300 enhanced = self.enforce_python_naming(&enhanced);
301 }
302
303 if self.config.enforce_error_handling {
304 enhanced = self.enforce_python_error_handling(&enhanced);
305 }
306
307 Ok(enhanced)
308 }
309
310 fn add_python_doc_comments(&self, content: &str) -> String {
312 let mut result = String::new();
313 let lines: Vec<&str> = content.lines().collect();
314 let mut i = 0;
315
316 while i < lines.len() {
317 let line = lines[i];
318
319 if (line.trim().starts_with("def ") || line.trim().starts_with("class "))
321 && !line.trim().starts_with("def _")
322 && !line.trim().starts_with("class _")
323 {
324 let has_docstring = if i + 1 < lines.len() {
326 let next_line = lines[i + 1].trim();
327 next_line.starts_with("\"\"\"") || next_line.starts_with("'''")
328 } else {
329 false
330 };
331
332 result.push_str(line);
333 result.push('\n');
334
335 if !has_docstring {
336 let item_name = if let Some(start) = line.find("def ") {
338 let after_def = &line[start + 4..];
339 after_def.split('(').next().unwrap_or("item")
340 } else if let Some(start) = line.find("class ") {
341 let after_class = &line[start + 6..];
342 after_class.split('(').next().unwrap_or("item")
343 } else {
344 "item"
345 };
346
347 let indent = line.len() - line.trim_start().len();
349 result.push_str(&" ".repeat(indent + 4));
350 result.push_str(&format!("\"\"\"{}.\"\"\"\n", item_name));
351 }
352
353 i += 1;
354 continue;
355 }
356
357 result.push_str(line);
358 result.push('\n');
359 i += 1;
360 }
361
362 result
363 }
364
365 fn enforce_python_naming(&self, content: &str) -> String {
367 content.to_string()
369 }
370
371 fn enforce_python_error_handling(&self, content: &str) -> String {
373 content.to_string()
375 }
376
377 fn enforce_generic_quality(&self, content: &str) -> Result<String, GenerationError> {
379 Ok(content.to_string())
381 }
382
383 pub fn check_doc_comments(&self, content: &str, language: &str) -> Vec<String> {
385 let mut issues = Vec::new();
386
387 match language {
388 "rust" => {
389 for line in content.lines() {
390 if (line.contains("pub fn ")
391 || line.contains("pub struct ")
392 || line.contains("pub enum "))
393 && !line.trim().starts_with("///")
394 {
395 issues.push(format!("Missing doc comment: {}", line.trim()));
396 }
397 }
398 }
399 "typescript" | "ts" => {
400 for line in content.lines() {
401 if (line.contains("export function ") || line.contains("export class "))
402 && !line.trim().starts_with("/**")
403 {
404 issues.push(format!("Missing JSDoc comment: {}", line.trim()));
405 }
406 }
407 }
408 "python" | "py" => {
409 for line in content.lines() {
410 if (line.trim().starts_with("def ") || line.trim().starts_with("class "))
411 && !line.trim().starts_with("def _")
412 && !line.trim().starts_with("class _")
413 {
414 issues.push(format!("Missing docstring: {}", line.trim()));
415 }
416 }
417 }
418 _ => {}
419 }
420
421 issues
422 }
423
424 pub fn check_error_handling(&self, content: &str, language: &str) -> Vec<String> {
426 let mut issues = Vec::new();
427
428 match language {
429 "rust" => {
430 for (idx, line) in content.lines().enumerate() {
432 if line.contains(".unwrap()") {
433 issues.push(format!("Line {}: Unsafe unwrap() call", idx + 1));
434 }
435 }
436 }
437 "typescript" | "ts" => {
438 for (idx, line) in content.lines().enumerate() {
440 if line.contains("throw ") && !line.contains("Error") {
441 issues.push(format!(
442 "Line {}: Generic throw without Error type",
443 idx + 1
444 ));
445 }
446 }
447 }
448 "python" | "py" => {
449 for (idx, line) in content.lines().enumerate() {
451 if line.trim() == "except:" {
452 issues.push(format!("Line {}: Bare except clause", idx + 1));
453 }
454 }
455 }
456 _ => {}
457 }
458
459 issues
460 }
461}
462
463impl Default for CodeQualityEnforcer {
464 fn default() -> Self {
465 Self::new()
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472
473 #[test]
474 fn test_add_rust_doc_comments() {
475 let enforcer = CodeQualityEnforcer::new();
476 let content = "pub fn hello() {\n println!(\"Hello\");\n}";
477 let enhanced = enforcer.add_rust_doc_comments(content);
478
479 assert!(enhanced.contains("///"));
480 assert!(enhanced.contains("hello"));
481 }
482
483 #[test]
484 fn test_add_typescript_doc_comments() {
485 let enforcer = CodeQualityEnforcer::new();
486 let content = "export function hello() {\n console.log(\"Hello\");\n}";
487 let enhanced = enforcer.add_typescript_doc_comments(content);
488
489 assert!(enhanced.contains("/**"));
490 assert!(enhanced.contains("hello"));
491 }
492
493 #[test]
494 fn test_add_python_doc_comments() {
495 let enforcer = CodeQualityEnforcer::new();
496 let content = "def hello():\n print(\"Hello\")";
497 let enhanced = enforcer.add_python_doc_comments(content);
498
499 assert!(enhanced.contains("\"\"\""));
500 assert!(enhanced.contains("hello"));
501 }
502
503 #[test]
504 fn test_check_doc_comments_rust() {
505 let enforcer = CodeQualityEnforcer::new();
506 let content = "pub fn hello() {}";
507 let issues = enforcer.check_doc_comments(content, "rust");
508
509 assert!(!issues.is_empty());
510 assert!(issues[0].contains("Missing doc comment"));
511 }
512
513 #[test]
514 fn test_check_error_handling_rust() {
515 let enforcer = CodeQualityEnforcer::new();
516 let content = "let x = result.unwrap();";
517 let issues = enforcer.check_error_handling(content, "rust");
518
519 assert!(!issues.is_empty());
520 assert!(issues[0].contains("unwrap"));
521 }
522
523 #[test]
524 fn test_enforce_file_rust() {
525 let enforcer = CodeQualityEnforcer::new();
526 let file = GeneratedFile {
527 path: "src/main.rs".to_string(),
528 content: "pub fn hello() {}".to_string(),
529 language: "rust".to_string(),
530 };
531
532 let enhanced = enforcer.enforce_file(&file).unwrap();
533 assert!(enhanced.content.contains("///"));
534 }
535}