diff --git a/bin/ecs.php b/bin/ecs.php index eb9e89d3486..e2fa0b3884a 100755 --- a/bin/ecs.php +++ b/bin/ecs.php @@ -3,11 +3,14 @@ declare(strict_types=1); // decoupled in own "*.php" file, so ECS, Rector and PHPStan works out of the box here +use Composer\InstalledVersions; +use Composer\XdebugHandler\XdebugHandler; +use Entropy\Console\ConsoleApplication; +use Entropy\Console\Output\OutputColorizer; +use Entropy\Console\Output\OutputPrinter; use PHP_CodeSniffer\Util\Tokens; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\ArgvInput; -use Symplify\EasyCodingStandard\Console\EasyCodingStandardConsoleApplication; -use Symplify\EasyCodingStandard\Console\Style\SymfonyStyleFactory; +use Symplify\EasyCodingStandard\Application\Version\StaticVersionResolver; +use Symplify\EasyCodingStandard\Console\ExitCode; use Symplify\EasyCodingStandard\DependencyInjection\EasyCodingStandardContainerFactory; use Symplify\EasyCodingStandard\DependencyInjection\ServiceContainerFactory; @@ -142,21 +145,85 @@ public function loadIfNotLoadedYet(string $file): void } } +$rawArgv = $_SERVER['argv'] ?? []; + +// @fixes https://github.com/rectorphp/rector/issues/2205 +$isXdebugAllowed = in_array('--xdebug', $rawArgv, true); +if (! $isXdebugAllowed && ! defined('PHPUNIT_COMPOSER_INSTALL')) { + $xdebugHandler = new XdebugHandler('ecs'); + $xdebugHandler->check(); + unset($xdebugHandler); +} + try { - $input = new ArgvInput(); $ecsContainerFactory = new EasyCodingStandardContainerFactory(); - $container = $ecsContainerFactory->createFromFromInput($input); + $container = $ecsContainerFactory->createFromArgv($rawArgv); } catch (Throwable $throwable) { - $symfonyStyleFactory = new SymfonyStyleFactory(); - $symfonyStyle = $symfonyStyleFactory->create(); + $outputPrinter = new OutputPrinter(new OutputColorizer()); + $outputPrinter->error($throwable->getMessage()); + $outputPrinter->writeln($throwable->getTraceAsString()); + exit(ExitCode::FAILURE); +} - $symfonyStyle->error($throwable->getMessage()); - $symfonyStyle->writeln($throwable->getTraceAsString()); - exit(Command::FAILURE); +// print version and exit +if (in_array('--version', $rawArgv, true) || in_array('-V', $rawArgv, true)) { + echo sprintf('EasyCodingStandard %s', StaticVersionResolver::PACKAGE_VERSION) . PHP_EOL; + echo sprintf('+ PHP_CodeSniffer %s', InstalledVersions::getPrettyVersion('squizlabs/php_codesniffer')) . PHP_EOL; + echo sprintf('+ PHP-CS-Fixer %s', InstalledVersions::getPrettyVersion('friendsofphp/php-cs-fixer')) . PHP_EOL; + exit(ExitCode::SUCCESS); } -/** @var EasyCodingStandardConsoleApplication $application */ -$application = $container->make(EasyCodingStandardConsoleApplication::class); +/** @var ConsoleApplication $application */ +$application = $container->make(ConsoleApplication::class); -$statusCode = $application->run(); +$statusCode = $application->run(ecs_normalize_argv($rawArgv)); exit($statusCode); + +/** + * Strip global/decoration flags Symfony Console handled implicitly, and normalize + * the "-c" config shortcut to "--config", so the Entropy input parser does not + * treat them as unknown command options. + * + * @param string[] $argv + * @return string[] + */ +function ecs_normalize_argv(array $argv): array +{ + $decorationFlags = [ + '--ansi', + '--no-ansi', + '--quiet', + '-q', + '--no-interaction', + '-n', + '-v', + '-vv', + '-vvv', + '--xdebug', + ]; + + if (in_array('--no-ansi', $argv, true)) { + putenv('NO_COLOR=1'); + } + + $normalized = []; + foreach ($argv as $arg) { + if (in_array($arg, $decorationFlags, true)) { + continue; + } + + if ($arg === '-c') { + $normalized[] = '--config'; + continue; + } + + if (str_starts_with($arg, '-c=')) { + $normalized[] = '--config=' . substr($arg, 3); + continue; + } + + $normalized[] = $arg; + } + + return $normalized; +} diff --git a/composer.json b/composer.json index e11839c850e..b65e9a9ce82 100644 --- a/composer.json +++ b/composer.json @@ -20,9 +20,8 @@ "nette/utils": "^4.1", "sebastian/diff": "^9.0", "squizlabs/php_codesniffer": "^4.0.1", - "symfony/console": "^6.4.24", "symfony/finder": "^7.4", - "symplify/easy-parallel": "^12.0", + "symplify/easy-parallel": "dev-main", "webmozart/assert": "^2.4" }, "require-dev": { @@ -82,6 +81,7 @@ "fix-cs": "bin/ecs check --fix --ansi" }, "replace": { + "symfony/console": "*", "symfony/event-dispatcher": "7.*", "symfony/process": "7.*", "symfony/stopwatch": "7.*" diff --git a/src/Application/EasyCodingStandardApplication.php b/src/Application/EasyCodingStandardApplication.php index 07fcdfa813a..fe76185ac83 100644 --- a/src/Application/EasyCodingStandardApplication.php +++ b/src/Application/EasyCodingStandardApplication.php @@ -5,8 +5,6 @@ namespace Symplify\EasyCodingStandard\Application; use ParseError; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; use Symplify\EasyCodingStandard\Caching\ChangedFilesDetector; use Symplify\EasyCodingStandard\Console\Style\EasyCodingStandardStyle; use Symplify\EasyCodingStandard\DependencyInjection\SimpleParameterProvider; @@ -38,7 +36,6 @@ public function __construct( private ScheduleFactory $scheduleFactory, private ParallelFileProcessor $parallelFileProcessor, private CpuCoreCountProvider $cpuCoreCountProvider, - private SymfonyStyle $symfonyStyle, private ParametersMerger $parametersMerger ) { } @@ -46,7 +43,7 @@ public function __construct( /** * @return array{coding_standard_errors?: CodingStandardError[], file_diffs?: FileDiff[], system_errors?: SystemError[]|string[], system_errors_count?: int} */ - public function run(Configuration $configuration, InputInterface $input): array + public function run(Configuration $configuration): array { // 1. find files in sources $filePaths = $this->sourceFinder->find($configuration->getSources()); @@ -87,11 +84,11 @@ public function run(Configuration $configuration, InputInterface $input): array if (! $isProgressBarStarted) { $fileCount = count($filePaths); - $this->symfonyStyle->progressStart($fileCount); + $this->easyCodingStandardStyle->progressStart($fileCount); $isProgressBarStarted = true; } - $this->symfonyStyle->progressAdvance($stepCount); + $this->easyCodingStandardStyle->progressAdvance($stepCount); // running in parallel here → nothing else to do }; @@ -106,7 +103,7 @@ public function run(Configuration $configuration, InputInterface $input): array $mainScript, $postFileCallback, $configuration->getConfig(), - $input + $configuration ); } diff --git a/src/Configuration/ConfigInitializer.php b/src/Configuration/ConfigInitializer.php index 41a417d026b..2877adce46d 100644 --- a/src/Configuration/ConfigInitializer.php +++ b/src/Configuration/ConfigInitializer.php @@ -5,14 +5,14 @@ namespace Symplify\EasyCodingStandard\Configuration; use Nette\Utils\FileSystem; -use Symfony\Component\Console\Style\SymfonyStyle; use Symplify\EasyCodingStandard\Application\FileProcessorCollector; +use Symplify\EasyCodingStandard\Console\Style\EasyCodingStandardStyle; final readonly class ConfigInitializer { public function __construct( private FileProcessorCollector $fileProcessorCollector, - private SymfonyStyle $symfonyStyle, + private EasyCodingStandardStyle $easyCodingStandardStyle, private InitPathsResolver $initPathsResolver, private \Symfony\Component\Filesystem\Filesystem $filesystem, ) { @@ -30,13 +30,16 @@ public function createConfig(string $projectDirectory): void // config already exists, nothing to add if ($doesConfigExist) { - $this->symfonyStyle->warning( + $this->easyCodingStandardStyle->warning( 'We found ecs.php config, but no rules in it. Register some rules or sets there first' ); return; } - $response = $this->symfonyStyle->ask('No "ecs.php" config found. Should we generate it for you?', 'yes'); + $response = $this->easyCodingStandardStyle->ask( + 'No "ecs.php" config found. Should we generate it for you?', + 'yes' + ); // be tolerant about input if (! in_array($response, ['yes', 'YES', 'y', 'Y'], true)) { @@ -52,7 +55,9 @@ public function createConfig(string $projectDirectory): void // create the ecs.php file FileSystem::write(getcwd() . '/ecs.php', $templateFileContents, null); - $this->symfonyStyle->success('The ecs.php config was generated! Re-run the command to tidy your code'); + $this->easyCodingStandardStyle->success( + 'The ecs.php config was generated! Re-run the command to tidy your code' + ); } private function fillPaths(string $projectDirectory, string $templateFileContents): string diff --git a/src/Configuration/ConfigurationFactory.php b/src/Configuration/ConfigurationFactory.php index 69667113222..86580802195 100644 --- a/src/Configuration/ConfigurationFactory.php +++ b/src/Configuration/ConfigurationFactory.php @@ -4,7 +4,6 @@ namespace Symplify\EasyCodingStandard\Configuration; -use Symfony\Component\Console\Input\InputInterface; use Symplify\EasyCodingStandard\Console\Output\OutputFormatterCollector; use Symplify\EasyCodingStandard\DependencyInjection\SimpleParameterProvider; use Symplify\EasyCodingStandard\Exception\Configuration\SourceNotFoundException; @@ -20,33 +19,32 @@ public function __construct( /** * Needs to run in the start of the life cycle, since the rest of workflow uses it. + * + * @param string[] $paths */ - public function createFromInput(InputInterface $input): Configuration - { - $paths = $this->resolvePaths($input); - - $isFixer = (bool) $input->getOption(Option::FIX); - $shouldClearCache = (bool) $input->getOption(Option::CLEAR_CACHE); - $showProgressBar = $this->canShowProgressBar($input); - $showErrorTable = ! (bool) $input->getOption(Option::NO_ERROR_TABLE); - $showDiffs = ! (bool) $input->getOption(Option::NO_DIFFS); - $parallelPort = (string) $input->getOption(Option::PARALLEL_PORT); - $parallelIdentifier = (string) $input->getOption(Option::PARALLEL_IDENTIFIER); - - $outputFormat = (string) $input->getOption(Option::OUTPUT_FORMAT); - - /** @var string|null $memoryLimit */ - $memoryLimit = $input->getOption(Option::MEMORY_LIMIT); + public function create( + array $paths, + bool $isFixer, + bool $shouldClearCache, + bool $noProgressBar, + bool $noErrorTable, + bool $noDiffs, + string $outputFormat, + ?string $config, + string $parallelPort, + string $parallelIdentifier, + ?string $memoryLimit, + bool $debug, + ): Configuration { + $paths = $this->resolvePaths($paths); + + $showProgressBar = $this->canShowProgressBar($debug, $outputFormat, $noProgressBar); + $showErrorTable = ! $noErrorTable; + $showDiffs = ! $noDiffs; $isParallel = SimpleParameterProvider::getBoolParameter(Option::PARALLEL); - $isReportingWithRealPath = SimpleParameterProvider::getBoolParameter(Option::REPORTING_REALPATH); - $config = $input->getOption(Option::CONFIG); - if ($config !== null) { - $config = (string) $config; - } - return new Configuration( $isFixer, $shouldClearCache, @@ -64,22 +62,19 @@ public function createFromInput(InputInterface $input): Configuration ); } - private function canShowProgressBar(InputInterface $input): bool + private function canShowProgressBar(bool $debug, string $outputFormat, bool $noProgressBar): bool { // --debug option shows more - $debug = (bool) $input->getOption(Option::DEBUG); if ($debug) { return false; } - $outputFormat = $input->getOption(Option::OUTPUT_FORMAT); $outputFormatter = $this->outputFormatterCollector->getByName($outputFormat); - if (! $outputFormatter->hasSupportForProgressBars()) { return false; } - return ! (bool) $input->getOption(Option::NO_PROGRESS_BAR); + return ! $noProgressBar; } /** @@ -97,12 +92,11 @@ private function ensurePathsExists(array $paths): void } /** + * @param string[] $paths * @return string[] */ - private function resolvePaths(InputInterface $input): array + private function resolvePaths(array $paths): array { - /** @var string[] $paths */ - $paths = (array) $input->getArgument(Option::PATHS); if ($paths === []) { // if not paths are provided from CLI, use the config ones $paths = SimpleParameterProvider::getArrayParameter(Option::PATHS); diff --git a/src/Console/Command/AbstractCheckCommand.php b/src/Console/Command/AbstractCheckCommand.php deleted file mode 100644 index 2e2c13146d6..00000000000 --- a/src/Console/Command/AbstractCheckCommand.php +++ /dev/null @@ -1,64 +0,0 @@ -addArgument( - Option::PATHS, - // optional is on purpose here, since path from ecs.php can se used - InputArgument::OPTIONAL | InputArgument::IS_ARRAY, - 'The path(s) to be checked.' - ); - - $this->addOption(Option::FIX, null, null, 'Fix found violations.'); - - $this->addOption(Option::CONFIG, 'c', InputOption::VALUE_REQUIRED, 'Path to config file'); - - $this->addOption(Option::CLEAR_CACHE, null, null, 'Clear cache for already checked files.'); - - $this->addOption( - Option::NO_PROGRESS_BAR, - null, - InputOption::VALUE_NONE, - 'Hide progress bar. Useful e.g. for nicer CI output.' - ); - - $this->addOption( - Option::NO_ERROR_TABLE, - null, - InputOption::VALUE_NONE, - 'Hide error table. Useful e.g. for fast check of error count.' - ); - $this->addOption( - Option::NO_DIFFS, - null, - InputOption::VALUE_NONE, - 'Hide diffs of changed files. Useful e.g. for nicer CI output.' - ); - - $this->addOption( - Option::OUTPUT_FORMAT, - null, - InputOption::VALUE_REQUIRED, - 'Select output format', - ConsoleOutputFormatter::getName() - ); - - $this->addOption(Option::MEMORY_LIMIT, null, InputOption::VALUE_REQUIRED, 'Memory limit for check'); - - // for parallel run - $this->addOption(Option::PARALLEL_PORT, null, InputOption::VALUE_REQUIRED); - $this->addOption(Option::PARALLEL_IDENTIFIER, null, InputOption::VALUE_REQUIRED); - } -} diff --git a/src/Console/Command/CheckCommand.php b/src/Console/Command/CheckCommand.php index 6429fdc47df..3bea4a5dd12 100644 --- a/src/Console/Command/CheckCommand.php +++ b/src/Console/Command/CheckCommand.php @@ -4,48 +4,92 @@ namespace Symplify\EasyCodingStandard\Console\Command; -use Override; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; +use Entropy\Console\Contract\CommandInterface; +use Entropy\Console\Contract\DefaultCommandInterface; use Symplify\EasyCodingStandard\Application\EasyCodingStandardApplication; use Symplify\EasyCodingStandard\Configuration\ConfigInitializer; use Symplify\EasyCodingStandard\Configuration\ConfigurationFactory; +use Symplify\EasyCodingStandard\Console\ExitCode; +use Symplify\EasyCodingStandard\Console\Output\ConsoleOutputFormatter; use Symplify\EasyCodingStandard\MemoryLimitter; use Symplify\EasyCodingStandard\Reporter\ProcessedFileReporter; -final class CheckCommand extends AbstractCheckCommand +final readonly class CheckCommand implements CommandInterface, DefaultCommandInterface { public function __construct( - private readonly ProcessedFileReporter $processedFileReporter, - private readonly MemoryLimitter $memoryLimitter, - private readonly ConfigInitializer $configInitializer, - private readonly EasyCodingStandardApplication $easyCodingStandardApplication, - private readonly ConfigurationFactory $configurationFactory, + private ProcessedFileReporter $processedFileReporter, + private MemoryLimitter $memoryLimitter, + private ConfigInitializer $configInitializer, + private EasyCodingStandardApplication $easyCodingStandardApplication, + private ConfigurationFactory $configurationFactory, ) { - parent::__construct(); } - #[Override] - protected function configure(): void + public function getName(): string { - $this->setName('check'); - $this->setDescription('Check coding standard in one or more directories'); - - parent::configure(); + return 'check'; } - protected function execute(InputInterface $input, OutputInterface $output): int + public function getDescription(): string { + return 'Check coding standard in one or more directories'; + } + + /** + * @param string $config Path to config file + * @param string $outputFormat Select output format + * @param string $memoryLimit Memory limit for check + * @param string $port [INTERNAL] parallel TCP port + * @param string $identifier [INTERNAL] parallel identifier + * @param string ...$paths The path(s) to be checked. + * + * @option $config + * @option $outputFormat + * @option $memoryLimit + * @option $port + * @option $identifier + * + * @api invoked via reflection by the Entropy console application + * + * @return ExitCode::* + */ + public function run( + bool $fix = false, + bool $clearCache = false, + bool $noProgressBar = false, + bool $noErrorTable = false, + bool $noDiffs = false, + bool $debug = false, + string $config = '', + string $outputFormat = ConsoleOutputFormatter::NAME, + string $memoryLimit = '', + string $port = '', + string $identifier = '', + string ...$paths, + ): int { // create ecs.php config file if does not exist yet if (! $this->configInitializer->areSomeCheckersRegistered()) { - $this->configInitializer->createConfig(getcwd()); - return self::SUCCESS; + $this->configInitializer->createConfig((string) getcwd()); + return ExitCode::SUCCESS; } - $configuration = $this->configurationFactory->createFromInput($input); + $configuration = $this->configurationFactory->create( + array_values($paths), + $fix, + $clearCache, + $noProgressBar, + $noErrorTable, + $noDiffs, + $outputFormat, + $config !== '' ? $config : null, + $port, + $identifier, + $memoryLimit !== '' ? $memoryLimit : null, + $debug, + ); $this->memoryLimitter->adjust($configuration); - $errorsAndDiffs = $this->easyCodingStandardApplication->run($configuration, $input); + $errorsAndDiffs = $this->easyCodingStandardApplication->run($configuration); return $this->processedFileReporter->report($errorsAndDiffs, $configuration); } } diff --git a/src/Console/Command/ListCheckersCommand.php b/src/Console/Command/ListCheckersCommand.php index 0bbd7a1eb8f..0f02133de54 100644 --- a/src/Console/Command/ListCheckersCommand.php +++ b/src/Console/Command/ListCheckersCommand.php @@ -4,51 +4,50 @@ namespace Symplify\EasyCodingStandard\Console\Command; +use Entropy\Console\Contract\CommandInterface; use Nette\Utils\Json; use PHP_CodeSniffer\Sniffs\Sniff; use PhpCsFixer\Fixer\FixerInterface; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; +use Symplify\EasyCodingStandard\Console\ExitCode; use Symplify\EasyCodingStandard\Console\Output\ConsoleOutputFormatter; use Symplify\EasyCodingStandard\Console\Reporter\CheckerListReporter; use Symplify\EasyCodingStandard\FixerRunner\Application\FixerFileProcessor; use Symplify\EasyCodingStandard\Skipper\SkipCriteriaResolver\SkippedClassResolver; use Symplify\EasyCodingStandard\SniffRunner\Application\SniffFileProcessor; -use Symplify\EasyCodingStandard\ValueObject\Option; -final class ListCheckersCommand extends Command +final readonly class ListCheckersCommand implements CommandInterface { public function __construct( - private readonly SniffFileProcessor $sniffFileProcessor, - private readonly FixerFileProcessor $fixerFileProcessor, - private readonly CheckerListReporter $checkerListReporter, - private readonly SkippedClassResolver $skippedClassResolver + private SniffFileProcessor $sniffFileProcessor, + private FixerFileProcessor $fixerFileProcessor, + private CheckerListReporter $checkerListReporter, + private SkippedClassResolver $skippedClassResolver ) { - parent::__construct(); } - protected function configure(): void + public function getName(): string { - $this->setName('list-checkers'); - $this->setDescription('Shows loaded checkers'); - - $this->addOption( - Option::OUTPUT_FORMAT, - null, - InputOption::VALUE_REQUIRED, - 'Select output format', - ConsoleOutputFormatter::getName() - ); - - $this->addOption(Option::CONFIG, 'c', InputOption::VALUE_REQUIRED, 'Path to config file'); + return 'list-checkers'; } - protected function execute(InputInterface $input, OutputInterface $output): int + public function getDescription(): string { - $outputFormat = $input->getOption(Option::OUTPUT_FORMAT); + return 'Shows loaded checkers'; + } + /** + * @param string $outputFormat Select output format + * @param string $config Path to config file + * + * @option $outputFormat + * @option $config + * + * @api invoked via reflection by the Entropy console application + * + * @return ExitCode::* + */ + public function run(string $outputFormat = ConsoleOutputFormatter::NAME, string $config = ''): int + { // include skipped rules to avoid adding those too $skippedCheckers = $this->getSkippedCheckers(); @@ -61,14 +60,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int echo Json::encode($data, Json::PRETTY) . PHP_EOL; - return Command::SUCCESS; + return ExitCode::SUCCESS; } $this->checkerListReporter->report($this->getSniffClasses(), 'from PHP_CodeSniffer'); $this->checkerListReporter->report($this->getFixerClasses(), 'from PHP-CS-Fixer'); $this->checkerListReporter->report($skippedCheckers, 'are skipped'); - return self::SUCCESS; + return ExitCode::SUCCESS; } /** diff --git a/src/Console/Command/WorkerCommand.php b/src/Console/Command/WorkerCommand.php index 31e7dd83872..cf311538443 100644 --- a/src/Console/Command/WorkerCommand.php +++ b/src/Console/Command/WorkerCommand.php @@ -6,13 +6,14 @@ use Clue\React\NDJson\Decoder; use Clue\React\NDJson\Encoder; -use Override; +use Entropy\Console\Contract\CommandInterface; +use Entropy\Console\Contract\HiddenCommandInterface; use React\EventLoop\StreamSelectLoop; use React\Socket\ConnectionInterface; use React\Socket\TcpConnector; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symplify\EasyCodingStandard\Configuration\ConfigurationFactory; +use Symplify\EasyCodingStandard\Console\ExitCode; +use Symplify\EasyCodingStandard\Console\Output\ConsoleOutputFormatter; use Symplify\EasyCodingStandard\MemoryLimitter; use Symplify\EasyCodingStandard\Parallel\WorkerRunner; use Symplify\EasyParallel\Enum\Action; @@ -25,28 +26,71 @@ * ↓↓↓ * https://github.com/phpstan/phpstan-src/commit/b84acd2e3eadf66189a64fdbc6dd18ff76323f67#diff-7f625777f1ce5384046df08abffd6c911cfbb1cfc8fcb2bdeaf78f337689e3e2 */ -final class WorkerCommand extends AbstractCheckCommand +final readonly class WorkerCommand implements CommandInterface, HiddenCommandInterface { public function __construct( - private readonly WorkerRunner $workerRunner, - private readonly MemoryLimitter $memoryLimitter, - private readonly ConfigurationFactory $configurationFactory, + private WorkerRunner $workerRunner, + private MemoryLimitter $memoryLimitter, + private ConfigurationFactory $configurationFactory, ) { - parent::__construct(); } - #[Override] - protected function configure(): void + public function getName(): string { - $this->setName('worker'); - $this->setDescription('[INTERNAL] Support for parallel process'); - - parent::configure(); + return 'worker'; } - protected function execute(InputInterface $input, OutputInterface $output): int + public function getDescription(): string { - $configuration = $this->configurationFactory->createFromInput($input); + return '[INTERNAL] Support for parallel process'; + } + + /** + * @param string $config Path to config file + * @param string $outputFormat Select output format + * @param string $memoryLimit Memory limit for check + * @param string $port [INTERNAL] parallel TCP port + * @param string $identifier [INTERNAL] parallel identifier + * @param string ...$paths The path(s) to be checked. + * + * @option $config + * @option $outputFormat + * @option $memoryLimit + * @option $port + * @option $identifier + * + * @api invoked via reflection by the Entropy console application + * + * @return ExitCode::* + */ + public function run( + bool $fix = false, + bool $clearCache = false, + bool $noProgressBar = false, + bool $noErrorTable = false, + bool $noDiffs = false, + bool $debug = false, + string $config = '', + string $outputFormat = ConsoleOutputFormatter::NAME, + string $memoryLimit = '', + string $port = '', + string $identifier = '', + string ...$paths, + ): int { + $configuration = $this->configurationFactory->create( + array_values($paths), + $fix, + $clearCache, + $noProgressBar, + $noErrorTable, + $noDiffs, + $outputFormat, + $config !== '' ? $config : null, + $port, + $identifier, + $memoryLimit !== '' ? $memoryLimit : null, + $debug, + ); $this->memoryLimitter->adjust($configuration); $streamSelectLoop = new StreamSelectLoop(); @@ -70,6 +114,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $streamSelectLoop->run(); - return self::SUCCESS; + return ExitCode::SUCCESS; } } diff --git a/src/Console/EasyCodingStandardConsoleApplication.php b/src/Console/EasyCodingStandardConsoleApplication.php deleted file mode 100644 index c57e27c4aa9..00000000000 --- a/src/Console/EasyCodingStandardConsoleApplication.php +++ /dev/null @@ -1,114 +0,0 @@ -setHidden(); - - $this->add($checkCommand); - $this->add($workerCommand); - $this->add($listCheckersCommand); - - $this->get('completion') - ->setHidden(); - $this->get('help') - ->setHidden(); - $this->get('list') - ->setHidden(); - - $this->setDefaultCommand('check'); - } - - #[Override] - public function doRun(InputInterface $input, OutputInterface $output): int - { - // @fixes https://github.com/rectorphp/rector/issues/2205 - $isXdebugAllowed = $input->hasParameterOption('--xdebug'); - if (! $isXdebugAllowed && ! defined('PHPUNIT_COMPOSER_INSTALL')) { - $xdebugHandler = new XdebugHandler('ecs'); - $xdebugHandler->check(); - unset($xdebugHandler); - } - - // skip in this case, since generate content must be clear from meta-info - if ($this->shouldPrintMetaInformation($input)) { - $output->writeln($this->getLongVersion()); - } - - $exitCode = parent::doRun($input, $output); - - // Append to the output of --version - if ($exitCode === 0 && $input->hasParameterOption(['--version', '-V'], true)) { - $output->writeln(sprintf('+ %s %s', 'PHP_CodeSniffer', PHP_CodeSniffer::VERSION)); - $output->writeln(sprintf('+ %s %s', 'PHP-CS-Fixer', PhpCsFixer::VERSION)); - } - - return $exitCode; - } - - #[Override] - protected function getDefaultInputDefinition(): InputDefinition - { - $inputDefinition = parent::getDefaultInputDefinition(); - $this->addExtraOptions($inputDefinition); - - return $inputDefinition; - } - - private function shouldPrintMetaInformation(InputInterface $input): bool - { - $hasNoArguments = $input->getFirstArgument() === null; - - if ($hasNoArguments) { - return false; - } - - $outputFormat = $input->getParameterOption('--' . Option::OUTPUT_FORMAT); - - return $outputFormat === ConsoleOutputFormatter::getName(); - } - - private function addExtraOptions(InputDefinition $inputDefinition): void - { - $inputDefinition->addOption(new InputOption( - Option::XDEBUG, - null, - InputOption::VALUE_NONE, - 'Allow running xdebug' - )); - - $inputDefinition->addOption(new InputOption( - Option::DEBUG, - null, - InputOption::VALUE_NONE, - 'Run in debug mode (alias for "-vvv")' - )); - } -} diff --git a/src/Console/ExitCode.php b/src/Console/ExitCode.php index da3e952b38e..e11f516a188 100644 --- a/src/Console/ExitCode.php +++ b/src/Console/ExitCode.php @@ -4,13 +4,11 @@ namespace Symplify\EasyCodingStandard\Console; -use Symfony\Component\Console\Command\Command; - final class ExitCode { - public const int SUCCESS = Command::SUCCESS; + public const int SUCCESS = 0; - public const int FAILURE = Command::FAILURE; + public const int FAILURE = 1; public const int CHANGED_CODE_OR_FOUND_ERRORS = 2; } diff --git a/src/Console/Formatter/ColorConsoleDiffFormatter.php b/src/Console/Formatter/ColorConsoleDiffFormatter.php index 41bbe1385ed..580ed8cc25c 100644 --- a/src/Console/Formatter/ColorConsoleDiffFormatter.php +++ b/src/Console/Formatter/ColorConsoleDiffFormatter.php @@ -5,7 +5,6 @@ namespace Symplify\EasyCodingStandard\Console\Formatter; use Nette\Utils\Strings; -use Symfony\Component\Console\Formatter\OutputFormatter; /** * Inspired by @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/src/Differ/DiffConsoleFormatter.php to be @@ -53,7 +52,7 @@ public function format(string $diff): string private function formatWithTemplate(string $diff, string $template): string { - $escapedDiff = OutputFormatter::escape(rtrim($diff)); + $escapedDiff = rtrim($diff); $escapedDiffLines = Strings::split($escapedDiff, self::NEWLINES_REGEX); diff --git a/src/Console/Reporter/CheckerListReporter.php b/src/Console/Reporter/CheckerListReporter.php index 1c1a288b4dd..cc3c629a50b 100644 --- a/src/Console/Reporter/CheckerListReporter.php +++ b/src/Console/Reporter/CheckerListReporter.php @@ -4,12 +4,12 @@ namespace Symplify\EasyCodingStandard\Console\Reporter; -use Symfony\Component\Console\Style\SymfonyStyle; +use Symplify\EasyCodingStandard\Console\Style\EasyCodingStandardStyle; final readonly class CheckerListReporter { public function __construct( - private SymfonyStyle $symfonyStyle + private EasyCodingStandardStyle $easyCodingStandardStyle ) { } @@ -28,7 +28,7 @@ public function report(array $checkerClasses, string $type): void count($checkerClasses) === 1 ? '' : 's', $type ); - $this->symfonyStyle->section($sectionMessage); - $this->symfonyStyle->listing($checkerClasses); + $this->easyCodingStandardStyle->section($sectionMessage); + $this->easyCodingStandardStyle->listing($checkerClasses); } } diff --git a/src/Console/Style/EasyCodingStandardStyle.php b/src/Console/Style/EasyCodingStandardStyle.php index b38a1a6a0a0..e3b1bc12892 100644 --- a/src/Console/Style/EasyCodingStandardStyle.php +++ b/src/Console/Style/EasyCodingStandardStyle.php @@ -4,25 +4,82 @@ namespace Symplify\EasyCodingStandard\Console\Style; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Console\Terminal; +use Entropy\Console\Output\OutputPrinter; +use Entropy\Console\Output\ProgressBar; use Symplify\EasyCodingStandard\SniffRunner\ValueObject\Error\CodingStandardError; -final class EasyCodingStandardStyle extends SymfonyStyle +final readonly class EasyCodingStandardStyle { /** * To fit in Linux/Windows terminal windows to prevent overflow. */ private const int BULGARIAN_CONSTANT = 8; + private const int DEFAULT_TERMINAL_WIDTH = 120; + public function __construct( - InputInterface $input, - OutputInterface $output, - private readonly Terminal $terminal + private OutputPrinter $outputPrinter, + private ProgressBar $progressBar, + private bool $isDebug = false, ) { - parent::__construct($input, $output); + } + + public function writeln(string $message): void + { + $this->outputPrinter->writeln($this->normalizeTags($message)); + } + + public function newLine(int $count = 1): void + { + $this->outputPrinter->newline($count); + } + + public function success(string $message): void + { + $this->outputPrinter->success($message); + } + + public function warning(string $message): void + { + $this->outputPrinter->warning($message); + } + + public function error(string $message): void + { + $this->outputPrinter->error($message); + } + + public function section(string $message): void + { + $this->outputPrinter->section($message); + } + + /** + * @param string[] $items + */ + public function listing(array $items): void + { + $this->outputPrinter->listing($items); + } + + public function ask(string $question, ?string $default = null): ?string + { + return $this->outputPrinter->ask($question, $default); + } + + public function isDebug(): bool + { + return $this->isDebug; + } + + public function progressStart(int $max): void + { + $this->progressBar->start($max); + } + + public function progressAdvance(int $step = 1): void + { + $this->progressBar->advance($step); } /** @@ -30,7 +87,6 @@ public function __construct( */ public function printErrors(array $codingStandardErrors): void { - /** @var CodingStandardError $codingStandardError */ foreach ($codingStandardErrors as $codingStandardError) { $this->separator(); @@ -47,6 +103,23 @@ public function printErrors(array $codingStandardErrors): void } } + /** + * Translate the Symfony-style console tags still emitted by the reporters into + * the smaller tag vocabulary understood by Entropy's OutputColorizer. + */ + private function normalizeTags(string $text): string + { + // drop bold/underscore styling, keep the text + $text = (string) preg_replace('#]+>(.*?)#su', '$1', $text); + + // → yellow, → green + $text = (string) preg_replace('#(.*?)#su', '$1', $text); + $text = (string) preg_replace('#(.*?)#su', '$1', $text); + + // normalize explicit closing tags (e.g. ) to the generic closing tag + return (string) preg_replace('##', '', $text); + } + private function separator(): void { $separator = str_repeat('-', $this->getTerminalWidth()); @@ -68,7 +141,12 @@ private function createMessageFromFileError(CodingStandardError $codingStandardE private function getTerminalWidth(): int { - return $this->terminal->getWidth() - self::BULGARIAN_CONSTANT; + $columns = getenv('COLUMNS'); + if (is_numeric($columns)) { + return (int) $columns - self::BULGARIAN_CONSTANT; + } + + return self::DEFAULT_TERMINAL_WIDTH - self::BULGARIAN_CONSTANT; } /** diff --git a/src/Console/Style/EasyCodingStandardStyleFactory.php b/src/Console/Style/EasyCodingStandardStyleFactory.php index 43c8d757ce3..a7f04cde25f 100644 --- a/src/Console/Style/EasyCodingStandardStyleFactory.php +++ b/src/Console/Style/EasyCodingStandardStyleFactory.php @@ -4,74 +4,25 @@ namespace Symplify\EasyCodingStandard\Console\Style; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Terminal; +use Entropy\Console\Output\OutputColorizer; +use Entropy\Console\Output\OutputPrinter; +use Entropy\Console\Output\ProgressBar; /** * @api */ final readonly class EasyCodingStandardStyleFactory { - public function __construct( - private Terminal $terminal - ) { - } - /** * @api */ public function create(): EasyCodingStandardStyle { - $argvInput = new ArgvInput(); - $consoleOutput = new ConsoleOutput(); - - $this->applySymfonyConsoleArgs($argvInput, $consoleOutput); - - // --debug is called - if ($argvInput->hasParameterOption('--debug')) { - $consoleOutput->setVerbosity(OutputInterface::VERBOSITY_DEBUG); - } - - // disable output for tests - if (defined('PHPUNIT_COMPOSER_INSTALL')) { - $consoleOutput->setVerbosity(OutputInterface::VERBOSITY_QUIET); - } - - return new EasyCodingStandardStyle($argvInput, $consoleOutput, $this->terminal); - } - - /** - * This method was derived from the `Application::configureIO` method of the Symfony Console - * component, under the MIT license. See NOTICE for the full license. - * - * @see https://github.com/symfony/console/blob/0aa29ca177f432ab68533432db0de059f39c92ae/Application.php#L905 - */ - private function applySymfonyConsoleArgs(InputInterface $input, OutputInterface $output): void - { - $enableAnsi = $input->hasParameterOption(['--ansi'], true); - $disableAnsi = $input->hasParameterOption(['--no-ansi'], true); - - match (true) { - $enableAnsi => $output->setDecorated(true), - $disableAnsi => $output->setDecorated(false), - default => null, - }; - - $enableQuiet = $input->hasParameterOption(['--quiet', '-q'], true); + $outputPrinter = new OutputPrinter(new OutputColorizer()); + $progressBar = new ProgressBar(); - $isVVV = $input->hasParameterOption('-vvv', true); - $isVV = $input->hasParameterOption('-vv', true); - $isV = $input->hasParameterOption('-v', true); + $isDebug = in_array('--debug', $_SERVER['argv'] ?? [], true); - match (true) { - $enableQuiet => $output->setVerbosity(OutputInterface::VERBOSITY_QUIET), - $isVVV => $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG), - $isVV => $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE), - $isV => $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE), - default => null, - }; + return new EasyCodingStandardStyle($outputPrinter, $progressBar, $isDebug); } } diff --git a/src/Console/Style/SymfonyStyleFactory.php b/src/Console/Style/SymfonyStyleFactory.php deleted file mode 100644 index 60b5d26a065..00000000000 --- a/src/Console/Style/SymfonyStyleFactory.php +++ /dev/null @@ -1,82 +0,0 @@ -hasParameterOption('--debug')) { - $consoleOutput->setVerbosity(OutputInterface::VERBOSITY_DEBUG); - } - - // disable output for tests - if (self::isPHPUnitRun()) { - $consoleOutput->setVerbosity(OutputInterface::VERBOSITY_QUIET); - } - - return new SymfonyStyle($argvInput, $consoleOutput); - } - - /** - * Never ever used static methods if not neccesary, this is just handy for tests + src to prevent duplication. - */ - private static function isPHPUnitRun(): bool - { - return defined('PHPUNIT_COMPOSER_INSTALL') || defined('__PHPUNIT_PHAR__'); - } - - /** - * This method was derived from the `Application::configureIO` method of the Symfony Console - * component, under the MIT license. See NOTICE for the full license. - * - * @see https://github.com/symfony/console/blob/0aa29ca177f432ab68533432db0de059f39c92ae/Application.php#L905 - */ - private static function applySymfonyConsoleArgs(InputInterface $input, OutputInterface $output): void - { - $enableAnsi = $input->hasParameterOption(['--ansi'], true); - $disableAnsi = $input->hasParameterOption(['--no-ansi'], true); - - match (true) { - $enableAnsi => $output->setDecorated(true), - $disableAnsi => $output->setDecorated(false), - default => null, - }; - - $enableQuiet = $input->hasParameterOption(['--quiet', '-q'], true); - - $isVVV = $input->hasParameterOption('-vvv', true); - $isVV = $input->hasParameterOption('-vv', true); - $isV = $input->hasParameterOption('-v', true); - - match (true) { - $enableQuiet => $output->setVerbosity(OutputInterface::VERBOSITY_QUIET), - $isVVV => $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG), - $isVV => $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE), - $isV => $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE), - default => null, - }; - } -} diff --git a/src/DependencyInjection/EasyCodingStandardContainerFactory.php b/src/DependencyInjection/EasyCodingStandardContainerFactory.php index ed6b6f01e63..3dfd9526e1f 100644 --- a/src/DependencyInjection/EasyCodingStandardContainerFactory.php +++ b/src/DependencyInjection/EasyCodingStandardContainerFactory.php @@ -4,7 +4,6 @@ namespace Symplify\EasyCodingStandard\DependencyInjection; -use Symfony\Component\Console\Input\ArgvInput; use Symplify\EasyCodingStandard\Caching\ChangedFilesDetector; use Symplify\EasyCodingStandard\Config\ECSConfig; @@ -13,20 +12,20 @@ */ final class EasyCodingStandardContainerFactory { - public function createFromFromInput(ArgvInput $argvInput): ECSConfig + /** + * @param string[] $argv + */ + public function createFromArgv(array $argv): ECSConfig { - // $easyCodingStandardKernel = new EasyCodingStandardKernel(); $serviceContainerFactory = new ServiceContainerFactory(); $inputConfigFiles = []; $rootECSConfig = getcwd() . DIRECTORY_SEPARATOR . 'ecs.php'; - if ($argvInput->hasParameterOption(['--config', '-c'])) { - $commandLineConfigFile = $argvInput->getParameterOption(['--config', '-c']); - if (is_string($commandLineConfigFile) && file_exists($commandLineConfigFile)) { - // must be realpath, so container builder knows the location - $inputConfigFiles[] = (string) realpath($commandLineConfigFile); - } + $commandLineConfigFile = $this->resolveConfigFromArgv($argv); + if ($commandLineConfigFile !== null && file_exists($commandLineConfigFile)) { + // must be realpath, so container builder knows the location + $inputConfigFiles[] = (string) realpath($commandLineConfigFile); } elseif (file_exists($rootECSConfig)) { $inputConfigFiles[] = $rootECSConfig; } @@ -43,4 +42,28 @@ public function createFromFromInput(ArgvInput $argvInput): ECSConfig return $ecsConfig; } + + /** + * Resolve "--config ", "--config=", "-c " or "-c=" from raw argv. + * + * @param string[] $argv + */ + private function resolveConfigFromArgv(array $argv): ?string + { + foreach ($argv as $index => $arg) { + if ($arg === '--config' || $arg === '-c') { + return $argv[$index + 1] ?? null; + } + + if (str_starts_with($arg, '--config=')) { + return substr($arg, strlen('--config=')); + } + + if (str_starts_with($arg, '-c=')) { + return substr($arg, strlen('-c=')); + } + } + + return null; + } } diff --git a/src/DependencyInjection/ServiceContainerFactory.php b/src/DependencyInjection/ServiceContainerFactory.php index 606431bb8ea..97fbd4b3bc2 100644 --- a/src/DependencyInjection/ServiceContainerFactory.php +++ b/src/DependencyInjection/ServiceContainerFactory.php @@ -10,13 +10,11 @@ use PhpCsFixer\Differ\DifferInterface; use PhpCsFixer\Differ\UnifiedDiffer; use PhpCsFixer\WhitespacesFixerConfig; -use Symfony\Component\Console\Style\SymfonyStyle; use Symplify\EasyCodingStandard\Caching\Cache; use Symplify\EasyCodingStandard\Caching\CacheFactory; use Symplify\EasyCodingStandard\Config\ECSConfig; use Symplify\EasyCodingStandard\Console\Style\EasyCodingStandardStyle; use Symplify\EasyCodingStandard\Console\Style\EasyCodingStandardStyleFactory; -use Symplify\EasyCodingStandard\Console\Style\SymfonyStyleFactory; use Symplify\EasyCodingStandard\FixerRunner\WhitespacesFixerConfigFactory; use Webmozart\Assert\Assert; @@ -41,7 +39,8 @@ static function (Container $container): EasyCodingStandardStyle { } ); - $ecsConfig->service(SymfonyStyle::class, static fn (): SymfonyStyle => SymfonyStyleFactory::create()); + // console commands - autodiscovered, then collected by the default CommandRegistry contract lookup + $ecsConfig->autodiscover(__DIR__ . '/../Console/Command'); // whitespace $ecsConfig->service(WhitespacesFixerConfig::class, static function (): WhitespacesFixerConfig { @@ -70,9 +69,9 @@ static function (Container $container): EasyCodingStandardStyle { Assert::isCallable($configClosure); if ($configClosure instanceof Closure && ! defined('PHPUNIT_COMPOSER_INSTALL')) { - /** @var SymfonyStyle $symfonyStyle */ - $symfonyStyle = $ecsConfig->make(SymfonyStyle::class); - $symfonyStyle->warning(sprintf( + /** @var EasyCodingStandardStyle $easyCodingStandardStyle */ + $easyCodingStandardStyle = $ecsConfig->make(EasyCodingStandardStyle::class); + $easyCodingStandardStyle->warning(sprintf( 'The "return function (ECSConfig $ecsConfig): void {}" config format is deprecated. Use "return ECSConfig::configure()" fluent API instead in "%s".', $configFile )); diff --git a/src/Parallel/Application/ParallelFileProcessor.php b/src/Parallel/Application/ParallelFileProcessor.php index f62eeb6f34a..1b4660fc515 100644 --- a/src/Parallel/Application/ParallelFileProcessor.php +++ b/src/Parallel/Application/ParallelFileProcessor.php @@ -10,12 +10,11 @@ use React\EventLoop\StreamSelectLoop; use React\Socket\ConnectionInterface; use React\Socket\TcpServer; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symplify\EasyCodingStandard\Console\Command\CheckCommand; +use Symplify\EasyCodingStandard\Console\ExitCode; use Symplify\EasyCodingStandard\DependencyInjection\SimpleParameterProvider; use Symplify\EasyCodingStandard\Parallel\ValueObject\Bridge; use Symplify\EasyCodingStandard\SniffRunner\ValueObject\Error\CodingStandardError; +use Symplify\EasyCodingStandard\ValueObject\Configuration; use Symplify\EasyCodingStandard\ValueObject\Error\FileDiff; use Symplify\EasyCodingStandard\ValueObject\Error\SystemError; use Symplify\EasyCodingStandard\ValueObject\Option; @@ -57,7 +56,7 @@ public function check( string $mainScript, callable $postFileCallback, ?string $projectConfigFile, - InputInterface $input + Configuration $configuration ): array { $jobs = array_reverse($schedule->getJobs()); $streamSelectLoop = new StreamSelectLoop(); @@ -124,6 +123,15 @@ public function check( $timeoutInSeconds = SimpleParameterProvider::getIntParameter(Option::PARALLEL_TIMEOUT_IN_SECONDS); + // options mirrored to each worker sub-process + $workerOptionValues = [ + Option::FIX => $configuration->isFixer(), + Option::CLEAR_CACHE => $configuration->shouldClearCache(), + Option::NO_ERROR_TABLE => ! $configuration->shouldShowErrorTable(), + Option::NO_DIFFS => ! $configuration->shouldShowDiffs(), + Option::MEMORY_LIMIT => $configuration->getMemoryLimit(), + ]; + for ($i = 0; $i < $numberOfProcesses; ++$i) { // nothing else to process, stop now if ($jobs === []) { @@ -133,11 +141,10 @@ public function check( $processIdentifier = Random::generate(); $workerCommandLine = $this->workerCommandLineFactory->create( $mainScript, - CheckCommand::class, 'worker', - Option::PATHS, $projectConfigFile, - $input, + $workerOptionValues, + $configuration->getSources(), $processIdentifier, $serverPort, ); @@ -200,7 +207,7 @@ function (array $json) use ( // 3. callable on exit function ($exitCode, string $stdErr) use (&$systemErrors, $processIdentifier): void { $this->processPool->tryQuitProcess($processIdentifier); - if ($exitCode === Command::SUCCESS) { + if ($exitCode === ExitCode::SUCCESS) { return; } diff --git a/src/ValueObject/Option.php b/src/ValueObject/Option.php index bb28fb7a413..d7cfa9022db 100644 --- a/src/ValueObject/Option.php +++ b/src/ValueObject/Option.php @@ -10,12 +10,8 @@ final class Option public const string CLEAR_CACHE = 'clear-cache'; - public const string NO_PROGRESS_BAR = 'no-progress-bar'; - public const string NO_ERROR_TABLE = 'no-error-table'; - public const string OUTPUT_FORMAT = 'output-format'; - public const string NO_DIFFS = 'no-diffs'; /** @@ -73,24 +69,16 @@ final class Option */ public const string INDENTATION_TAB = 'tab'; - public const string XDEBUG = 'xdebug'; - - public const string DEBUG = 'debug'; - /** * @see \Symplify\EasyCodingStandard\Config\ECSConfig::parallel() */ public const string PARALLEL = 'parallel'; - public const string CONFIG = 'config'; - /** * @see \Symplify\EasyCodingStandard\Config\ECSConfig::parallel() */ public const string PARALLEL_JOB_SIZE = 'parallel_job_size'; - public const string PARALLEL_PORT = 'port'; - public const string PARALLEL_IDENTIFIER = 'identifier'; /** diff --git a/tests/Console/Command/CommandRegistrationTest.php b/tests/Console/Command/CommandRegistrationTest.php new file mode 100644 index 00000000000..5fafbd9ca15 --- /dev/null +++ b/tests/Console/Command/CommandRegistrationTest.php @@ -0,0 +1,48 @@ +commandRegistry = $this->make(CommandRegistry::class); + } + + public function testCommandsAreRegistered(): void + { + $this->assertTrue($this->commandRegistry->has('check')); + $this->assertTrue($this->commandRegistry->has('worker')); + $this->assertTrue($this->commandRegistry->has('list-checkers')); + } + + public function testCheckIsTheDefaultCommand(): void + { + $this->assertInstanceOf(CheckCommand::class, $this->commandRegistry->getDefault()); + } + + public function testWorkerCommandIsHidden(): void + { + $visibleCommandClasses = array_map( + static fn (object $command): string => $command::class, + $this->commandRegistry->getVisible() + ); + + $this->assertNotContains(WorkerCommand::class, $visibleCommandClasses); + } +} diff --git a/tests/Console/Style/EasyCodingStandardStyleTest.php b/tests/Console/Style/EasyCodingStandardStyleTest.php new file mode 100644 index 00000000000..574a81d4573 --- /dev/null +++ b/tests/Console/Style/EasyCodingStandardStyleTest.php @@ -0,0 +1,57 @@ +easyCodingStandardStyle = new EasyCodingStandardStyle( + new OutputPrinter(new OutputColorizer()), + new ProgressBar() + ); + + $this->normalizeTagsReflectionMethod = new ReflectionMethod($this->easyCodingStandardStyle, 'normalizeTags'); + } + + #[DataProvider('provideData')] + public function testNormalizeTags(string $input, string $expected): void + { + $normalized = $this->normalizeTagsReflectionMethod->invoke($this->easyCodingStandardStyle, $input); + + $this->assertSame($expected, $normalized); + } + + public static function provideData(): Iterator + { + yield 'comment tag becomes yellow' => ['hello', 'hello']; + + yield 'info tag becomes green' => ['done', 'done']; + + yield 'bold options are stripped, text kept' => ['title', 'title']; + + yield 'explicit color closing tag is normalized' => ['err', 'err']; + + yield 'plain text is left untouched' => ['no tags here', 'no tags here']; + } +}