Skip to content

Commit

Permalink
Refactoring Config, Adding Baseline (#4)
Browse files Browse the repository at this point in the history
* Refactoring the configuration handling
* Refactoring the report generation
* Fixing the output table
* Updating readme.md
* Refining the HTML Report
  • Loading branch information
floriankraemer authored Sep 21, 2024
1 parent 9bc428c commit 9c34982
Show file tree
Hide file tree
Showing 21 changed files with 509 additions and 274 deletions.
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
"analyze": [
"phpstan analyse src/"
],
"analyse": [
"phpstan analyse src/"
],
"phpmd": [
"bin/phpmd ./src text cleancode,codesize,controversial,design"
],
Expand Down
28 changes: 0 additions & 28 deletions config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
excludePatterns: []

cognitive:
excludedClasses: [],
excludedMethods: [],
Expand Down Expand Up @@ -40,29 +38,3 @@ halstead:
time: 0.0
bugs: 0.0
volume: 0.0

metrics:
lineCount:
threshold: 60
scale: 25.0
argCount:
threshold: 4
scale: 1.0
returnCount:
threshold: 2
scale: 5.0
variableCount:
threshold: 2
scale: 5.0
propertyCallCount:
threshold: 2
scale: 15.0
ifCount:
threshold: 3
scale: 1.0
ifNestingLevel:
threshold: 1
scale: 1.0
elseCount:
threshold: 1
scale: 1.0
44 changes: 44 additions & 0 deletions phpstats.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Project name
projectName: "Test"

# Directories and files for analysis relative to the configuration files directory.
# By default, it is "."
include:
- "./src"

# Directories and files excluded from analysis.
# Please note that for correct work, you need to add a slash at the end
# so that only the necessary folders are excluded, and not all that have the same value in the path.
#
# For example:
# "src/utils" can exclude both the desired "src/utils" folder and the "src/utilsForMe" folder for example.
#
# By default, it is empty
# exclude:
# - ""

# The port on which the server will be launched
# to interact with the analyzer from other programs.
# By default, it is 8080
port: 8080

# The path where the cache will be stored.
# Caching can significantly speed up data collection.
# By default, it is set to the value of the temporary folder + /phpstats.
cacheDir: ""

# Disables caching.
# By default, it is false
disableCache: false

# Path to the project relative to which all imports are allowed.
# By default, it is equal to the analyzed directory.
projectPath: ""

# File extensions to be included in the analysis.
# By default, it is php, inc, php5, phtml.
extensions:
- "php"
- "inc"
- "php5"
- "phtml"
12 changes: 12 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ Cognitive Complexity Analysis
php analyse.php metrics:cognitive <path-to-folder>
```

Generate a report, supported types are `json`, `csv`, `html`.

```bash
php analyse.php metrics:cognitive <path-to-folder> --report-type json --report-file cognitive.json
```

You can also pass a baseline file to compare the results to. The JSON report is used as baseline. The output will now show a delta if a value was changed.

```bash
php analyse.php metrics:cognitive <path-to-folder> --baseline cognitive.json
```

Halstead Complexity Analysis

```bash
Expand Down
18 changes: 16 additions & 2 deletions src/Business/AbstractMetricCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ abstract class AbstractMetricCollector
protected NodeTraverserInterface $traverser;
protected DirectoryScanner $directoryScanner;

/**
* @param array<string, mixed> $config
* @return array<int, string>
*/
protected function getExcludePatternsFromConfig(array $config): array
{
if (isset($config['excludePatterns'])) {
return $config['excludePatterns'];
}

return [];
}

public function __construct()
{
$this->parser = (new ParserFactory())->createForHostVersion();
Expand All @@ -33,11 +46,12 @@ public function __construct()
* Find source files using DirectoryScanner
*
* @param string $path Path to the directory or file to scan
* @param array<int, string> $exclude List of regx to exclude
* @return Generator<mixed, SplFileInfo, mixed, mixed> An iterable of SplFileInfo objects
*/
protected function findSourceFiles(string $path): iterable
protected function findSourceFiles(string $path, array $exclude = []): iterable
{
return $this->directoryScanner->scan([$path], ['^(?!.*\.php$).+']); // Exclude non-PHP files
return $this->directoryScanner->scan([$path], ['^(?!.*\.php$).+'] + $exclude); // Exclude non-PHP files

Check warning on line 54 in src/Business/AbstractMetricCollector.php

View workflow job for this annotation

GitHub Actions / Coding Standard & Static Analysis

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ */ protected function findSourceFiles(string $path, array $exclude = []): iterable { - return $this->directoryScanner->scan([$path], ['^(?!.*\.php$).+'] + $exclude); + return $this->directoryScanner->scan([$path], [] + $exclude); // Exclude non-PHP files } protected function traverseAbstractSyntaxTree(string $code): void

Check warning on line 54 in src/Business/AbstractMetricCollector.php

View workflow job for this annotation

GitHub Actions / Coding Standard & Static Analysis

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ */ protected function findSourceFiles(string $path, array $exclude = []): iterable { - return $this->directoryScanner->scan([$path], ['^(?!.*\.php$).+'] + $exclude); + return $this->directoryScanner->scan([$path], [] + $exclude); // Exclude non-PHP files } protected function traverseAbstractSyntaxTree(string $code): void

Check warning on line 54 in src/Business/AbstractMetricCollector.php

View workflow job for this annotation

GitHub Actions / Coding Standard & Static Analysis

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ */ protected function findSourceFiles(string $path, array $exclude = []): iterable { - return $this->directoryScanner->scan([$path], ['^(?!.*\.php$).+'] + $exclude); + return $this->directoryScanner->scan([$path], [] + $exclude); // Exclude non-PHP files } protected function traverseAbstractSyntaxTree(string $code): void
}


Expand Down
25 changes: 14 additions & 11 deletions src/Business/Cognitive/CognitiveMetricsCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@
*/
class CognitiveMetricsCollector extends AbstractMetricCollector
{
public function __construct()
{
parent::__construct();
}

public function collect(string $path): CognitiveMetricsCollection
/**
* Collect cognitive metrics from the given path
*
* @param string $path
* @param array<string, mixed> $config
* @return CognitiveMetricsCollection
*/
public function collect(string $path, array $config = []): CognitiveMetricsCollection
{
$files = $this->findSourceFiles($path);
$files = $this->findSourceFiles($path, $this->getExcludePatternsFromConfig($config));

return $this->findMetrics($files);
}
Expand All @@ -35,6 +37,7 @@ public function collect(string $path): CognitiveMetricsCollection
protected function findMetrics(iterable $files): CognitiveMetricsCollection
{
$metricsCollection = new CognitiveMetricsCollection();
$visitor = new CognitiveMetricsVisitor();

foreach ($files as $file) {
$code = file_get_contents($file->getRealPath());
Expand All @@ -43,9 +46,7 @@ protected function findMetrics(iterable $files): CognitiveMetricsCollection
throw new RuntimeException("Could not read file: {$file->getRealPath()}");
}

$visitor = new CognitiveMetricsVisitor();
$this->traverser->addVisitor($visitor);

$this->traverseAbstractSyntaxTree($code);

$methodMetrics = $visitor->getMethodMetrics();
Expand All @@ -63,8 +64,10 @@ protected function findMetrics(iterable $files): CognitiveMetricsCollection
* @param array<string, mixed> $methodMetrics
* @param CognitiveMetricsCollection $metricsCollection
*/
private function processMethodMetrics(array $methodMetrics, CognitiveMetricsCollection $metricsCollection): void
{
private function processMethodMetrics(
array $methodMetrics,
CognitiveMetricsCollection $metricsCollection
): void {
foreach ($methodMetrics as $classAndMethod => $metrics) {
[$class, $method] = explode('::', $classAndMethod);

Expand Down
26 changes: 14 additions & 12 deletions src/Business/Cognitive/Exporter/HtmlExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ class HtmlExporter implements DataExporterInterface
'Method',
'Line Count',
'Argument Count',
'If Count',
'If Nesting Level',
'Else Count',
'Return Count',
'Variable Count',
'Property Call Count',
'If Nesting Level',
'Else Count',
'Combined Cognitive Complexity'
];

Expand Down Expand Up @@ -65,8 +66,8 @@ private function generateHtml(CognitiveMetricsCollection $metrics): string
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4">Metrics Report</h1>
<div class="container-fluid">
<h1 class="mb-4">Cognitive Metrics Report</h1>
<table class="table table-bordered table-striped">
<thead>
<tr>
Expand All @@ -81,14 +82,15 @@ private function generateHtml(CognitiveMetricsCollection $metrics): string
<tr>
<td><?php echo htmlspecialchars($data->getClass(), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars($data->getMethod(), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars((string)$data->getLineCount(), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars((string)$data->getArgCount(), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars((string)$data->getReturnCount(), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars((string)$data->getVariableCount(), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars((string)$data->getPropertyCallCount(), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars((string)$data->getIfNestingLevel(), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars((string)$data->getElseCount(), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars((string)$data->getScore(), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars((string)$data->getLineCount(), ENT_QUOTES, 'UTF-8') . ' (' . number_format($data->getLineCountWeight(), 3) . ')'; ?></td>
<td><?php echo htmlspecialchars((string)$data->getArgCount(), ENT_QUOTES, 'UTF-8') . ' (' . number_format($data->getArgCountWeight(), 3) . ')'; ?></td>
<td><?php echo htmlspecialchars((string)$data->getIfCount(), ENT_QUOTES, 'UTF-8') . ' (' . number_format($data->getIfCountWeight(), 3) . ')'; ?></td>
<td><?php echo htmlspecialchars((string)$data->getIfNestingLevel(), ENT_QUOTES, 'UTF-8') . ' (' . number_format($data->getIfNestingLevelWeight(), 3) . ')'; ?></td>
<td><?php echo htmlspecialchars((string)$data->getElseCount(), ENT_QUOTES, 'UTF-8') . ' (' . number_format($data->getElseCountWeight(), 3) . ')'; ?></td>
<td><?php echo htmlspecialchars((string)$data->getReturnCount(), ENT_QUOTES, 'UTF-8') . ' (' . number_format($data->getReturnCountWeight(), 3) . ')'; ?></td>
<td><?php echo htmlspecialchars((string)$data->getVariableCount(), ENT_QUOTES, 'UTF-8') . ' (' . number_format($data->getVariableCountWeight(), 3) . ')'; ?></td>
<td><?php echo htmlspecialchars((string)$data->getPropertyCallCount(), ENT_QUOTES, 'UTF-8') . ' (' . number_format($data->getPropertyCallCountWeight(), 3) . ')'; ?></td>
<td><?php echo number_format($data->getScore(), 3); ?></td>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
Expand Down
27 changes: 16 additions & 11 deletions src/Business/Cognitive/Exporter/JsonExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,26 @@ public function export(CognitiveMetricsCollection $metricsCollection, string $fi
'methods' => [
$metrics->getMethod() => [
'name' => $metrics->getMethod(),
'line_count' => $metrics->getLineCount(),
'arg_count' => $metrics->getArgCount(),
'return_count' => $metrics->getReturnCount(),
'variable_count' => $metrics->getVariableCount(),
'property_call_count' => $metrics->getPropertyCallCount(),
'if_nesting_level' => $metrics->getIfNestingLevel(),
'else_count' => $metrics->getElseCount(),
'lineCount' => $metrics->getLineCount(),
'lineCountWeight' => $metrics->getLineCountWeight(),
'argCount' => $metrics->getArgCount(),
'argCountWeight' => $metrics->getArgCountWeight(),
'returnCount' => $metrics->getReturnCount(),
'returnCountWeight' => $metrics->getReturnCountWeight(),
'variableCount' => $metrics->getVariableCount(),
'variableCountWeight' => $metrics->getVariableCountWeight(),
'propertyCallCount' => $metrics->getPropertyCallCount(),
'propertyCallCountWeight' => $metrics->getPropertyCallCountWeight(),
'ifCount' => $metrics->getIfCount(),
'ifCountWeight' => $metrics->getIfCountWeight(),
'ifNestingLevel' => $metrics->getIfNestingLevel(),
'ifNestingLevelWeight' => $metrics->getIfNestingLevelWeight(),
'elseCount' => $metrics->getElseCount(),
'elseCountWeight' => $metrics->getElseCountWeight(),
'score' => $metrics->getScore()
]
]
];

$metrics->setScore(
$metrics->getPropertyCallCount() + $metrics->getVariableCount() + $metrics->getArgCount()
);
}
}

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

declare(strict_types=1);

namespace Phauthentic\CodeQualityMetrics\Business\Cognitive;

/**
* Enum to represent the metric names.
*/
enum MetricNames: string
{
case LINE_COUNT = 'lineCount';
case ARG_COUNT = 'argCount';
case RETURN_COUNT = 'returnCount';
case VARIABLE_COUNT = 'variableCount';
case PROPERTY_CALL_COUNT = 'propertyCallCount';
case IF_COUNT = 'ifCount';
case IF_NESTING_LEVEL = 'ifNestingLevel';
case ELSE_COUNT = 'elseCount';
}
48 changes: 4 additions & 44 deletions src/Business/Cognitive/ScoreCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,6 @@
*/
class ScoreCalculator
{
/**
* @var array<string, mixed>
*/
private array $defaultConfig = [
'lineCount' => [
'threshold' => 60,
'scale' => 2.0,
],
'argCount' => [
'threshold' => 4,
'scale' => 1.0,
],
'returnCount' => [
'threshold' => 2,
'scale' => 5.0,
],
'variableCount' => [
'threshold' => 2,
'scale' => 5.0,
],
'propertyCallCount' => [
'threshold' => 2,
'scale' => 15.0,
],
'ifCount' => [
'threshold' => 3,
'scale' => 1.0,
],
'ifNestingLevel' => [
'threshold' => 1,
'scale' => 1.0,
],
'elseCount' => [
'threshold' => 1,
'scale' => 1.0,
],
];

/**
* @var string[]
*/
Expand All @@ -63,14 +25,12 @@ class ScoreCalculator

/**
* @param CognitiveMetrics $metrics
* @param array<string, mixed> $config
* @param array<string, mixed> $metricConfiguration
* @return void
*/
public function calculate(CognitiveMetrics $metrics, array $config = []): void
public function calculate(CognitiveMetrics $metrics, array $metricConfiguration = []): void
{
// Merge the provided config with the default config
$config['metrics'] = $config['metrics'] ?? $this->defaultConfig;
$config = array_merge($this->defaultConfig, $config['metrics']);
$metricConfiguration = $metricConfiguration['metrics'];

// List of metric types to process
$metricTypes = [
Expand All @@ -85,7 +45,7 @@ public function calculate(CognitiveMetrics $metrics, array $config = []): void
];

// Calculate and set weights for each metric type
$this->calculateMetricWeights($metricTypes, $metrics, $config);
$this->calculateMetricWeights($metricTypes, $metrics, $metricConfiguration);

// Calculate the overall score
$this->calculateScore($metrics);
Expand Down
Loading

0 comments on commit 9c34982

Please sign in to comment.