diff --git a/src/Console/Command/ListRulesCommand.php b/src/Console/Command/ListRulesCommand.php index 4ecfae3b9c4..965e21de21a 100644 --- a/src/Console/Command/ListRulesCommand.php +++ b/src/Console/Command/ListRulesCommand.php @@ -4,15 +4,27 @@ namespace Rector\Console\Command; +use Nette\Utils\Json; +use Rector\ChangesReporting\Output\ConsoleOutputFormatter; +use Rector\Configuration\Option; +use Rector\Contract\Rector\RectorInterface; +use Rector\PostRector\Contract\Rector\PostRectorInterface; +use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver; 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 Symfony\Component\Console\Style\SymfonyStyle; final class ListRulesCommand extends Command { + /** + * @param RectorInterface[] $rectors + */ public function __construct( - private readonly SymfonyStyle $symfonyStyle + private readonly SymfonyStyle $symfonyStyle, + private readonly SkippedClassResolver $skippedClassResolver, + private readonly array $rectors ) { parent::__construct(); } @@ -20,17 +32,84 @@ public function __construct( protected function configure(): void { $this->setName('list-rules'); - $this->setDescription('[DEPRECATED] Show loaded Rectors'); + $this->setDescription('Show loaded Rectors'); $this->setAliases(['show-rules']); + + $this->addOption( + Option::OUTPUT_FORMAT, + null, + InputOption::VALUE_REQUIRED, + 'Select output format', + ConsoleOutputFormatter::NAME + ); + + $this->addOption(Option::ONLY, null, InputOption::VALUE_REQUIRED, 'Fully qualified rule class name'); } protected function execute(InputInterface $input, OutputInterface $output): int { - $this->symfonyStyle->error( - 'The "list-rules" command is deprecated and no longer provided. Run Rector with the set or rules you desire instead.' + $rectorClasses = $this->resolveRectorClasses(); + + $skippedClasses = $this->getSkippedRectorClasses(); + + $outputFormat = $input->getOption(Option::OUTPUT_FORMAT); + if ($outputFormat === 'json') { + $data = [ + 'rectors' => $rectorClasses, + 'skipped-rectors' => $skippedClasses, + ]; + + echo Json::encode($data, pretty: true) . PHP_EOL; + return Command::SUCCESS; + } + + $this->symfonyStyle->title('Loaded Rector rules'); + $this->symfonyStyle->listing($rectorClasses); + + if ($skippedClasses !== []) { + $this->symfonyStyle->title('Skipped Rector rules'); + $this->symfonyStyle->listing($skippedClasses); + } + + $this->symfonyStyle->newLine(); + $this->symfonyStyle->note(sprintf('Loaded %d rules', count($rectorClasses))); + + return Command::SUCCESS; + } + + /** + * @return array> + */ + private function resolveRectorClasses(): array + { + $customRectors = array_filter( + $this->rectors, + static fn (RectorInterface $rector): bool => ! $rector instanceof PostRectorInterface ); - return Command::FAILURE; + $rectorClasses = array_map(static fn (RectorInterface $rector): string => $rector::class, $customRectors); + sort($rectorClasses); + + return array_unique($rectorClasses); + } + + /** + * @return array + */ + private function getSkippedRectorClasses(): array + { + $skippedRectorClasses = []; + + foreach ($this->skippedClassResolver->resolve() as $rectorClass => $fileList) { + // ignore specific skips + if ($fileList !== null) { + continue; + } + + $skippedRectorClasses[] = $rectorClass; + } + + return $skippedRectorClasses; } }