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