Skip to content

Commit faec530

Browse files
Merge branch 'master' of github.com:Phauthentic/cognitive-code-analysis into experiment/more-metrics
# Conflicts: # src/Command/Presentation/CognitiveMetricTextRenderer.php
2 parents a26b3d7 + 3b5a554 commit faec530

14 files changed

Lines changed: 585 additions & 41 deletions

File tree

.github/workflows/phar-build.yaml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: 'Build and Release PHAR'
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
jobs:
9+
build-phar:
10+
name: Build PHAR
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Setup PHP
18+
uses: shivammathur/setup-php@v2
19+
with:
20+
php-version: '8.2'
21+
extensions: json, fileinfo
22+
tools: composer
23+
24+
- name: Install dependencies
25+
run: composer install --no-dev --optimize-autoloader
26+
27+
- name: Build PHAR
28+
run: composer build-phar
29+
30+
- name: Upload PHAR artifact
31+
uses: actions/upload-artifact@v4
32+
with:
33+
name: phpcca.phar
34+
path: phpcca.phar
35+
36+
release:
37+
name: Create Release
38+
needs: build-phar
39+
runs-on: ubuntu-latest
40+
41+
steps:
42+
- name: Download PHAR artifact
43+
uses: actions/download-artifact@v4
44+
with:
45+
name: phpcca.phar
46+
47+
- name: Create GitHub Release
48+
uses: ncipollo/release-action@v1
49+
with:
50+
artifacts: phpcca.phar
51+
token: ${{ secrets.GITHUB_TOKEN }}
52+
tag: ${{ github.ref_name }}
53+
name: Release ${{ github.ref_name }}
54+
draft: true
55+
generateReleaseNotes: true

box.json.dist

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
{
2-
"main": "./analyse.php",
2+
"main": "./bin/phpcca",
33
"compression": "GZ",
4-
"output": "phpcca.phar"
4+
"output": "phpcca.phar",
5+
"force-autodiscovery": true,
6+
"files": [
7+
"config.yml",
8+
"composer.json",
9+
"readme.md",
10+
"LICENSE"
11+
]
512
}

readme.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Cognitive Code Analysis is an approach to understanding and improving code by fo
66
77
[Source: Human Cognitive Limitations. Broad, Consistent, Clinical Application of Physiological Principles Will Require Decision Support](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5822395/)
88

9-
## Installation
9+
## Installation ⚙️
1010

1111
```bash
1212
composer require --dev phauthentic/cognitive-code-analysis
@@ -42,7 +42,7 @@ Note that this requires a version control system (VCS) to be set up, such as Git
4242
bin/phpcca churn <path-to-folder>
4343
```
4444

45-
## Documentation
45+
## Documentation 📚
4646

