Skip to content

Commit 40943e4

Browse files
authored
[Scripts] Add list-non-composer-based-rules.php script (#8276)
1 parent 16be33c commit 40943e4

5 files changed

Lines changed: 135 additions & 23 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Scripts\Finder\RectorClassFinder;
6+
use Rector\Scripts\Finder\RectorSetFilesFinder;
7+
use Rector\Scripts\Resolver\UsedRectorClassResolver;
8+
use Symfony\Component\Console\Input\ArrayInput;
9+
use Symfony\Component\Console\Output\ConsoleOutput;
10+
use Symfony\Component\Console\Style\SymfonyStyle;
11+
12+
require __DIR__ . '/../vendor/autoload.php';
13+
14+
// 1. find all rector rules in doctrine, phpunit and symfony packages; core rules are not package-version bound
15+
$rectorClassFinder = new RectorClassFinder();
16+
17+
$rectorClasses = $rectorClassFinder->find([
18+
__DIR__ . '/../vendor/rector/rector-doctrine',
19+
__DIR__ . '/../vendor/rector/rector-phpunit',
20+
__DIR__ . '/../vendor/rector/rector-symfony',
21+
]);
22+
23+
$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput());
24+
$symfonyStyle->writeln(sprintf('<fg=green>Found Rector %d rules</>', count($rectorClasses)));
25+
26+
// 2. find "composer-based.php" sets, that bind rules to the installed package version
27+
$rectorSetFilesFinder = new RectorSetFilesFinder();
28+
29+
$rectorSetFiles = $rectorSetFilesFinder->find([
30+
__DIR__ . '/../vendor/rector/rector-symfony/config/sets',
31+
__DIR__ . '/../vendor/rector/rector-doctrine/config/sets',
32+
__DIR__ . '/../vendor/rector/rector-phpunit/config/sets',
33+
]);
34+
35+
$composerBasedSetFiles = array_filter(
36+
$rectorSetFiles,
37+
static fn (string $rectorSetFile): bool => basename($rectorSetFile) === 'composer-based.php'
38+
);
39+
40+
$symfonyStyle->writeln(sprintf('<fg=green>Found %d composer-based sets</>', count($composerBasedSetFiles)));
41+
$symfonyStyle->listing($composerBasedSetFiles);
42+
43+
$usedRectorClassResolver = new UsedRectorClassResolver();
44+
$usedRectorRules = $usedRectorClassResolver->resolve($composerBasedSetFiles);
45+
46+
$symfonyStyle->writeln(
47+
sprintf('<fg=yellow>Found %d Rector rules used in composer-based sets</>', count($usedRectorRules))
48+
);
49+
50+
// these rules are not bound to any package version, so they never belong to a composer-based set
51+
$versionAgnosticSetFileNames = ['code-quality.php', 'typed-collections.php', 'typed-collections-docblocks.php'];
52+
53+
$versionAgnosticSetFiles = array_filter($rectorSetFiles, static fn (string $rectorSetFile): bool => array_any($versionAgnosticSetFileNames, fn (string $versionAgnosticSetFileName): bool => str_ends_with(basename($rectorSetFile), $versionAgnosticSetFileName)));
54+
55+
$versionAgnosticRectorRules = $usedRectorClassResolver->resolve($versionAgnosticSetFiles);
56+
57+
$symfonyStyle->writeln(
58+
sprintf(
59+
'<fg=yellow>Found %d Rector rules used in version-agnostic sets</>',
60+
count($versionAgnosticRectorRules)
61+
)
62+
);
63+
64+
$nonComposerBasedRectorRules = array_diff($rectorClasses, $usedRectorRules, $versionAgnosticRectorRules);
65+
66+
$symfonyStyle->newLine();
67+
$symfonyStyle->listing($nonComposerBasedRectorRules);
68+
69+
$symfonyStyle->writeln(
70+
sprintf(
71+
'<fg=yellow;options=bold>Found %d Rector rules not in any composer-based set, likely dead</>',
72+
count($nonComposerBasedRectorRules)
73+
)
74+
);
75+
$symfonyStyle->newLine();

scripts/list-unused-rules.php

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
declare(strict_types=1);
44

