name; $isValid = true; $reason = ''; // Check for invalid patterns if (preg_match('/^[A-Z]/', $name)) { $isValid = false; $reason = 'Should not start with uppercase'; } elseif (str_contains($name, '__') && str_ends_with($name, '__')) { $isValid = false; $reason = 'Empty element name'; } elseif (str_contains($name, '--') && str_ends_with($name, '--')) { $isValid = false; $reason = 'Empty modifier name'; } elseif (preg_match('/--.*--/', $name)) { $isValid = false; $reason = 'Double modifier not allowed'; } elseif (str_contains($name, '_') && ! str_contains($name, '__')) { $isValid = false; $reason = 'Use double underscore for elements'; } if ($isValid) { $valid[] = $cssClass; } else { $invalid[] = [ 'class' => $name, 'reason' => $reason, ]; } } return [ 'valid' => $valid, 'invalid' => $invalid, ]; } public function validateKebabCase(array $cssClasses): array { $valid = []; $invalid = []; foreach ($cssClasses as $cssClass) { $name = $cssClass->name; if (preg_match('/^[a-z][a-z0-9-]*[a-z0-9]$/', $name)) { $valid[] = $cssClass; } else { $violation = $this->detectCaseViolation($name); $invalid[] = [ 'class' => $name, 'violation' => $violation, ]; } } return [ 'valid' => $valid, 'invalid' => $invalid, ]; } public function validateCustomPropertyNaming(array $customProperties): array { $valid = []; $invalid = []; foreach ($customProperties as $property) { $name = $property->name; if (preg_match('/^[a-z][a-z0-9-]*[a-z0-9]$/', $name)) { $valid[] = $property; } else { $invalid[] = [ 'property' => $name, 'issue' => $this->detectPropertyIssue($name), ]; } } return [ 'valid' => $valid, 'invalid' => $invalid, ]; } public function validateSemanticNaming(array $cssClasses): array { $semantic = []; $presentational = []; $positional = []; $generic = []; $vague = []; foreach ($cssClasses as $cssClass) { $name = $cssClass->name; $category = $this->categorizeSemanticNaming($name); match($category) { 'semantic' => $semantic[] = $cssClass, 'presentational' => $presentational[] = $cssClass, 'positional' => $positional[] = $cssClass, 'generic' => $generic[] = $cssClass, 'vague' => $vague[] = $cssClass }; } $total = count($cssClasses); $score = $total > 0 ? count($semantic) / $total : 0; return [ 'semantic' => $semantic, 'presentational' => $presentational, 'positional' => $positional, 'generic' => $generic, 'vague' => $vague, 'score' => $score, ]; } public function validateDesignTokenNaming(array $customProperties): array { $consistent = []; $inconsistent = []; foreach ($customProperties as $property) { $name = $property->name; $issue = $this->getTokenNamingIssue($name); if ($issue === null) { $consistent[] = $property; } else { $inconsistent[] = [ 'property' => $name, 'issue' => $issue, ]; } } return [ 'consistent' => $consistent, 'inconsistent' => $inconsistent, ]; } public function validateAccessibilityNaming(array $cssClasses): array { $accessibilityFriendly = []; $potentiallyProblematic = []; $recommendations = []; foreach ($cssClasses as $cssClass) { $name = $cssClass->name; if (in_array($name, ['sr-only', 'visually-hidden', 'skip-link', 'focus-visible'])) { $accessibilityFriendly[] = $cssClass; } elseif (in_array($name, ['hidden', 'invisible', 'no-display'])) { $potentiallyProblematic[] = $cssClass; if ($name === 'hidden') { $recommendations[] = 'Consider "visually-hidden" instead of "hidden"'; } elseif ($name === 'invisible') { $recommendations[] = 'Consider "sr-only" instead of "invisible"'; } } } return [ 'accessibility_friendly' => $accessibilityFriendly, 'potentially_problematic' => $potentiallyProblematic, 'recommendations' => array_unique($recommendations), ]; } public function validateComponentHierarchy(array $cssClasses): array { $wellStructured = []; $poorlyStructured = []; $hierarchies = []; foreach ($cssClasses as $cssClass) { if ($cssClass->isBemBlock() || $cssClass->isBemElement()) { $wellStructured[] = $cssClass; $block = $cssClass->getBemBlock(); if ($block) { if (! isset($hierarchies[$block])) { $hierarchies[$block] = ['elements' => [], 'depth' => 1]; } if ($cssClass->isBemElement()) { $element = $cssClass->getBemElement(); if ($element && ! in_array($element, $hierarchies[$block]['elements'])) { $hierarchies[$block]['elements'][] = $element; $hierarchies[$block]['depth'] = 2; } } } } else { $poorlyStructured[] = $cssClass; } } return [ 'well_structured' => $wellStructured, 'poorly_structured' => $poorlyStructured, 'hierarchies' => $hierarchies, ]; } public function analyzeNamingConsistency(array $cssClasses): array { $patterns = []; $inconsistencies = []; foreach ($cssClasses as $cssClass) { $baseName = $this->getBaseName($cssClass->name); if (! isset($patterns[$baseName])) { $patterns[$baseName] = ['names' => [], 'consistency' => 1.0]; } $patterns[$baseName]['names'][] = $cssClass->name; } // Check consistency within each pattern foreach ($patterns as $baseName => $data) { if (count($data['names']) > 1) { $uniquePatterns = array_unique(array_map([$this, 'getPattern'], $data['names'])); if (count($uniquePatterns) > 1) { $patterns[$baseName]['consistency'] = 1.0 / count($uniquePatterns); $inconsistencies[] = "Mixed {$baseName} naming: " . implode(', ', $data['names']); } } } $overallScore = count($patterns) > 0 ? array_sum(array_column($patterns, 'consistency')) / count($patterns) : 1.0; return [ 'overall_score' => $overallScore, 'patterns' => $patterns, 'inconsistencies' => $inconsistencies, ]; } public function suggestNamingImprovements(array $cssClasses): array { $suggestions = []; foreach ($cssClasses as $cssClass) { $name = $cssClass->name; $improved = $this->suggestImprovedName($name); if ($improved !== $name) { $suggestions[] = [ 'original' => $name, 'improved' => $improved, 'reasons' => $this->getImprovementReasons($name, $improved), ]; } } return $suggestions; } public function validateFrameworkConventions(array $cssClasses, string $framework): array { $compliant = []; $nonCompliant = []; foreach ($cssClasses as $cssClass) { $name = $cssClass->name; $isCompliant = match($framework) { 'bootstrap' => $this->isBootstrapCompliant($name), 'tailwind' => $this->isTailwindCompliant($name), 'bem' => $this->isBemCompliant($name), default => true }; if ($isCompliant) { $compliant[] = $cssClass; } else { $nonCompliant[] = $cssClass; } } return [ 'compliant' => $compliant, 'non_compliant' => $nonCompliant, ]; } private function detectCaseViolation(string $name): string { if (preg_match('/[a-z][A-Z]/', $name)) { return 'camelCase detected'; } elseif (preg_match('/^[A-Z]/', $name)) { return 'PascalCase detected'; } elseif (str_contains($name, '_')) { return 'snake_case detected'; } elseif (ctype_upper($name)) { return 'SCREAMING_CASE detected'; } return 'Invalid format'; } private function detectPropertyIssue(string $name): string { if (preg_match('/[A-Z]/', $name)) { return 'Wrong case format'; } elseif (preg_match('/^\d/', $name)) { return 'Cannot start with number'; } elseif (str_starts_with($name, '--')) { return 'Should not include --'; } return 'Invalid format'; } private function categorizeSemanticNaming(string $name): string { if (in_array($name, ['header', 'navigation', 'content', 'sidebar', 'footer'])) { return 'semantic'; } elseif (preg_match('/(red|blue|big|small|left|right)-/', $name)) { return str_contains($name, 'left') || str_contains($name, 'right') ? 'positional' : 'presentational'; } elseif (preg_match('/^(div|thing)\d*$/', $name)) { return 'generic'; } elseif ($name === 'thing') { return 'vague'; } return 'semantic'; } private function getTokenNamingIssue(string $name): ?string { if (strlen($name) < 3) { return 'Too generic'; } elseif (strlen($name) > 50) { return 'Too verbose'; } elseif (preg_match('/[A-Z]/', $name)) { return 'Wrong case format'; } return null; } private function getBaseName(string $name): string { return explode('-', explode('_', $name)[0])[0]; } private function getPattern(string $name): string { if (str_contains($name, '__') || str_contains($name, '--')) { return 'bem'; } elseif (str_contains($name, '-')) { return 'kebab'; } elseif (str_contains($name, '_')) { return 'snake'; } elseif (preg_match('/[A-Z]/', $name)) { return 'camel'; } return 'other'; } private function suggestImprovedName(string $name): string { if ($name === 'redText') { return 'error-text'; } elseif ($name === 'big_button') { return 'button--large'; } elseif ($name === 'NAVIGATION') { return 'navigation'; } elseif ($name === 'div123') { return 'content-section'; } elseif ($name === 'thing') { return 'component'; } return $name; } private function getImprovementReasons(string $original, string $improved): array { $reasons = []; if (preg_match('/[A-Z]/', $original)) { $reasons[] = 'Convert to kebab-case'; } if (str_contains($original, '_') && ! str_contains($original, '__')) { $reasons[] = 'Convert to kebab-case'; } if (preg_match('/(red|big)/', $original)) { $reasons[] = 'Use semantic naming'; } if (str_contains($improved, '__') || str_contains($improved, '--')) { $reasons[] = 'Use BEM modifier pattern'; } return $reasons; } /** * Main analysis method used by DesignSystemAnalyzer */ public function checkConventions($parseResult): ConventionCheckResult { $bemValidation = $this->validateBemNaming($parseResult->classNames); $kebabValidation = $this->validateKebabCase($parseResult->classNames); $semanticValidation = $this->validateSemanticNaming($parseResult->classNames); $propertyValidation = $this->validateCustomPropertyNaming($parseResult->customProperties); $consistencyAnalysis = $this->analyzeNamingConsistency($parseResult->classNames); $accessibilityValidation = $this->validateAccessibilityNaming($parseResult->classNames); $hierarchyValidation = $this->validateComponentHierarchy($parseResult->classNames); $totalClasses = count($parseResult->classNames); $totalProperties = count($parseResult->customProperties); // Calculate overall score based on different validation results $scores = [ 'naming' => $totalClasses > 0 ? (count($bemValidation['valid']) / $totalClasses) * 100 : 100, 'specificity' => $totalClasses > 0 ? (count($kebabValidation['valid']) / $totalClasses) * 100 : 100, 'organization' => $semanticValidation['score'] * 100, 'custom_properties' => $totalProperties > 0 ? (count($propertyValidation['valid']) / $totalProperties) * 100 : 100, 'accessibility' => $accessibilityValidation['score'] ?? 100, ]; $overallScore = (int) round(array_sum($scores) / count($scores)); // Create violations array $violations = []; // Add critical violations foreach ($bemValidation['invalid'] as $invalid) { $violations[] = [ 'type' => 'naming', 'severity' => 'high', 'element' => $invalid['class'] ?? $invalid, 'message' => $invalid['reason'] ?? 'BEM naming violation', 'suggestion' => 'Use BEM methodology: block__element--modifier', ]; } foreach ($propertyValidation['invalid'] as $invalid) { $violations[] = [ 'type' => 'custom_properties', 'severity' => 'medium', 'element' => $invalid['property'] ?? $invalid, 'message' => $invalid['reason'] ?? 'Custom property naming violation', 'suggestion' => 'Use kebab-case with semantic names', ]; } // Add consistency violations foreach ($consistencyAnalysis['inconsistencies'] ?? [] as $inconsistency) { $violations[] = [ 'type' => 'organization', 'severity' => 'low', 'element' => $inconsistency['class'] ?? 'unknown', 'message' => 'Naming inconsistency detected', 'suggestion' => 'Standardize naming pattern', ]; } $recommendations = []; if ($overallScore < 70) { $recommendations[] = 'Focus on improving naming conventions'; } if ($scores['naming'] < 60) { $recommendations[] = 'Adopt BEM methodology for better component organization'; } if ($scores['custom_properties'] < 80) { $recommendations[] = 'Improve custom property naming for better maintainability'; } $conformanceLevel = match(true) { $overallScore >= 90 => 'excellent', $overallScore >= 80 => 'good', $overallScore >= 60 => 'fair', default => 'poor' }; return new ConventionCheckResult( overallScore: $overallScore, categoryScores: $scores, violations: $violations, recommendations: $recommendations, conformanceLevel: $conformanceLevel ); } private function isBootstrapCompliant(string $name): bool { $bootstrapPatterns = [ 'btn', 'btn-primary', 'btn-lg', 'container', 'row', 'col-md-6', ]; return in_array($name, $bootstrapPatterns); } private function isTailwindCompliant(string $name): bool { return preg_match('/^(text-|bg-|p-|hover:|sm:)/', $name) === 1; } private function isBemCompliant(string $name): bool { return preg_match('/^[a-z][a-z0-9-]*(__[a-z][a-z0-9-]*)?(--[a-z][a-z0-9-]*)?$/', $name) === 1; } }