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
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@

interface ChangeCounterInterface
{
public function getNumberOfChangesForFile(string $filename): int;
public function getNumberOfChangesForFile(string $filename, string $since): int;
}
36 changes: 31 additions & 5 deletions src/Business/Churn/ChangeCounter/GitChangeCounter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,47 @@

namespace Phauthentic\CognitiveCodeAnalysis\Business\Churn\ChangeCounter;

use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;

/**
*
*/
class GitChangeCounter implements ChangeCounterInterface
{
public function getNumberOfChangesForFile(string $filename): int
/**
* @throws CognitiveAnalysisException
*/
public function getNumberOfChangesForFile(string $filename, string $since): int
{
$command = sprintf(
'git -C %s rev-list --since=%s --no-merges --count HEAD -- %s',
escapeshellarg(dirname($filename)),
escapeshellarg('1900-01-01'),
escapeshellarg($filename)
escapeshellarg($since),
escapeshellarg(basename($filename))
);

$output = [];
$returnVar = 0;
exec($command, $output, $returnVar);
$resultCode = 0;
exec($command, $output, $resultCode);

$this->assertValidExitCode($resultCode, $output, $filename);

return (int)$output[0];
}

/**
* @param int $resultCode
* @param array<int, string> $output
* @param string $filename
* @return void
* @throws CognitiveAnalysisException
*/
public function assertValidExitCode(int $resultCode, array $output, string $filename): void
{
if ($resultCode !== 0 || empty($output)) {
throw new CognitiveAnalysisException(
'Failed to execute git command or no changes found for file: ' . $filename
);
}
}
}
8 changes: 4 additions & 4 deletions src/Business/Cognitive/BaselineService.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive;

use JsonException;
use RuntimeException;
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;

