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' => '/(? 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 $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 self::SCORE_MIN; } 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 self::SCORE_MIN; } 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 self::SCORE_MIN; } /** * @return array */ 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'; } }