Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
171 lines
5.5 KiB
PHP
171 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Console\Commands;
|
|
|
|
use App\Framework\Console\CommandGroup;
|
|
use App\Framework\Console\ConsoleColor;
|
|
use App\Framework\Console\ConsoleCommand;
|
|
use App\Framework\Console\ConsoleInput;
|
|
use App\Framework\Console\ConsoleOutputInterface;
|
|
use App\Framework\Console\ExitCode;
|
|
use App\Framework\Filesystem\FileScanner;
|
|
use App\Framework\Filesystem\ValueObjects\FilePath;
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\Quality\CodeQuality\CodeQualityScanner;
|
|
use App\Framework\Quality\CodeQuality\Results\CodeQualityReport;
|
|
use App\Framework\Quality\CodeQuality\Rules\DisallowInheritanceRule;
|
|
use App\Framework\Quality\CodeQuality\Rules\FinalClassRule;
|
|
use App\Framework\Quality\CodeQuality\Rules\ReadonlyGetterRule;
|
|
use App\Framework\Quality\CodeQuality\Rules\ReadonlyPropertyRule;
|
|
use App\Framework\Quality\CodeQuality\Rules\RequireStringableImplementationRule;
|
|
use App\Framework\Reflection\ReflectionService;
|
|
|
|
#[CommandGroup(
|
|
name: 'Quality',
|
|
description: 'Code quality inspections and automated rule checks',
|
|
icon: '🧹',
|
|
priority: 80
|
|
)]
|
|
final readonly class CodeQualityScanCommand
|
|
{
|
|
private CodeQualityScanner $scanner;
|
|
|
|
public function __construct(
|
|
FileScanner $fileScanner,
|
|
ReflectionService $reflectionService,
|
|
?Logger $logger = null
|
|
) {
|
|
$this->scanner = new CodeQualityScanner(
|
|
$fileScanner,
|
|
$reflectionService,
|
|
[
|
|
new RequireStringableImplementationRule(),
|
|
new FinalClassRule(),
|
|
new ReadonlyPropertyRule(),
|
|
new ReadonlyGetterRule(),
|
|
new DisallowInheritanceRule(),
|
|
],
|
|
$logger
|
|
);
|
|
}
|
|
|
|
#[ConsoleCommand('quality:scan', 'Check framework classes against predefined rules')]
|
|
public function scan(ConsoleInput $input, ConsoleOutputInterface $output): ExitCode
|
|
{
|
|
$pathArgument = $input->getArgument(0);
|
|
$directory = $pathArgument !== null
|
|
? FilePath::create($pathArgument)
|
|
: FilePath::create(getcwd() . '/src');
|
|
|
|
$skipErrors = $input->hasOption('skip-errors');
|
|
|
|
if (! $directory->exists() || ! $directory->isDirectory()) {
|
|
$output->writeLine(
|
|
sprintf('❌ Directory "%s" does not exist or is not a directory.', $directory->toString()),
|
|
ConsoleColor::RED
|
|
);
|
|
|
|
return ExitCode::INVALID_INPUT;
|
|
}
|
|
|
|
$output->writeLine(
|
|
sprintf('🔍 Scanning %s for code quality violations...', $directory->toString()),
|
|
ConsoleColor::BRIGHT_CYAN
|
|
);
|
|
|
|
$report = $this->scanner->scan($directory, $skipErrors);
|
|
|
|
if ($report->hasErrors()) {
|
|
$output->newLine();
|
|
$headerColor = $skipErrors ? ConsoleColor::YELLOW : ConsoleColor::BRIGHT_RED;
|
|
$headerIcon = $skipErrors ? '⚠️' : '❌';
|
|
$output->writeLine(
|
|
sprintf(
|
|
'%s Encountered %d file/class error(s) while scanning.',
|
|
$headerIcon,
|
|
count($report->errors())
|
|
),
|
|
$headerColor
|
|
);
|
|
|
|
foreach ($report->errors() as $error) {
|
|
$output->writeLine(' • ' . $error, $headerColor);
|
|
}
|
|
|
|
if (! $skipErrors) {
|
|
return ExitCode::FAILURE;
|
|
}
|
|
}
|
|
|
|
$this->renderReport($report, $output);
|
|
|
|
return $report->hasViolations() ? ExitCode::FAILURE : ExitCode::SUCCESS;
|
|
}
|
|
|
|
private function renderReport(CodeQualityReport $report, ConsoleOutputInterface $output): void
|
|
{
|
|
$output->newLine();
|
|
|
|
$summaryColor = $report->hasViolations() ? ConsoleColor::BRIGHT_RED : ConsoleColor::GREEN;
|
|
$summaryIcon = $report->hasViolations() ? '❌' : '✅';
|
|
$summaryMessage = $report->hasViolations()
|
|
? sprintf('Found %d violation(s).', $report->countViolations())
|
|
: 'No violations found.';
|
|
|
|
$output->writeLine(
|
|
sprintf(
|
|
'%s %s Scanned %d files, %d classes in %.2fs',
|
|
$summaryIcon,
|
|
$summaryMessage,
|
|
$report->inspectedFiles(),
|
|
$report->inspectedClasses(),
|
|
$report->durationSeconds()
|
|
),
|
|
$summaryColor
|
|
);
|
|
|
|
if (! $report->hasViolations()) {
|
|
return;
|
|
}
|
|
|
|
$output->newLine();
|
|
|
|
foreach ($report->groupedByRule() as $ruleName => $violations) {
|
|
$output->writeLine(
|
|
sprintf('%s (%d)', $ruleName, count($violations)),
|
|
ConsoleColor::BRIGHT_YELLOW
|
|
);
|
|
|
|
foreach ($violations as $violation) {
|
|
$lineInfo = $violation->line() !== null ? ':' . $violation->line() : '';
|
|
|
|
$output->writeLine(
|
|
sprintf(
|
|
' • %s',
|
|
$violation->className()->getFullyQualified()
|
|
),
|
|
ConsoleColor::WHITE
|
|
);
|
|
|
|
$output->writeLine(
|
|
sprintf(
|
|
' ↳ %s%s',
|
|
$violation->filePath()->toString(),
|
|
$lineInfo
|
|
),
|
|
ConsoleColor::GRAY
|
|
);
|
|
|
|
$output->writeLine(
|
|
sprintf(' %s', $violation->message()),
|
|
ConsoleColor::BRIGHT_RED
|
|
);
|
|
|
|
$output->newLine();
|
|
}
|
|
}
|
|
}
|
|
}
|