/**
*
Expand Down Expand Up @@ -36,17 +36,17 @@ public function calculateDeltas(CognitiveMetricsCollection $metricsCollection, a
*
* @param string $baselineFile
* @return array<string, array<string, mixed>> $baseline
* @throws JsonException
* @throws JsonException|CognitiveAnalysisException
*/
public function loadBaseline(string $baselineFile): array
{
if (!file_exists($baselineFile)) {
throw new RuntimeException('Baseline file does not exist.');
throw new CognitiveAnalysisException('Baseline file does not exist.');
}

$baseline = file_get_contents($baselineFile);
if ($baseline === false) {
throw new RuntimeException('Failed to read baseline file.');
throw new CognitiveAnalysisException('Failed to read baseline file.');
}

return json_decode($baseline, true, 512, JSON_THROW_ON_ERROR);
Expand Down
9 changes: 6 additions & 3 deletions src/Business/Cognitive/Exporter/CsvExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Exporter;

use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection;
use RuntimeException;
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;

/**
*
Expand Down Expand Up @@ -40,16 +40,19 @@ class CsvExporter implements DataExporterInterface
'Combined Cognitive Complexity'
];

/**
* @throws CognitiveAnalysisException
*/
public function export(CognitiveMetricsCollection $metrics, string $filename): void
{
$basename = dirname($filename);
if (!is_dir($basename)) {
throw new RuntimeException(sprintf('Directory %s does not exist', $basename));
throw new CognitiveAnalysisException(sprintf('Directory %s does not exist', $basename));
}

$file = fopen($filename, 'wb');
if ($file === false) {
throw new RuntimeException(sprintf('Could not open file %s for writing', $filename));
throw new CognitiveAnalysisException(sprintf('Could not open file %s for writing', $filename));
}

fputcsv($file, $this->header);
Expand Down
6 changes: 3 additions & 3 deletions src/Business/Cognitive/Exporter/HtmlExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Delta;
use RuntimeException;
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;

/**
* HtmlExporter class for exporting metrics as an HTML file.
Expand Down Expand Up @@ -41,7 +41,7 @@ public function export(CognitiveMetricsCollection $metrics, string $filename): v
$html = $this->generateHtml($metrics);

if (file_put_contents($filename, $html) === false) {
throw new RuntimeException('Could not write to file');
throw new CognitiveAnalysisException('Could not write to file');
}
}

Expand Down Expand Up @@ -133,7 +133,7 @@ private function generateHtml(CognitiveMetricsCollection $metrics): string
$result = ob_get_clean();

if ($result === false) {
throw new RuntimeException('Could not generate HTML');
throw new CognitiveAnalysisException('Could not generate HTML');
}

return $result;
Expand Down
7 changes: 4 additions & 3 deletions src/Business/Cognitive/Exporter/JsonExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@

namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Exporter;

use JsonException;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection;
use RuntimeException;
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;

/**
*
*/
class JsonExporter implements DataExporterInterface
{
/**
* @throws \JsonException
* @throws JsonException|CognitiveAnalysisException
*/
public function export(CognitiveMetricsCollection $metricsCollection, string $filename): void
{
Expand Down Expand Up @@ -50,7 +51,7 @@ public function export(CognitiveMetricsCollection $metricsCollection, string $fi
$jsonData = json_encode($jsonData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);

if (file_put_contents($filename, $jsonData) === false) {
throw new RuntimeException("Unable to write to file: $filename");
throw new CognitiveAnalysisException("Unable to write to file: $filename");
}
}
}
6 changes: 5 additions & 1 deletion src/Business/DirectoryScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use FilesystemIterator;
use Generator;
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
Expand Down Expand Up @@ -39,10 +40,13 @@ public function scan(array $paths, array $exclude = []): Generator
}
}

/**
* @throws CognitiveAnalysisException
*/
private function assertValidPath(string $path): void
{
if (!file_exists($path)) {
throw new RuntimeException("Path does not exist: $path");
throw new CognitiveAnalysisException("Path does not exist: $path");
}
}

Expand Down
7 changes: 5 additions & 2 deletions src/Business/MetricsFacade.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,16 @@ public function getCognitiveMetrics(string $path): CognitiveMetricsCollection
/**
* @return array<string, array<string, mixed>>
*/
public function calculateChurn(string $path, string $vcsType = 'git'): array
public function calculateChurn(string $path, string $vcsType = 'git', string $since = '1900-01-01'): array
{
$metricsCollection = $this->getCognitiveMetrics($path);

$counter = $this->changeCounterFactory->create($vcsType);
foreach ($metricsCollection as $metric) {
$metric->setTimesChanged($counter->getNumberOfChangesForFile($metric->getFilename()));
$metric->setTimesChanged($counter->getNumberOfChangesForFile(
filename: $metric->getFilename(),
since: $since,
));
}

return $this->churnCalculator->calculate($metricsCollection);
Expand Down
13 changes: 11 additions & 2 deletions src/Command/ChurnCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class ChurnCommand extends Command

public const OPTION_VCS = 'vcs';

public const OPTION_SINCE = 'since';

public const OPTION_DEBUG = 'debug';

/**
Expand Down Expand Up @@ -57,9 +59,15 @@ protected function configure(): void
description: 'Path to a configuration file',
)
->addOption(
name: self::OPTION_VCS,
name: self::OPTION_SINCE,
shortcut: 's',
mode: InputArgument::OPTIONAL,
description: 'Where to start counting changes from',
default: '2000-01-01'
)
->addOption(
name: self::OPTION_VCS,
mode: InputArgument::OPTIONAL,
description: 'Path to a configuration file',
default: 'git'
)
Expand All @@ -83,7 +91,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
{
$classes = $this->metricsFacade->calculateChurn(
path: $input->getArgument(self::ARGUMENT_PATH),
vcsType: $input->getOption(self::OPTION_VCS)
vcsType: $input->getOption(self::OPTION_VCS),
since: $input->getOption(self::OPTION_SINCE),
);

$this->churnTextRenderer->renderChurnTable(
Expand Down
61 changes: 43 additions & 18 deletions src/Command/Presentation/CognitiveMetricTextRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetrics;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection;
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
use Phauthentic\CognitiveCodeAnalysis\Config\CognitiveConfig;
use RuntimeException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Output\OutputInterface;

Expand All @@ -21,7 +21,7 @@ public function __construct(
) {
}

public function metricExceedsThreshold(CognitiveMetrics $metric, CognitiveConfig $config): bool
private function metricExceedsThreshold(CognitiveMetrics $metric, CognitiveConfig $config): bool
{
return
$config->showOnlyMethodsExceedingThreshold &&
Expand All @@ -31,6 +31,7 @@ public function metricExceedsThreshold(CognitiveMetrics $metric, CognitiveConfig
/**
* @param CognitiveMetricsCollection $metricsCollection
* @param CognitiveConfig $config
* @throws CognitiveAnalysisException
*/
public function render(CognitiveMetricsCollection $metricsCollection, CognitiveConfig $config): void
{
Expand All @@ -49,22 +50,18 @@ public function render(CognitiveMetricsCollection $metricsCollection, CognitiveC
continue;
}

$row = $this->prepareTableRow($metric);
$rows[] = $row;
$rows[] = $this->prepareTableRows($metric);
$filename = $metric->getFileName();
}

if (empty($rows)) {
continue;
}

$this->renderTable((string)$className, $rows, $filename);
}
}

