Skip to content

Commit 86c1707

Browse files
Made the Cyclomatic and Halstead metrics configurable (#38)
* Made the cyclomatic and halstead metrics configurable. * Refactoring the CognitiveMetricTextRenderer * Updating the Configuration documentation.
1 parent 1371d60 commit 86c1707

File tree

7 files changed

+117
-56
lines changed

7 files changed

+117
-56
lines changed

config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ cognitive:
33
excludePatterns:
44
scoreThreshold: 0.5
55
showOnlyMethodsExceedingThreshold: false
6+
showHalsteadComplexity: false
7+
showCyclomaticComplexity: false
68
metrics:
79
lineCount:
810
threshold: 60

docs/Configuration.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,15 @@ cognitive:
6767
threshold: 1
6868
scale: 1.0
6969
```
70+
71+
## Showing additional metrics
72+
73+
You can show the Halstead Complexity and the Cyclomatic Complexity in the output by setting the following configuration options to `true`, the default is `false`.
74+
75+
This can be useful to get a better understanding of the complexity of your code. Keep in mind that each of them measures a different aspect of complexity, so it is recommended to use them in conjunction with cognitive complexity.
76+
77+
```yaml
78+
cognitive:
79+
showHalsteadComplexity: false
80+
showCyclomaticComplexity: false
81+
```

src/Command/Presentation/CognitiveMetricTextRenderer.php

Lines changed: 82 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private function renderTable(string $className, array $rows, string $filename):
8787
*/
8888
private function getTableHeaders(): array
8989
{
90-
return [
90+
$fields = [
9191
"Method Name",
9292
"Lines",
9393
"Arguments",
@@ -98,11 +98,40 @@ private function getTableHeaders(): array
9898
"If Nesting\nLevel",
9999
"Else",
100100
"Cognitive\nComplexity",
101-
"Halstead\nVolume",
102-
"Halstead\nDifficulty",
103-
"Halstead\nEffort",
104-
"Cyclomatic\nComplexity"
105101
];
102+
103+
$fields = $this->addHalsteadHeaders($fields);
104+
$fields = $this->addCyclomaticHeaders($fields);
105+
106+
return $fields;
107+
}
108+
109+
/**
110+
* @param array<string> $fields
111+
* @return array<string>
112+
*/
113+
private function addHalsteadHeaders(array $fields): array
114+
{
115+
if ($this->configService->getConfig()->showHalsteadComplexity) {
116+
$fields[] = "Halstead\nVolume";
117+
$fields[] = "Halstead\nDifficulty";
118+
$fields[] = "Halstead\nEffort";
119+
}
120+
121+
return $fields;
122+
}
123+
124+
/**
125+
* @param array<string> $fields
126+
* @return array<string>
127+
*/
128+
private function addCyclomaticHeaders(array $fields): array
129+
{
130+
if ($this->configService->getConfig()->showCyclomaticComplexity) {
131+
$fields[] = "Cyclomatic\nComplexity";
132+
}
133+
134+
return $fields;
106135
}
107136

108137
/**
@@ -160,10 +189,7 @@ private function getKeys(): array
160189
*/
161190
private function metricsToArray(CognitiveMetrics $metrics): array
162191
{
163-
$halstead = $metrics->getHalstead();
164-
$cyclomatic = $metrics->getCyclomatic();
165-
166-
return [
192+
$fields = [
167193
'methodName' => $metrics->getMethod(),
168194
'lineCount' => $metrics->getLineCount(),
169195
'argCount' => $metrics->getArgCount(),
@@ -174,11 +200,42 @@ private function metricsToArray(CognitiveMetrics $metrics): array
174200
'ifNestingLevel' => $metrics->getIfNestingLevel(),
175201
'elseCount' => $metrics->getElseCount(),
176202
'score' => $this->formatScore($metrics->getScore()),
177-
'halsteadVolume' => $this->formatHalsteadVolume($halstead),
178-
'halsteadDifficulty' => $this->formatHalsteadDifficulty($halstead),
179-
'halsteadEffort' => $this->formatHalsteadEffort($halstead),
180-
'cyclomaticComplexity' => $this->formatCyclomaticComplexity($cyclomatic),
181203
];
204+
205+
$fields = $this->addHalsteadFields($fields, $metrics->getHalstead());
206+
$fields = $this->addCyclomaticFields($fields, $metrics->getCyclomatic());
207+
208+
return $fields;
209+
}
210+
211+
/**
212+
* @param array<string, mixed> $fields
213+
* @param HalsteadMetrics|null $halstead
214+
* @return array<string, mixed>
215+
*/
216+
private function addHalsteadFields(array $fields, ?HalsteadMetrics $halstead): array
217+
{
218+
if ($this->configService->getConfig()->showHalsteadComplexity) {
219+
$fields['halsteadVolume'] = $this->formatHalsteadVolume($halstead);
220+
$fields['halsteadDifficulty'] = $this->formatHalsteadDifficulty($halstead);
221+
$fields['halsteadEffort'] = $this->formatHalsteadEffort($halstead);
222+
}
223+
224+
return $fields;
225+
}
226+
227+
/**
228+
* @param array<string, mixed> $fields
229+
* @param CyclomaticMetrics|null $cyclomatic
230+
* @return array<string, mixed>
231+
*/
232+
private function addCyclomaticFields(array $fields, ?CyclomaticMetrics $cyclomatic): array
233+
{
234+
if ($this->configService->getConfig()->showCyclomaticComplexity) {
235+
$fields['cyclomaticComplexity'] = $this->formatCyclomaticComplexity($cyclomatic);
236+
}
237+
238+
return $fields;
182239
}
183240

184241
private function formatScore(float $score): string
@@ -188,28 +245,21 @@ private function formatScore(float $score): string
188245
: '<info>' . $score . '</info>';
189246
}
190247

191-
/**
192-
* @param HalsteadMetrics|null $halstead
193-
* @return string
194-
*/
195248
private function formatHalsteadVolume(?HalsteadMetrics $halstead): string
196249
{
197250
if (!$halstead) {
198251
return '-';
199252
}
253+
200254
$value = round($halstead->getVolume(), 3);
201-
if ($value >= 1000) {
202-
return '<error>' . $value . '</error>';
203-
}
204-
if ($value >= 100) {
205-
return '<comment>' . $value . '</comment>';
206-
}
207-
return (string)$value;
255+
256+
return match (true) {
257+
$value >= 1000 => '<error>' . $value . '</error>',
258+
$value >= 100 => '<comment>' . $value . '</comment>',
259+
default => (string)$value,
260+
};
208261
}
209262

210-
/**
211-
* @param HalsteadMetrics|null $halstead
212-
*/
213263
private function formatHalsteadDifficulty(?HalsteadMetrics $halstead): string
214264
{
215265
if (!$halstead) {
@@ -225,9 +275,6 @@ private function formatHalsteadDifficulty(?HalsteadMetrics $halstead): string
225275
return (string)$value;
226276
}
227277

228-
/**
229-
* @param HalsteadMetrics|null $halstead
230-
*/
231278
private function formatHalsteadEffort(?HalsteadMetrics $halstead): string
232279
{
233280
if (!$halstead) {
@@ -243,9 +290,6 @@ private function formatHalsteadEffort(?HalsteadMetrics $halstead): string
243290
return (string)$value;
244291
}
245292

246-
/**
247-
* @param CyclomaticMetrics|null $cyclomatic
248-
*/
249293
private function formatCyclomaticComplexity(?CyclomaticMetrics $cyclomatic): string
250294
{
251295
if (!$cyclomatic) {
@@ -262,14 +306,11 @@ private function formatCyclomaticComplexity(?CyclomaticMetrics $cyclomatic): str
262306

263307
private function colorCyclomaticRisk(string $risk): string
264308
{
265-
$riskLower = strtolower($risk);
266-
if ($riskLower === 'medium') {
267-
return '<comment>' . $risk . '</comment>';
268-
}
269-
if ($riskLower === 'high') {
270-
return '<error>' . $risk . '</error>';
271-
}
272-
return $risk;
309+
return match (strtolower($risk)) {
310+
'medium' => '<comment>' . $risk . '</comment>',
311+
'high' => '<error>' . $risk . '</error>',
312+
default => $risk,
313+
};
273314
}
274315

275316
/**

src/Config/CognitiveConfig.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use InvalidArgumentException;
88

99
/**
10-
*
10+
* @SuppressWarnings(BooleanArgumentFlag)
1111
*/
1212
class CognitiveConfig
1313
{
@@ -21,7 +21,9 @@ public function __construct(
2121
public readonly array $excludePatterns,
2222
public readonly array $metrics,
2323
public readonly bool $showOnlyMethodsExceedingThreshold,
24-
public readonly float $scoreThreshold
24+
public readonly float $scoreThreshold,
25+
public readonly bool $showHalsteadComplexity = false,
26+
public readonly bool $showCyclomaticComplexity = false,
2527
) {
2628
}
2729
}

src/Config/ConfigFactory.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,22 @@ class ConfigFactory
1515
*/
1616
public function fromArray(array $config): CognitiveConfig
1717
{
18-
$metrics = [];
19-
foreach ($config['cognitive']['metrics'] as $name => $metric) {
20-
$metrics[$name] = new MetricsConfig(
18+
$metrics = array_map(function ($metric) {
19+
return new MetricsConfig(
2120
$metric['threshold'],
2221
$metric['scale'],
2322
$metric['enabled']
2423
);
25-
}
24+
}, $config['cognitive']['metrics']);
2625

2726
return new CognitiveConfig(
28-
$config['cognitive']['excludeFilePatterns'],
29-
$config['cognitive']['excludePatterns'],
30-
$metrics,
31-
$config['cognitive']['showOnlyMethodsExceedingThreshold'],
32-
$config['cognitive']['scoreThreshold']
27+
excludeFilePatterns: $config['cognitive']['excludeFilePatterns'],
28+
excludePatterns: $config['cognitive']['excludePatterns'],
29+
metrics: $metrics,
30+
showOnlyMethodsExceedingThreshold: $config['cognitive']['showOnlyMethodsExceedingThreshold'],
31+
scoreThreshold: $config['cognitive']['scoreThreshold'],
32+
showHalsteadComplexity: $config['cognitive']['showHalsteadComplexity'] ?? false,
33+
showCyclomaticComplexity: $config['cognitive']['showCyclomaticComplexity'] ?? false
3334
);
3435
}
3536
}

src/Config/ConfigLoader.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ public function getConfigTreeBuilder(): TreeBuilder
9696
->booleanNode('showOnlyMethodsExceedingThreshold')
9797
->defaultValue(false)
9898
->end()
99+
->booleanNode('showHalsteadComplexity')
100+
->defaultValue(false)
101+
->end()
102+
->booleanNode('showCyclomaticComplexity')
103+
->defaultValue(false)
104+
->end()
99105
->arrayNode('metrics')
100106
->useAttributeAsKey('metric')
101107
->arrayPrototype()

src/PhpParser/CyclomaticComplexityVisitor.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class CyclomaticComplexityVisitor extends NodeVisitorAbstract
3838
private string $currentClassName = '';
3939
private string $currentMethod = '';
4040

41-
// Complexity counters for current method
41+
// Complexity counters for the current method
4242
private int $currentMethodComplexity = 1; // Base complexity
4343
private int $ifCount = 0;
4444
private int $elseIfCount = 0;
@@ -112,9 +112,6 @@ private function setCurrentClassOnEnterNode(Node $node): void
112112
}
113113
}
114114

115-
/**
116-
* Ensures the FQCN always starts with a backslash.
117-
*/
118115
private function normalizeFqcn(string $fqcn): string
119116
{
120117
return str_starts_with($fqcn, '\\') ? $fqcn : '\\' . $fqcn;

0 commit comments

Comments
 (0)