Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added debug output, optimized performance #8

Merged
merged 2 commits into from
Sep 26, 2024
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
3 changes: 0 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,3 @@ jobs:

- name: Run phpstan
run: bin/phpstan -V && bin/phpstan --error-format=github

- name: Run Infection
run: bin/infection
26 changes: 25 additions & 1 deletion src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Phauthentic\CodeQualityMetrics\Business\Cognitive\ScoreCalculator;
use Phauthentic\CodeQualityMetrics\Business\DirectoryScanner;
use Phauthentic\CodeQualityMetrics\Business\Halstead\HalsteadMetricsCollector;
use Phauthentic\CodeQualityMetrics\Command\Cognitive\CognitiveCollectorShellOutputPlugin;
use Phauthentic\CodeQualityMetrics\Command\CognitiveMetricsCommand;
use Phauthentic\CodeQualityMetrics\Command\HalsteadMetricsCommand;
use Phauthentic\CodeQualityMetrics\Business\MetricsFacade;
Expand All @@ -21,6 +22,10 @@
use PhpParser\ParserFactory;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

Expand Down Expand Up @@ -74,6 +79,19 @@ private function registerServices(): void

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

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

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

$this->containerBuilder->register(CognitiveCollectorShellOutputPlugin::class, CognitiveCollectorShellOutputPlugin::class)
->setArguments([
new Reference(InputInterface::class),
new Reference(OutputInterface::class)
])
->setPublic(true);
}

private function bootstrap(): void
Expand All @@ -93,6 +111,9 @@ private function bootstrapMetricsCollectors(): void
new Reference(ParserFactory::class),
new Reference(NodeTraverserInterface::class),
new Reference(DirectoryScanner::class),
[
$this->containerBuilder->get(CognitiveCollectorShellOutputPlugin::class)
]
])
->setPublic(true);

Expand Down Expand Up @@ -157,7 +178,10 @@ public function run(): void
{
$application = $this->containerBuilder->get(SymfonyApplication::class);
// @phpstan-ignore-next-line
$application->run();
$application->run(
$this->containerBuilder->get(InputInterface::class),
$this->containerBuilder->get(OutputInterface::class)
);
}

public function get(string $id): mixed
Expand Down
7 changes: 6 additions & 1 deletion src/Business/AbstractMetricCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Phauthentic\CodeQualityMetrics\Business;

use Generator;
use Phauthentic\CodeQualityMetrics\Business\Cognitive\FindMetricsPluginInterface;
use PhpParser\Error;
use PhpParser\NodeTraverserInterface;
use PhpParser\Parser;
Expand Down Expand Up @@ -32,10 +33,14 @@ protected function getExcludePatternsFromConfig(array $config): array
return [];
}

