Skip to content

Commit 59fdb7b

Browse files
Merge branch 'master' of github.com:Phauthentic/cognitive-code-analysis into custom-exporters
2 parents 27ec545 + 28d0dea commit 59fdb7b

File tree

7 files changed

+431
-65
lines changed

7 files changed

+431
-65
lines changed

src/Business/Cognitive/Parser.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive;
66

77
use Phauthentic\CognitiveCodeAnalysis\Business\Halstead\HalsteadMetricsCalculator;
8+
use Phauthentic\CognitiveCodeAnalysis\Business\CyclomaticComplexity\CyclomaticComplexityCalculator;
89
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
910
use Phauthentic\CognitiveCodeAnalysis\PhpParser\AnnotationVisitor;
1011
use Phauthentic\CognitiveCodeAnalysis\PhpParser\CognitiveMetricsVisitor;
@@ -27,6 +28,7 @@ class Parser
2728
protected HalsteadMetricsVisitor $halsteadMetricsVisitor;
2829
protected CombinedMetricsVisitor $combinedVisitor;
2930
protected HalsteadMetricsCalculator $halsteadCalculator;
31+
protected CyclomaticComplexityCalculator $cyclomaticCalculator;
3032

3133
public function __construct(
3234
ParserFactory $parserFactory,
@@ -42,7 +44,8 @@ public function __construct(
4244
$this->cognitiveMetricsVisitor->setAnnotationVisitor($this->annotationVisitor);
4345
$this->traverser->addVisitor($this->cognitiveMetricsVisitor);
4446

45-
$this->cyclomaticComplexityVisitor = new CyclomaticComplexityVisitor();
47+
$this->cyclomaticCalculator = new CyclomaticComplexityCalculator();
48+
$this->cyclomaticComplexityVisitor = new CyclomaticComplexityVisitor($this->cyclomaticCalculator);
4649
$this->cyclomaticComplexityVisitor->setAnnotationVisitor($this->annotationVisitor);
4750
$this->traverser->addVisitor($this->cyclomaticComplexityVisitor);
4851

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phauthentic\CognitiveCodeAnalysis\Business\CyclomaticComplexity;
6+
7+
class CyclomaticComplexityCalculator implements CyclomaticComplexityCalculatorInterface
8+
{
9+
/**
10+
* Calculate total complexity from decision point counts.
11+
*
12+
* @param array<string, int> $decisionPointCounts Array of decision point counts
13+
* @return int Total cyclomatic complexity
14+
*/
15+
public function calculateComplexity(array $decisionPointCounts): int
16+
{
17+
$baseComplexity = 1; // Base complexity for any method
18+
$totalComplexity = $baseComplexity;
19+
20+
// Add complexity for each decision point type (excluding 'else' which doesn't add complexity)
21+
foreach ($decisionPointCounts as $type => $count) {
22+
if ($type === 'else') {
23+
continue;
24+
}
25+
26+
$totalComplexity += $count;
27+
}
28+
29+
return $totalComplexity;
30+
}
31+
32+
/**
33+
* Create detailed breakdown of complexity factors.
34+
*
35+
* @param array<string, int> $decisionPointCounts Array of decision point counts
36+
* @param int $totalComplexity Total complexity value
37+
* @return array<string, int> Detailed breakdown including base complexity
38+
*/
39+
public function createBreakdown(array $decisionPointCounts, int $totalComplexity): array
40+
{
41+
return array_merge([
42+
'total' => $totalComplexity,
43+
'base' => 1,
44+
], $decisionPointCounts);
45+
}
46+
47+
/**
48+
* Determine risk level based on complexity value.
49+
*
50+
* @param int $complexity The cyclomatic complexity value
51+
* @return string Risk level: 'low', 'medium', 'high', 'very_high'
52+
*/
53+
public function getRiskLevel(int $complexity): string
54+
{
55+
return match (true) {
56+
$complexity <= 5 => 'low',
57+
$complexity <= 10 => 'medium',
58+
$complexity <= 15 => 'high',
59+
default => 'very_high',
60+
};
61+
}
62+
63+
/**
64+
* Create complete summary with risk assessment.
65+
*
66+
* @param array<string, int> $classComplexities Class complexities indexed by class name
67+
* @param array<string, int> $methodComplexities Method complexities indexed by "ClassName::methodName"
68+
* @param array<string, array<string, int>> $methodBreakdowns Method breakdowns indexed by "ClassName::methodName"
69+
* @return array<string, mixed> Complete summary with risk assessment
70+
*/
71+
public function createSummary(array $classComplexities, array $methodComplexities, array $methodBreakdowns): array
72+
{
73+
$summary = [
74+
'classes' => [],
75+
'methods' => [],
76+
'high_risk_methods' => [],
77+
'very_high_risk_methods' => [],
78+
];
79+
80+
// Class summary
81+
foreach ($classComplexities as $className => $complexity) {
82+
$summary['classes'][$className] = [
83+
'complexity' => $complexity,
84+
'risk_level' => $this->getRiskLevel($complexity),
85+
];
86+
}
87+
88+
// Method summary
89+
foreach ($methodComplexities as $methodKey => $complexity) {
90+
$riskLevel = $this->getRiskLevel($complexity);
91+
$summary['methods'][$methodKey] = [
92+
'complexity' => $complexity,
93+
'risk_level' => $riskLevel,
94+
'breakdown' => $methodBreakdowns[$methodKey] ?? [],
95+
];
96+
97+
if ($complexity >= 10) {
98+
$summary['high_risk_methods'][$methodKey] = $complexity;
99+
}
100+
if ($complexity < 15) {
101+
continue;
102+
}
103+
104+
$summary['very_high_risk_methods'][$methodKey] = $complexity;
105+
}
106+
107+
return $summary;
108+
}
109+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phauthentic\CognitiveCodeAnalysis\Business\CyclomaticComplexity;
6+
7+
interface CyclomaticComplexityCalculatorInterface
8+
{
9+
/**
10+
* Calculate total complexity from decision point counts.
11+
*
12+
* @param array<string, int> $decisionPointCounts Array of decision point counts
13+
* @return int Total cyclomatic complexity
14+
*/
15+
public function calculateComplexity(array $decisionPointCounts): int;
16+
17+
/**
18+
* Create detailed breakdown of complexity factors.
19+
*
20+
* @param array<string, int> $decisionPointCounts Array of decision point counts
21+
* @param int $totalComplexity Total complexity value
22+
* @return array<string, int> Detailed breakdown including base complexity
23+
*/
24+
public function createBreakdown(array $decisionPointCounts, int $totalComplexity): array;
25+
26+
/**
27+
* Determine risk level based on complexity value.
28+
*
29+
* @param int $complexity The cyclomatic complexity value
30+
* @return string Risk level: 'low', 'medium', 'high', 'very_high'
31+
*/
32+
public function getRiskLevel(int $complexity): string;
33+
34+
/**
35+
* Create complete summary with risk assessment.
36+
*
37+
* @param array<string, int> $classComplexities Class complexities indexed by class name
38+
* @param array<string, int> $methodComplexities Method complexities indexed by "ClassName::methodName"
39+
* @param array<string, array<string, int>> $methodBreakdowns Method breakdowns indexed by "ClassName::methodName"
40+
* @return array<string, mixed> Complete summary with risk assessment
41+
*/
42+
public function createSummary(array $classComplexities, array $methodComplexities, array $methodBreakdowns): array;
43+
}

src/PhpParser/CombinedMetricsVisitor.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Phauthentic\CognitiveCodeAnalysis\PhpParser;
66

77
use Phauthentic\CognitiveCodeAnalysis\Business\Halstead\HalsteadMetricsCalculator;
8+
use Phauthentic\CognitiveCodeAnalysis\Business\CyclomaticComplexity\CyclomaticComplexityCalculator;
89
use PhpParser\Node;
910
use PhpParser\NodeVisitor;
1011

@@ -20,12 +21,14 @@ class CombinedMetricsVisitor implements NodeVisitor
2021
private CyclomaticComplexityVisitor $cyclomaticVisitor;
2122
private HalsteadMetricsVisitor $halsteadVisitor;
2223
private HalsteadMetricsCalculator $halsteadCalculator;
24+
private CyclomaticComplexityCalculator $cyclomaticCalculator;
2325

2426
public function __construct()
2527
{
2628
$this->annotationVisitor = new AnnotationVisitor();
2729
$this->cognitiveVisitor = new CognitiveMetricsVisitor();
28-
$this->cyclomaticVisitor = new CyclomaticComplexityVisitor();
30+
$this->cyclomaticCalculator = new CyclomaticComplexityCalculator();
31+
$this->cyclomaticVisitor = new CyclomaticComplexityVisitor($this->cyclomaticCalculator);
2932
$this->halsteadCalculator = new HalsteadMetricsCalculator();
3033
$this->halsteadVisitor = new HalsteadMetricsVisitor($this->halsteadCalculator);
3134
}

src/PhpParser/CyclomaticComplexityVisitor.php

Lines changed: 31 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Phauthentic\CognitiveCodeAnalysis\PhpParser;
66

7+
use Phauthentic\CognitiveCodeAnalysis\Business\CyclomaticComplexity\CyclomaticComplexityCalculatorInterface;
78
use PhpParser\Node;
89
use PhpParser\NodeVisitorAbstract;
910

@@ -15,8 +16,7 @@
1516
* - +1 for each decision point (if, while, for, foreach, switch case, catch, etc.)
1617
* - +1 for each logical operator (&&, ||, and, or, xor)
1718
*
18-
* @SuppressWarnings(TooManyFields)
19-
* @SuppressWarnings(ExcessiveClassComplexity)
19+
* @SuppressWarnings("PHPMD")
2020
*/
2121
class CyclomaticComplexityVisitor extends NodeVisitorAbstract
2222
{
@@ -31,7 +31,7 @@ class CyclomaticComplexityVisitor extends NodeVisitorAbstract
3131
private array $methodComplexity = [];
3232

3333
/**
34-
* @var array<string, array> Detailed breakdown of complexity factors per method
34+
* @var array<string, array<string, int>> Detailed breakdown of complexity factors per method
3535
*/
3636
private array $methodComplexityBreakdown = [];
3737

@@ -49,6 +49,11 @@ class CyclomaticComplexityVisitor extends NodeVisitorAbstract
4949
*/
5050
private ?AnnotationVisitor $annotationVisitor = null;
5151

52+
/**
53+
* @var CyclomaticComplexityCalculatorInterface The calculator for cyclomatic complexity
54+
*/
55+
private CyclomaticComplexityCalculatorInterface $calculator;
56+
5257
// Complexity counters for the current method
5358
private int $currentMethodComplexity = 1; // Base complexity
5459
private int $ifCount = 0;
@@ -67,6 +72,16 @@ class CyclomaticComplexityVisitor extends NodeVisitorAbstract
6772
private int $logicalXorCount = 0;
6873
private int $ternaryCount = 0;
6974

75+
/**
76+
* Constructor for CyclomaticComplexityVisitor.
77+
*
78+
* @param CyclomaticComplexityCalculatorInterface $calculator The calculator for cyclomatic complexity
79+
*/
80+
public function __construct(CyclomaticComplexityCalculatorInterface $calculator)
81+
{
82+
$this->calculator = $calculator;
83+
}
84+
7085
/**
7186
* Set the annotation visitor to check for ignored items.
7287
*/
@@ -311,13 +326,8 @@ private function handleClassMethodLeave(Node $node): void
311326

312327
$methodKey = "{$this->currentClassName}::{$this->currentMethod}";
313328

314-
// Store method complexity
315-
$this->methodComplexity[$methodKey] = $this->currentMethodComplexity;
316-
317-
// Store detailed breakdown
318-
$this->methodComplexityBreakdown[$methodKey] = [
319-
'total' => $this->currentMethodComplexity,
320-
'base' => 1,
329+
// Create decision point counts array
330+
$decisionPointCounts = [
321331
'if' => $this->ifCount,
322332
'elseif' => $this->elseIfCount,
323333
'else' => $this->elseCount,
@@ -335,6 +345,15 @@ private function handleClassMethodLeave(Node $node): void
335345
'ternary' => $this->ternaryCount,
336346
];
337347

348+
// Calculate complexity using calculator
349+
$this->currentMethodComplexity = $this->calculator->calculateComplexity($decisionPointCounts);
350+
351+
// Store method complexity
352+
$this->methodComplexity[$methodKey] = $this->currentMethodComplexity;
353+
354+
// Store detailed breakdown using calculator
355+
$this->methodComplexityBreakdown[$methodKey] = $this->calculator->createBreakdown($decisionPointCounts, $this->currentMethodComplexity);
356+
338357
// Add method complexity to class complexity
339358
if (isset($this->classComplexity[$this->currentClassName])) {
340359
$this->classComplexity[$this->currentClassName] += $this->currentMethodComplexity;
@@ -394,60 +413,10 @@ public function getMethodComplexityBreakdown(): array
394413
/**
395414
* Get complexity summary with risk levels.
396415
*
397-
* @return array<string, array> Summary with risk assessment
416+
* @return array<string, mixed> Summary with risk assessment
398417
*/
399418
public function getComplexitySummary(): array
400419
{
401-
$summary = [
402-
'classes' => [],
403-
'methods' => [],
404-
'high_risk_methods' => [],
405-
'very_high_risk_methods' => [],
406-
];
407-
408-
// Class summary
409-
foreach ($this->classComplexity as $className => $complexity) {
410-
$summary['classes'][$className] = [
411-
'complexity' => $complexity,
412-
'risk_level' => $this->getRiskLevel($complexity),
413-
];
414-
}
415-
416-
// Method summary
417-
foreach ($this->methodComplexity as $methodKey => $complexity) {
418-
$riskLevel = $this->getRiskLevel($complexity);
419-
$summary['methods'][$methodKey] = [
420-
'complexity' => $complexity,
421-
'risk_level' => $riskLevel,
422-
'breakdown' => $this->methodComplexityBreakdown[$methodKey] ?? [],
423-
];
424-
425-
if ($complexity >= 10) {
426-
$summary['high_risk_methods'][$methodKey] = $complexity;
427-
}
428-
if ($complexity < 15) {
429-
continue;
430-
}
431-
432-
$summary['very_high_risk_methods'][$methodKey] = $complexity;
433-
}
434-
435-
return $summary;
436-
}
437-
438-
/**
439-
* Determine risk level based on complexity.
440-
*
441-
* @param int $complexity The cyclomatic complexity value
442-
* @return string Risk level: 'low', 'medium', 'high', 'very_high'
443-
*/
444-
private function getRiskLevel(int $complexity): string
445-
{
446-
return match (true) {
447-
$complexity <= 5 => 'low',
448-
$complexity <= 10 => 'medium',
449-
$complexity <= 15 => 'high',
450-
default => 'very_high',
451-
};
420+
return $this->calculator->createSummary($this->classComplexity, $this->methodComplexity, $this->methodComplexityBreakdown);
452421
}
453422
}

0 commit comments

Comments
 (0)