ricecoder_generation/
code_quality_enforcer.rs

1//! Code quality enforcement for generated code
2//!
3//! Ensures generated code follows project standards including:
4//! - Doc comments for all public types and functions
5//! - Language-specific naming conventions
6//! - Error handling for fallible operations
7//! - Optional unit test generation
8
9use crate::error::GenerationError;
10use crate::models::GeneratedFile;
11
12/// Configuration for code quality enforcement
13#[derive(Debug, Clone)]
14pub struct CodeQualityConfig {
15    /// Whether to require doc comments for public items
16    pub require_doc_comments: bool,
17    /// Whether to generate unit tests
18    pub generate_tests: bool,
19    /// Whether to enforce naming conventions
20    pub enforce_naming: bool,
21    /// Whether to enforce error handling
22    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/// Enforces code quality standards on generated code
37#[derive(Debug, Clone)]
38pub struct CodeQualityEnforcer {
39    /// Configuration for quality enforcement
40    config: CodeQualityConfig,
41}
42
43impl CodeQualityEnforcer {
44    /// Creates a new CodeQualityEnforcer with default configuration
45    pub fn new() -> Self {
46        Self {
47            config: CodeQualityConfig::default(),
48        }
49    }
50
51    /// Creates a new CodeQualityEnforcer with custom configuration
52    pub fn with_config(config: CodeQualityConfig) -> Self {
53        Self { config }
54    }
55
56    /// Enforces code quality standards on generated files
57    ///
58    /// # Arguments
59    /// * `files` - The generated files to enforce quality on
60    ///
61    /// # Returns
62    /// A vector of enhanced files with quality improvements
63    ///
64    /// # Errors
65    /// Returns `GenerationError` if quality enforcement fails
66    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    /// Enforces quality standards on a single file
81    pub fn enforce_file(&self, file: &GeneratedFile) -> Result<GeneratedFile, GenerationError> {
82        let mut content = file.content.clone();
83
84        // Apply quality standards based on language
85        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                // For unknown languages, apply generic quality standards
97                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    /// Enforces Rust-specific quality standards
109    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    /// Adds doc comments to Rust public items
128    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            // Check if this is a public item without doc comments
137            if (line.contains("pub fn ")
138                || line.contains("pub struct ")
139                || line.contains("pub enum "))
140                && !line.trim().starts_with("///")
141            {
142                // Check if previous line has doc comment
143                let has_doc = if i > 0 {
144                    lines[i - 1].trim().starts_with("///")
145                } else {
146                    false
147                };
148
149                if !has_doc {
150                    // Extract the item name
151                    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                    // Add doc comment
167                    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    /// Enforces Rust naming conventions
182    fn enforce_rust_naming(&self, content: &str) -> String {
183        // This is a simplified implementation
184        // In a real implementation, we would use a proper parser
185        content.to_string()
186    }
187
188    /// Enforces Rust error handling patterns
189    fn enforce_rust_error_handling(&self, content: &str) -> String {
190        let result = content.to_string();
191
192        // Check for functions that should return Result
193        if result.contains("fn ") && result.contains("io::") {
194            // This is a simplified check - in reality we'd need proper parsing
195            if !result.contains("Result<") && !result.contains("-> ") {
196                // Function likely needs error handling
197            }
198        }
199
200        result
201    }
202
203    /// Enforces TypeScript-specific quality standards
204    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    /// Adds JSDoc comments to TypeScript public items
223    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            // Check if this is a public export without JSDoc
232            if (line.contains("export function ")
233                || line.contains("export class ")
234                || line.contains("export interface "))
235                && !line.trim().starts_with("/**")
236            {
237                // Check if previous line has JSDoc
238                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                    // Extract the item name
246                    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                    // Add JSDoc comment
261                    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    /// Enforces TypeScript naming conventions
280    fn enforce_typescript_naming(&self, content: &str) -> String {
281        // This is a simplified implementation
282        content.to_string()
283    }
284
285    /// Enforces TypeScript error handling patterns
286    fn enforce_typescript_error_handling(&self, content: &str) -> String {
287        // This is a simplified implementation
288        content.to_string()
289    }
290
291    /// Enforces Python-specific quality standards
292    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    /// Adds docstrings to Python public items
311    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            // Check if this is a function or class definition
320            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                // Check if next line has docstring
325                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                    // Extract the item name
337                    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                    // Add docstring
348                    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    /// Enforces Python naming conventions
366    fn enforce_python_naming(&self, content: &str) -> String {
367        // This is a simplified implementation
368        content.to_string()
369    }
370
371    /// Enforces Python error handling patterns
372    fn enforce_python_error_handling(&self, content: &str) -> String {
373        // This is a simplified implementation
374        content.to_string()
375    }
376
377    /// Enforces generic quality standards for unknown languages
378    fn enforce_generic_quality(&self, content: &str) -> Result<String, GenerationError> {
379        // For unknown languages, return content as-is
380        Ok(content.to_string())
381    }
382
383    /// Checks if code has doc comments for public items
384    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    /// Checks if code has proper error handling
425    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                // Check for unwrap() calls
431                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                // Check for missing error handling
439                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                // Check for bare except
450                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}