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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"bin/phpbench run benchmarks --report=aggregate"
],
"all": [
"@csfix",
"@cscheck",
"@analyze",
"@phpmd",
Expand Down
5 changes: 5 additions & 0 deletions src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Phauthentic\CognitiveCodeAnalysis\Business\Churn\ChangeCounter\ChangeCounterFactory;
use Phauthentic\CognitiveCodeAnalysis\Business\Churn\ChurnCalculator;
use Phauthentic\CognitiveCodeAnalysis\Business\CodeCoverage\CodeCoverageFactory;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Baseline;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollector;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsSorter;
Expand Down Expand Up @@ -100,6 +101,9 @@ private function registerServices(): void
$this->containerBuilder->register(CognitiveMetricsSorter::class, CognitiveMetricsSorter::class)
->setPublic(true);

$this->containerBuilder->register(CodeCoverageFactory::class, CodeCoverageFactory::class)
->setPublic(true);

$this->containerBuilder->register(Processor::class, Processor::class)
->setPublic(true);

Expand Down Expand Up @@ -222,6 +226,7 @@ private function registerCommands(): void
new Reference(Baseline::class),
new Reference(CognitiveMetricsReportHandler::class),
new Reference(CognitiveMetricsSorter::class),
new Reference(CodeCoverageFactory::class),
])
->setPublic(true);

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

declare(strict_types=1);

namespace Phauthentic\CognitiveCodeAnalysis\Business\CodeCoverage;

use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;

class CodeCoverageFactory
{
/**
* @throws CognitiveAnalysisException
*/
public function createFromName(string $name, string $filePath): CoverageReportReaderInterface
{
return match (strtolower($name)) {
'clover' => new CloverReader($filePath),
'cobertura' => new CoberturaReader($filePath),
default => throw new CognitiveAnalysisException("Unknown code coverage implementation: {$name}"),
};
}
}
12 changes: 12 additions & 0 deletions src/Business/Cognitive/CognitiveMetrics.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class CognitiveMetrics implements JsonSerializable

private ?HalsteadMetrics $halstead = null;
private ?CyclomaticMetrics $cyclomatic = null;
private ?float $coverage = null;

