Skip to main content

perl_tdd_support/tdd/
test_generator.rs

1//! Test generator for TDD workflow support
2//!
3//! This module provides automatic test generation for Perl code,
4//! supporting the red-green-refactor cycle of Test-Driven Development.
5//!
6//! ## LSP Workflow Integration
7//!
8//! Test generation operates within the **LSP workflow**:
9//! **Parse → Index → Navigate → Complete → Analyze**
10//!
11//! - **Parse Stage**: Analyzes Perl functions/methods to determine test requirements
12//! - **Index Stage**: Standardizes test patterns and identifies edge cases
13//! - **Navigate Stage**: Links related test cases and maintains test dependency relationships
14//! - **Complete Stage**: Generates test code using appropriate test frameworks (Test::More, Test2::V0)
15//! - **Analyze Stage**: Updates test coverage metrics and integrates with workspace symbols
16//!
17//! Essential for maintaining high-quality test coverage in enterprise Perl development
18//! systems where reliability and correctness are critical business requirements.
19//!
20//! ## Usage Examples
21//!
22//! ```rust,ignore
23//! use perl_parser::test_generator::{TestGenerator, TestFramework};
24//! use perl_parser::{Parser, ast::Node};
25//!
26//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
27//! let mut parser = Parser::new("sub add { my ($a, $b) = @_; return $a + $b; }");
28//! let ast = parser.parse()?;
29//! let generator = TestGenerator::new(TestFramework::TestMore);
30//! let test_cases = generator.generate_tests(&ast, "sub add { my ($a, $b) = @_; return $a + $b; }");
31//! println!("Generated {} test cases", test_cases.len());
32//! # Ok(())
33//! # }
34//! ```
35
36use crate::ast::{Node, NodeKind};
37use std::collections::HashMap;
38
39/// Test generator for creating unit tests from code
40///
41/// Automatically generates comprehensive test suites with support for multiple
42/// test frameworks and TDD workflow patterns.
43pub struct TestGenerator {
44    /// Test framework to use (Test::More, Test2::V0, etc.)
45    framework: TestFramework,
46    /// Options for test generation
47    options: TestGeneratorOptions,
48}
49
50/// Supported Perl test frameworks
51///
52/// Different test frameworks provide varying levels of functionality and syntax.
53#[derive(Debug, Clone, PartialEq)]
54pub enum TestFramework {
55    /// Test::More - Classic Perl testing framework
56    TestMore,
57    /// Test2::V0 - Modern Test2 ecosystem
58    Test2V0,
59    /// Test::Simple - Basic testing framework
60    TestSimple,
61    /// Test::Class - Class-based testing framework
62    TestClass,
63}
64
65/// Configuration options for test generation
66///
67/// Controls various aspects of test generation including coverage,
68/// performance testing, and mock usage.
69#[derive(Debug, Clone)]
70pub struct TestGeneratorOptions {
71    /// Generate tests for private methods
72    pub test_private: bool,
73    /// Generate edge case tests
74    pub edge_cases: bool,
75    /// Generate mock objects for dependencies
76    pub use_mocks: bool,
77    /// Generate data-driven tests
78    pub data_driven: bool,
79    /// Generate performance tests
80    pub perf_tests: bool,
81    /// Expected return values for generated tests
82    pub expected_values: HashMap<String, String>,
83    /// Performance thresholds in seconds for benchmarks
84    pub perf_thresholds: HashMap<String, f64>,
85}
86
87impl Default for TestGeneratorOptions {
88    fn default() -> Self {
89        Self {
90            test_private: false,
91            edge_cases: true,
92            use_mocks: true,
93            data_driven: true,
94            perf_tests: false,
95            expected_values: HashMap::new(),
96            perf_thresholds: HashMap::new(),
97        }
98    }
99}
100
101/// Generated test case
102#[derive(Debug, Clone)]
103pub struct TestCase {
104    /// Unique identifier for the test (e.g., "test_add")
105    pub name: String,
106    /// Human-readable description of what the test validates
107    pub description: String,
108    /// Generated Perl test code
109    pub code: String,
110    /// Whether the test is marked as TODO (needs user input)
111    pub is_todo: bool,
112}
113
114impl TestGenerator {
115    /// Create a new test generator for Perl parsing workflow test automation
116    ///
117    /// # Arguments
118    ///
119    /// * `framework` - Test framework to use for generating test code
120    ///
121    /// # Returns
122    ///
123    /// A configured test generator ready for Perl script test generation
124    ///
125    /// # Examples
126    ///
127    /// ```rust,ignore
128    /// use perl_parser::{TestGenerator, TestFramework};
129    ///
130    /// let generator = TestGenerator::new(TestFramework::TestMore);
131    /// // Generator ready for Perl parsing workflow test generation
132    /// ```
133    pub fn new(framework: TestFramework) -> Self {
134        Self { framework, options: TestGeneratorOptions::default() }
135    }
136
137    /// Create a new test generator with custom options
138    ///
139    /// # Arguments
140    ///
141    /// * `framework` - Test framework to use for generating test code
142    /// * `options` - Custom configuration options for test generation
143    pub fn with_options(framework: TestFramework, options: TestGeneratorOptions) -> Self {
144        Self { framework, options }
145    }
146
147    /// Generate tests for a given AST
148    pub fn generate_tests(&self, ast: &Node, source: &str) -> Vec<TestCase> {
149        let mut tests = Vec::new();
150
151        // Find all subroutines
152        let subs = self.find_subroutines(ast);
153
154        for sub in subs {
155            // Generate basic test
156            tests.push(self.generate_basic_test(&sub, source));
157
158            // Generate edge case tests if enabled
159            if self.options.edge_cases {
160                tests.extend(self.generate_edge_cases(&sub, source));
161            }
162
163            // Generate data-driven tests if enabled
164            if self.options.data_driven {
165                if let Some(test) = self.generate_data_driven_test(&sub, source) {
166                    tests.push(test);
167                }
168            }
169
170            // Generate performance test if enabled
171            if self.options.perf_tests {
172                tests.push(self.generate_perf_test(&sub, source));
173            }
174        }
175
176        // Generate module-level tests
177        tests.extend(self.generate_module_tests(ast, source));
178
179        tests
180    }
181
182    /// Find all subroutines in the AST
183    fn find_subroutines(&self, node: &Node) -> Vec<SubroutineInfo> {
184        let mut subs = Vec::new();
185        self.find_subroutines_recursive(node, &mut subs);
186        subs
187    }
188
189    fn find_subroutines_recursive(&self, node: &Node, subs: &mut Vec<SubroutineInfo>) {
190        match &node.kind {
191            NodeKind::Subroutine { name, signature, .. } => {
192                if let Some(name) = name {
193                    let is_private = name.starts_with('_');
194                    if !is_private || self.options.test_private {
195                        // Extract parameters from signature
196                        let params = self.extract_parameters(signature.as_deref());
197                        subs.push(SubroutineInfo {
198                            name: name.clone(),
199                            params,
200                            node: node.clone(),
201                            is_private,
202                        });
203                    }
204                }
205            }
206            _ => {
207                for child in node.children() {
208                    self.find_subroutines_recursive(child, subs);
209                }
210            }
211        }
212    }
213
214    /// Generate a basic test for a subroutine
215    fn generate_basic_test(&self, sub: &SubroutineInfo, _source: &str) -> TestCase {
216        let test_name = format!("test_{}", sub.name);
217        let description = format!("Basic test for {}", sub.name);
218
219        let code = match self.framework {
220            TestFramework::TestMore => self.generate_test_more_basic(&sub.name, &sub.params),
221            TestFramework::Test2V0 => self.generate_test2_basic(&sub.name, &sub.params),
222            TestFramework::TestSimple => self.generate_test_simple_basic(&sub.name, &sub.params),
223            TestFramework::TestClass => self.generate_test_class_basic(&sub.name, &sub.params),
224        };
225
226        TestCase { name: test_name, description, code, is_todo: false }
227    }
228
229    fn generate_test_more_basic(&self, name: &str, params: &Option<Vec<String>>) -> String {
230        let mut code = String::new();
231        code.push_str("use Test::More;\n\n");
232
233        code.push_str(&format!("subtest '{}' => sub {{\n", name));
234
235        if let Some(params) = params {
236            let args = self.generate_sample_args(params.len());
237            code.push_str(&format!("    my $result = {}({});\n", name, args));
238        } else {
239            code.push_str(&format!("    my $result = {}();\n", name));
240        }
241
242        if let Some(expected) = self.options.expected_values.get(name) {
243            code.push_str(&format!("    is($result, {}, 'Returns expected value');\n", expected));
244        } else {
245            code.push_str("    ok(defined $result, 'Function returns defined value');\n");
246        }
247
248        if name.starts_with("is_") || name.starts_with("has_") {
249            code.push_str("    ok($result == 0 || $result == 1, 'Returns boolean');\n");
250        }
251
252        code.push_str("};\n");
253        code
254    }
255
256    fn generate_test2_basic(&self, name: &str, params: &Option<Vec<String>>) -> String {
257        let mut code = String::new();
258        code.push_str("use Test2::V0;\n\n");
259
260        code.push_str(&format!("subtest '{}' => sub {{\n", name));
261
262        if let Some(params) = params {
263            let args = self.generate_sample_args(params.len());
264            code.push_str(&format!("    my $result = {}({});\n", name, args));
265            code.push_str("    ok($result, 'Function returns value');\n");
266        } else {
267            code.push_str(&format!("    my $result = {}();\n", name));
268            code.push_str("    ok($result, 'Function returns value');\n");
269        }
270
271        code.push_str("};\n");
272        code
273    }
274
275    fn generate_test_simple_basic(&self, name: &str, params: &Option<Vec<String>>) -> String {
276        let mut code = String::new();
277        code.push_str("use Test::Simple tests => 1;\n\n");
278
279        if let Some(params) = params {
280            let args = self.generate_sample_args(params.len());
281            code.push_str(&format!("ok({}({}), 'Test {}');\n", name, args, name));
282        } else {
283            code.push_str(&format!("ok({}(), 'Test {}');\n", name, name));
284        }
285
286        code
287    }
288
289    fn generate_test_class_basic(&self, name: &str, params: &Option<Vec<String>>) -> String {
290        let mut code = String::new();
291        code.push_str("use Test::Class::Most;\n\n");
292
293        code.push_str(&format!("sub test_{} : Test {{\n", name));
294        code.push_str("    my $self = shift;\n");
295
296        if let Some(params) = params {
297            let args = self.generate_sample_args(params.len());
298            code.push_str(&format!("    my $result = $self->{}({});\n", name, args));
299        } else {
300            code.push_str(&format!("    my $result = $self->{}();\n", name));
301        }
302
303        code.push_str("    ok($result, 'Function works');\n");
304        code.push_str("}\n");
305
306        code
307    }
308
309    /// Generate edge case tests
310    fn generate_edge_cases(&self, sub: &SubroutineInfo, _source: &str) -> Vec<TestCase> {
311        let mut tests = Vec::new();
312
313        if sub.params.is_some() {
314            // Test with undef parameters
315            tests.push(self.generate_undef_test(sub));
316
317            // Test with empty parameters
318            tests.push(self.generate_empty_test(sub));
319
320            // Test with wrong type parameters
321            tests.push(self.generate_type_test(sub));
322        }
323
324        tests
325    }
326
327    fn generate_undef_test(&self, sub: &SubroutineInfo) -> TestCase {
328        let test_name = format!("test_{}_undef", sub.name);
329        let description = format!("Test {} with undef parameters", sub.name);
330
331        let code = match self.framework {
332            TestFramework::TestMore => {
333                format!(
334                    "use Test::More;\n\n\
335                     subtest '{} with undef' => sub {{\n    \
336                     eval {{ {}(undef) }};\n    \
337                     ok(!$@, 'Handles undef gracefully');\n\
338                     }};\n",
339                    sub.name, sub.name
340                )
341            }
342            _ => String::new(), // Simplified for other frameworks
343        };
344
345        TestCase { name: test_name, description, code, is_todo: false }
346    }
347
348    fn generate_empty_test(&self, sub: &SubroutineInfo) -> TestCase {
349        let test_name = format!("test_{}_empty", sub.name);
350        let description = format!("Test {} with empty parameters", sub.name);
351
352        let code = match self.framework {
353            TestFramework::TestMore => {
354                format!(
355                    "use Test::More;\n\n\
356                     subtest '{} with empty params' => sub {{\n    \
357                     eval {{ {}('', [], {{}}) }};\n    \
358                     ok(!$@, 'Handles empty values');\n\
359                     }};\n",
360                    sub.name, sub.name
361                )
362            }
363            _ => String::new(),
364        };
365
366        TestCase { name: test_name, description, code, is_todo: false }
367    }
368
369    fn generate_type_test(&self, sub: &SubroutineInfo) -> TestCase {
370        let test_name = format!("test_{}_types", sub.name);
371        let description = format!("Test {} with different types", sub.name);
372
373        let code = match self.framework {
374            TestFramework::TestMore => {
375                format!(
376                    "use Test::More;\n\n\
377                     subtest '{} type checking' => sub {{\n    \
378                     # Test with different types\n    \
379                     eval {{ {}(123) }};\n    \
380                     eval {{ {}('string') }};\n    \
381                     eval {{ {}([1,2,3]) }};\n    \
382                     eval {{ {}({{a=>1}}) }};\n    \
383                     pass('Handles different types');\n\
384                     }};\n",
385                    sub.name, sub.name, sub.name, sub.name, sub.name
386                )
387            }
388            _ => String::new(),
389        };
390
391        TestCase { name: test_name, description, code, is_todo: false }
392    }
393
394    /// Generate data-driven test
395    fn generate_data_driven_test(&self, sub: &SubroutineInfo, _source: &str) -> Option<TestCase> {
396        sub.params.as_ref()?;
397
398        let test_name = format!("test_{}_data_driven", sub.name);
399        let description = format!("Data-driven test for {}", sub.name);
400
401        let code = match self.framework {
402            TestFramework::TestMore => {
403                format!(
404                    "use Test::More;\n\n\
405                     my @test_cases = (\n    \
406                     {{ input => 'test1', expected => 'result1' }},\n    \
407                     {{ input => 'test2', expected => 'result2' }},\n    \
408                     {{ input => 'test3', expected => 'result3' }},\n\
409                     );\n\n\
410                     for my $case (@test_cases) {{\n    \
411                     my $result = {}($case->{{input}});\n    \
412                     is($result, $case->{{expected}}, \n       \
413                     \"{}($case->{{input}}) returns $case->{{expected}}\");\n\
414                     }}\n",
415                    sub.name, sub.name
416                )
417            }
418            _ => String::new(),
419        };
420
421        Some(TestCase {
422            name: test_name,
423            description,
424            code,
425            is_todo: true, // Mark as TODO since user needs to fill in test data
426        })
427    }
428
429    /// Generate performance test
430    fn generate_perf_test(&self, sub: &SubroutineInfo, _source: &str) -> TestCase {
431        let test_name = format!("test_{}_performance", sub.name);
432        let description = format!("Performance test for {}", sub.name);
433
434        let code = match self.framework {
435            TestFramework::TestMore => {
436                let mut snippet = String::new();
437                snippet.push_str("use Test::More;\n");
438                snippet.push_str("use Benchmark qw(timeit);\n\n");
439                snippet.push_str(&format!("subtest '{} performance' => sub {{\n", sub.name));
440                snippet.push_str(&format!(
441                    "    my $result = timeit(1000, sub {{ {}() }});\n",
442                    sub.name
443                ));
444                if let Some(threshold) = self.options.perf_thresholds.get(&sub.name) {
445                    snippet.push_str(&format!(
446                        "    cmp_ok($result->real, '<', {}, 'Executes under threshold');\n",
447                        threshold
448                    ));
449                } else {
450                    snippet.push_str("    ok($result->real >= 0, 'Execution time recorded');\n");
451                }
452                snippet.push_str("};\n");
453                snippet
454            }
455            _ => String::new(),
456        };
457
458        TestCase { name: test_name, description, code, is_todo: true }
459    }
460
461    /// Generate module-level tests
462    fn generate_module_tests(&self, ast: &Node, _source: &str) -> Vec<TestCase> {
463        let mut tests = Vec::new();
464
465        // Find package declaration
466        if let Some(package_name) = self.find_package_name(ast) {
467            // Generate module load test
468            tests.push(self.generate_module_load_test(&package_name));
469
470            // Generate export test if module exports functions
471            if self.has_exports(ast) {
472                tests.push(self.generate_export_test(&package_name));
473            }
474
475            // Generate new() test if it's an OO module
476            if self.has_constructor(ast) {
477                tests.push(self.generate_constructor_test(&package_name));
478            }
479        }
480
481        tests
482    }
483
484    fn find_package_name(&self, node: &Node) -> Option<String> {
485        match &node.kind {
486            NodeKind::Package { name, .. } => Some(name.clone()),
487            _ => {
488                for child in node.children() {
489                    if let Some(name) = self.find_package_name(child) {
490                        return Some(name);
491                    }
492                }
493                None
494            }
495        }
496    }
497
498    fn has_exports(&self, node: &Node) -> bool {
499        // Check if module uses Exporter
500        self.find_use_statement(node, "Exporter").is_some()
501    }
502
503    fn has_constructor(&self, node: &Node) -> bool {
504        // Check if module has a new() method
505        self.find_subroutine(node, "new").is_some()
506    }
507
508    fn find_use_statement(&self, node: &Node, module: &str) -> Option<Node> {
509        match &node.kind {
510            NodeKind::Use { module: m, .. } if m == module => Some(node.clone()),
511            _ => {
512                for child in node.children() {
513                    if let Some(result) = self.find_use_statement(child, module) {
514                        return Some(result);
515                    }
516                }
517                None
518            }
519        }
520    }
521
522    fn find_subroutine(&self, node: &Node, name: &str) -> Option<Node> {
523        match &node.kind {
524            NodeKind::Subroutine { name: Some(n), .. } if n == name => Some(node.clone()),
525            _ => {
526                for child in node.children() {
527                    if let Some(result) = self.find_subroutine(child, name) {
528                        return Some(result);
529                    }
530                }
531                None
532            }
533        }
534    }
535
536    fn generate_module_load_test(&self, package: &str) -> TestCase {
537        let test_name = "test_module_loads".to_string();
538        let description = format!("Test that {} loads correctly", package);
539
540        let code = match self.framework {
541            TestFramework::TestMore => {
542                format!(
543                    "use Test::More;\n\n\
544                     BEGIN {{\n    \
545                     use_ok('{}') || print \"Bail out!\\n\";\n\
546                     }}\n\n\
547                     diag(\"Testing {} ${}::VERSION, Perl $], $^X\");\n",
548                    package, package, package
549                )
550            }
551            _ => String::new(),
552        };
553
554        TestCase { name: test_name, description, code, is_todo: false }
555    }
556
557    fn generate_export_test(&self, package: &str) -> TestCase {
558        let test_name = "test_exports".to_string();
559        let description = format!("Test {} exports", package);
560
561        let code = match self.framework {
562            TestFramework::TestMore => {
563                format!(
564                    "use Test::More;\n\
565                     use {};\n\n\
566                     can_ok('main', @{}::EXPORT);\n",
567                    package, package
568                )
569            }
570            _ => String::new(),
571        };
572
573        TestCase { name: test_name, description, code, is_todo: false }
574    }
575
576    fn generate_constructor_test(&self, package: &str) -> TestCase {
577        let test_name = "test_constructor".to_string();
578        let description = format!("Test {} constructor", package);
579
580        let code = match self.framework {
581            TestFramework::TestMore => {
582                format!(
583                    "use Test::More;\n\
584                     use {};\n\n\
585                     subtest 'constructor' => sub {{\n    \
586                     my $obj = {}->new();\n    \
587                     isa_ok($obj, '{}');\n    \
588                     can_ok($obj, 'new');\n\
589                     }};\n",
590                    package, package, package
591                )
592            }
593            _ => String::new(),
594        };
595
596        TestCase { name: test_name, description, code, is_todo: false }
597    }
598
599    fn generate_sample_args(&self, count: usize) -> String {
600        let args: Vec<String> = (0..count).map(|i| format!("'arg{}'", i + 1)).collect();
601        args.join(", ")
602    }
603
604    /// Extract parameters from a subroutine signature
605    fn extract_parameters(&self, signature: Option<&Node>) -> Option<Vec<String>> {
606        if let Some(sig) = signature {
607            if let NodeKind::Signature { parameters } = &sig.kind {
608                let mut param_names = Vec::new();
609                for param in parameters {
610                    match &param.kind {
611                        NodeKind::MandatoryParameter { variable }
612                        | NodeKind::OptionalParameter { variable, .. } => {
613                            if let NodeKind::Variable { name, .. } = &variable.kind {
614                                param_names.push(name.clone());
615                            }
616                        }
617                        _ => {}
618                    }
619                }
620                if param_names.is_empty() { None } else { Some(param_names) }
621            } else {
622                None
623            }
624        } else {
625            None
626        }
627    }
628}
629
630#[derive(Debug, Clone)]
631struct SubroutineInfo {
632    name: String,
633    params: Option<Vec<String>>,
634    #[allow(dead_code)]
635    node: Node,
636    #[allow(dead_code)]
637    is_private: bool,
638}
639
640/// Test runner integration for executing Perl tests
641///
642/// Provides functionality for running test suites, watch mode for continuous
643/// testing, and coverage tracking integration.
644pub struct TestRunner {
645    /// Command to run tests (e.g., "prove -l")
646    test_command: String,
647    /// Whether watch mode is enabled for continuous testing
648    watch_mode: bool,
649    /// Whether coverage tracking is enabled
650    coverage: bool,
651}
652
653impl Default for TestRunner {
654    fn default() -> Self {
655        Self::new()
656    }
657}
658
659impl TestRunner {
660    /// Create a new test runner with default settings
661    ///
662    /// Uses "prove -l" as the default test command.
663    pub fn new() -> Self {
664        Self { test_command: "prove -l".to_string(), watch_mode: false, coverage: false }
665    }
666
667    /// Create a new test runner with a custom command
668    ///
669    /// # Arguments
670    ///
671    /// * `command` - The shell command to execute tests (e.g., "prove -v -l")
672    pub fn with_command(command: String) -> Self {
673        Self { test_command: command, watch_mode: false, coverage: false }
674    }
675
676    /// Run tests and return results
677    pub fn run_tests(&self, test_files: &[String]) -> TestResults {
678        let mut results = TestResults::default();
679
680        // Build command
681        let mut cmd = self.test_command.clone();
682
683        if self.coverage {
684            cmd = format!("cover -test {}", cmd);
685        }
686
687        for file in test_files {
688            cmd.push(' ');
689            cmd.push_str(file);
690        }
691
692        // Execute tests (simplified - would use std::process::Command in real impl)
693        results.total = test_files.len();
694        results.passed = test_files.len(); // Assume all pass for now
695
696        results
697    }
698
699    /// Run tests in watch mode
700    pub fn watch(&self, _test_files: &[String]) -> Result<(), String> {
701        if !self.watch_mode {
702            return Err("Watch mode not enabled".to_string());
703        }
704
705        // Would implement file watching here
706        Ok(())
707    }
708
709    /// Get test coverage
710    pub fn get_coverage(&self) -> Option<CoverageReport> {
711        if !self.coverage {
712            return None;
713        }
714
715        Some(CoverageReport {
716            line_coverage: 85.0,
717            branch_coverage: 75.0,
718            function_coverage: 90.0,
719            uncovered_lines: vec![],
720        })
721    }
722}
723
724/// Results from executing a test suite
725#[derive(Debug, Default, Clone)]
726pub struct TestResults {
727    /// Total number of tests executed
728    pub total: usize,
729    /// Number of tests that passed
730    pub passed: usize,
731    /// Number of tests that failed
732    pub failed: usize,
733    /// Number of tests that were skipped
734    pub skipped: usize,
735    /// Number of tests marked as TODO
736    pub todo: usize,
737    /// Error messages from failed tests
738    pub errors: Vec<String>,
739}
740
741/// Code coverage metrics from test execution
742#[derive(Debug)]
743pub struct CoverageReport {
744    /// Percentage of lines covered (0.0 to 100.0)
745    pub line_coverage: f64,
746    /// Percentage of branches covered (0.0 to 100.0)
747    pub branch_coverage: f64,
748    /// Percentage of functions covered (0.0 to 100.0)
749    pub function_coverage: f64,
750    /// Line numbers that were not covered by tests
751    pub uncovered_lines: Vec<usize>,
752}
753
754/// Refactoring suggestions for the green-to-refactor phase
755///
756/// Analyzes code to identify opportunities for improvement such as
757/// duplicate code, complex methods, and naming issues.
758pub struct RefactoringSuggester {
759    /// Collection of refactoring suggestions found during analysis
760    suggestions: Vec<RefactoringSuggestion>,
761}
762
763/// A specific refactoring suggestion with context and actions
764#[derive(Debug, Clone)]
765pub struct RefactoringSuggestion {
766    /// Short title summarizing the issue
767    pub title: String,
768    /// Detailed description of the problem and recommended fix
769    pub description: String,
770    /// Importance level for prioritizing refactoring efforts
771    pub priority: Priority,
772    /// Type of refactoring suggested
773    pub category: RefactoringCategory,
774    /// LSP code action identifier (e.g., "extract_method", "rename")
775    pub code_action: Option<String>,
776}
777
778/// Priority level for refactoring suggestions
779#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
780pub enum Priority {
781    /// Minor improvement, can be addressed when convenient
782    Low,
783    /// Moderate issue that should be addressed
784    Medium,
785    /// Important issue requiring attention soon
786    High,
787    /// Urgent issue that should be fixed immediately
788    Critical,
789}
790
791/// Categories of refactoring suggestions
792#[derive(Debug, Clone, PartialEq)]
793pub enum RefactoringCategory {
794    /// Code that appears in multiple places and should be extracted
795    DuplicateCode,
796    /// Method with high cyclomatic complexity
797    ComplexMethod,
798    /// Method with too many lines of code
799    LongMethod,
800    /// Function with excessive parameter count
801    TooManyParameters,
802    /// Unreachable or unused code
803    DeadCode,
804    /// Inefficient code that could be optimized
805    Performance,
806    /// Poorly named variables or functions
807    Naming,
808    /// Structural issues with code organization
809    Structure,
810}
811
812impl Default for RefactoringSuggester {
813    fn default() -> Self {
814        Self::new()
815    }
816}
817
818impl RefactoringSuggester {
819    /// Create a new refactoring suggester
820    pub fn new() -> Self {
821        Self { suggestions: Vec::new() }
822    }
823
824    /// Analyze code and generate refactoring suggestions
825    pub fn analyze(&mut self, ast: &Node, source: &str) -> Vec<RefactoringSuggestion> {
826        self.suggestions.clear();
827
828        // Check for duplicate code
829        self.check_duplicate_code(ast, source);
830
831        // Check for complex methods
832        self.check_complex_methods(ast, source);
833
834        // Check for long methods
835        self.check_long_methods(ast, source);
836
837        // Check for too many parameters
838        self.check_parameter_count(ast);
839
840        // Check for naming issues
841        self.check_naming(ast);
842
843        // Sort by priority
844        self.suggestions.sort_by_key(|s| s.priority.clone());
845        self.suggestions.reverse();
846
847        self.suggestions.clone()
848    }
849
850    fn check_duplicate_code(&mut self, _ast: &Node, _source: &str) {
851        // Simplified duplicate detection
852        // In real implementation, would use similarity algorithms
853    }
854
855    fn check_complex_methods(&mut self, ast: &Node, _source: &str) {
856        self.check_complex_methods_recursive(ast);
857    }
858
859    fn check_complex_methods_recursive(&mut self, node: &Node) {
860        match &node.kind {
861            NodeKind::Subroutine { name, .. } => {
862                let complexity = self.calculate_cyclomatic_complexity(node);
863                if complexity > 10 {
864                    self.suggestions.push(RefactoringSuggestion {
865                        title: format!("High complexity in {}", name.as_ref().unwrap_or(&"anonymous".to_string())),
866                        description: format!("Cyclomatic complexity is {}. Consider breaking into smaller functions.", complexity),
867                        priority: if complexity > 20 { Priority::High } else { Priority::Medium },
868                        category: RefactoringCategory::ComplexMethod,
869                        code_action: Some("extract_method".to_string()),
870                    });
871                }
872            }
873            _ => {
874                for child in node.children() {
875                    self.check_complex_methods_recursive(child);
876                }
877            }
878        }
879    }
880
881    fn calculate_cyclomatic_complexity(&self, node: &Node) -> usize {
882        let mut complexity = 1;
883        self.count_decision_points(node, &mut complexity);
884        complexity
885    }
886
887    fn count_decision_points(&self, node: &Node, complexity: &mut usize) {
888        match &node.kind {
889            NodeKind::If { .. }
890            | NodeKind::While { .. }
891            | NodeKind::For { .. }
892            | NodeKind::Ternary { .. } => {
893                *complexity += 1;
894            }
895            NodeKind::Binary { op: operator, .. } => {
896                if operator == "&&" || operator == "||" || operator == "and" || operator == "or" {
897                    *complexity += 1;
898                }
899            }
900            _ => {}
901        }
902
903        for child in node.children() {
904            self.count_decision_points(child, complexity);
905        }
906    }
907
908    fn check_long_methods(&mut self, ast: &Node, source: &str) {
909        self.check_long_methods_recursive(ast, source);
910    }
911
912    fn check_long_methods_recursive(&mut self, node: &Node, source: &str) {
913        match &node.kind {
914            NodeKind::Subroutine { name, .. } => {
915                let lines = self.count_lines(node, source);
916                if lines > 50 {
917                    self.suggestions.push(RefactoringSuggestion {
918                        title: format!(
919                            "Long method: {}",
920                            name.as_ref().unwrap_or(&"anonymous".to_string())
921                        ),
922                        description: format!(
923                            "Method has {} lines. Consider breaking into smaller functions.",
924                            lines
925                        ),
926                        priority: if lines > 100 { Priority::High } else { Priority::Medium },
927                        category: RefactoringCategory::LongMethod,
928                        code_action: Some("extract_method".to_string()),
929                    });
930                }
931            }
932            _ => {
933                for child in node.children() {
934                    self.check_long_methods_recursive(child, source);
935                }
936            }
937        }
938    }
939
940    fn count_lines(&self, node: &Node, source: &str) -> usize {
941        let start = node.location.start;
942        let end = node.location.end;
943
944        let text = &source[start..end.min(source.len())];
945        text.lines().count()
946    }
947
948    fn check_parameter_count(&mut self, ast: &Node) {
949        self.check_parameter_count_recursive(ast);
950    }
951
952    fn check_parameter_count_recursive(&mut self, node: &Node) {
953        match &node.kind {
954            NodeKind::Subroutine { name, signature, .. } => {
955                let params = self.extract_parameters(signature.as_deref());
956                if let Some(params) = &params {
957                    if params.len() > 5 {
958                        self.suggestions.push(RefactoringSuggestion {
959                            title: format!(
960                                "Too many parameters in {}",
961                                name.as_ref().unwrap_or(&"anonymous".to_string())
962                            ),
963                            description: format!(
964                                "Function has {} parameters. Consider using a hash or object.",
965                                params.len()
966                            ),
967                            priority: Priority::Medium,
968                            category: RefactoringCategory::TooManyParameters,
969                            code_action: Some("introduce_parameter_object".to_string()),
970                        });
971                    }
972                }
973            }
974            _ => {
975                for child in node.children() {
976                    self.check_parameter_count_recursive(child);
977                }
978            }
979        }
980    }
981
982    fn check_naming(&mut self, ast: &Node) {
983        self.check_naming_recursive(ast);
984    }
985
986    fn check_naming_recursive(&mut self, node: &Node) {
987        match &node.kind {
988            NodeKind::Subroutine { name: Some(name), .. } => {
989                if !self.is_good_name(name) {
990                    self.suggestions.push(RefactoringSuggestion {
991                        title: format!("Poor naming: {}", name),
992                        description: "Consider using a more descriptive name".to_string(),
993                        priority: Priority::Low,
994                        category: RefactoringCategory::Naming,
995                        code_action: Some("rename".to_string()),
996                    });
997                }
998            }
999            NodeKind::VariableDeclaration { variable, .. } => {
1000                if let NodeKind::Variable { name, .. } = &variable.kind {
1001                    if !self.is_good_variable_name(name) {
1002                        self.suggestions.push(RefactoringSuggestion {
1003                            title: format!("Poor variable name: {}", name),
1004                            description:
1005                                "Single letter variables should only be used for loop counters"
1006                                    .to_string(),
1007                            priority: Priority::Low,
1008                            category: RefactoringCategory::Naming,
1009                            code_action: Some("rename".to_string()),
1010                        });
1011                    }
1012                }
1013            }
1014            NodeKind::VariableListDeclaration { variables, .. } => {
1015                for var_node in variables {
1016                    if let NodeKind::Variable { name, .. } = &var_node.kind {
1017                        if !self.is_good_variable_name(name) {
1018                            self.suggestions.push(RefactoringSuggestion {
1019                                title: format!("Poor variable name: {}", name),
1020                                description:
1021                                    "Single letter variables should only be used for loop counters"
1022                                        .to_string(),
1023                                priority: Priority::Low,
1024                                category: RefactoringCategory::Naming,
1025                                code_action: Some("rename".to_string()),
1026                            });
1027                        }
1028                    }
1029                }
1030            }
1031            _ => {
1032                for child in node.children() {
1033                    self.check_naming_recursive(child);
1034                }
1035            }
1036        }
1037    }
1038
1039    fn is_good_name(&self, name: &str) -> bool {
1040        // Check for meaningful names
1041        name.len() > 2 && !name.chars().all(|c| c.is_uppercase())
1042    }
1043
1044    fn is_good_variable_name(&self, name: &str) -> bool {
1045        // Allow single letters for common loop variables
1046        if name.len() == 1 {
1047            return matches!(name, "$i" | "$j" | "$k" | "$n" | "$_");
1048        }
1049
1050        // Remove sigil for checking
1051        let clean_name = name.trim_start_matches(['$', '@', '%', '*']);
1052        clean_name.len() > 1
1053    }
1054
1055    /// Extract parameters from a subroutine signature
1056    fn extract_parameters(&self, signature: Option<&Node>) -> Option<Vec<String>> {
1057        if let Some(sig) = signature {
1058            if let NodeKind::Signature { parameters } = &sig.kind {
1059                let mut param_names = Vec::new();
1060                for param in parameters {
1061                    match &param.kind {
1062                        NodeKind::MandatoryParameter { variable }
1063                        | NodeKind::OptionalParameter { variable, .. } => {
1064                            if let NodeKind::Variable { name, .. } = &variable.kind {
1065                                param_names.push(name.clone());
1066                            }
1067                        }
1068                        _ => {}
1069                    }
1070                }
1071                if param_names.is_empty() { None } else { Some(param_names) }
1072            } else {
1073                None
1074            }
1075        } else {
1076            None
1077        }
1078    }
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083    use super::*;
1084
1085    #[test]
1086    fn test_generate_basic_test() {
1087        let generator = TestGenerator::new(TestFramework::TestMore);
1088        let ast = Node::new(
1089            NodeKind::Subroutine {
1090                name: Some("add".to_string()),
1091                name_span: None,
1092                signature: Some(Box::new(Node::new(
1093                    NodeKind::Signature {
1094                        parameters: vec![
1095                            Node::new(
1096                                NodeKind::MandatoryParameter {
1097                                    variable: Box::new(Node::new(
1098                                        NodeKind::Variable {
1099                                            name: "$a".to_string(),
1100                                            sigil: "$".to_string(),
1101                                        },
1102                                        crate::ast::SourceLocation { start: 0, end: 0 },
1103                                    )),
1104                                },
1105                                crate::ast::SourceLocation { start: 0, end: 0 },
1106                            ),
1107                            Node::new(
1108                                NodeKind::MandatoryParameter {
1109                                    variable: Box::new(Node::new(
1110                                        NodeKind::Variable {
1111                                            name: "$b".to_string(),
1112                                            sigil: "$".to_string(),
1113                                        },
1114                                        crate::ast::SourceLocation { start: 0, end: 0 },
1115                                    )),
1116                                },
1117                                crate::ast::SourceLocation { start: 0, end: 0 },
1118                            ),
1119                        ],
1120                    },
1121                    crate::ast::SourceLocation { start: 0, end: 0 },
1122                ))),
1123                body: Box::new(Node::new(
1124                    NodeKind::Block { statements: vec![] },
1125                    crate::ast::SourceLocation { start: 0, end: 0 },
1126                )),
1127                attributes: vec![],
1128                prototype: None,
1129            },
1130            crate::ast::SourceLocation { start: 0, end: 0 },
1131        );
1132
1133        let tests = generator.generate_tests(&ast, "sub add { }");
1134        assert!(!tests.is_empty());
1135        assert!(tests[0].code.contains("Test::More"));
1136        assert!(tests[0].code.contains("add"));
1137    }
1138
1139    #[test]
1140    fn test_refactoring_suggestions() {
1141        let mut suggester = RefactoringSuggester::new();
1142
1143        // Create a complex subroutine
1144        let ast = Node::new(
1145            NodeKind::Subroutine {
1146                name: Some("complex_function".to_string()),
1147                name_span: None,
1148                signature: Some(Box::new(Node::new(
1149                    NodeKind::Signature {
1150                        parameters: (0..7)
1151                            .map(|i| {
1152                                Node::new(
1153                                    NodeKind::MandatoryParameter {
1154                                        variable: Box::new(Node::new(
1155                                            NodeKind::Variable {
1156                                                name: format!("$param{}", i),
1157                                                sigil: "$".to_string(),
1158                                            },
1159                                            crate::ast::SourceLocation { start: 0, end: 0 },
1160                                        )),
1161                                    },
1162                                    crate::ast::SourceLocation { start: 0, end: 0 },
1163                                )
1164                            })
1165                            .collect(),
1166                    },
1167                    crate::ast::SourceLocation { start: 0, end: 0 },
1168                ))),
1169                body: Box::new(Node::new(
1170                    NodeKind::Block {
1171                        statements: vec![
1172                            // Add some if statements to increase complexity
1173                            Node::new(
1174                                NodeKind::If {
1175                                    condition: Box::new(Node::new(
1176                                        NodeKind::Variable {
1177                                            name: "$a".to_string(),
1178                                            sigil: "$".to_string(),
1179                                        },
1180                                        crate::ast::SourceLocation { start: 0, end: 0 },
1181                                    )),
1182                                    then_branch: Box::new(Node::new(
1183                                        NodeKind::Block { statements: vec![] },
1184                                        crate::ast::SourceLocation { start: 0, end: 0 },
1185                                    )),
1186                                    elsif_branches: vec![],
1187                                    else_branch: None,
1188                                },
1189                                crate::ast::SourceLocation { start: 0, end: 0 },
1190                            ),
1191                        ],
1192                    },
1193                    crate::ast::SourceLocation { start: 0, end: 0 },
1194                )),
1195                attributes: vec![],
1196                prototype: None,
1197            },
1198            crate::ast::SourceLocation { start: 0, end: 0 },
1199        );
1200
1201        let suggestions = suggester.analyze(&ast, "sub complex_function { }");
1202
1203        // Should suggest parameter object for too many params
1204        assert!(suggestions.iter().any(|s| s.category == RefactoringCategory::TooManyParameters));
1205    }
1206
1207    #[test]
1208    fn test_cyclomatic_complexity() {
1209        let suggester = RefactoringSuggester::new();
1210
1211        // Create a node with multiple decision points
1212        let node = Node::new(
1213            NodeKind::Block {
1214                statements: vec![Node::new(
1215                    NodeKind::If {
1216                        condition: Box::new(Node::new(
1217                            NodeKind::Variable { name: "$x".to_string(), sigil: "$".to_string() },
1218                            crate::ast::SourceLocation { start: 0, end: 0 },
1219                        )),
1220                        then_branch: Box::new(Node::new(
1221                            NodeKind::Block { statements: vec![] },
1222                            crate::ast::SourceLocation { start: 0, end: 0 },
1223                        )),
1224                        elsif_branches: vec![],
1225                        else_branch: None,
1226                    },
1227                    crate::ast::SourceLocation { start: 0, end: 0 },
1228                )],
1229            },
1230            crate::ast::SourceLocation { start: 0, end: 0 },
1231        );
1232
1233        let complexity = suggester.calculate_cyclomatic_complexity(&node);
1234        assert_eq!(complexity, 2); // Base 1 + 1 if statement
1235    }
1236}