Skip to content

Commit 0aad7d0

Browse files
authored
[internal] Add list unused rules command (#7543)
* [internal] Add list unused rules command * remove --only option from list-rules command, as goal is to show all rules
1 parent 822bf37 commit 0aad7d0

5 files changed

Lines changed: 133 additions & 15 deletions

File tree

bin/list-unused-rules.php

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Loaders\RobotLoader;
6+
use Rector\Bridge\SetRectorsResolver;
7+
use Symfony\Component\Console\Input\ArrayInput;
8+
use Symfony\Component\Console\Output\ConsoleOutput;
9+
use Symfony\Component\Console\Style\SymfonyStyle;
10+
use Symfony\Component\Finder\Finder;
11+
use Symfony\Component\Finder\SplFileInfo;
12+
use Webmozart\Assert\Assert;
13+
14+
require __DIR__ . '/../vendor/autoload.php';
15+
16+
// 1. find all rector rules in here and in all vendor/rector dirs
17+
$rectorClassFinder = new RectorClassFinder();
18+
19+
$rectorClasses = $rectorClassFinder->find([
20+
__DIR__ . '/../rules',
21+
__DIR__ . '/../vendor/rector/rector-doctrine',
22+
__DIR__ . '/../vendor/rector/rector-phpunit',
23+
__DIR__ . '/../vendor/rector/rector-symfony',
24+
__DIR__ . '/../vendor/rector/rector-downgrade-php',
25+
]);
26+
27+
$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput());
28+
$symfonyStyle->writeln(sprintf('<fg=green>Found Rector %d rules</>', count($rectorClasses)));
29+
30+
$rectorSeFinder = new RectorSetFinder();
31+
32+
$rectorSetFiles = $rectorSeFinder->find([
33+
__DIR__ . '/../config/set',
34+
__DIR__ . '/../vendor/rector/rector-symfony/config/sets',
35+
__DIR__ . '/../vendor/rector/rector-doctrine/config/sets',
36+
__DIR__ . '/../vendor/rector/rector-phpunit/config/sets',
37+
__DIR__ . '/../vendor/rector/rector-downgrade-php/config/set',
38+
]);
39+
40+
$symfonyStyle->writeln(sprintf('<fg=green>Found %d sets</>', count($rectorSetFiles)));
41+
42+
$usedRectorClassResolver = new UsedRectorClassResolver();
43+
$usedRectorRules = $usedRectorClassResolver->resolve($rectorSetFiles);
44+
45+
$symfonyStyle->newLine();
46+
$symfonyStyle->writeln(sprintf('<fg=yellow>Found %d used Rector rules in sets</>', count($usedRectorRules)));
47+
48+
$unusedRectorRules = array_diff($rectorClasses, $usedRectorRules);
49+
$symfonyStyle->writeln(
50+
sprintf('<fg=yellow;options=bold>Found %d Rector rules not in any set</>', count($unusedRectorRules))
51+
);
52+
53+
$symfonyStyle->newLine();
54+
$symfonyStyle->listing($unusedRectorRules);
55+
56+
final class RectorClassFinder
57+
{
58+
/**
59+
* @param string[] $dirs
60+
* @return string[]
61+
*/
62+
public function find(array $dirs): array
63+
{
64+
$robotLoader = new RobotLoader();
65+
$robotLoader->acceptFiles = ['*Rector.php'];
66+
$robotLoader->addDirectory(...$dirs);
67+
68+
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/rector-rules');
69+
$robotLoader->refresh();
70+
71+
return array_keys($robotLoader->getIndexedClasses());
72+
}
73+
}
74+
75+
final class RectorSetFinder
76+
{
77+
/**
78+
* @param string[] $configDirs
79+
* @return string[]
80+
*/
81+
public function find(array $configDirs): array
82+
{
83+
Assert::allString($configDirs);
84+
Assert::allDirectory($configDirs);
85+
86+
// find set files
87+
$finder = (new Finder())->in($configDirs)
88+
->files()
89+
->name('*.php');
90+
91+
/** @var SplFileInfo[] $setFileInfos */
92+
$setFileInfos = iterator_to_array($finder->getIterator());
93+
94+
$setFiles = [];
95+
foreach ($setFileInfos as $setFileInfo) {
96+
$setFiles[] = $setFileInfo->getRealPath();
97+
}
98+
99+
return $setFiles;
100+
}
101+
}
102+
103+
final class UsedRectorClassResolver
104+
{
105+
/**
106+
* @param string[] $rectorSetFiles
107+
* @return string[]
108+
*/
109+
public function resolve(array $rectorSetFiles): array
110+
{
111+
$setRectorsResolver = new SetRectorsResolver();
112+
$rulesConfiguration = $setRectorsResolver->resolveFromFilePathsIncludingConfiguration($rectorSetFiles);
113+
114+
$usedRectorRules = [];
115+
foreach ($rulesConfiguration as $ruleConfiguration) {
116+
$usedRectorRules[] = is_string($ruleConfiguration) ? $ruleConfiguration : array_keys($ruleConfiguration)[0];
117+
}
118+
119+
sort($usedRectorRules);
120+
121+
return array_unique($usedRectorRules);
122+
}
123+
}

