Skip to content

Commit 1e35d55

Browse files
Adding more report Report Generators (#68)
Add CI report formats: Checkstyle, JUnit, SARIF, GitLab Code Quality, and GitHub Actions This commit adds five new CI-friendly report formats to the cognitive report system, enabling integration with popular CI/CD platforms and code quality tools. New Report Formats: - Checkstyle XML: Widely supported format for Jenkins, Maven Checkstyle Plugin, and IDEs. Emits violations grouped by file with severity levels. - JUnit XML: Standard format for Jenkins JUnit plugin and Maven Surefire. Models methods as test cases with failures for those exceeding threshold. - SARIF 2.1.0: GitHub Code Scanning format with rule definitions, results, locations, and fingerprints for deduplication. - GitLab Code Quality: CodeClimate-style JSON array format for GitLab MR widgets and pipeline Code Quality tab. - GitHub Actions: Workflow command format (::warning/::error) for inline annotations in GitHub Actions logs and PR "Files changed" tab.
1 parent fd381b9 commit 1e35d55

13 files changed

Lines changed: 1343 additions & 5 deletions

readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Cognitive Code Analysis is an approach to understanding and improving code by fo
1111
* Cognitive Complexity Analysis:
1212
* Calculates a cognitive complexity score for each class and method
1313
* Provides detailed cognitive complexity metrics
14-
* Generate reports in various formats (JSON, CSV, HTML)
14+
* Generate reports in various formats (JSON, CSV, HTML, Markdown, Checkstyle XML, JUnit XML, SARIF, GitLab Code Quality, GitHub Actions)
1515
* Baseline comparison to track complexity changes over time
1616
* Configurable thresholds and weights for complexity analysis
1717
* Optional result cache for faster subsequent runs (must be enabled in config)
@@ -36,7 +36,7 @@ Cognitive Complexity Analysis
3636
bin/phpcca analyse <path-to-folder>
3737
```
3838

39-
Generate a report, supported types are `json`, `csv`, `html`.
39+
Generate a report, supported types are `json`, `csv`, `html`, `markdown`, `checkstyle`, `junit`, `sarif`, `gitlab-codequality`, `github-actions`.
4040

4141
```bash
4242
bin/phpcca analyse <path-to-folder> --report-type json --report-file cognitive.json
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Report;
6+
7+
use DOMDocument;
8+
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetrics;
9+
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection;
10+
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
11+
use Phauthentic\CognitiveCodeAnalysis\Config\CognitiveConfig;
12+
13+
/**
14+
* Checkstyle XML report for CI (Jenkins, Maven Checkstyle Plugin, IDEs).
15+
* Emits one violation per method that exceeds the cognitive complexity threshold.
16+
*/
17+
class CheckstyleReport implements ReportGeneratorInterface
18+
{
19+
private const SOURCE = 'CognitiveComplexity';
20+
private const VERSION = '8.0';
21+
22+
public function __construct(
23+
private readonly CognitiveConfig $config
24+
) {
25+
}
26+
27+
public function export(CognitiveMetricsCollection $metrics, string $filename): void
28+
{
29+
$directory = dirname($filename);
30+
if (!is_dir($directory)) {
31+
throw new CognitiveAnalysisException(sprintf('Directory %s does not exist', $directory));
32+
}
33+
34+
$violations = $this->filterViolations($metrics);
35+
$groupedByFile = $this->groupByFile($violations);
36+
37+
$dom = new DOMDocument('1.0', 'UTF-8');
38+
$dom->formatOutput = true;
39+
40+
$root = $dom->createElement('checkstyle');
41+
$root->setAttribute('version', self::VERSION);
42+
$dom->appendChild($root);
43+
44+
foreach ($groupedByFile as $filePath => $fileMetrics) {
45+
$normalizedPath = $this->normalizePath($filePath);
46+
$fileEl = $dom->createElement('file');
47+
$fileEl->setAttribute('name', $normalizedPath);
48+
$root->appendChild($fileEl);
49+
50+
foreach ($fileMetrics as $metric) {
51+
$errorEl = $dom->createElement('error');
52+
$errorEl->setAttribute('line', (string) $metric->getLine());
53+
$errorEl->setAttribute('column', '1');
54+
$errorEl->setAttribute('severity', $this->scoreToSeverity($metric->getScore()));
55+
$errorEl->setAttribute('message', $this->buildMessage($metric));
56+
$errorEl->setAttribute('source', self::SOURCE);
57+
$fileEl->appendChild($errorEl);
58+
}
59+
}
60+
61+
$xml = $dom->saveXML();
62+
if ($xml === false) {
63+
throw new CognitiveAnalysisException('Could not generate Checkstyle XML');
64+
}
65+
66+
if (file_put_contents($filename, $xml) === false) {
67+
throw new CognitiveAnalysisException("Unable to write to file: {$filename}");
68+
}
69+
}
70+
71+
/**
72+
* @return CognitiveMetrics[]
73+
*/
74+
private function filterViolations(CognitiveMetricsCollection $metrics): array
75+
{
76+
$result = [];
77+
foreach ($metrics as $metric) {
78+
if ($metric->getScore() <= $this->config->scoreThreshold) {
79+
continue;
80+
}
81+
82+
$result[] = $metric;
83+
}
84+
85+
return $result;
86+
}
87+
88+
/**
89+
* @param CognitiveMetrics[] $violations
90+
* @return array<string, CognitiveMetrics[]>
91+
*/
92+
private function groupByFile(array $violations): array
93+
{
94+
$grouped = [];
95+
foreach ($violations as $metric) {
96+
$path = $metric->getFileName();
97+
if (!isset($grouped[$path])) {
98+
$grouped[$path] = [];
99+
}
100+
$grouped[$path][] = $metric;
101+
}
102+
103+
return $grouped;
104+
}
105+
106+
private function normalizePath(string $path): string
107+
{
108+
$path = str_replace('\\', '/', $path);
109+
110+
return ltrim($path, './');
111+
}
112+
113+
private function scoreToSeverity(float $score): string
114+
{
115+
$threshold = $this->config->scoreThreshold;
116+
if ($score >= $threshold * 2) {
117+
return 'error';
118+
}
119+
120+
return 'warning';
121+
}
122+
123+
private function buildMessage(CognitiveMetrics $metric): string
124+
{
125+
$threshold = $this->config->scoreThreshold;
126+
$score = $metric->getScore();
127+
$method = $metric->getMethod();
128+
129+
return sprintf(
130+
'Method %s has cognitive complexity %s (threshold: %s)',
131+
$method,
132+
number_format($score, 1),
133+
number_format($threshold, 1)
134+
);
135+
}
136+
}

src/Business/Cognitive/Report/CognitiveReportFactory.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function __construct(
2424
/**
2525
* Create an exporter instance based on the report type.
2626
*
27-
* @param string $type The type of exporter to create (json, csv, html, markdown)
27+
* @param string $type The type of exporter to create (json, csv, html, markdown, checkstyle, junit, sarif, gitlab-codequality, github-actions)
2828
* @return ReportGeneratorInterface
2929
* @throws InvalidArgumentException If the type is not supported
3030
*/
@@ -39,6 +39,11 @@ public function create(string $type): ReportGeneratorInterface
3939
'csv' => new CsvReport(),
4040
'html' => new HtmlReport(),
4141
'markdown' => new MarkdownReport($config),
42+
'checkstyle' => new CheckstyleReport($config),
43+
'junit' => new JUnitReport($config),
44+
'sarif' => new SarifReport($config),
45+
'gitlab-codequality' => new GitLabCodeQualityReport($config),
46+
'github-actions' => new GitHubActionsReport($config),
4247
default => null,
4348
};
4449

@@ -86,7 +91,17 @@ public function getSupportedTypes(): array
8691
$customReporters = $config->customReporters['cognitive'] ?? [];
8792

8893
return array_merge(
89-
['json', 'csv', 'html', 'markdown'],
94+
[
95+
'json',
96+
'csv',
97+
'html',
98+
'markdown',
99+
'checkstyle',
100+
'junit',
101+
'sarif',
102+
'gitlab-codequality',
103+
'github-actions',
104+
],
90105
array_keys($customReporters)
91106
);
92107
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Report;
6+
7+
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetrics;
8+
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection;
9+
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
10+
use Phauthentic\CognitiveCodeAnalysis\Config\CognitiveConfig;
11+
12+
/**
13+
* GitHub Actions workflow command report (::warning / ::error).
14+
* Writes one line per method over threshold for CI log annotations.
15+
*/
16+
class GitHubActionsReport implements ReportGeneratorInterface
17+
{
18+
public function __construct(
19+
private readonly CognitiveConfig $config
20+
) {
21+
}
22+
23+
public function export(CognitiveMetricsCollection $metrics, string $filename): void
24+
{
25+
$directory = dirname($filename);
26+
if (!is_dir($directory)) {
27+
throw new CognitiveAnalysisException(sprintf('Directory %s does not exist', $directory));
28+
}
29+
30+
$lines = [];
31+
foreach ($metrics as $metric) {
32+
if ($metric->getScore() <= $this->config->scoreThreshold) {
33+
continue;
34+
}
35+
36+
$level = $this->scoreToLevel($metric->getScore());
37+
$path = $this->normalizePath($metric->getFileName());
38+
$line = $metric->getLine();
39+
$message = $this->buildMessage($metric);
40+
$lines[] = sprintf('::%s file=%s,line=%d::%s', $level, $path, $line, $message);
41+
}
42+
43+
$content = implode("\n", $lines);
44+
if ($lines !== []) {
45+
$content .= "\n";
46+
}
47+
48+
if (file_put_contents($filename, $content) === false) {
49+
throw new CognitiveAnalysisException("Unable to write to file: {$filename}");
50+
}
51+
}
52+
53+
private function normalizePath(string $path): string
54+
{
55+
$path = str_replace('\\', '/', $path);
56+
57+
return ltrim($path, './');
58+
}
59+
60+
private function scoreToLevel(float $score): string
61+
{
62+
$threshold = $this->config->scoreThreshold;
63+
if ($score >= $threshold * 2) {
64+
return 'error';
65+
}
66+
67+
return 'warning';
68+
}
69+
70+
private function buildMessage(CognitiveMetrics $metric): string
71+
{
72+
$score = $metric->getScore();
73+
$threshold = $this->config->scoreThreshold;
74+
$method = $metric->getMethod();
75+
76+
return sprintf(
77+
'Method %s has cognitive complexity %s (threshold: %s)',
78+
$method,
79+
number_format($score, 1),
80+
number_format($threshold, 1)
81+
);
82+
}
83+
}

0 commit comments

Comments
 (0)