diff --git a/bin/list-unused-rules.php b/bin/list-unused-rules.php new file mode 100644 index 00000000000..d9c908bb8d8 --- /dev/null +++ b/bin/list-unused-rules.php @@ -0,0 +1,123 @@ +find([ + __DIR__ . '/../rules', + __DIR__ . '/../vendor/rector/rector-doctrine', + __DIR__ . '/../vendor/rector/rector-phpunit', + __DIR__ . '/../vendor/rector/rector-symfony', + __DIR__ . '/../vendor/rector/rector-downgrade-php', +]); + +$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput()); +$symfonyStyle->writeln(sprintf('Found Rector %d rules', count($rectorClasses))); + +$rectorSeFinder = new RectorSetFinder(); + +$rectorSetFiles = $rectorSeFinder->find([ + __DIR__ . '/../config/set', + __DIR__ . '/../vendor/rector/rector-symfony/config/sets', + __DIR__ . '/../vendor/rector/rector-doctrine/config/sets', + __DIR__ . '/../vendor/rector/rector-phpunit/config/sets', + __DIR__ . '/../vendor/rector/rector-downgrade-php/config/set', +]); + +$symfonyStyle->writeln(sprintf('Found %d sets', count($rectorSetFiles))); + +$usedRectorClassResolver = new UsedRectorClassResolver(); +$usedRectorRules = $usedRectorClassResolver->resolve($rectorSetFiles); + +$symfonyStyle->newLine(); +$symfonyStyle->writeln(sprintf('Found %d used Rector rules in sets', count($usedRectorRules))); + +$unusedRectorRules = array_diff($rectorClasses, $usedRectorRules); +$symfonyStyle->writeln( + sprintf('Found %d Rector rules not in any set', count($unusedRectorRules)) +); + +$symfonyStyle->newLine(); +$symfonyStyle->listing($unusedRectorRules); + +final class RectorClassFinder +{ + /** + * @param string[] $dirs + * @return string[] + */ + public function find(array $dirs): array + { + $robotLoader = new RobotLoader(); + $robotLoader->acceptFiles = ['*Rector.php']; + $robotLoader->addDirectory(...$dirs); + + $robotLoader->setTempDirectory(sys_get_temp_dir() . '/rector-rules'); + $robotLoader->refresh(); + + return array_keys($robotLoader->getIndexedClasses()); + } +} + +final class RectorSetFinder +{ + /** + * @param string[] $configDirs + * @return string[] + */ + public function find(array $configDirs): array + { + Assert::allString($configDirs); + Assert::allDirectory($configDirs); + + // find set files + $finder = (new Finder())->in($configDirs) + ->files() + ->name('*.php'); + + /** @var SplFileInfo[] $setFileInfos */ + $setFileInfos = iterator_to_array($finder->getIterator()); + + $setFiles = []; + foreach ($setFileInfos as $setFileInfo) { + $setFiles[] = $setFileInfo->getRealPath(); + } + + return $setFiles; + } +} + +final class UsedRectorClassResolver +{ + /** + * @param string[] $rectorSetFiles + * @return string[] + */ + public function resolve(array $rectorSetFiles): array + { + $setRectorsResolver = new SetRectorsResolver(); + $rulesConfiguration = $setRectorsResolver->resolveFromFilePathsIncludingConfiguration($rectorSetFiles); + + $usedRectorRules = []; + foreach ($rulesConfiguration as $ruleConfiguration) { + $usedRectorRules[] = is_string($ruleConfiguration) ? $ruleConfiguration : array_keys($ruleConfiguration)[0]; + } + + sort($usedRectorRules); + + return array_unique($usedRectorRules); + } +} diff --git a/composer-dependency-analyser.php b/composer-dependency-analyser.php index ab40195069f..3221035e148 100644 --- a/composer-dependency-analyser.php +++ b/composer-dependency-analyser.php @@ -17,6 +17,9 @@ // ensure use version ^3.2.0 ->ignoreErrorsOnPackage('composer/pcre', [ErrorType::UNUSED_DEPENDENCY]) + // use din /bin, but only local script + ->ignoreErrorsOnPackage('nette/robot-loader', [ErrorType::DEV_DEPENDENCY_IN_PROD]) + ->ignoreErrorsOnPaths([ __DIR__ . '/stubs', __DIR__ . '/tests', diff --git a/composer.json b/composer.json index 850a0ece814..6c7df99f587 100644 --- a/composer.json +++ b/composer.json @@ -42,6 +42,7 @@ "webmozart/assert": "^1.11" }, "require-dev": { + "nette/robot-loader": "^4.1", "php-parallel-lint/php-parallel-lint": "^1.4", "phpecs/phpecs": "^2.2", "phpstan/extension-installer": "^1.4", diff --git a/phpstan.neon b/phpstan.neon index db92a0ee7e1..dee738738c1 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -354,3 +354,7 @@ parameters: path: src/CustomRules/SimpleNodeDumper.php - '#Method Rector\\Utils\\Rector\\RemoveRefactorDuplicatedNodeInstanceCheckRector\:\:getInstanceofNodeClass\(\) should return class\-string\|null but returns class\-string#' + + - + path: bin/list-unused-rules.php + identifier: symplify.multipleClassLikeInFile diff --git a/src/Console/Command/ListRulesCommand.php b/src/Console/Command/ListRulesCommand.php index c6f8b37c54c..237a90733e7 100644 --- a/src/Console/Command/ListRulesCommand.php +++ b/src/Console/Command/ListRulesCommand.php @@ -6,8 +6,6 @@ use Nette\Utils\Json; use Rector\ChangesReporting\Output\ConsoleOutputFormatter; -use Rector\Configuration\ConfigurationRuleFilter; -use Rector\Configuration\OnlyRuleResolver; use Rector\Configuration\Option; use Rector\Contract\Rector\RectorInterface; use Rector\PostRector\Contract\Rector\PostRectorInterface; @@ -26,8 +24,6 @@ final class ListRulesCommand extends Command public function __construct( private readonly SymfonyStyle $symfonyStyle, private readonly SkippedClassResolver $skippedClassResolver, - private readonly OnlyRuleResolver $onlyRuleResolver, - private readonly ConfigurationRuleFilter $configurationRuleFilter, private readonly array $rectors ) { parent::__construct(); @@ -53,12 +49,7 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $onlyRule = $input->getOption(Option::ONLY); - if ($onlyRule !== null) { - $onlyRule = $this->onlyRuleResolver->resolve($onlyRule); - } - - $rectorClasses = $this->resolveRectorClasses($onlyRule); + $rectorClasses = $this->resolveRectorClasses(); $skippedClasses = $this->getSkippedCheckers(); @@ -90,17 +81,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** * @return array> */ - private function resolveRectorClasses(?string $onlyRule): array + private function resolveRectorClasses(): array { $customRectors = array_filter( $this->rectors, static fn (RectorInterface $rector): bool => ! $rector instanceof PostRectorInterface ); - if ($onlyRule !== null) { - $customRectors = $this->configurationRuleFilter->filterOnlyRule($customRectors, $onlyRule); - } - $rectorClasses = array_map(static fn (RectorInterface $rector): string => $rector::class, $customRectors); sort($rectorClasses);