Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Business/Cognitive/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive;

use Phauthentic\CognitiveCodeAnalysis\Business\Halstead\HalsteadMetricsCalculator;
use Phauthentic\CognitiveCodeAnalysis\Business\CyclomaticComplexity\CyclomaticComplexityCalculator;
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
use Phauthentic\CognitiveCodeAnalysis\PhpParser\AnnotationVisitor;
use Phauthentic\CognitiveCodeAnalysis\PhpParser\CognitiveMetricsVisitor;
Expand All @@ -27,6 +28,7 @@ class Parser
protected HalsteadMetricsVisitor $halsteadMetricsVisitor;
protected CombinedMetricsVisitor $combinedVisitor;
protected HalsteadMetricsCalculator $halsteadCalculator;
protected CyclomaticComplexityCalculator $cyclomaticCalculator;

public function __construct(
ParserFactory $parserFactory,
Expand All @@ -42,7 +44,8 @@ public function __construct(
$this->cognitiveMetricsVisitor->setAnnotationVisitor($this->annotationVisitor);
$this->traverser->addVisitor($this->cognitiveMetricsVisitor);

$this->cyclomaticComplexityVisitor = new CyclomaticComplexityVisitor();
$this->cyclomaticCalculator = new CyclomaticComplexityCalculator();
$this->cyclomaticComplexityVisitor = new CyclomaticComplexityVisitor($this->cyclomaticCalculator);
$this->cyclomaticComplexityVisitor->setAnnotationVisitor($this->annotationVisitor);
$this->traverser->addVisitor($this->cyclomaticComplexityVisitor);

Expand Down
109 changes: 109 additions & 0 deletions src/Business/CyclomaticComplexity/CyclomaticComplexityCalculator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

namespace Phauthentic\CognitiveCodeAnalysis\Business\CyclomaticComplexity;

class CyclomaticComplexityCalculator implements CyclomaticComplexityCalculatorInterface
{
/**
* Calculate total complexity from decision point counts.
*
* @param array<string, int> $decisionPointCounts Array of decision point counts
* @return int Total cyclomatic complexity
*/
public function calculateComplexity(array $decisionPointCounts): int
{
$baseComplexity = 1; // Base complexity for any method
$totalComplexity = $baseComplexity;

// Add complexity for each decision point type (excluding 'else' which doesn't add complexity)
foreach ($decisionPointCounts as $type => $count) {
if ($type === 'else') {
continue;
}

$totalComplexity += $count;
}

return $totalComplexity;
}

/**
* Create detailed breakdown of complexity factors.
*
* @param array<string, int> $decisionPointCounts Array of decision point counts
* @param int $totalComplexity Total complexity value
* @return array<string, int> Detailed breakdown including base complexity
*/
public function createBreakdown(array $decisionPointCounts, int $totalComplexity): array
{
return array_merge([
'total' => $totalComplexity,
'base' => 1,
], $decisionPointCounts);
}

/**
* Determine risk level based on complexity value.
*
* @param int $complexity The cyclomatic complexity value
* @return string Risk level: 'low', 'medium', 'high', 'very_high'
*/
public function getRiskLevel(int $complexity): string
{
return match (true) {
$complexity <= 5 => 'low',
$complexity <= 10 => 'medium',
$complexity <= 15 => 'high',
default => 'very_high',
};
}

/**
* Create complete summary with risk assessment.
*
* @param array<string, int> $classComplexities Class complexities indexed by class name
* @param array<string, int> $methodComplexities Method complexities indexed by "ClassName::methodName"
* @param array<string, array<string, int>> $methodBreakdowns Method breakdowns indexed by "ClassName::methodName"
* @return array<string, mixed> Complete summary with risk assessment
*/
public function createSummary(array $classComplexities, array $methodComplexities, array $methodBreakdowns): array
{
$summary = [
'classes' => [],
'methods' => [],
'high_risk_methods' => [],
'very_high_risk_methods' => [],
];

// Class summary
foreach ($classComplexities as $className => $complexity) {
$summary['classes'][$className] = [
'complexity' => $complexity,
'risk_level' => $this->getRiskLevel($complexity),
];
}

// Method summary
foreach ($methodComplexities as $methodKey => $complexity) {
$riskLevel = $this->getRiskLevel($complexity);
$summary['methods'][$methodKey] = [
'complexity' => $complexity,
'risk_level' => $riskLevel,
'breakdown' => $methodBreakdowns[$methodKey] ?? [],
];

if ($complexity >= 10) {
$summary['high_risk_methods'][$methodKey] = $complexity;
}
if ($complexity < 15) {
continue;
}

$summary['very_high_risk_methods'][$methodKey] = $complexity;
}

return $summary;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Phauthentic\CognitiveCodeAnalysis\Business\CyclomaticComplexity;

interface CyclomaticComplexityCalculatorInterface
{
/**
* Calculate total complexity from decision point counts.
*
* @param array<string, int> $decisionPointCounts Array of decision point counts
* @return int Total cyclomatic complexity
*/
public function calculateComplexity(array $decisionPointCounts): int;

/**
* Create detailed breakdown of complexity factors.
*
* @param array<string, int> $decisionPointCounts Array of decision point counts
* @param int $totalComplexity Total complexity value
* @return array<string, int> Detailed breakdown including base complexity
*/
public function createBreakdown(array $decisionPointCounts, int $totalComplexity): array;

/**
* Determine risk level based on complexity value.
*
* @param int $complexity The cyclomatic complexity value
* @return string Risk level: 'low', 'medium', 'high', 'very_high'
*/
public function getRiskLevel(int $complexity): string;

/**
* Create complete summary with risk assessment.
*
* @param array<string, int> $classComplexities Class complexities indexed by class name
* @param array<string, int> $methodComplexities Method complexities indexed by "ClassName::methodName"
* @param array<string, array<string, int>> $methodBreakdowns Method breakdowns indexed by "ClassName::methodName"
* @return array<string, mixed> Complete summary with risk assessment
*/
public function createSummary(array $classComplexities, array $methodComplexities, array $methodBreakdowns): array;
}
5 changes: 4 additions & 1 deletion src/PhpParser/CombinedMetricsVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Phauthentic\CognitiveCodeAnalysis\PhpParser;

use Phauthentic\CognitiveCodeAnalysis\Business\Halstead\HalsteadMetricsCalculator;
use Phauthentic\CognitiveCodeAnalysis\Business\CyclomaticComplexity\CyclomaticComplexityCalculator;
use PhpParser\Node;
use PhpParser\NodeVisitor;

Expand All @@ -20,12 +21,14 @@ class CombinedMetricsVisitor implements NodeVisitor
private CyclomaticComplexityVisitor $cyclomaticVisitor;
private HalsteadMetricsVisitor $halsteadVisitor;
private HalsteadMetricsCalculator $halsteadCalculator;
private CyclomaticComplexityCalculator $cyclomaticCalculator;

public function __construct()
{
$this->annotationVisitor = new AnnotationVisitor();
$this->cognitiveVisitor = new CognitiveMetricsVisitor();
$this->cyclomaticVisitor = new CyclomaticComplexityVisitor();
$this->cyclomaticCalculator = new CyclomaticComplexityCalculator();
$this->cyclomaticVisitor = new CyclomaticComplexityVisitor($this->cyclomaticCalculator);
$this->halsteadCalculator = new HalsteadMetricsCalculator();
$this->halsteadVisitor = new HalsteadMetricsVisitor($this->halsteadCalculator);
}
Expand Down
93 changes: 31 additions & 62 deletions src/PhpParser/CyclomaticComplexityVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Phauthentic\CognitiveCodeAnalysis\PhpParser;

use Phauthentic\CognitiveCodeAnalysis\Business\CyclomaticComplexity\CyclomaticComplexityCalculatorInterface;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;

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

/**
* @var array<string, array> Detailed breakdown of complexity factors per method
* @var array<string, array<string, int>> Detailed breakdown of complexity factors per method
*/
private array $methodComplexityBreakdown = [];

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

/**
* @var CyclomaticComplexityCalculatorInterface The calculator for cyclomatic complexity
*/
private CyclomaticComplexityCalculatorInterface $calculator;

// Complexity counters for the current method
private int $currentMethodComplexity = 1; // Base complexity
private int $ifCount = 0;
Expand All @@ -67,6 +72,16 @@ class CyclomaticComplexityVisitor extends NodeVisitorAbstract
private int $logicalXorCount = 0;
private int $ternaryCount = 0;

/**
* Constructor for CyclomaticComplexityVisitor.
*
* @param CyclomaticComplexityCalculatorInterface $calculator The calculator for cyclomatic complexity
*/
public function __construct(CyclomaticComplexityCalculatorInterface $calculator)
{
$this->calculator = $calculator;
}

/**
* Set the annotation visitor to check for ignored items.
*/
Expand Down Expand Up @@ -311,13 +326,8 @@ private function handleClassMethodLeave(Node $node): void

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

// Store method complexity
$this->methodComplexity[$methodKey] = $this->currentMethodComplexity;

// Store detailed breakdown
$this->methodComplexityBreakdown[$methodKey] = [
'total' => $this->currentMethodComplexity,
'base' => 1,
// Create decision point counts array
$decisionPointCounts = [
'if' => $this->ifCount,
'elseif' => $this->elseIfCount,
'else' => $this->elseCount,
Expand All @@ -335,6 +345,15 @@ private function handleClassMethodLeave(Node $node): void
'ternary' => $this->ternaryCount,
];

// Calculate complexity using calculator
$this->currentMethodComplexity = $this->calculator->calculateComplexity($decisionPointCounts);

// Store method complexity
$this->methodComplexity[$methodKey] = $this->currentMethodComplexity;

// Store detailed breakdown using calculator
$this->methodComplexityBreakdown[$methodKey] = $this->calculator->createBreakdown($decisionPointCounts, $this->currentMethodComplexity);

// Add method complexity to class complexity
if (isset($this->classComplexity[$this->currentClassName])) {
$this->classComplexity[$this->currentClassName] += $this->currentMethodComplexity;
Expand Down Expand Up @@ -394,60 +413,10 @@ public function getMethodComplexityBreakdown(): array
/**
* Get complexity summary with risk levels.
*
* @return array<string, array> Summary with risk assessment
* @return array<string, mixed> Summary with risk assessment
*/
public function getComplexitySummary(): array
{
$summary = [
'classes' => [],
'methods' => [],
'high_risk_methods' => [],
'very_high_risk_methods' => [],
];

// Class summary
foreach ($this->classComplexity as $className => $complexity) {
$summary['classes'][$className] = [
'complexity' => $complexity,
'risk_level' => $this->getRiskLevel($complexity),
];
}

// Method summary
foreach ($this->methodComplexity as $methodKey => $complexity) {
$riskLevel = $this->getRiskLevel($complexity);
$summary['methods'][$methodKey] = [
'complexity' => $complexity,
'risk_level' => $riskLevel,
'breakdown' => $this->methodComplexityBreakdown[$methodKey] ?? [],
];

if ($complexity >= 10) {
$summary['high_risk_methods'][$methodKey] = $complexity;
}
if ($complexity < 15) {
continue;
}

$summary['very_high_risk_methods'][$methodKey] = $complexity;
}

return $summary;
}

/**
* Determine risk level based on complexity.
*
* @param int $complexity The cyclomatic complexity value
* @return string Risk level: 'low', 'medium', 'high', 'very_high'
*/
private function getRiskLevel(int $complexity): string
{
return match (true) {
$complexity <= 5 => 'low',
$complexity <= 10 => 'medium',
$complexity <= 15 => 'high',
default => 'very_high',
};
return $this->calculator->createSummary($this->classComplexity, $this->methodComplexity, $this->methodComplexityBreakdown);
}
}
Loading