4747
* [Cognitive Complexity Analysis](./docs/Cognitive-Complexity-Analysis.md#cognitive-complexity-analysis)
4848
* [Why bother?](./docs/Cognitive-Complexity-Analysis.md#why-bother)
@@ -56,8 +56,9 @@ bin/phpcca churn <path-to-folder>
5656
* [Examples](#examples)
5757
* [Wordpress WP_Debug_Data](#wordpress-wp_debug_data)
5858
* [Doctrine Paginator](#doctrine-paginator)
59+
* [Reporting Issues](#reporting-issues)
5960

60-
## Resources
61+
## Resources 🔗
6162

6263
These pages and papers provide more information on cognitive limitations and readability and the impact on the business.
6364

@@ -74,7 +75,7 @@ These pages and papers provide more information on cognitive limitations and rea
7475
* **Cyclomatic Complexity**
7576
* [Cyclomatic Complexity](https://en.wikipedia.org/wiki/Cyclomatic_complexity)
7677

77-
## Examples
78+
## Examples 📖
7879

7980
### Cognitive Metrics
8081

@@ -121,7 +122,13 @@ Class: Doctrine\ORM\Tools\Pagination\Paginator
121122
└───────────────────────────────────────────┴────────┴───────────┴─────────┴───────────┴──────────┴───────┴────────────┴───────────┴────────────┘
122123
```
123124

124-
## License
125+
## Reporting Issues 🪲
126+
127+
If you find a bug or have a feature request, please open an issue on the [GitHub repository](https://github.com/Phauthentic/cognitive-code-analysis/issues/new).
128+
129+
Especially the AST-parser used under the hood to analyse the code might have issues with certain code constructs, so please provide a minimal example that reproduces the issue.
130+
131+
## License ⚖️
125132

126133
Copyright Florian Krämer
127134

src/Application.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ private function registerServices(): void
8181

8282
$this->containerBuilder->register(CognitiveMetricTextRenderer::class, CognitiveMetricTextRenderer::class)
8383
->setArguments([
84-
new Reference(OutputInterface::class)
84+
new Reference(OutputInterface::class),
85+
new Reference(ConfigService::class)
8586
])
8687
->setPublic(true);
8788

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phauthentic\CognitiveCodeAnalysis\Business\Churn\Exporter;
6+
7+
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
8+
9+
/**
10+
* Exports churn data as an SVG treemap.
11+
*
12+
* The size of the rectangles in the treemap is scaled proportionally to the "churn" value of each class.
13+
* Mathematical calculations are delegated to the TreemapMath class, which handles score normalization,
14+
* color mapping, and treemap layout calculations using a slice-and-dice algorithm.
15+
*
16+
* @SuppressWarnings("PHPMD.ShortVariable")
17+
*/
18+
class SvgTreemapExporter implements DataExporterInterface
19+
{
20+
private const SVG_WIDTH = 1200;
21+
private const SVG_HEIGHT = 800;
22+
private const PADDING = 2;
23+
24+
private TreemapMath $treemapMath;
25+
26+
public function __construct()
27+
{
28+
$this->treemapMath = new TreemapMath();
29+
}
30+
31+
/**
32+
* @param array<string, array<string, mixed>> $classes
33+
* @param string $filename
34+
* @throws CognitiveAnalysisException
35+
*/
36+
public function export(array $classes, string $filename): void
37+
{
38+
$svg = $this->generateSvgTreemap(classes: $classes);
39+
40+
if (file_put_contents($filename, $svg) === false) {
41+
throw new CognitiveAnalysisException("Unable to write to file: $filename");
42+
}
43+
}
44+
45+
/**
46+
* Generates a treemap SVG for the churn data.
47+
*
48+
* @param array<string, array<string, mixed>> $classes
49+
* @return string
50+
*/
51+
private function generateSvgTreemap(array $classes): string
52+
{
53+
$items = $this->treemapMath->prepareItems($classes);
54+
55+
[$minScore, $maxScore] = $this->treemapMath->findScoreRange($items);
56+
57+
$rects = $this->treemapMath->calculateTreemapLayout(
58+
items: $items,
59+
x: 0,
60+
y: 0,
61+
width: self::SVG_WIDTH,
62+
height: self::SVG_HEIGHT,
63+
vertical: true,
64+
padding: self::PADDING
65+
);
66+
67+
$svgRects = $this->renderSvgRects(rects: $rects, minScore: $minScore, maxScore: $maxScore);
68+
69+
return $this->wrapSvg(rectsSvg: $svgRects);
70+
}
71+
72+
/**
73+
* Renders SVG rectangles for the treemap.
74+
*
75+
* @param array<int, array<string, mixed>> $rects
76+
* @param float $minScore
77+
* @param float $maxScore
78+
* @return string
79+
*/
80+
private function renderSvgRects(array $rects, float $minScore, float $maxScore): string
81+
{
82+
$svgRects = [];
83+
foreach ($rects as $rect) {
84+
$normalizedScore = $this->treemapMath->normalizeScore(score: $rect['score'], minScore: $minScore, maxScore: $maxScore);
85+
$svgRects[] = $this->renderSvgRect(rect: $rect, normalizedScore: $normalizedScore);
86+
}
87+
88+
return implode("\n", $svgRects);
89+
}
90+
91+
/**
92+
* Renders a single SVG rectangle.
93+
*
94+
* @param array<string, mixed> $rect
95+
* @param float $normalizedScore
96+
* @return string
97+
*/
98+
private function renderSvgRect(array $rect, float $normalizedScore): string
99+
{
100+
$x = $rect['x'] + self::PADDING;
101+
$y = $rect['y'] + self::PADDING;
102+
$width = max(0, $rect['width'] - self::PADDING * 2);
103+
$height = max(0, $rect['height'] - self::PADDING * 2);
104+
$color = $this->treemapMath->scoreToColor(score: $normalizedScore);
105+
$class = htmlspecialchars($rect['class']);
106+
$churn = $rect['churn'];
107+
$score = $rect['score'];
108+
$textX = $x + 4;
109+
$textY = $y + 18;
110+
$label = htmlspecialchars(mb_strimwidth($rect['class'], 0, 40, ''));
111+
112+
return sprintf(
113+
'<g><rect x="%.2f" y="%.2f" width="%.2f" height="%.2f" fill="%s" stroke="#222" stroke-width="1"/><title>%s&#10;Churn: %s&#10;Score: %s</title><text x="%.2f" y="%.2f" font-size="13" fill="#000">%s</text></g>',
114+
$x,
115+
$y,
116+
$width,
117+
$height,
118+
$color,
119+
$class,
120+
$churn,
121+
$score,
122+
$textX,
123+
$textY,
124+
$label
125+
);
126+
}
127+
128+
/**
129+
* Wraps SVG rectangles in the SVG document.
130+
*
131+
* @param string $rectsSvg
132+
* @return string
133+
*/
134+
private function wrapSvg(string $rectsSvg): string
135+
{
136+
$width = self::SVG_WIDTH;
137+
$height = self::SVG_HEIGHT;
138+
return <<<SVG
139+
<?xml version="1.0" encoding="UTF-8"?>
140+
<svg width="{$width}" height="{$height}" xmlns="http://www.w3.org/2000/svg">
141+
<style>
142+
text { pointer-events: none; font-family: Arial, sans-serif; }
143+
rect:hover { stroke: #000; stroke-width: 2; }
144+
</style>
145+
<rect x="0" y="0" width="{$width}" height="{$height}" fill="#f8f9fa"/>
146+
<g>
147+
<text x="20" y="30" font-size="28" fill="#333">Churn Treemap</text>
148+
</g>
149+
<g>
150+
{$rectsSvg}
151+
</g>
152+
</svg>
153+
SVG;
154+
}
155+
}

0 commit comments

Comments
 (0)