/**
* @param array<string, mixed> $metrics
Expand Down Expand Up @@ -340,6 +341,16 @@ public function getScore(): float
return $this->score;
}

public function setCoverage(?float $coverage): void
{
$this->coverage = $coverage;
}

public function getCoverage(): ?float
{
return $this->coverage;
}

public function getLineCountWeightDelta(): ?Delta
{
return $this->lineCountWeightDelta;
Expand Down Expand Up @@ -435,6 +446,7 @@ public function toArray(): array
'ifCountWeightDelta' => $this->ifCountWeightDelta,
'ifNestingLevelWeightDelta' => $this->ifNestingLevelWeightDelta,
'elseCountWeightDelta' => $this->elseCountWeightDelta,
'coverage' => $this->coverage,
];
}

Expand Down
45 changes: 3 additions & 42 deletions src/Business/Cognitive/CognitiveMetricsCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use Phauthentic\CognitiveCodeAnalysis\Config\CognitiveConfig;
use Phauthentic\CognitiveCodeAnalysis\Config\ConfigService;
use SplFileInfo;
use Symfony\Component\Messenger\Exception\ExceptionInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Throwable;

Expand All @@ -21,11 +20,6 @@
*/
class CognitiveMetricsCollector
{
/**
* @var array<string, array<string, string>>|null Cached ignored items from the last parsing operation
*/
private ?array $ignoredItems = null;

public function __construct(
protected readonly Parser $parser,
protected readonly DirectoryScanner $directoryScanner,
Expand All @@ -40,7 +34,7 @@ public function __construct(
* @param string $path
* @param CognitiveConfig $config
* @return CognitiveMetricsCollection
* @throws CognitiveAnalysisException|ExceptionInterface
* @throws CognitiveAnalysisException
*/
public function collect(string $path, CognitiveConfig $config): CognitiveMetricsCollection
{
Expand All @@ -53,7 +47,7 @@ public function collect(string $path, CognitiveConfig $config): CognitiveMetrics
* @param array<string> $paths Array of paths to process
* @param CognitiveConfig $config
* @return CognitiveMetricsCollection Merged collection of metrics from all paths
* @throws CognitiveAnalysisException|ExceptionInterface
* @throws CognitiveAnalysisException
*/
public function collectFromPaths(array $paths, CognitiveConfig $config): CognitiveMetricsCollection
{
Expand Down Expand Up @@ -88,7 +82,6 @@ private function getCodeFromFile(SplFileInfo $file): string
*
* @param iterable<SplFileInfo> $files
* @return CognitiveMetricsCollection
* @throws ExceptionInterface
*/
private function findMetrics(iterable $files): CognitiveMetricsCollection
{
Expand All @@ -101,9 +94,6 @@ private function findMetrics(iterable $files): CognitiveMetricsCollection
$this->getCodeFromFile($file)
);

// Store ignored items from the parser
$this->ignoredItems = $this->parser->getIgnored();

$fileCount++;

// Clear memory periodically to prevent memory leaks
Expand Down Expand Up @@ -197,6 +187,7 @@ private function isExcluded(string $classAndMethod): bool
* @param string $path Path to the directory or file to scan
* @param array<int, string> $exclude List of regx to exclude
* @return iterable<mixed, SplFileInfo> An iterable of SplFileInfo objects
* @throws CognitiveAnalysisException
*/
private function findSourceFiles(string $path, array $exclude = []): iterable
{
Expand All @@ -206,36 +197,6 @@ private function findSourceFiles(string $path, array $exclude = []): iterable
);
}

/**
* Get all ignored classes and methods from the last parsing operation.
*
* @return array<string, array<string, string>> Array with 'classes' and 'methods' keys
*/
public function getIgnored(): array
{
return $this->ignoredItems ?? ['classes' => [], 'methods' => []];
}

/**
* Get ignored classes from the last parsing operation.
*
* @return array<string, string> Array of ignored class FQCNs
*/
public function getIgnoredClasses(): array
{
return $this->ignoredItems['classes'] ?? [];
}

/**
* Get ignored methods from the last parsing operation.
*
* @return array<string, string> Array of ignored method keys (ClassName::methodName)
*/
public function getIgnoredMethods(): array
{
return $this->ignoredItems['methods'] ?? [];
}

/**
* Get the project root directory path.
*
Expand Down
85 changes: 52 additions & 33 deletions src/Business/MetricsFacade.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@

namespace Phauthentic\CognitiveCodeAnalysis\Business;

use JsonException;
use Phauthentic\CognitiveCodeAnalysis\Business\Churn\ChangeCounter\ChangeCounterFactory;
use Phauthentic\CognitiveCodeAnalysis\Business\Churn\ChurnCalculator;
use Phauthentic\CognitiveCodeAnalysis\Business\Churn\Exporter\ChurnExporterFactory;
use Phauthentic\CognitiveCodeAnalysis\Business\CodeCoverage\CoverageReportReaderInterface;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetrics;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollector;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Exporter\CognitiveExporterFactory;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\ScoreCalculator;
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
use Phauthentic\CognitiveCodeAnalysis\Config\CognitiveConfig;
use Phauthentic\CognitiveCodeAnalysis\Config\ConfigService;

Expand Down Expand Up @@ -81,14 +80,20 @@ public function getCognitiveMetrics(string $path): CognitiveMetricsCollection
* Collects and returns cognitive metrics for multiple paths.
*
* @param array<string> $paths Array of file or directory paths to collect metrics from.
* @param CoverageReportReaderInterface|null $coverageReader Optional coverage reader for coverage data.
* @return CognitiveMetricsCollection The collected cognitive metrics from all paths.
*/
public function getCognitiveMetricsFromPaths(array $paths): CognitiveMetricsCollection
public function getCognitiveMetricsFromPaths(array $paths, ?CoverageReportReaderInterface $coverageReader = null): CognitiveMetricsCollection
{
$metricsCollection = $this->cognitiveMetricsCollector->collectFromPaths($paths, $this->configService->getConfig());

foreach ($metricsCollection as $metric) {
$this->scoreCalculator->calculate($metric, $this->configService->getConfig());

// Add coverage data if reader is provided
if ($coverageReader !== null) {
$this->addCoverageToMetric($metric, $coverageReader);
}
}

return $metricsCollection;
Expand Down Expand Up @@ -136,36 +141,6 @@ public function getConfig(): CognitiveConfig
return $this->configService->getConfig();
}

/**
* Get all ignored classes and methods from the last metrics collection.
*
* @return array<string, array<string, string>> Array with 'classes' and 'methods' keys
*/
public function getIgnored(): array
{
return $this->cognitiveMetricsCollector->getIgnored();
}

/**
* Get ignored classes from the last metrics collection.
*
* @return array<string, string> Array of ignored class FQCNs
*/
public function getIgnoredClasses(): array
{
return $this->cognitiveMetricsCollector->getIgnoredClasses();
}

/**
* Get ignored methods from the last metrics collection.
*
* @return array<string, string> Array of ignored method keys (ClassName::methodName)
*/
public function getIgnoredMethods(): array
{
return $this->cognitiveMetricsCollector->getIgnoredMethods();
}

/**
* @param array<string, array<string, mixed>> $classes
*/
Expand All @@ -188,4 +163,48 @@ public function exportMetricsReport(
$exporter = $this->getCognitiveExporterFactory()->create($reportType);
$exporter->export($metricsCollection, $filename);
}

/**
* Add coverage data to a metric
*/
private function addCoverageToMetric(
CognitiveMetrics $metric,
CoverageReportReaderInterface $coverageReader
): void {
// Strip leading backslash from class name for coverage lookup
$className = ltrim($metric->getClass(), '\\');

// Try to get method-level coverage first
$coverageDetails = $coverageReader->getCoverageDetails($className);
if ($coverageDetails !== null) {
$this->addMethodLevelCoverage($metric, $coverageDetails);
return;
}

// Fall back to class-level coverage if details not available
$coverage = $coverageReader->getLineCoverage($className);
if ($coverage !== null) {
$metric->setCoverage($coverage);
}
}

/**
* Add method-level coverage from coverage details
*/
private function addMethodLevelCoverage(
CognitiveMetrics $metric,
CodeCoverage\CoverageDetails $coverageDetails
): void {
$methods = $coverageDetails->getMethods();
$methodName = $metric->getMethod();

if (isset($methods[$methodName])) {
$methodCoverage = $methods[$methodName];
$metric->setCoverage($methodCoverage->getLineRate());
return;
}

// Fall back to class-level coverage if method not found
$metric->setCoverage($coverageDetails->getLineRate());
}
}
Loading