Skip to content

Commit ceaf458

Browse files
HTML and Markdown report generation with single table configuration (#69)
* Enhance HTML and Markdown report generation with single table configuration - Updated HtmlReport to support a single table layout for metrics when configured. - Modified MarkdownReport to include a single table format for all methods. - Added new test cases to validate the single table output for both HTML and Markdown reports. - Introduced configuration options to toggle between grouped and single table formats in reports. * Refactor MarkdownReport to streamline file handling - Updated file handling in MarkdownReport to use a local variable for the file handle, improving clarity and reducing potential errors. - Removed unnecessary type assertions and adjusted docblocks for better type hinting. - Ensured consistent usage of the file handle across methods for writing report data. * Refactor MarkdownReport to improve class section handling - Introduced a new method `writeGroupedByClassBatch` to encapsulate the logic for writing class sections to the report. - Created a dedicated `buildClassSection` method to streamline the construction of class report sections, enhancing code readability and maintainability. - Removed redundant code and improved the flow of data writing to the file handle. * Update Dockerfile to use PHP 8.5-cli - Changed base image from php:8.3-cli to php:8.5-cli to leverage new features and improvements in PHP 8.5. - Updated package installation to ensure compatibility with the new PHP version.
1 parent 7fd0b66 commit ceaf458

9 files changed

Lines changed: 428 additions & 116 deletions

File tree

docker/php/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM php:8.3-cli
1+
FROM php:8.5-cli
22

33
RUN apt-get update \
44
&& apt-get install -y libzip-dev zip git wget gpg \

src/Business/Cognitive/Report/CognitiveReportFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function create(string $type): ReportGeneratorInterface
3737
$builtIn = match ($type) {
3838
'json' => new JsonReport(),
3939
'csv' => new CsvReport(),
40-
'html' => new HtmlReport(),
40+
'html' => new HtmlReport($config),
4141
'markdown' => new MarkdownReport($config),
4242
'checkstyle' => new CheckstyleReport($config),
4343
'junit' => new JUnitReport($config),

src/Business/Cognitive/Report/HtmlReport.php

Lines changed: 126 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Report;
66

7+
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetrics;
78
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection;
89
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Delta;
910
use Phauthentic\CognitiveCodeAnalysis\Business\Utility\Datetime;
1011
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
12+
use Phauthentic\CognitiveCodeAnalysis\Config\CognitiveConfig;
1113

1214
/**
1315
* HtmlReport class for exporting metrics as an HTML file.
@@ -27,15 +29,16 @@ class HtmlReport implements ReportGeneratorInterface
2729
'Return Count',
2830
'Variable Count',
2931
'Property Call Count',
30-
'Combined Cognitive Complexity'
32+
'Combined Cognitive Complexity',
3133
];
3234

35+
public function __construct(
36+
private readonly CognitiveConfig $config,
37+
) {
38+
}
39+
3340
/**
3441
* Export metrics to an HTML file using Bootstrap 5.
35-
*
36-
* @param CognitiveMetricsCollection $metrics
37-
* @param string $filename
38-
* @return void
3942
*/
4043
public function export(CognitiveMetricsCollection $metrics, string $filename): void
4144
{
@@ -69,17 +72,68 @@ public function generateMetricRow(int $count, float $weight, ?Delta $delta): str
6972
return $metricRow . '</td>';
7073
}
7174

72-
/**
73-
* Generate HTML content using the metrics data.
74-
*
75-
* @param CognitiveMetricsCollection $metrics
76-
* @return string
77-
*/
7875
private function generateHtml(CognitiveMetricsCollection $metrics): string
76+
{
77+
return $this->config->groupByClass
78+
? $this->generateGroupedHtml($metrics)
79+
: $this->generateSingleTableHtml($metrics);
80+
}
81+
82+
private function generateGroupedHtml(CognitiveMetricsCollection $metrics): string
83+
{
84+
$groupedByClass = $metrics->groupBy('class');
85+
86+
ob_start();
87+
$this->renderDocumentStart(count($groupedByClass));
88+
foreach ($groupedByClass as $class => $methods) {
89+
?>
90+
<table class="table table-bordered">
91+
<thead>
92+
<tr>
93+
<th colspan="<?php echo count($this->header); ?>" class="table-primary"><?php echo $this->escape((string)$class); ?></th>
94+
</tr>
95+
<?php echo $this->renderHeaderRow(false); ?>
96+
</thead>
97+
<tbody>
98+
<?php foreach ($methods as $data) : ?>
99+
<?php echo $this->renderMethodRow($data, false); ?>
100+
<?php endforeach; ?>
101+
</tbody>
102+
</table>
103+
<?php
104+
}
105+
$this->renderDocumentEnd();
106+
107+
return $this->captureOutput();
108+
}
109+
110+
private function generateSingleTableHtml(CognitiveMetricsCollection $metrics): string
79111
{
80112
$groupedByClass = $metrics->groupBy('class');
113+
$totalMethods = count($metrics);
81114

82115
ob_start();
116+
$this->renderDocumentStart(count($groupedByClass));
117+
?>
118+
<h2 class="h5 mb-3">All Methods (<?php echo $totalMethods; ?> total)</h2>
119+
<table class="table table-bordered">
120+
<thead>
121+
<?php echo $this->renderHeaderRow(true); ?>
122+
</thead>
123+
<tbody>
124+
<?php foreach ($metrics as $data) : ?>
125+
<?php echo $this->renderMethodRow($data, true); ?>
126+
<?php endforeach; ?>
127+
</tbody>
128+
</table>
129+
<?php
130+
$this->renderDocumentEnd();
131+
132+
return $this->captureOutput();
133+
}
134+
135+
private function renderDocumentStart(int $classCount): void
136+
{
83137
?>
84138
<!DOCTYPE html>
85139
<html lang="en">
@@ -93,44 +147,73 @@ private function generateHtml(CognitiveMetricsCollection $metrics): string
93147
<div class="container-fluid">
94148
<h1 class="mb-4">Cognitive Metrics Report - <?php echo (new Datetime())->format('Y-m-d H:i:s') ?></h1>
95149
<p>
96-
This report contains the cognitive complexity metrics for the analyzed code in <?php echo count($groupedByClass); ?> classes.
150+
This report contains the cognitive complexity metrics for the analyzed code in <?php echo $classCount; ?> classes.
97151
</p>
152+
<?php
153+
}
98154

99-
<?php foreach ($groupedByClass as $class => $methods) : ?>
100-
<table class="table table-bordered">
101-
<thead>
102-
<tr>
103-
<th colspan="10" class="table-primary"><?php echo $this->escape((string)$class); ?></th>
104-
</tr>
105-
<tr>
106-
<?php foreach ($this->header as $column) : ?>
107-
<th class="table-secondary"><?php echo $this->escape($column); ?></th>
108-
<?php endforeach; ?>
109-
</tr>
110-
</thead>
111-
<tbody>
112-
<?php foreach ($methods as $data) : ?>
113-
<tr>
114-
<td><?php echo $this->escape($data->getMethod()); ?></td>
115-
<?php echo $this->generateMetricRow($data->getLineCount(), $data->getLineCountWeight(), $data->getLineCountWeightDelta()); ?>
116-
<?php echo $this->generateMetricRow($data->getArgCount(), $data->getArgCountWeight(), $data->getArgCountWeightDelta()); ?>
117-
<?php echo $this->generateMetricRow($data->getIfCount(), $data->getIfCountWeight(), $data->getIfCountWeightDelta()); ?>
118-
<?php echo $this->generateMetricRow($data->getIfNestingLevel(), $data->getIfNestingLevelWeight(), $data->getIfNestingLevelWeightDelta()); ?>
119-
<?php echo $this->generateMetricRow($data->getElseCount(), $data->getElseCountWeight(), $data->getElseCountWeightDelta()); ?>
120-
<?php echo $this->generateMetricRow($data->getReturnCount(), $data->getReturnCountWeight(), $data->getReturnCountWeightDelta()); ?>
121-
<?php echo $this->generateMetricRow($data->getVariableCount(), $data->getVariableCountWeight(), $data->getVariableCountWeightDelta()); ?>
122-
<?php echo $this->generateMetricRow($data->getPropertyCallCount(), $data->getPropertyCallCountWeight(), $data->getPropertyCallCountWeightDelta()); ?>
123-
<td><?php echo $this->formatNumber($data->getScore()); ?></td>
124-
</tr>
125-
<?php endforeach; ?>
126-
</tbody>
127-
</table>
128-
<?php endforeach; ?>
129-
155+
private function renderDocumentEnd(): void
156+
{
157+
?>
130158
</div>
131159
</body>
132160
</html>
133161
<?php
162+
}
163+
164+
private function renderHeaderRow(bool $includeClass): string
165+
{
166+
ob_start();
167+
?>
168+
<tr>
169+
<?php if ($includeClass) : ?>
170+
<th class="table-secondary">Class</th>
171+
<?php endif; ?>
172+
<?php foreach ($this->header as $column) : ?>
173+
<th class="table-secondary"><?php echo $this->escape($column); ?></th>
174+
<?php endforeach; ?>
175+
</tr>
176+
<?php
177+
$result = ob_get_clean();
178+
179+
if ($result === false) {
180+
throw new CognitiveAnalysisException('Could not generate HTML header row');
181+
}
182+
183+
return $result;
184+
}
185+
186+
private function renderMethodRow(CognitiveMetrics $data, bool $includeClass): string
187+
{
188+
ob_start();
189+
?>
190+
<tr>
191+
<?php if ($includeClass) : ?>
192+
<td><?php echo $this->escape($data->getClass()); ?></td>
193+
<?php endif; ?>
194+
<td><?php echo $this->escape($data->getMethod()); ?></td>
195+
<?php echo $this->generateMetricRow($data->getLineCount(), $data->getLineCountWeight(), $data->getLineCountWeightDelta()); ?>
196+
<?php echo $this->generateMetricRow($data->getArgCount(), $data->getArgCountWeight(), $data->getArgCountWeightDelta()); ?>
197+
<?php echo $this->generateMetricRow($data->getIfCount(), $data->getIfCountWeight(), $data->getIfCountWeightDelta()); ?>
198+
<?php echo $this->generateMetricRow($data->getIfNestingLevel(), $data->getIfNestingLevelWeight(), $data->getIfNestingLevelWeightDelta()); ?>
199+
<?php echo $this->generateMetricRow($data->getElseCount(), $data->getElseCountWeight(), $data->getElseCountWeightDelta()); ?>
200+
<?php echo $this->generateMetricRow($data->getReturnCount(), $data->getReturnCountWeight(), $data->getReturnCountWeightDelta()); ?>
201+
<?php echo $this->generateMetricRow($data->getVariableCount(), $data->getVariableCountWeight(), $data->getVariableCountWeightDelta()); ?>
202+
<?php echo $this->generateMetricRow($data->getPropertyCallCount(), $data->getPropertyCallCountWeight(), $data->getPropertyCallCountWeightDelta()); ?>
203+
<td><?php echo $this->formatNumber($data->getScore()); ?></td>
204+
</tr>
205+
<?php
206+
$result = ob_get_clean();
207+
208+
if ($result === false) {
209+
throw new CognitiveAnalysisException('Could not generate HTML method row');
210+
}
211+
212+
return $result;
213+
}
214+
215+
private function captureOutput(): string
216+
{
134217
$result = ob_get_clean();
135218

136219
if ($result === false) {

src/Business/Cognitive/Report/MarkdownReport.php

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class MarkdownReport implements ReportGeneratorInterface, StreamableReportInterf
2020
use MarkdownFormatterTrait;
2121

2222
private CognitiveConfig $config;
23-
/** @var resource|false|null */
23+
/** @var resource|null */
2424
private $fileHandle = null;
2525
private bool $isStreaming = false;
2626
/** @var array<int|string> */
@@ -428,11 +428,12 @@ private function generateSingleTableMarkdown(CognitiveMetricsCollection $metrics
428428
*/
429429
public function startReport(string $filename): void
430430
{
431-
$this->fileHandle = fopen($filename, 'wb');
432-
if ($this->fileHandle === false) {
431+
$fileHandle = fopen($filename, 'wb');
432+
if ($fileHandle === false) {
433433
throw new CognitiveAnalysisException(sprintf('Could not open file %s for writing', $filename));
434434
}
435435

436+
$this->fileHandle = $fileHandle;
436437
$this->isStreaming = true;
437438
$this->processedClasses = [];
438439

@@ -447,21 +448,38 @@ public function startReport(string $filename): void
447448

448449
$header .= "---\n\n";
449450

450-
fwrite($this->fileHandle, $header);
451+
if (!$this->config->groupByClass) {
452+
$header .= "## All Methods\n\n";
453+
$header .= $this->buildTableHeaderWithClass() . "\n";
454+
$header .= $this->buildTableSeparatorWithClass() . "\n";
455+
}
456+
457+
fwrite($fileHandle, $header);
451458
}
452459

453460
/**
454461
* @throws CognitiveAnalysisException
455462
*/
456463
public function writeMetricBatch(CognitiveMetricsCollection $batch): void
457464
{
458-
if (!$this->isStreaming || $this->fileHandle === null) {
465+
$fileHandle = $this->fileHandle;
466+
if (!$this->isStreaming || $fileHandle === null) {
459467
throw new CognitiveAnalysisException('Streaming not started. Call startReport() first.');
460468
}
461469

462-
// Type guard: fileHandle is guaranteed to be resource at this point
463-
assert($this->fileHandle !== false);
470+
if (!$this->config->groupByClass) {
471+
$this->writeSingleTableBatch($batch, $fileHandle);
472+
return;
473+
}
474+
475+
$this->writeGroupedByClassBatch($batch, $fileHandle);
476+
}
464477

478+
/**
479+
* @param resource $fileHandle
480+
*/
481+
private function writeGroupedByClassBatch(CognitiveMetricsCollection $batch, $fileHandle): void
482+
{
465483
$groupedByClass = $batch->groupBy('class');
466484

467485
foreach ($groupedByClass as $class => $methods) {
@@ -471,54 +489,69 @@ public function writeMetricBatch(CognitiveMetricsCollection $batch): void
471489
continue;
472490
}
473491

474-
// Check if we've already processed this class
475492
if (in_array($class, $this->processedClasses, true)) {
476493
continue;
477494
}
478495

479496
$this->processedClasses[] = $class;
480497

481-
// Get file path from first method in the collection
482-
$firstMethod = null;
483-
foreach ($filteredMethods as $method) {
484-
$firstMethod = $method;
485-
break;
486-
}
498+
fwrite($fileHandle, $this->buildClassSection((string) $class, $filteredMethods));
499+
}
500+
}
487501

488-
$classSection = "* **Class:** " . $this->escape((string)$class) . "\n";
489-
if ($firstMethod !== null) {
490-
$classSection .= "* **File:** " . $this->escape($firstMethod->getFileName()) . "\n";
491-
}
492-
$classSection .= "\n";
502+
private function buildClassSection(string $class, CognitiveMetricsCollection $filteredMethods): string
503+
{
504+
$firstMethod = null;
505+
foreach ($filteredMethods as $method) {
506+
$firstMethod = $method;
507+
break;
508+
}
493509

494-
// Table header and separator
495-
$classSection .= $this->buildTableHeader() . "\n";
496-
$classSection .= $this->buildTableSeparator() . "\n";
510+
$classSection = "* **Class:** " . $this->escape($class) . "\n";
511+
if ($firstMethod !== null) {
512+
$classSection .= "* **File:** " . $this->escape($firstMethod->getFileName()) . "\n";
513+
}
514+
$classSection .= "\n";
515+
$classSection .= $this->buildTableHeader() . "\n";
516+
$classSection .= $this->buildTableSeparator() . "\n";
497517

498-
// Table rows
499-
foreach ($filteredMethods as $data) {
500-
$classSection .= $this->buildTableRow($data) . "\n";
501-
}
518+
foreach ($filteredMethods as $data) {
519+
$classSection .= $this->buildTableRow($data) . "\n";
520+
}
502521

503-
$classSection .= "\n---\n\n";
522+
return $classSection . "\n---\n\n";
523+
}
504524

505-
fwrite($this->fileHandle, $classSection);
525+
/**
526+
* @param resource $fileHandle
527+
*/
528+
private function writeSingleTableBatch(CognitiveMetricsCollection $batch, $fileHandle): void
529+
{
530+
$filteredMetrics = $this->filterMetrics($batch);
531+
$rows = '';
532+
533+
foreach ($filteredMetrics as $data) {
534+
$rows .= $this->buildTableRowWithClass($data) . "\n";
506535
}
536+
537+
if ($rows === '') {
538+
return;
539+
}
540+
541+
fwrite($fileHandle, $rows);
507542
}
508543

509544
/**
510545
* @throws CognitiveAnalysisException
511546
*/
512547
public function finalizeReport(): void
513548
{
514-
if (!$this->isStreaming || $this->fileHandle === null) {
549+
$fileHandle = $this->fileHandle;
550+
if (!$this->isStreaming || $fileHandle === null) {
515551
throw new CognitiveAnalysisException('Streaming not started or file handle not available.');
516552
}
517553

518-
// Type guard: fileHandle is guaranteed to be resource at this point
519-
assert($this->fileHandle !== false);
520-
521-
fclose($this->fileHandle);
554+
fclose($fileHandle);
522555

523556
$this->isStreaming = false;
524557
$this->fileHandle = null;

0 commit comments

Comments
 (0)