From 0e5a8cb90353b87f4cad509e8eca1a6451087455 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 31 Jul 2026 19:42:37 +0200 Subject: [PATCH] [Config] Add ruleWithConfigurationComposerVersionBound() to register configuration bound to installed package version --- src/Config/RectorConfig.php | 37 ++++++++++++++++++++++ tests/Config/RectorConfigTest.php | 52 +++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/src/Config/RectorConfig.php b/src/Config/RectorConfig.php index c914f347a44..fd70120a55c 100644 --- a/src/Config/RectorConfig.php +++ b/src/Config/RectorConfig.php @@ -4,10 +4,12 @@ namespace Rector\Config; +use Composer\Semver\Semver; use Illuminate\Container\Container; use Override; use Rector\Caching\Contract\CacheMetaExtensionInterface; use Rector\Caching\Contract\ValueObject\Storage\CacheStorageInterface; +use Rector\Composer\InstalledPackageResolver; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; use Rector\Configuration\RectorConfigBuilder; @@ -42,6 +44,8 @@ final class RectorConfig extends Container */ private array $autotagInterfaces = [Command::class, ResettableInterface::class]; + private ?InstalledPackageResolver $installedPackageResolver = null; + private static ?bool $recreated = null; public static function configure(): RectorConfigBuilder @@ -195,6 +199,32 @@ public function ruleWithConfiguration(string $rectorClass, array $configuration) SimpleParameterProvider::addParameter(Option::REGISTERED_RECTOR_RULES, $rectorClass); } + /** + * Register the rule configuration only if the package version installed in the analysed project satisfies + * the version constraint. Useful for configuration valid since a specific package version, + * e.g. an attribute added in PHPUnit 11. + * + * @param class-string $rectorClass + * @param mixed[] $configuration + */ + public function ruleWithConfigurationComposerVersionBound( + string $rectorClass, + array $configuration, + string $packageName, + string $versionConstraint + ): void { + $packageVersion = $this->resolveInstalledPackageVersion($packageName); + if ($packageVersion === null) { + return; + } + + if (! Semver::satisfies($packageVersion, $versionConstraint)) { + return; + } + + $this->ruleWithConfiguration($rectorClass, $configuration); + } + /** * @param class-string $rectorClass */ @@ -488,4 +518,11 @@ public function setOverflowLevels(array $levelOverflows): void { SimpleParameterProvider::addParameter(Option::LEVEL_OVERFLOWS, $levelOverflows); } + + private function resolveInstalledPackageVersion(string $packageName): ?string + { + $this->installedPackageResolver ??= new InstalledPackageResolver(); + + return $this->installedPackageResolver->resolvePackageVersion($packageName); + } } diff --git a/tests/Config/RectorConfigTest.php b/tests/Config/RectorConfigTest.php index cdee28e9358..ac0d3a8ca85 100644 --- a/tests/Config/RectorConfigTest.php +++ b/tests/Config/RectorConfigTest.php @@ -7,6 +7,11 @@ use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; +use Rector\Renaming\Rector\MethodCall\RenameMethodRector; +use Rector\Renaming\Rector\Name\RenameClassRector; +use Rector\Renaming\Rector\PropertyFetch\RenamePropertyRector; +use Rector\Renaming\ValueObject\MethodCallRename; +use Rector\Renaming\ValueObject\RenameProperty; use Rector\Symfony\Set\TwigSetList; use Rector\Testing\PHPUnit\AbstractLazyTestCase; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromReturnNewRector; @@ -29,4 +34,51 @@ public function test(): void // only collect root withRules() $this->assertCount(1, SimpleParameterProvider::provideArrayParameter(Option::ROOT_STANDALONE_REGISTERED_RULES)); } + + public function testRuleWithConfigurationComposerVersionBoundOnSatisfiedConstraint(): void + { + $rectorConfig = $this->getContainer(); + + $rectorConfig->ruleWithConfigurationComposerVersionBound( + RenameClassRector::class, + [ + 'SomeOldClass' => 'SomeNewClass', + ], + 'phpunit/phpunit', + '>=9.0' + ); + + $registeredRectorRules = SimpleParameterProvider::provideArrayParameter(Option::REGISTERED_RECTOR_RULES); + $this->assertContains(RenameClassRector::class, $registeredRectorRules); + } + + public function testRuleWithConfigurationComposerVersionBoundOnUnsatisfiedConstraint(): void + { + $rectorConfig = $this->getContainer(); + + $rectorConfig->ruleWithConfigurationComposerVersionBound( + RenameMethodRector::class, + [new MethodCallRename('SomeClass', 'oldMethod', 'newMethod')], + 'phpunit/phpunit', + '<9.0' + ); + + $registeredRectorRules = SimpleParameterProvider::provideArrayParameter(Option::REGISTERED_RECTOR_RULES); + $this->assertNotContains(RenameMethodRector::class, $registeredRectorRules); + } + + public function testRuleWithConfigurationComposerVersionBoundOnMissingPackage(): void + { + $rectorConfig = $this->getContainer(); + + $rectorConfig->ruleWithConfigurationComposerVersionBound( + RenamePropertyRector::class, + [new RenameProperty('SomeClass', 'oldProperty', 'newProperty')], + 'not-installed/package', + '>=1.0' + ); + + $registeredRectorRules = SimpleParameterProvider::provideArrayParameter(Option::REGISTERED_RECTOR_RULES); + $this->assertNotContains(RenamePropertyRector::class, $registeredRectorRules); + } }