Backup #1029

ID1029
Dateipfad/var/www/dev.campus.systemische-tools.de/src/Infrastructure/CodeAnalysis/CodeQualityChecker.php
Version2
Typ modified
Größe7.8 KB
Hash2390a7f6737878a9e98ff04be57f89bcc9d551ca2a1a8eef9b581d27138c63e2
Datum2025-12-25 00:21:34
Geändert vonclaude-code-hook
GrundClaude Code Pre-Hook Backup vor Edit-Operation
Datei existiert Ja

Dateiinhalt

<?php

declare(strict_types=1);

namespace Infrastructure\CodeAnalysis;

// @responsibility: Code-Qualitätsanalyse (Hardcoded, Complexity, Metrics)

final class CodeQualityChecker
{
    /** @var array<string, string> Regex patterns for hardcoded detection */
    private const HARDCODED_PATTERNS = [
        'password' => '/["\'](?:password|passwd|pwd)["\']\\s*[=:]\\s*["\'][^"\']{3,}["\']/i',
        'api_key' => '/["\'](?:api[_-]?key|apikey|secret[_-]?key)["\']\\s*[=:]\\s*["\'][^"\']{8,}["\']/i',
        'token' => '/["\'](?:token|auth[_-]?token|access[_-]?token)["\']\\s*[=:]\\s*["\'][^"\']{8,}["\']/i',
        'ip_address' => '/["\']\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}["\']/i',
        'url_with_creds' => '/https?:\\/\\/[^:]+:[^@]+@/i',
        'magic_number' => '/(?<![\\w])(?:100|1000|60|24|365|3600|86400)(?![\\w])/',
    ];

    /** @var array<string, int> Thresholds for scoring */
    private const THRESHOLDS = [
        'loc_excellent' => 100,
        'loc_good' => 200,
        'loc_acceptable' => 400,
        'loc_poor' => 600,
        'methods_excellent' => 5,
        'methods_good' => 10,
        'methods_acceptable' => 15,
        'methods_poor' => 20,
        'deps_excellent' => 3,
        'deps_good' => 7,
        'deps_acceptable' => 12,
        'deps_poor' => 20,
    ];

    /**
     * Analysiert eine Datei auf Qualitätsprobleme.
     *
     * @param array<string, mixed> $analysisData Daten aus code_analysis
     * @return array{
     *     complexity_score: int,
     *     loc_score: int,
     *     dependency_score: int,
     *     hardcoded_count: int,
     *     issues_count: int,
     *     warnings_count: int,
     *     quality_grade: string,
     *     issues_json: string
     * }
     */
    public function analyze(array $analysisData): array
    {
        $issues = [];
        $warnings = [];

        $filePath = $analysisData['file_path'] ?? '';
        // Use stored content from DB (scan runs as root with full access)
        $content = $analysisData['file_content'] ?? '';

        // LOC Analysis
        $lineCount = $analysisData['line_count'] ?? 0;
        $locScore = $this->calculateLocScore($lineCount);
        if ($lineCount > self::THRESHOLDS['loc_poor']) {
            $issues[] = [
                'type' => 'complexity',
                'rule' => 'file-too-long',
                'message' => "Datei hat {$lineCount} Zeilen (max empfohlen: " . self::THRESHOLDS['loc_acceptable'] . ")",
                'severity' => 'warning',
            ];
        }

        // Method Count Analysis
        $functions = json_decode($analysisData['functions'] ?? '[]', true);
        $methodCount = is_array($functions) ? count($functions) : 0;
        $methodScore = $this->calculateMethodScore($methodCount);
        if ($methodCount > self::THRESHOLDS['methods_poor']) {
            $issues[] = [
                'type' => 'srp',
                'rule' => 'too-many-methods',
                'message' => "Klasse hat {$methodCount} Methoden (SRP-Verletzung möglich)",
                'severity' => 'warning',
            ];
        }

        // Dependency Analysis
        $uses = json_decode($analysisData['uses'] ?? '[]', true);
        $depsCount = is_array($uses) ? count($uses) : 0;
        $depScore = $this->calculateDependencyScore($depsCount);
        if ($depsCount > self::THRESHOLDS['deps_poor']) {
            $issues[] = [
                'type' => 'coupling',
                'rule' => 'too-many-dependencies',
                'message' => "Klasse hat {$depsCount} Dependencies (hohe Kopplung)",
                'severity' => 'warning',
            ];
        }

        // Hardcoded Detection
        $hardcodedIssues = $this->detectHardcoded($content, $filePath);
        $hardcodedCount = count($hardcodedIssues);
        $issues = array_merge($issues, $hardcodedIssues);

        // Calculate complexity score (weighted average)
        $complexityScore = (int) (($locScore * 0.3 + $methodScore * 0.3 + $depScore * 0.4));

        // Penalize for hardcoded values
        $complexityScore = max(0, $complexityScore - ($hardcodedCount * 10));

        // Determine grade
        $totalIssues = count($issues);
        $totalWarnings = count(array_filter($issues, fn($i) => ($i['severity'] ?? '') === 'warning'));
        $grade = $this->calculateGrade($complexityScore, $totalIssues);

        return [
            'complexity_score' => $complexityScore,
            'loc_score' => $locScore,
            'dependency_score' => $depScore,
            'hardcoded_count' => $hardcodedCount,
            'issues_count' => $totalIssues,
            'warnings_count' => $totalWarnings,
            'quality_grade' => $grade,
            'issues_json' => json_encode($issues, JSON_UNESCAPED_UNICODE),
        ];
    }