/**
* @param array<int, FindMetricsPluginInterface> $findMetricsPlugins
*/
public function __construct(
protected readonly ParserFactory $parserFactory,
protected readonly NodeTraverserInterface $traverser,
protected readonly DirectoryScanner $directoryScanner
protected readonly DirectoryScanner $directoryScanner,
protected readonly array $findMetricsPlugins = []
) {
$this->parser = $this->parserFactory->createForHostVersion();
}
Expand Down
56 changes: 34 additions & 22 deletions src/Business/Cognitive/CognitiveMetrics.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@
class CognitiveMetrics implements JsonSerializable
{
/**
* @var array<int, string>
* @var array<string, string>
*/
private array $metrics = [
'lineCount',
'argCount',
'returnCount',
'variableCount',
'propertyCallCount',
'ifCount',
'ifNestingLevel',
'elseCount'
'lineCount' => 'lineCount',
'argCount' => 'argCount',
'returnCount' => 'returnCount',
'variableCount' => 'variableCount',
'propertyCallCount' => 'propertyCallCount',
'ifCount' => 'ifCount',
'ifNestingLevel' => 'ifNestingLevel',
'elseCount' => 'elseCount'
];

private string $class;
Expand Down Expand Up @@ -64,6 +64,7 @@ public function __construct(array $metrics)
{
$this->assertArrayKeyIsPresent($metrics, 'class');
$this->assertArrayKeyIsPresent($metrics, 'method');

$this->method = $metrics['method'];
$this->class = $metrics['class'];

Expand All @@ -77,10 +78,20 @@ public function __construct(array $metrics)
*/
private function setRequiredMetricProperties(array $metrics): void
{
foreach ($this->metrics as $metricName) {
$this->assertArrayKeyIsPresent($metrics, $metricName);
$this->$metricName = $metrics[$metricName];
$missingKeys = array_diff_key($this->metrics, $metrics);
if (!empty($missingKeys)) {
throw new InvalidArgumentException('Missing required keys');
}

// Not pretty to set each but more efficient than using a loop and $this->metrics
$this->lineCount = $metrics['lineCount'];
$this->argCount = $metrics['argCount'];
$this->returnCount = $metrics['returnCount'];
$this->variableCount = $metrics['variableCount'];
$this->propertyCallCount = $metrics['propertyCallCount'];
$this->ifCount = $metrics['ifCount'];
$this->ifNestingLevel = $metrics['ifNestingLevel'];
$this->elseCount = $metrics['elseCount'];
}

/**
Expand All @@ -89,12 +100,15 @@ private function setRequiredMetricProperties(array $metrics): void
*/
private function setOptionalMetricProperties(array $metrics): void
{
foreach ($this->metrics as $metricName) {
$property = $metricName . 'Weight';
if (array_key_exists($property, $metrics)) {
$this->$property = $metrics[$property];
}
}
// Not pretty to set each but more efficient than using a loop and $this->metrics

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not only not so pretty, it also changes the behavior, doesn't it? Perviously it checked if the prop in metrics array exists and if yes set it, so you could have passed deltas to change only like 2 metrics, with your change you always need to put a full set of $metrics, because every not included property will be set to 0.0. That's probably not an issue in this case because this method probably is only called once?

$this->lineCountWeight = $metrics['lineCountWeight'] ?? 0.0;
$this->argCountWeight = $metrics['argCountWeight'] ?? 0.0;
$this->returnCountWeight = $metrics['returnCountWeight'] ?? 0.0;
$this->variableCountWeight = $metrics['variableCountWeight'] ?? 0.0;
$this->propertyCallCountWeight = $metrics['propertyCallCountWeight'] ?? 0.0;
$this->ifCountWeight = $metrics['ifCountWeight'] ?? 0.0;
$this->ifNestingLevelWeight = $metrics['ifNestingLevelWeight'] ?? 0.0;
$this->elseCountWeight = $metrics['elseCountWeight'] ?? 0.0;
}

private function assertSame(self $other): void
Expand All @@ -108,8 +122,7 @@ private function assertSame(self $other): void
$this->getClass(),
$this->getMethod(),
$other->getClass(),
$other->getMethod(
)
$other->getMethod()
));
}

