diff --git a/src/Configuration/OnlyRuleResolver.php b/src/Configuration/OnlyRuleResolver.php index 2667ddef002..1232506f083 100644 --- a/src/Configuration/OnlyRuleResolver.php +++ b/src/Configuration/OnlyRuleResolver.php @@ -4,6 +4,7 @@ namespace Rector\Configuration; +use PHPStan\Reflection\ReflectionProvider; use Rector\Contract\Rector\RectorInterface; use Rector\Exception\Configuration\RectorRuleNameAmbiguousException; use Rector\Exception\Configuration\RectorRuleNotFoundException; @@ -17,7 +18,8 @@ * @param RectorInterface[] $rectors */ public function __construct( - private array $rectors + private array $rectors, + private ReflectionProvider $reflectionProvider ) { } @@ -82,6 +84,11 @@ public function resolve(string $rule): string PHP_EOL ); } else { + // the rule class exists, it is just missing in the config + if ($this->isRectorRuleClass($rule)) { + throw new RectorRuleNotFoundException($this->createUnregisteredMessage($rule)); + } + $message = sprintf( 'Rule "%s" was not found.%sMake sure it is registered in your config or in one of the sets', $rule, @@ -91,4 +98,34 @@ public function resolve(string $rule): string throw new RectorRuleNotFoundException($message); } + + /** + * Is this an existing rule class, that is just not registered in the config? + */ + private function isRectorRuleClass(string $className): bool + { + if (! $this->reflectionProvider->hasClass($className)) { + return false; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if ($classReflection->isAbstract()) { + return false; + } + + return $classReflection->implementsInterface(RectorInterface::class); + } + + private function createUnregisteredMessage(string $ruleClass): string + { + $shortRuleClass = substr((string) strrchr($ruleClass, '\\'), 1); + + return sprintf( + 'Rule "%s" exists, but is not registered in your Rector config.%sRegister it in your rector.php:' + . PHP_EOL . PHP_EOL . ' ->withRules([%s::class])', + $ruleClass, + PHP_EOL, + $shortRuleClass + ); + } } diff --git a/tests/Configuration/OnlyRuleResolverTest.php b/tests/Configuration/OnlyRuleResolverTest.php index 8af2df9fd26..e2fdcedea04 100644 --- a/tests/Configuration/OnlyRuleResolverTest.php +++ b/tests/Configuration/OnlyRuleResolverTest.php @@ -4,6 +4,7 @@ namespace Rector\Tests\Configuration; +use PHPStan\Reflection\ReflectionProvider; use Rector\Configuration\OnlyRuleResolver; use Rector\Contract\Rector\RectorInterface; use Rector\DeadCode\Rector\Assign\RemoveDoubleAssignRector; @@ -11,6 +12,7 @@ use Rector\Exception\Configuration\RectorRuleNameAmbiguousException; use Rector\Exception\Configuration\RectorRuleNotFoundException; use Rector\Testing\PHPUnit\AbstractLazyTestCase; +use Rector\TypeDeclaration\Rector\StmtsAwareInterface\SafeDeclareStrictTypesRector; final class OnlyRuleResolverTest extends AbstractLazyTestCase { @@ -23,9 +25,10 @@ protected function setUp(): void $this->bootFromConfigFiles([__DIR__ . '/config/only_rule_resolver_config.php']); $rectorConfig = self::getContainer(); - $this->onlyRuleResolver = new OnlyRuleResolver(iterator_to_array( - $rectorConfig->tagged(RectorInterface::class) - )); + $this->onlyRuleResolver = new OnlyRuleResolver( + iterator_to_array($rectorConfig->tagged(RectorInterface::class)), + $this->make(ReflectionProvider::class) + ); } public function testResolveOk(): void @@ -109,6 +112,18 @@ public function testResolveShortOkTwoLevels(): void ); } + public function testResolveExistingButNotRegistered(): void + { + $this->expectExceptionMessageIsOrContains( + 'Rule "Rector\TypeDeclaration\Rector\StmtsAwareInterface\SafeDeclareStrictTypesRector" exists, but is not registered in your Rector config.' + . PHP_EOL . 'Register it in your rector.php:' . PHP_EOL . PHP_EOL + . ' ->withRules([SafeDeclareStrictTypesRector::class])' + ); + $this->expectException(RectorRuleNotFoundException::class); + + $this->onlyRuleResolver->resolve(SafeDeclareStrictTypesRector::class); + } + public function testResolveShortAmbiguous(): void { $this->expectExceptionMessageIsOrContains(