    private function calculateLocScore(int $loc): int
    {
        if ($loc <= self::THRESHOLDS['loc_excellent']) {
            return 100;
        }
        if ($loc <= self::THRESHOLDS['loc_good']) {
            return 80;
        }
        if ($loc <= self::THRESHOLDS['loc_acceptable']) {
            return 60;
        }
        if ($loc <= self::THRESHOLDS['loc_poor']) {
            return 40;
        }

        return 20;
    }

    private function calculateMethodScore(int $count): int
    {
        if ($count <= self::THRESHOLDS['methods_excellent']) {
            return 100;
        }
        if ($count <= self::THRESHOLDS['methods_good']) {
            return 80;
        }
        if ($count <= self::THRESHOLDS['methods_acceptable']) {
            return 60;
        }
        if ($count <= self::THRESHOLDS['methods_poor']) {
            return 40;
        }

        return 20;
    }

    private function calculateDependencyScore(int $count): int
    {
        if ($count <= self::THRESHOLDS['deps_excellent']) {
            return 100;
        }
        if ($count <= self::THRESHOLDS['deps_good']) {
            return 80;
        }
        if ($count <= self::THRESHOLDS['deps_acceptable']) {
            return 60;
        }
        if ($count <= self::THRESHOLDS['deps_poor']) {
            return 40;
        }

        return 20;
    }

    /**
     * @return array<array{type: string, rule: string, message: string, severity: string, line?: int}>
     */
    private function detectHardcoded(string $content, string $filePath): array
    {
        $issues = [];

        // Skip config files and test files
        $filename = basename($filePath);
        if (preg_match('/^(config|\.env|test|spec)/i', $filename)) {
            return [];
        }

        foreach (self::HARDCODED_PATTERNS as $type => $pattern) {
            // Skip magic numbers in config/constant files
            if ($type === 'magic_number' && preg_match('/const|config/i', $filePath)) {
                continue;
            }

            if (preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {
                foreach ($matches[0] as $match) {
                    $line = substr_count(substr($content, 0, $match[1]), "\n") + 1;
                    $issues[] = [
                        'type' => 'hardcoded',
                        'rule' => "hardcoded-{$type}",
                        'message' => "Möglicher hardcoded {$type}: " . substr($match[0], 0, 30) . '...',
                        'severity' => $type === 'magic_number' ? 'info' : 'warning',
                        'line' => $line,
                    ];
                }
            }
        }

        return $issues;
    }

    private function calculateGrade(int $score, int $issueCount): string
    {
        // Penalize for issues
        $adjustedScore = $score - ($issueCount * 5);

        if ($adjustedScore >= 90) {
            return 'A';
        }
        if ($adjustedScore >= 75) {
            return 'B';
        }
        if ($adjustedScore >= 60) {
            return 'C';
        }
        if ($adjustedScore >= 40) {
            return 'D';
        }

        return 'F';
    }
}

Vollständig herunterladen

Aktionen

Herunterladen

Andere Versionen dieser Datei

ID Version Typ Größe Datum
1806 11 modified 13.1 KB 2025-12-27 15:29
1805 10 modified 11.6 KB 2025-12-27 15:28
1804 9 modified 11.6 KB 2025-12-27 15:17
1325 8 modified 12.0 KB 2025-12-25 16:24
1324 7 modified 12.1 KB 2025-12-25 16:24
1323 6 modified 12.5 KB 2025-12-25 16:24
1308 5 modified 7.9 KB 2025-12-25 16:14
1113 4 modified 7.9 KB 2025-12-25 09:23
1030 3 modified 7.9 KB 2025-12-25 00:21
1029 2 modified 7.8 KB 2025-12-25 00:21
964 1 modified 7.8 KB 2025-12-23 22:29

← Zurück zur Übersicht