composer-dependency-analyser.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
// ensure use version ^3.2.0
1818
->ignoreErrorsOnPackage('composer/pcre', [ErrorType::UNUSED_DEPENDENCY])
1919

20+
// use din /bin, but only local script
21+
->ignoreErrorsOnPackage('nette/robot-loader', [ErrorType::DEV_DEPENDENCY_IN_PROD])
22+
2023
->ignoreErrorsOnPaths([
2124
__DIR__ . '/stubs',
2225
__DIR__ . '/tests',

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"webmozart/assert": "^1.11"
4343
},
4444
"require-dev": {
45+
"nette/robot-loader": "^4.1",
4546
"php-parallel-lint/php-parallel-lint": "^1.4",
4647
"phpecs/phpecs": "^2.2",
4748
"phpstan/extension-installer": "^1.4",

phpstan.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,7 @@ parameters:
354354
path: src/CustomRules/SimpleNodeDumper.php
355355

356356
- '#Method Rector\\Utils\\Rector\\RemoveRefactorDuplicatedNodeInstanceCheckRector\:\:getInstanceofNodeClass\(\) should return class\-string<PhpParser\\Node>\|null but returns class\-string#'
357+
358+
-
359+
path: bin/list-unused-rules.php
360+
identifier: symplify.multipleClassLikeInFile

src/Console/Command/ListRulesCommand.php

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
use Nette\Utils\Json;
88
use Rector\ChangesReporting\Output\ConsoleOutputFormatter;
9-
use Rector\Configuration\ConfigurationRuleFilter;
10-
use Rector\Configuration\OnlyRuleResolver;
119
use Rector\Configuration\Option;
1210
use Rector\Contract\Rector\RectorInterface;
1311
use Rector\PostRector\Contract\Rector\PostRectorInterface;
@@ -26,8 +24,6 @@ final class ListRulesCommand extends Command
2624
public function __construct(
2725
private readonly SymfonyStyle $symfonyStyle,
2826
private readonly SkippedClassResolver $skippedClassResolver,
29-
private readonly OnlyRuleResolver $onlyRuleResolver,
30-
private readonly ConfigurationRuleFilter $configurationRuleFilter,
3127
private readonly array $rectors
3228
) {
3329
parent::__construct();
@@ -53,12 +49,7 @@ protected function configure(): void
5349

5450
protected function execute(InputInterface $input, OutputInterface $output): int
5551
{
56-
$onlyRule = $input->getOption(Option::ONLY);
57-
if ($onlyRule !== null) {
58-
$onlyRule = $this->onlyRuleResolver->resolve($onlyRule);
59-
}
60-
61-
$rectorClasses = $this->resolveRectorClasses($onlyRule);
52+
$rectorClasses = $this->resolveRectorClasses();
6253

6354
$skippedClasses = $this->getSkippedCheckers();
6455

@@ -90,17 +81,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
9081
/**
9182
* @return array<class-string<RectorInterface>>
9283
*/
93-
private function resolveRectorClasses(?string $onlyRule): array
84+
private function resolveRectorClasses(): array
9485
{
9586
$customRectors = array_filter(
9687
$this->rectors,
9788
static fn (RectorInterface $rector): bool => ! $rector instanceof PostRectorInterface
9889
);
9990

100-
if ($onlyRule !== null) {
101-
$customRectors = $this->configurationRuleFilter->filterOnlyRule($customRectors, $onlyRule);
102-
}
103-
10491
$rectorClasses = array_map(static fn (RectorInterface $rector): string => $rector::class, $customRectors);
10592
sort($rectorClasses);
10693

0 commit comments

Comments
 (0)