/**
* @param string $className
* @param array<int, mixed> $rows
* @param string $filename
*/
private function renderTable(string $className, array $rows, string $filename): void
{
Expand Down Expand Up @@ -103,22 +100,18 @@ private function getTableHeaders(): array
/**
* @param CognitiveMetrics $metrics
* @return array<string, mixed>
* @throws CognitiveAnalysisException
*/
private function prepareTableRow(CognitiveMetrics $metrics): array
private function prepareTableRows(CognitiveMetrics $metrics): array
{
$row = $this->metricsToArray($metrics);
$keys = $this->getKeys();

foreach ($keys as $key) {
$getMethod = 'get' . $key;
$getMethodWeight = 'get' . $key . 'Weight';
$getDeltaMethod = 'get' . $key . 'WeightDelta';
$weight = $metrics->{$getMethodWeight}();
$row[$key] = $metrics->{$getMethod}() . ' (' . round($weight, 3) . ')';
$row = $this->roundWeighs($key, $metrics, $row);

if (!method_exists($metrics, $getDeltaMethod)) {
throw new RuntimeException('Method not found: ' . $getDeltaMethod);
}
$getDeltaMethod = 'get' . $key . 'WeightDelta';
$this->assertDeltaMethodExists($metrics, $getDeltaMethod);

$delta = $metrics->{$getDeltaMethod}();
if ($delta === null || $delta->hasNotChanged()) {
Expand Down Expand Up @@ -169,7 +162,39 @@ private function metricsToArray(CognitiveMetrics $metrics): array
'ifCount' => $metrics->getIfCount(),
'ifNestingLevel' => $metrics->getIfNestingLevel(),
'elseCount' => $metrics->getElseCount(),
'score' => $metrics->getScore() > 0.5 ? '<error>' . $metrics->getScore() . '</error>' : '<info>' . $metrics->getScore() . '</info>',
'score' => $metrics->getScore() > 0.5
? '<error>' . $metrics->getScore() . '</error>'
: '<info>' . $metrics->getScore() . '</info>',
];
}

/**
* @param CognitiveMetrics $metrics
* @param string $getDeltaMethod
* @return void
* @throws CognitiveAnalysisException
*/
private function assertDeltaMethodExists(CognitiveMetrics $metrics, string $getDeltaMethod): void
{
if (!method_exists($metrics, $getDeltaMethod)) {
throw new CognitiveAnalysisException('Method not found: ' . $getDeltaMethod);
}
}

/**
* @param string $key
* @param CognitiveMetrics $metrics
* @param array<string, mixed> $row
* @return array<string, mixed>
*/
private function roundWeighs(string $key, CognitiveMetrics $metrics, array $row): array
{
$getMethod = 'get' . $key;
$getMethodWeight = 'get' . $key . 'Weight';

$weight = $metrics->{$getMethodWeight}();
$row[$key] = $metrics->{$getMethod}() . ' (' . round($weight, 3) . ')';

return $row;
}
}
3 changes: 2 additions & 1 deletion tests/Unit/Business/Cognitive/BaselineServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\BaselineService;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetrics;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection;
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use JsonException;
Expand All @@ -25,7 +26,7 @@ protected function setUp(): void

public function testLoadBaselineThrowsExceptionIfFileDoesNotExist(): void
{
$this->expectException(RuntimeException::class);
$this->expectException(CognitiveAnalysisException::class);
$this->expectExceptionMessage('Baseline file does not exist.');

$this->baselineService->loadBaseline('non_existent_file.json');
Expand Down
Loading