Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ endef
GIT_VERSION := $(shell git describe --tags --abbrev=0 2>/dev/null || echo "dev-main")
PHP_VERSION := $(shell php -r 'echo PHP_VERSION;' 2>/dev/null || echo "n/a")
PROJECT_NAME := TWIG METRICS
REPO_URL := https://github.com/smnandre/twig-metrics
REPO_URL := https://github.com/smnandre/twigmetrics

about:
@echo "┌───────────────────────────── 🌿 ────────────────────────────────┐"; \
Expand Down
45 changes: 22 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ $ bin/twigmetrics analyze templates/


╭─ Template Files ─────╮ ╭─ Logical Comp... ────╮ ╭─ Twig Callables ─────╮
│ ● ● ● ● ○ ○ C │ │ ● ● ● ● ● A+ │ │ ● ● ● ● ● A+
│ ● ● ● ● ○ ○ C │ │ ● ● ● ● ● B │ │ ● ● ● ● ● B
╰──────────────────────╯ ╰──────────────────────╯ ╰──────────────────────╯

╭─ Code Style ─────────╮ ╭─ Architecture ───────╮ ╭─ Maintainability ────╮
│ ● ● ● ● ● A+ │ │ ● ● ● ● C │ │ ● ● ● ● ● ● A+ │
│ ● ● ● ● ● C │ │ ● ● ● ● B │ │ ● ● ● ● ● ● A+ │
╰──────────────────────╯ ╰──────────────────────╯ ╰──────────────────────╯
```

Expand Down Expand Up @@ -92,10 +92,10 @@ vendor/bin/twigmetrics path/to/templates
```
╭─ Template Files ───────────────────────────────────────────────────────────╮
│ │
│ Templates ................. 188 Directories ................ 19
Total Lines ............ 11,204 Avg Lines/Template ....... 59.6
Characters ............. 503.8k Chars/Template .......... 2,680
Dir Depth Avg ............. 1.6 File Size CV % .......... 76.3%
Total Templates ........... 188 Total Lines .............. 11,213
Average Lines/File ....... 59.6 Median Lines ................. 48
Size Coefficient (CV) .... 0.77 Gini Index ................ 0.380
Directories ................ 19 Characters ............... 503.8k
│ │
╰────────────────────────────────────────────────────────────────────────────╯
```
Expand All @@ -105,10 +105,9 @@ vendor/bin/twigmetrics path/to/templates
```
╭─ Logical Complexity ───────────────────────────────────────────────────────╮
│ │
│ Avg Complexity ............ 8.3 Max Complexity ............. 79 │
│ Avg Depth ................. 1.2 Max Depth ................... 6 │
│ IFs/Template .............. 1.3 FORs/Template ............. 0.6 │
│ Nested Control Depth ........ 6 │
│ Avg Complexity ............. 8.3 Max Complexity .............. 79 │
│ Avg Depth .................. 1.2 Max Depth .................... 6 │
│ IFs/Template ............... 1.3 FORs/Template .............. 0.6 │
│ │
╰────────────────────────────────────────────────────────────────────────────╯
```
Expand All @@ -118,9 +117,9 @@ vendor/bin/twigmetrics path/to/templates
```
╭─ Twig Callables ───────────────────────────────────────────────────────────╮
│ │
Funcs/Template ............ 2.9 Filters/Template ......... 18.9
Vars/Template ............ 13.5 Unique Funcs ............... 23
Unique Filters ............. 31 Macros Defined .............. 5
Total Calls ............. 4,632 Unique Functions ............. 23
Unique Filters ............. 32 Unique Tests .................. 7
Funcs/Template ............ 2.9 Filters/Template ........... 18.9
│ │
╰────────────────────────────────────────────────────────────────────────────╯
```
Expand All @@ -130,10 +129,10 @@ vendor/bin/twigmetrics path/to/templates
```
╭─ Code Style ───────────────────────────────────────────────────────────────╮
│ │
│ Avg Line Length .......... 41.0 Comments/Template ......... 0.6
Comment Ratio ............ 1.0% Trail Spaces/Line ........ 0.00
Empty Lines % ............ 8.2% Formatting Score ......... 92.7
Indent Consistency % ... 100.0% Naming Conv. Score % .... 97.5%
Avg Line Length ........... 41.0 Max Line Length ............ 217
Indent Consistency ...... 100.0% P95 Length ................. 217
Consistency Score ........ 92.7% Style Violations ........... 128
Comments/Template .......... 0.6 Mixed Indentation ............ 0
│ │
╰────────────────────────────────────────────────────────────────────────────╯
```
Expand All @@ -143,9 +142,9 @@ vendor/bin/twigmetrics path/to/templates
```
╭─ Architecture ─────────────────────────────────────────────────────────────╮
│ │
Extends/Template ......... 0.22 Includes/Template ........ 0.57
Embeds/Template .......... 0.04 Imports/Template ......... 0.00
Avg Inherit Depth ......... 0.2 Standalone Files .......... 122
Imports/Template ......... 0.00 Extends/Template ........... 0.22
Avg Inherit Depth ......... 0.2 Includes/Template .......... 0.57
Embeds/Template .......... 0.04 Blocks/Template ............ 1.13
│ │
╰────────────────────────────────────────────────────────────────────────────╯
```
Expand All @@ -155,9 +154,9 @@ vendor/bin/twigmetrics path/to/templates
```
╭─ Maintainability ──────────────────────────────────────────────────────────╮
│ │
Large Templates (>200L) ..... 4 High Complexity (>20) ...... 17
Deep Nesting (>5) ........... 1 Empty Templates ............. 0
Standalone ................ 122 Risk Score .................. A
Empty Lines Ratio ....... 10.0% MI Average ................ 107.2
MI Median ............... 106.7 Comment Density ............ 1.3%
High Risk ................... 3 Medium Risk .................. 40
│ │
╰────────────────────────────────────────────────────────────────────────────╯
```
Expand Down
2 changes: 1 addition & 1 deletion bin/twigmetrics
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ foreach ($possibleFiles as $possibleFile) {
}

if (null === $file) {
throw new \RuntimeException('Unable to locate autoload.php file.');
throw new RuntimeException('Unable to locate autoload.php file.');
}

require_once $file;
Expand Down
12 changes: 6 additions & 6 deletions src/Analyzer/BatchAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ public function analyze(array $files): BatchAnalysisResult

$dependencyGraph = $this->buildDependencyGraph($results);

$enhancedResults = $this->enhanceWithCrossTemplateInsights($results, $dependencyGraph);
$augmentedResults = $this->addCrossTemplateInsights($results, $dependencyGraph);

$totalTime = microtime(true) - $startTime;

return new BatchAnalysisResult($enhancedResults, $dependencyGraph, $totalTime, $errors);
return new BatchAnalysisResult($augmentedResults, $dependencyGraph, $totalTime, $errors);
}

/**
Expand Down Expand Up @@ -95,13 +95,13 @@ private function buildDependencyGraph(array $results): array
*
* @return AnalysisResult[]
*/
private function enhanceWithCrossTemplateInsights(array $results, array $dependencyGraph): array
private function addCrossTemplateInsights(array $results, array $dependencyGraph): array
{
$referenceCounter = $this->calculateReferenceFrequency($dependencyGraph);
$this->analyzeBlockUsage($results);
$architecturalRoles = $this->determineArchitecturalRoles($results, $referenceCounter);

$enhancedResults = [];
$augmented = [];
foreach ($results as $result) {
$templatePath = $result->getRelativePath();
$metrics = $result->getData();
Expand All @@ -114,10 +114,10 @@ private function enhanceWithCrossTemplateInsights(array $results, array $depende

$metrics['potential_issues'] = $this->identifyPotentialIssues($metrics);

$enhancedResults[] = new AnalysisResult($result->file, $metrics, $result->analysisTime);
$augmented[] = new AnalysisResult($result->file, $metrics, $result->analysisTime);
}

return $enhancedResults;
return $augmented;
}

/**
Expand Down
58 changes: 58 additions & 0 deletions src/Analyzer/BlockUsageAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace TwigMetrics\Analyzer;

use TwigMetrics\Metric\BlockMetrics;

/**
* @author Simon André <[email protected]>
*/
final class BlockUsageAnalyzer
{
/**
* @param AnalysisResult[] $results
*/
public function analyzeBlockUsage(array $results): BlockMetrics
{
$defined = [];
$used = [];
$orphaned = [];

foreach ($results as $result) {
$blocks = $result->getMetric('provided_blocks') ?? $result->getMetric('blocksDefinitions') ?? [];
if (is_array($blocks)) {
foreach ($blocks as $blockName) {
$defined[$blockName] = ($defined[$blockName] ?? 0) + 1;
}
}
}

foreach ($results as $result) {
$usages = $result->getMetric('used_blocks') ?? $result->getMetric('blocksUsage') ?? [];
if (is_array($usages)) {
foreach ($usages as $blockName) {
$used[$blockName] = ($used[$blockName] ?? 0) + 1;
}
}
}

foreach ($defined as $blockName => $count) {
if (!isset($used[$blockName])) {
$orphaned[] = (string) $blockName;
}
}

$usageCount = count($used);
$definedCount = count($defined);

return new BlockMetrics(
totalDefined: array_sum($defined),
totalUsed: array_sum($used),
orphanedBlocks: $orphaned,
usageRatio: $definedCount > 0 ? $usageCount / $definedCount : 0.0,
averageReuse: $usageCount > 0 ? array_sum($used) / $usageCount : 0.0,
);
}
}
68 changes: 68 additions & 0 deletions src/Analyzer/CallableSecurityAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace TwigMetrics\Analyzer;

use TwigMetrics\Metric\SecurityMetrics;

/**
* @author Simon André <[email protected]>
*/
final class CallableSecurityAnalyzer
{
private const RISKY_FUNCTIONS = ['dump', 'eval', 'include_raw'];
private const RISKY_FILTERS = ['raw', 'unsafe'];

/**
* @param AnalysisResult[] $results
*/
public function analyzeSecurityScore(array $results): SecurityMetrics
{
$risks = [];
$score = 100;

foreach ($results as $result) {
$functions = $result->getMetric('functions_detail') ?? [];
$filters = $result->getMetric('filters_detail') ?? [];

if (is_array($functions)) {
foreach ($functions as $func => $count) {
if (in_array((string) $func, self::RISKY_FUNCTIONS, true)) {
$risks[(string) $func] = ($risks[(string) $func] ?? 0) + (int) $count;
$score -= 5;
}
}
}

if (is_array($filters)) {
foreach ($filters as $filter => $count) {
if (in_array((string) $filter, self::RISKY_FILTERS, true)) {
$risks[(string) $filter] = ($risks[(string) $filter] ?? 0) + (int) $count;
$score -= 3;
}
}
}
}

return new SecurityMetrics(
score: max(0, $score),
risks: $risks,
deprecatedCount: $this->countDeprecated($results),
);
}

/**
* @param AnalysisResult[] $results
*/
private function countDeprecated(array $results): int
{
$count = 0;
foreach ($results as $result) {
$deprecated = $result->getMetric('deprecated_callables') ?? 0;
$count += (int) (is_numeric($deprecated) ? $deprecated : 0);
}

return $count;
}
}
Loading