1use crate::ast::{Node, NodeKind};
6
7#[derive(Debug, Clone)]
12pub struct Diagnostic {
13 pub range: (usize, usize),
15 pub severity: DiagnosticSeverity,
17 pub code: Option<String>,
19 pub message: String,
21 pub related_information: Vec<String>,
23 pub tags: Vec<String>,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum DiagnosticSeverity {
30 Error,
32 Warning,
34 Information,
36 Hint,
38}
39
40pub struct TestGenerator {
42 framework: String,
43}
44
45impl TestGenerator {
46 pub fn new(framework: &str) -> Self {
48 Self { framework: framework.to_string() }
49 }
50
51 pub fn generate_test(&self, name: &str, params: usize) -> String {
53 let args = (0..params).map(|i| format!("'arg{}'", i + 1)).collect::<Vec<_>>().join(", ");
54
55 match self.framework.as_str() {
56 "Test2::V0" => {
57 format!(
58 "use Test2::V0;\n\n\
59 subtest '{}' => sub {{\n \
60 my $result = {}({});\n \
61 ok($result, 'Function returns value');\n\
62 }};\n\n\
63 done_testing();\n",
64 name, name, args
65 )
66 }
67 _ => {
68 format!(
70 "use Test::More;\n\n\
71 subtest '{}' => sub {{\n \
72 my $result = {}({});\n \
73 ok(defined $result, 'Function returns defined value');\n\
74 }};\n\n\
75 done_testing();\n",
76 name, name, args
77 )
78 }
79 }
80 }
81
82 pub fn find_subroutines(&self, node: &Node) -> Vec<SubroutineInfo> {
84 let mut subs = Vec::new();
85 self.find_subroutines_recursive(node, &mut subs);
86 subs
87 }
88
89 fn find_subroutines_recursive(&self, node: &Node, subs: &mut Vec<SubroutineInfo>) {
90 match &node.kind {
91 NodeKind::Subroutine { name, signature, .. } => {
92 subs.push(SubroutineInfo {
93 name: name.clone().unwrap_or_else(|| "anonymous".to_string()),
94 param_count: signature
95 .as_ref()
96 .map(|s| {
97 if let NodeKind::Signature { parameters } = &s.kind {
98 parameters.len()
99 } else {
100 0
101 }
102 })
103 .unwrap_or(0),
104 });
105 }
106 NodeKind::Program { statements } => {
107 for stmt in statements {
108 self.find_subroutines_recursive(stmt, subs);
109 }
110 }
111 NodeKind::Block { statements } => {
112 for stmt in statements {
113 self.find_subroutines_recursive(stmt, subs);
114 }
115 }
116 NodeKind::If { then_branch, elsif_branches, else_branch, .. } => {
117 self.find_subroutines_recursive(then_branch, subs);
118 for (_, branch) in elsif_branches {
119 self.find_subroutines_recursive(branch, subs);
120 }
121 if let Some(branch) = else_branch {
122 self.find_subroutines_recursive(branch, subs);
123 }
124 }
125 NodeKind::While { body, continue_block, .. }
126 | NodeKind::For { body, continue_block, .. } => {
127 self.find_subroutines_recursive(body, subs);
128 if let Some(cont) = continue_block {
129 self.find_subroutines_recursive(cont, subs);
130 }
131 }
132 NodeKind::Foreach { body, .. }
133 | NodeKind::Given { body, .. }
134 | NodeKind::When { body, .. }
135 | NodeKind::Default { body } => {
136 self.find_subroutines_recursive(body, subs);
137 }
138 NodeKind::Package { block, .. } => {
139 if let Some(blk) = block {
140 self.find_subroutines_recursive(blk, subs);
141 }
142 }
143 NodeKind::Class { body, .. } => {
144 self.find_subroutines_recursive(body, subs);
145 }
146 _ => {
147 }
149 }
150 }
151}
152
153#[derive(Debug, Clone)]
155pub struct SubroutineInfo {
156 pub name: String,
158 pub param_count: usize,
160}
161
162pub struct RefactoringAnalyzer {
164 max_complexity: usize,
165 max_lines: usize,
166 max_params: usize,
167}
168
169impl Default for RefactoringAnalyzer {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175impl RefactoringAnalyzer {
176 pub fn new() -> Self {
178 Self { max_complexity: 10, max_lines: 50, max_params: 5 }
179 }
180
181 pub fn analyze(&self, node: &Node, source: &str) -> Vec<RefactoringSuggestion> {
183 let mut suggestions = Vec::new();
184 self.analyze_recursive(node, source, &mut suggestions);
185 suggestions
186 }
187
188 fn analyze_recursive(
189 &self,
190 node: &Node,
191 source: &str,
192 suggestions: &mut Vec<RefactoringSuggestion>,
193 ) {
194 match &node.kind {
195 NodeKind::Subroutine { name, signature, body, .. } => {
196 let sub_name = name.clone().unwrap_or_else(|| "anonymous".to_string());
197
198 let param_count = signature
200 .as_ref()
201 .map(|s| {
202 if let NodeKind::Signature { parameters } = &s.kind {
203 parameters.len()
204 } else {
205 0
206 }
207 })
208 .unwrap_or(0);
209 if param_count > self.max_params {
210 suggestions.push(RefactoringSuggestion {
211 title: format!("Too many parameters in {}", sub_name),
212 description: format!(
213 "Function has {} parameters, consider using a hash",
214 param_count
215 ),
216 category: RefactoringCategory::TooManyParameters,
217 });
218 }
219
220 let complexity = self.calculate_complexity(body);
222 if complexity > self.max_complexity {
223 suggestions.push(RefactoringSuggestion {
224 title: format!("High complexity in {}", sub_name),
225 description: format!(
226 "Cyclomatic complexity is {}, consider breaking into smaller functions",
227 complexity
228 ),
229 category: RefactoringCategory::HighComplexity,
230 });
231 }
232
233 let lines = self.count_lines(body, source);
235 if lines > self.max_lines {
236 suggestions.push(RefactoringSuggestion {
237 title: format!("Long method: {}", sub_name),
238 description: format!(
239 "Method has {} lines, consider breaking into smaller functions",
240 lines
241 ),
242 category: RefactoringCategory::LongMethod,
243 });
244 }
245
246 self.analyze_recursive(body, source, suggestions);
248 }
249 NodeKind::Program { statements } | NodeKind::Block { statements } => {
250 for stmt in statements {
251 self.analyze_recursive(stmt, source, suggestions);
252 }
253 }
254 NodeKind::If { then_branch, elsif_branches, else_branch, .. } => {
255 self.analyze_recursive(then_branch, source, suggestions);
256 for (_, branch) in elsif_branches {
257 self.analyze_recursive(branch, source, suggestions);
258 }
259 if let Some(branch) = else_branch {
260 self.analyze_recursive(branch, source, suggestions);
261 }
262 }
263 NodeKind::While { body, .. }
264 | NodeKind::For { body, .. }
265 | NodeKind::Foreach { body, .. }
266 | NodeKind::Given { body, .. }
267 | NodeKind::When { body, .. }
268 | NodeKind::Default { body }
269 | NodeKind::Class { body, .. } => {
270 self.analyze_recursive(body, source, suggestions);
271 }
272 NodeKind::Package { block, .. } => {
273 if let Some(blk) = block {
274 self.analyze_recursive(blk, source, suggestions);
275 }
276 }
277 _ => {
278 }
280 }
281 }
282
283 fn calculate_complexity(&self, node: &Node) -> usize {
284 let mut complexity = 1;
285 self.count_decision_points(node, &mut complexity);
286 complexity
287 }
288
289 fn count_decision_points(&self, node: &Node, complexity: &mut usize) {
290 match &node.kind {
291 NodeKind::If { elsif_branches, .. } => {
292 *complexity += 1 + elsif_branches.len();
293 }
294 NodeKind::While { .. } | NodeKind::For { .. } | NodeKind::Foreach { .. } => {
295 *complexity += 1;
296 }
297 NodeKind::Binary { op, left, right } => {
298 if op == "&&" || op == "||" || op == "and" || op == "or" {
299 *complexity += 1;
300 }
301 self.count_decision_points(left, complexity);
302 self.count_decision_points(right, complexity);
303 return;
304 }
305 _ => {}
306 }
307
308 match &node.kind {
310 NodeKind::Program { statements } | NodeKind::Block { statements } => {
311 for stmt in statements {
312 self.count_decision_points(stmt, complexity);
313 }
314 }
315 NodeKind::If { then_branch, elsif_branches, else_branch, .. } => {
316 self.count_decision_points(then_branch, complexity);
317 for (_, branch) in elsif_branches {
318 self.count_decision_points(branch, complexity);
319 }
320 if let Some(branch) = else_branch {
321 self.count_decision_points(branch, complexity);
322 }
323 }
324 NodeKind::While { body, .. }
325 | NodeKind::For { body, .. }
326 | NodeKind::Foreach { body, .. }
327 | NodeKind::Given { body, .. }
328 | NodeKind::When { body, .. }
329 | NodeKind::Default { body }
330 | NodeKind::Class { body, .. } => {
331 self.count_decision_points(body, complexity);
332 }
333 _ => {}
334 }
335 }
336
337 fn count_lines(&self, node: &Node, source: &str) -> usize {
338 let start = node.location.start;
339 let end = node.location.end.min(source.len());
340
341 if start >= end {
342 return 0;
343 }
344
345 source[start..end].lines().count()
346 }
347}
348
349#[derive(Debug, Clone)]
351pub struct RefactoringSuggestion {
352 pub title: String,
354 pub description: String,
356 pub category: RefactoringCategory,
358}
359
360#[derive(Debug, Clone, PartialEq)]
362pub enum RefactoringCategory {
363 TooManyParameters,
365 HighComplexity,
367 LongMethod,
369}
370
371#[derive(Debug, Clone, PartialEq)]
373pub enum TddState {
374 Red,
375 Green,
376 Refactor,
377 Idle,
378}
379
380pub struct TddWorkflow {
382 state: TddState,
383 generator: TestGenerator,
384 analyzer: RefactoringAnalyzer,
385}
386
387impl TddWorkflow {
388 pub fn new(framework: &str) -> Self {
390 Self {
391 state: TddState::Idle,
392 generator: TestGenerator::new(framework),
393 analyzer: RefactoringAnalyzer::new(),
394 }
395 }
396
397 pub fn start_cycle(&mut self, test_name: &str) -> TddResult {
399 self.state = TddState::Red;
400 TddResult {
401 state: self.state.clone(),
402 message: format!("Starting TDD cycle for '{}'", test_name),
403 }
404 }
405
406 pub fn run_tests(&mut self, success: bool) -> TddResult {
408 self.state = if success { TddState::Green } else { TddState::Red };
409
410 TddResult {
411 state: self.state.clone(),
412 message: if success {
413 "Tests passing, ready to refactor".to_string()
414 } else {
415 "Tests failing, fix implementation".to_string()
416 },
417 }
418 }
419
420 pub fn start_refactor(&mut self) -> TddResult {
422 self.state = TddState::Refactor;
423 TddResult {
424 state: self.state.clone(),
425 message: "Refactoring phase - improve code while keeping tests green".to_string(),
426 }
427 }
428
429 pub fn complete_cycle(&mut self) -> TddResult {
431 self.state = TddState::Idle;
432 TddResult { state: self.state.clone(), message: "TDD cycle complete".to_string() }
433 }
434
435 pub fn generate_test(&self, name: &str, params: usize) -> String {
437 self.generator.generate_test(name, params)
438 }
439
440 pub fn analyze_for_refactoring(&self, ast: &Node, source: &str) -> Vec<RefactoringSuggestion> {
442 self.analyzer.analyze(ast, source)
443 }
444
445 pub fn get_coverage_diagnostics(&self, uncovered_lines: &[usize]) -> Vec<Diagnostic> {
447 uncovered_lines
448 .iter()
449 .map(|&line| Diagnostic {
450 range: (line, line),
451 severity: DiagnosticSeverity::Warning,
452 code: Some("tdd.uncovered".to_string()),
453 message: "Line not covered by tests".to_string(),
454 related_information: vec![],
455 tags: vec![],
456 })
457 .collect()
458 }
459}
460
461#[derive(Debug, Clone)]
463pub struct TddResult {
464 pub state: TddState,
466 pub message: String,
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473 use crate::SourceLocation;
474
475 #[test]
476 fn test_test_generation() {
477 let generator = TestGenerator::new("Test::More");
478 let test = generator.generate_test("add", 2);
479 assert!(test.contains("Test::More"));
480 assert!(test.contains("add"));
481 assert!(test.contains("arg1"));
482 assert!(test.contains("arg2"));
483 }
484
485 #[test]
486 fn test_find_subroutines() {
487 let ast = Node::new(
488 NodeKind::Program {
489 statements: vec![Node::new(
490 NodeKind::Subroutine {
491 name: Some("test_func".to_string()),
492 name_span: None,
493 prototype: None,
494 signature: None,
495 attributes: vec![],
496 body: Box::new(Node::new(
497 NodeKind::Block { statements: vec![] },
498 SourceLocation { start: 0, end: 0 },
499 )),
500 },
501 SourceLocation { start: 0, end: 0 },
502 )],
503 },
504 SourceLocation { start: 0, end: 0 },
505 );
506
507 let generator = TestGenerator::new("Test::More");
508 let subs = generator.find_subroutines(&ast);
509 assert_eq!(subs.len(), 1);
510 assert_eq!(subs[0].name, "test_func");
511 }
512
513 #[test]
514 fn test_refactoring_suggestions() {
515 let analyzer = RefactoringAnalyzer::new();
516
517 let ast = Node::new(
519 NodeKind::Subroutine {
520 name: Some("complex".to_string()),
521 name_span: None,
522 prototype: None,
523 signature: Some(Box::new(Node::new(
524 NodeKind::Signature {
525 parameters: vec![
526 Node::new(
527 NodeKind::MandatoryParameter {
528 variable: Box::new(Node::new(
529 NodeKind::Variable {
530 sigil: "$".to_string(),
531 name: "a".to_string(),
532 },
533 SourceLocation { start: 0, end: 0 },
534 )),
535 },
536 SourceLocation { start: 0, end: 0 },
537 ),
538 Node::new(
539 NodeKind::MandatoryParameter {
540 variable: Box::new(Node::new(
541 NodeKind::Variable {
542 sigil: "$".to_string(),
543 name: "b".to_string(),
544 },
545 SourceLocation { start: 0, end: 0 },
546 )),
547 },
548 SourceLocation { start: 0, end: 0 },
549 ),
550 Node::new(
551 NodeKind::MandatoryParameter {
552 variable: Box::new(Node::new(
553 NodeKind::Variable {
554 sigil: "$".to_string(),
555 name: "c".to_string(),
556 },
557 SourceLocation { start: 0, end: 0 },
558 )),
559 },
560 SourceLocation { start: 0, end: 0 },
561 ),
562 Node::new(
563 NodeKind::MandatoryParameter {
564 variable: Box::new(Node::new(
565 NodeKind::Variable {
566 sigil: "$".to_string(),
567 name: "d".to_string(),
568 },
569 SourceLocation { start: 0, end: 0 },
570 )),
571 },
572 SourceLocation { start: 0, end: 0 },
573 ),
574 Node::new(
575 NodeKind::MandatoryParameter {
576 variable: Box::new(Node::new(
577 NodeKind::Variable {
578 sigil: "$".to_string(),
579 name: "e".to_string(),
580 },
581 SourceLocation { start: 0, end: 0 },
582 )),
583 },
584 SourceLocation { start: 0, end: 0 },
585 ),
586 Node::new(
587 NodeKind::MandatoryParameter {
588 variable: Box::new(Node::new(
589 NodeKind::Variable {
590 sigil: "$".to_string(),
591 name: "f".to_string(),
592 },
593 SourceLocation { start: 0, end: 0 },
594 )),
595 },
596 SourceLocation { start: 0, end: 0 },
597 ),
598 ],
600 },
601 SourceLocation { start: 0, end: 0 },
602 ))),
603 attributes: vec![],
604 body: Box::new(Node::new(
605 NodeKind::Block { statements: vec![] },
606 SourceLocation { start: 0, end: 0 },
607 )),
608 },
609 SourceLocation { start: 0, end: 0 },
610 );
611
612 let suggestions = analyzer.analyze(&ast, "sub complex($a, $b, $c, $d, $e, $f) { }");
613 assert!(!suggestions.is_empty());
614 assert!(suggestions.iter().any(|s| s.category == RefactoringCategory::TooManyParameters));
615 }
616
617 #[test]
618 fn test_tdd_workflow() {
619 let mut workflow = TddWorkflow::new("Test::More");
620
621 let _result = workflow.start_cycle("add");
623 assert_eq!(workflow.state, TddState::Red);
624
625 let _result = workflow.run_tests(false);
627 assert_eq!(workflow.state, TddState::Red);
628
629 let _result = workflow.run_tests(true);
631 assert_eq!(workflow.state, TddState::Green);
632
633 let _result = workflow.start_refactor();
635 assert_eq!(workflow.state, TddState::Refactor);
636
637 let _result = workflow.complete_cycle();
639 assert_eq!(workflow.state, TddState::Idle);
640 }
641}