Expand Down Expand Up @@ -146,12 +159,11 @@ public static function fromArray(array $metrics): self
*/
private function assertArrayKeyIsPresent(array $array, string $key): void
{
if (!array_key_exists($key, $array)) {
if (!isset($array[$key])) {
throw new InvalidArgumentException("Missing required key: $key");
}
}

// Getters for read-only attributes
public function getClass(): string
{
return $this->class;
Expand Down
16 changes: 4 additions & 12 deletions src/Business/Cognitive/CognitiveMetricsCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class CognitiveMetricsCollection implements IteratorAggregate, Countable, JsonSe
*/
public function add(CognitiveMetrics $metric): void
{
$this->metrics[] = $metric;
$this->metrics[$metric->getClass() . '::' . $metric->getMethod()] = $metric;
}

/**
Expand Down Expand Up @@ -75,21 +75,13 @@ public function filterWithScoreGreaterThan(float $score): CognitiveMetricsCollec

public function contains(CognitiveMetrics $otherMetric): bool
{
foreach ($this->metrics as $metric) {
if ($otherMetric->equals($metric)) {
return true;
}
}

return false;
return isset($this->metrics[$otherMetric->getClass() . '::' . $otherMetric->getMethod()]);
}

public function getClassWithMethod(string $class, string $method): ?CognitiveMetrics
{
foreach ($this->metrics as $metric) {
if ($metric->getClass() === $class && $metric->getMethod() === $method) {
return $metric;
}
if (isset($this->metrics[$class . '::' . $method])) {
return $this->metrics[$class . '::' . $method];
}

return null;
Expand Down
18 changes: 17 additions & 1 deletion src/Business/Cognitive/CognitiveMetricsCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ protected function findMetrics(iterable $files): CognitiveMetricsCollection
$metricsCollection = new CognitiveMetricsCollection();
$visitor = new CognitiveMetricsVisitor();

foreach ($this->findMetricsPlugins as $plugin) {
$plugin->beforeIteration($files);
}

foreach ($files as $file) {
foreach ($this->findMetricsPlugins as $plugin) {
$plugin->beforeFindMetrics($file);
}

$code = file_get_contents($file->getRealPath());

if ($code === false) {
Expand All @@ -53,6 +61,14 @@ protected function findMetrics(iterable $files): CognitiveMetricsCollection
$this->traverser->removeVisitor($visitor);

$this->processMethodMetrics($methodMetrics, $metricsCollection);

foreach ($this->findMetricsPlugins as $plugin) {
$plugin->afterFindMetrics($file);
}
}

foreach ($this->findMetricsPlugins as $plugin) {
$plugin->afterIteration($metricsCollection);
}

return $metricsCollection;
Expand All @@ -76,7 +92,7 @@ private function processMethodMetrics(
'method' => $method
]);

$metric = CognitiveMetrics::fromArray($metricsArray);
$metric = new CognitiveMetrics($metricsArray);

if (!$metricsCollection->contains($metric)) {
$metricsCollection->add($metric);
Expand Down
24 changes: 24 additions & 0 deletions src/Business/Cognitive/FindMetricsPluginInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Phauthentic\CodeQualityMetrics\Business\Cognitive;

use SplFileInfo;

/**
* CognitiveMetricsCollector class that collects cognitive metrics from source files
*/
interface FindMetricsPluginInterface
{
public function beforeFindMetrics(SplFileInfo $fileInfo): void;

public function afterFindMetrics(SplFileInfo $fileInfo): void;

/**
* @param iterable<SplFileInfo> $files
*/
public function beforeIteration(iterable $files): void;

public function afterIteration(CognitiveMetricsCollection $metricsCollection): void;
}
76 changes: 76 additions & 0 deletions src/Command/Cognitive/CognitiveCollectorShellOutputPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Phauthentic\CodeQualityMetrics\Command\Cognitive;

use Phauthentic\CodeQualityMetrics\Business\Cognitive\CognitiveMetricsCollection;
use Phauthentic\CodeQualityMetrics\Business\Cognitive\FindMetricsPluginInterface;
use Phauthentic\CodeQualityMetrics\Command\CognitiveMetricsCommand;
use SplFileInfo;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
*
*/
class CognitiveCollectorShellOutputPlugin implements FindMetricsPluginInterface
{
private float $startTime;
private int $count = 1;

public function __construct(
private readonly InputInterface $input,
private readonly OutputInterface $output
) {
}

public function beforeFindMetrics(SplFileInfo $fileInfo): void
{
$this->startTime = microtime(true);
}

public function afterFindMetrics(SplFileInfo $fileInfo): void
{
if (
$this->input->hasOption(CognitiveMetricsCommand::OPTION_DEBUG)
&& $this->input->getOption(CognitiveMetricsCommand::OPTION_DEBUG) === false
) {
return;
}

$runtime = microtime(true) - $this->startTime;

$this->output->writeln('Processed ' . $fileInfo->getRealPath());
$this->output->writeln('Number: ' . $this->count . ' Memory: ' . $this->formatBytes(memory_get_usage(true)) . ' -- Runtime: ' . round($runtime, 4) . 's');

$this->count++;
}

public function beforeIteration(iterable $files): void
{
}

public function afterIteration(CognitiveMetricsCollection $metricsCollection): void
{
}

/**
* Converts memory size to a human-readable format (bytes, KB, MB, GB, TB).
*
* @param int $size Memory size in bytes.
* @return string Human-readable memory size.
*/
private function formatBytes(int $size): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;

while ($size >= 1024 && $i < count($units) - 1) {
$size /= 1024;
$i++;
}

return round($size, 2) . ' ' . $units[$i];
}
}
Loading
Loading