From 2c77ccf8e6d3283717c5af6e5df1d1e81c29b1d5 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 3 Aug 2026 17:14:31 +0200 Subject: [PATCH 1/5] [Scripts] Add list-non-composer-based-rules.php script, skip deprecated rules in list-unused-rules.php --- scripts/list-non-composer-based-rules.php | 63 +++++++++++++++++++ scripts/list-unused-rules.php | 24 +------ scripts/src/Finder/RectorClassFinder.php | 22 +++++++ .../src/Resolver/UsedRectorClassResolver.php | 29 +++++++++ 4 files changed, 115 insertions(+), 23 deletions(-) create mode 100644 scripts/list-non-composer-based-rules.php create mode 100644 scripts/src/Resolver/UsedRectorClassResolver.php diff --git a/scripts/list-non-composer-based-rules.php b/scripts/list-non-composer-based-rules.php new file mode 100644 index 00000000000..df70dbf135c --- /dev/null +++ b/scripts/list-non-composer-based-rules.php @@ -0,0 +1,63 @@ +find([ + __DIR__ . '/../rules', + __DIR__ . '/../vendor/rector/rector-doctrine', + __DIR__ . '/../vendor/rector/rector-phpunit', + __DIR__ . '/../vendor/rector/rector-symfony', +]); + +$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput()); +$symfonyStyle->writeln(sprintf('Found Rector %d rules', count($rectorClasses))); + +// 2. find "composer-based.php" sets, that bind rules to the installed package version +$rectorSetFilesFinder = new RectorSetFilesFinder(); + +$rectorSetFiles = $rectorSetFilesFinder->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', +]); + +$composerBasedSetFiles = array_filter( + $rectorSetFiles, + static fn (string $rectorSetFile): bool => basename($rectorSetFile) === 'composer-based.php' +); + +$symfonyStyle->writeln(sprintf('Found %d composer-based sets', count($composerBasedSetFiles))); +$symfonyStyle->listing($composerBasedSetFiles); + +$usedRectorClassResolver = new UsedRectorClassResolver(); +$usedRectorRules = $usedRectorClassResolver->resolve($composerBasedSetFiles); + +$symfonyStyle->writeln( + sprintf('Found %d Rector rules used in composer-based sets', count($usedRectorRules)) +); + +$nonComposerBasedRectorRules = array_diff($rectorClasses, $usedRectorRules); + +$symfonyStyle->newLine(); +$symfonyStyle->listing($nonComposerBasedRectorRules); + +$symfonyStyle->writeln( + sprintf( + 'Found %d Rector rules not in any composer-based set, likely dead', + count($nonComposerBasedRectorRules) + ) +); +$symfonyStyle->newLine(); diff --git a/scripts/list-unused-rules.php b/scripts/list-unused-rules.php index d8281ef9417..7d75890964c 100644 --- a/scripts/list-unused-rules.php +++ b/scripts/list-unused-rules.php @@ -2,10 +2,10 @@ declare(strict_types=1); -use Rector\Bridge\SetRectorsResolver; use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\Scripts\Finder\RectorClassFinder; use Rector\Scripts\Finder\RectorSetFilesFinder; +use Rector\Scripts\Resolver\UsedRectorClassResolver; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Style\SymfonyStyle; @@ -64,25 +64,3 @@ sprintf('Skipped %d configurable Rector rules', count($configurableRectorRules)) ); $symfonyStyle->newLine(); - -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/scripts/src/Finder/RectorClassFinder.php b/scripts/src/Finder/RectorClassFinder.php index dbc1b39c57a..3e5740cfe35 100644 --- a/scripts/src/Finder/RectorClassFinder.php +++ b/scripts/src/Finder/RectorClassFinder.php @@ -4,6 +4,7 @@ namespace Rector\Scripts\Finder; +use Deprecated; use Nette\Loaders\RobotLoader; use Rector\Configuration\Deprecation\Contract\DeprecatedInterface; use ReflectionClass; @@ -39,9 +40,30 @@ public function find(array $dirs): array continue; } + if ($this->isDeprecated($rectorClassReflection)) { + continue; + } + $usableRectorClasses[] = $rectorClass; } return $usableRectorClasses; } + + /** + * @param ReflectionClass $reflectionClass + */ + private function isDeprecated(ReflectionClass $reflectionClass): bool + { + if ($reflectionClass->getAttributes(Deprecated::class) !== []) { + return true; + } + + $docComment = $reflectionClass->getDocComment(); + if (! is_string($docComment)) { + return false; + } + + return str_contains($docComment, '@deprecated'); + } } diff --git a/scripts/src/Resolver/UsedRectorClassResolver.php b/scripts/src/Resolver/UsedRectorClassResolver.php new file mode 100644 index 00000000000..d28863f2504 --- /dev/null +++ b/scripts/src/Resolver/UsedRectorClassResolver.php @@ -0,0 +1,29 @@ +resolveFromFilePathsIncludingConfiguration($rectorSetFiles); + + $usedRectorRules = []; + foreach ($rulesConfiguration as $ruleConfiguration) { + $usedRectorRules[] = is_string($ruleConfiguration) ? $ruleConfiguration : array_keys($ruleConfiguration)[0]; + } + + sort($usedRectorRules); + + return array_unique($usedRectorRules); + } +} From f830b1c85e1e3e9a32390b22e5d644aa96ebad1c Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 3 Aug 2026 17:20:28 +0200 Subject: [PATCH 2/5] [Scripts] Skip core rules in list-non-composer-based-rules.php --- scripts/list-non-composer-based-rules.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/list-non-composer-based-rules.php b/scripts/list-non-composer-based-rules.php index df70dbf135c..ce4d315f564 100644 --- a/scripts/list-non-composer-based-rules.php +++ b/scripts/list-non-composer-based-rules.php @@ -11,11 +11,10 @@ require __DIR__ . '/../vendor/autoload.php'; -// 1. find all rector rules in core and in doctrine, phpunit and symfony packages +// 1. find all rector rules in doctrine, phpunit and symfony packages; core rules are not package-version bound $rectorClassFinder = new RectorClassFinder(); $rectorClasses = $rectorClassFinder->find([ - __DIR__ . '/../rules', __DIR__ . '/../vendor/rector/rector-doctrine', __DIR__ . '/../vendor/rector/rector-phpunit', __DIR__ . '/../vendor/rector/rector-symfony', @@ -28,7 +27,6 @@ $rectorSetFilesFinder = new RectorSetFilesFinder(); $rectorSetFiles = $rectorSetFilesFinder->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', From 3fe5ebaecfc0ed8caf492cc20746a23fa5ccdfa8 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 3 Aug 2026 17:42:03 +0200 Subject: [PATCH 3/5] [Scripts] Skip code-quality set rules in list-non-composer-based-rules.php --- scripts/list-non-composer-based-rules.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/scripts/list-non-composer-based-rules.php b/scripts/list-non-composer-based-rules.php index ce4d315f564..9c332a177ca 100644 --- a/scripts/list-non-composer-based-rules.php +++ b/scripts/list-non-composer-based-rules.php @@ -47,7 +47,19 @@ sprintf('Found %d Rector rules used in composer-based sets', count($usedRectorRules)) ); -$nonComposerBasedRectorRules = array_diff($rectorClasses, $usedRectorRules); +// code-quality rules are not bound to any package version, so they never belong to a composer-based set +$codeQualitySetFiles = array_filter( + $rectorSetFiles, + static fn (string $rectorSetFile): bool => str_ends_with(basename($rectorSetFile), 'code-quality.php') +); + +$codeQualityRectorRules = $usedRectorClassResolver->resolve($codeQualitySetFiles); + +$symfonyStyle->writeln( + sprintf('Found %d Rector rules used in code-quality sets', count($codeQualityRectorRules)) +); + +$nonComposerBasedRectorRules = array_diff($rectorClasses, $usedRectorRules, $codeQualityRectorRules); $symfonyStyle->newLine(); $symfonyStyle->listing($nonComposerBasedRectorRules); From a760c77a26952e2de746705bfda4215b5110c053 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 3 Aug 2026 17:50:53 +0200 Subject: [PATCH 4/5] load twig + doctrine configs --- src/Configuration/RectorConfigBuilder.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Configuration/RectorConfigBuilder.php b/src/Configuration/RectorConfigBuilder.php index 6619f46b1d0..89e70287a98 100644 --- a/src/Configuration/RectorConfigBuilder.php +++ b/src/Configuration/RectorConfigBuilder.php @@ -754,6 +754,14 @@ public function withComposerBased( $this->sets[] = PHPUnitSetList::COMPOSER_BASED; } + if ($doctrine) { + $this->sets[] = DoctrineSetList::COMPOSER_BASED; + } + + if ($twig) { + $this->sets[] = \Rector\Symfony\Set\TwigSetList::COMPOSER_BASED; + } + if ($symfony) { // single set, as every rule inside is bound to the installed Symfony package version on its own $this->sets[] = SymfonySetList::COMPOSER_BASED; From b48a5143168a05597c9dd2cc3aa38e5e633473de Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 3 Aug 2026 17:57:51 +0200 Subject: [PATCH 5/5] [Scripts] Skip typed-collections set rules in list-non-composer-based-rules.php --- scripts/list-non-composer-based-rules.php | 18 ++++++++++-------- src/Configuration/RectorConfigBuilder.php | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/scripts/list-non-composer-based-rules.php b/scripts/list-non-composer-based-rules.php index 9c332a177ca..337187818ac 100644 --- a/scripts/list-non-composer-based-rules.php +++ b/scripts/list-non-composer-based-rules.php @@ -47,19 +47,21 @@ sprintf('Found %d Rector rules used in composer-based sets', count($usedRectorRules)) ); -// code-quality rules are not bound to any package version, so they never belong to a composer-based set -$codeQualitySetFiles = array_filter( - $rectorSetFiles, - static fn (string $rectorSetFile): bool => str_ends_with(basename($rectorSetFile), 'code-quality.php') -); +// these rules are not bound to any package version, so they never belong to a composer-based set +$versionAgnosticSetFileNames = ['code-quality.php', 'typed-collections.php', 'typed-collections-docblocks.php']; + +$versionAgnosticSetFiles = array_filter($rectorSetFiles, static fn (string $rectorSetFile): bool => array_any($versionAgnosticSetFileNames, fn (string $versionAgnosticSetFileName): bool => str_ends_with(basename($rectorSetFile), $versionAgnosticSetFileName))); -$codeQualityRectorRules = $usedRectorClassResolver->resolve($codeQualitySetFiles); +$versionAgnosticRectorRules = $usedRectorClassResolver->resolve($versionAgnosticSetFiles); $symfonyStyle->writeln( - sprintf('Found %d Rector rules used in code-quality sets', count($codeQualityRectorRules)) + sprintf( + 'Found %d Rector rules used in version-agnostic sets', + count($versionAgnosticRectorRules) + ) ); -$nonComposerBasedRectorRules = array_diff($rectorClasses, $usedRectorRules, $codeQualityRectorRules); +$nonComposerBasedRectorRules = array_diff($rectorClasses, $usedRectorRules, $versionAgnosticRectorRules); $symfonyStyle->newLine(); $symfonyStyle->listing($nonComposerBasedRectorRules); diff --git a/src/Configuration/RectorConfigBuilder.php b/src/Configuration/RectorConfigBuilder.php index 89e70287a98..dc47d11e396 100644 --- a/src/Configuration/RectorConfigBuilder.php +++ b/src/Configuration/RectorConfigBuilder.php @@ -755,11 +755,11 @@ public function withComposerBased( } if ($doctrine) { - $this->sets[] = DoctrineSetList::COMPOSER_BASED; + // $this->sets[] = DoctrineSetList::COMPOSER_BASED; } if ($twig) { - $this->sets[] = \Rector\Symfony\Set\TwigSetList::COMPOSER_BASED; + // $this->sets[] = TwigSetList::COMPOSER_BASED; } if ($symfony) {