5-
use Rector\Bridge\SetRectorsResolver;
65
use Rector\Contract\Rector\ConfigurableRectorInterface;
76
use Rector\Scripts\Finder\RectorClassFinder;
87
use Rector\Scripts\Finder\RectorSetFilesFinder;
8+
use Rector\Scripts\Resolver\UsedRectorClassResolver;
99
use Symfony\Component\Console\Input\ArrayInput;
1010
use Symfony\Component\Console\Output\ConsoleOutput;
1111
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -64,25 +64,3 @@
6464
sprintf('<fg=green>Skipped %d configurable Rector rules</>', count($configurableRectorRules))
6565
);
6666
$symfonyStyle->newLine();
67-
68-
final class UsedRectorClassResolver
69-
{
70-
/**
71-
* @param string[] $rectorSetFiles
72-
* @return string[]
73-
*/
74-
public function resolve(array $rectorSetFiles): array
75-
{
76-
$setRectorsResolver = new SetRectorsResolver();
77-
$rulesConfiguration = $setRectorsResolver->resolveFromFilePathsIncludingConfiguration($rectorSetFiles);
78-
79-
$usedRectorRules = [];
80-
foreach ($rulesConfiguration as $ruleConfiguration) {
81-
$usedRectorRules[] = is_string($ruleConfiguration) ? $ruleConfiguration : array_keys($ruleConfiguration)[0];
82-
}
83-
84-
sort($usedRectorRules);
85-
86-
return array_unique($usedRectorRules);
87-
}
88-
}

scripts/src/Finder/RectorClassFinder.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Rector\Scripts\Finder;
66

7+
use Deprecated;
78
use Nette\Loaders\RobotLoader;
89
use Rector\Configuration\Deprecation\Contract\DeprecatedInterface;
910
use ReflectionClass;
@@ -39,9 +40,30 @@ public function find(array $dirs): array
3940
continue;
4041
}
4142

43+
if ($this->isDeprecated($rectorClassReflection)) {
44+
continue;
45+
}
46+
4247
$usableRectorClasses[] = $rectorClass;
4348
}
4449

4550
return $usableRectorClasses;
4651
}
52+
53+
/**
54+
* @param ReflectionClass<object> $reflectionClass
55+
*/
56+
private function isDeprecated(ReflectionClass $reflectionClass): bool
57+
{
58+
if ($reflectionClass->getAttributes(Deprecated::class) !== []) {
59+
return true;
60+
}
61+
62+
$docComment = $reflectionClass->getDocComment();
63+
if (! is_string($docComment)) {
64+
return false;
65+
}
66+
67+
return str_contains($docComment, '@deprecated');
68+
}
4769
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Scripts\Resolver;
6+
7+
use Rector\Bridge\SetRectorsResolver;
8+
9+
final class UsedRectorClassResolver
10+
{
11+
/**
12+
* @param string[] $rectorSetFiles
13+
* @return string[]
14+
*/
15+
public function resolve(array $rectorSetFiles): array
16+
{
17+
$setRectorsResolver = new SetRectorsResolver();
18+
$rulesConfiguration = $setRectorsResolver->resolveFromFilePathsIncludingConfiguration($rectorSetFiles);
19+
20+
$usedRectorRules = [];
21+
foreach ($rulesConfiguration as $ruleConfiguration) {
22+
$usedRectorRules[] = is_string($ruleConfiguration) ? $ruleConfiguration : array_keys($ruleConfiguration)[0];
23+
}
24+
25+
sort($usedRectorRules);
26+
27+
return array_unique($usedRectorRules);
28+
}
29+
}

src/Configuration/RectorConfigBuilder.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,14 @@ public function withComposerBased(
754754
$this->sets[] = PHPUnitSetList::COMPOSER_BASED;
755755
}
756756

757+
if ($doctrine) {
758+
// $this->sets[] = DoctrineSetList::COMPOSER_BASED;
759+
}
760+
761+
if ($twig) {
762+
// $this->sets[] = TwigSetList::COMPOSER_BASED;
763+
}
764+
757765
if ($symfony) {
758766
// single set, as every rule inside is bound to the installed Symfony package version on its own
759767
$this->sets[] = SymfonySetList::COMPOSER_BASED;

0 commit comments

Comments
 (0)