Skip to content

Commit

Permalink
[DX] Add oustide any set to help detecting rules that could be part o…
Browse files Browse the repository at this point in the history
…f some set (#3853)
  • Loading branch information
TomasVotruba committed May 14, 2023
1 parent 7f49261 commit c24338f
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 43 deletions.
5 changes: 2 additions & 3 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
use Rector\PSR4\Composer\PSR4NamespaceMatcher;
use Rector\PSR4\Contract\PSR4AutoloadNamespaceMatcherInterface;
use Rector\Utils\Command\MissingInSetCommand;
use Rector\Utils\Command\OutsideAnySetCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Style\SymfonyStyle;
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
Expand Down Expand Up @@ -129,9 +130,6 @@
$rectorConfig->import($extensionConfigFile);
}

// require only in dev
$rectorConfig->import(__DIR__ . '/../utils/compiler/config/config.php', null, 'not_found');

$services->load('Rector\Core\\', __DIR__ . '/../src')
->exclude([
__DIR__ . '/../src/Rector',
Expand Down Expand Up @@ -214,6 +212,7 @@
->factory([service(PHPStanServicesFactory::class), 'createDynamicSourceLocatorProvider']);

$services->set(MissingInSetCommand::class);
$services->set(OutsideAnySetCommand::class);

// phpdoc parser
$services->set(SmartPhpParser::class)
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ parameters:
paths:
- utils/RuleDocGenerator/PostRectorOutFilter.php
- src/FileSystem/FileAndDirectoryFilter.php
- utils/Finder/RectorClassFinder.php

# phpdoc node traversing
-
Expand Down
44 changes: 4 additions & 40 deletions utils/Command/MissingInSetCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

namespace Rector\Utils\Command;

use Nette\Loaders\RobotLoader;
use Nette\Utils\FileSystem;
use Nette\Utils\Strings;
use Rector\CodeQuality\Rector\ClassConstFetch\ConvertStaticPrivateConstantToSelfRector;
use Rector\CodeQuality\Rector\ClassMethod\NarrowUnionTypeDocRector;
Expand All @@ -19,6 +17,8 @@
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector;
use Rector\TypeDeclaration\Rector\Ternary\FlipNegatedTernaryInstanceofRector;
use Rector\TypeDeclaration\Rector\While_\WhileNullableToInstanceofRector;
use Rector\Utils\Finder\RectorClassFinder;
use Rector\Utils\Finder\SetRectorClassesResolver;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
Expand Down Expand Up @@ -58,12 +58,6 @@ final class MissingInSetCommand extends Command
NarrowUnionTypeDocRector::class,
];

/**
* @see https://regex101.com/r/HtsmKC/1
* @var string
*/
private const RECTOR_CLASS_REGEX = '#use (?<class_name>[\\\\\w]+Rector)#m';

public function __construct(
private readonly SymfonyStyle $symfonyStyle
) {
Expand All @@ -81,8 +75,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$hasError = false;

foreach (self::RULES_DIRECTORY_TO_SET_CONFIG as $rulesDirectory => $setFile) {
$classesInSetFile = $this->resolveClassesFromSetFiles($setFile);
$existingRectorClasses = $this->findRectorClasses($rulesDirectory);
$classesInSetFile = SetRectorClassesResolver::resolve($setFile);
$existingRectorClasses = RectorClassFinder::find([$rulesDirectory]);

$rectorClassesNotInSetConfig = array_diff($existingRectorClasses, $classesInSetFile);

Expand Down Expand Up @@ -125,34 +119,4 @@ static function (string $rectorClass): bool {

return self::SUCCESS;
}

/**
* @return string[]
*/
private function resolveClassesFromSetFiles(string $setFile): array
{
$rectorClasses = [];

$setFileContents = FileSystem::read($setFile);
$matches = Strings::matchAll($setFileContents, self::RECTOR_CLASS_REGEX);

foreach ($matches as $match) {
$rectorClasses[] = $match['class_name'];
}

return $rectorClasses;
}

/**
* @return string[]
*/
private function findRectorClasses(string $rulesDirectory): array
{
$robotLoader = new RobotLoader();
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/rector-missing-in-set');
$robotLoader->addDirectory($rulesDirectory);
$robotLoader->rebuild();

return array_keys($robotLoader->getIndexedClasses());
}
}
61 changes: 61 additions & 0 deletions utils/Command/OutsideAnySetCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Rector\Utils\Command;

use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Set\ValueObject\SetList;
use Rector\Utils\Finder\RectorClassFinder;
use Rector\Utils\Finder\SetRectorClassesResolver;
use ReflectionClass;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

final class OutsideAnySetCommand extends Command
{
public function __construct(
private readonly SymfonyStyle $symfonyStyle
) {
parent::__construct();
}

protected function configure(): void
{
$this->setName('outside-any-set');
$this->setDescription('[DEV] Show rules outside any set');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$setDefinedRectorClasses = $this->resolveSetDefinedRectorClasses();
$existingRectorClasses = RectorClassFinder::find([__DIR__ . '/../../rules']);

$rectorClassesOutsideAnySet = array_diff($existingRectorClasses, $setDefinedRectorClasses);

sort($rectorClassesOutsideAnySet);

$this->symfonyStyle->listing($rectorClassesOutsideAnySet);

return self::SUCCESS;
}

/**
* @return array<class-string<RectorInterface>>
*/
private function resolveSetDefinedRectorClasses(): array
{
$setListReflectionClass = new ReflectionClass(SetList::class);

$setDefinedRectorClasses = [];

foreach ($setListReflectionClass->getConstants() as $constantValue) {
$currentSetDefinedRectorClasses = SetRectorClassesResolver::resolve($constantValue);
$setDefinedRectorClasses = array_merge($setDefinedRectorClasses, $currentSetDefinedRectorClasses);
}

return $setDefinedRectorClasses;
}
}
32 changes: 32 additions & 0 deletions utils/Finder/RectorClassFinder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Rector\Utils\Finder;

use Nette\Loaders\RobotLoader;
use Rector\Core\Contract\Rector\RectorInterface;

final class RectorClassFinder
{
/**
* @param string[] $directories
* @return array<class-string<RectorInterface>>
*/
public static function find(array $directories): array
{
$robotLoader = new RobotLoader();
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/rector-missing-in-set');
$robotLoader->addDirectory(...$directories);

$robotLoader->acceptFiles = ['*Rector.php'];
$robotLoader->rebuild();

$filePathsToRectorClasses = array_keys($robotLoader->getIndexedClasses());

return array_filter(
$filePathsToRectorClasses,
static fn (string $rectorClass): bool => is_a($rectorClass, RectorInterface::class, true)
);
}
}
40 changes: 40 additions & 0 deletions utils/Finder/SetRectorClassesResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Rector\Utils\Finder;

use Nette\Utils\FileSystem;
use Nette\Utils\Strings;
use Rector\Core\Contract\Rector\RectorInterface;

final class SetRectorClassesResolver
{
/**
* @see https://regex101.com/r/HtsmKC/1
* @var string
*/
private const RECTOR_CLASS_REGEX = '#use (?<class_name>[\\\\\w]+Rector)#m';

/**
* @return array<class-string<RectorInterface>>
*/
public static function resolve(string $setFile): array
{
$rectorClasses = [];

$setFileContents = FileSystem::read($setFile);
$matches = Strings::matchAll($setFileContents, self::RECTOR_CLASS_REGEX);

foreach ($matches as $match) {
$rectorClassName = $match['class_name'];
if ($rectorClassName === 'Rector\Config\Rector') {
continue;
}

$rectorClasses[] = $rectorClassName;
}

return $rectorClasses;
}
}

0 comments on commit c24338f

Please sign in to comment.