diff --git a/bin/rector.php b/bin/rector.php index ef8161e1ffa..532970222d8 100755 --- a/bin/rector.php +++ b/bin/rector.php @@ -147,6 +147,7 @@ public function loadIfExistsAndNotLoadedYet(string $filePath): void do { $errors[] = $throwable->getMessage(); } while ($throwable = $throwable->getPrevious()); + echo Json::encode([ 'fatal_errors' => $errors, ]); diff --git a/src/Configuration/OnlyRuleResolver.php b/src/Configuration/OnlyRuleResolver.php index 69204dd1c51..6234183c8b2 100644 --- a/src/Configuration/OnlyRuleResolver.php +++ b/src/Configuration/OnlyRuleResolver.php @@ -7,6 +7,7 @@ use Rector\Contract\Rector\RectorInterface; use Rector\Exception\Configuration\RectorRuleNameAmbiguousException; use Rector\Exception\Configuration\RectorRuleNotFoundException; +use ReflectionClass; /** * @see \Rector\Tests\Configuration\OnlyRuleResolverTest @@ -106,7 +107,7 @@ private function isRectorRuleClass(string $className): bool return false; } - $reflectionClass = new \ReflectionClass($className); + $reflectionClass = new ReflectionClass($className); if ($reflectionClass->isAbstract()) { return false; } diff --git a/src/Configuration/Option.php b/src/Configuration/Option.php index 5c3025fc8d9..2b4e1a3162c 100644 --- a/src/Configuration/Option.php +++ b/src/Configuration/Option.php @@ -45,6 +45,14 @@ final class Option */ public const string POLYFILL_PACKAGES = 'polyfill_packages'; + /** + * PHP version explicitly picked in withPhpSets(), e.g. withPhpSets(php82: true). + * Polyfilled rules above this version are skipped, as the version is an intended ceiling. + * + * @internal + */ + public const string POLYFILL_CEILING_PHP_VERSION = 'polyfill_ceiling_php_version'; + /** * @internal Use @see \Rector\Config\RectorConfig::importNames() instead * @var string diff --git a/src/Configuration/RectorConfigBuilder.php b/src/Configuration/RectorConfigBuilder.php index cf520b64fbb..b6424132a97 100644 --- a/src/Configuration/RectorConfigBuilder.php +++ b/src/Configuration/RectorConfigBuilder.php @@ -191,6 +191,8 @@ final class RectorConfigBuilder private ?bool $isWithPhpLevelUsed = null; + private ?int $pickedPhpSetsVersion = null; + /** * @var array,bool> */ @@ -221,6 +223,13 @@ public function __invoke(RectorConfig $rectorConfig): void $this->sets[] = SetList::PHP_POLYFILLS; } + if ($this->pickedPhpSetsVersion !== null) { + SimpleParameterProvider::setParameter( + Option::POLYFILL_CEILING_PHP_VERSION, + $this->pickedPhpSetsVersion + ); + } + // merge sets together $this->sets = array_merge($this->sets, $this->groupLoadedSets); @@ -601,6 +610,9 @@ public function withPhpSets( return $this->addPhpLevelSets(ComposerJsonPhpVersionResolver::resolveFromCwdOrFail()); } + // explicitly picked version is a ceiling, even for polyfilled rules + $this->pickedPhpSetsVersion = $pickedPhpVersions[0]; + return $this->addPhpLevelSets($pickedPhpVersions[0]); } diff --git a/src/VersionBonding/PhpVersionedFilter.php b/src/VersionBonding/PhpVersionedFilter.php index 9bd904408de..478812b2037 100644 --- a/src/VersionBonding/PhpVersionedFilter.php +++ b/src/VersionBonding/PhpVersionedFilter.php @@ -4,6 +4,8 @@ namespace Rector\VersionBonding; +use Rector\Configuration\Option; +use Rector\Configuration\Parameter\SimpleParameterProvider; use Rector\Contract\Rector\RectorInterface; use Rector\Php\PhpVersionProvider; use Rector\Php\PolyfillPackagesProvider; @@ -28,10 +30,13 @@ public function __construct( public function filter(array $rectors): array { $minProjectPhpVersion = $this->phpVersionProvider->provide(); + $ceilingPhpVersion = $this->resolveCeilingPhpVersion(); $activeRectors = []; foreach ($rectors as $rector) { - if ($rector instanceof RelatedPolyfillInterface) { + // polyfill package can raise the rule above the project PHP version, + // but never above an explicitly picked withPhpSets() version + if ($rector instanceof RelatedPolyfillInterface && $ceilingPhpVersion === null) { $polyfillPackageNames = $this->polyfillPackagesProvider->provide(); if (in_array($rector->providePolyfillPackage(), $polyfillPackageNames, true)) { @@ -45,12 +50,30 @@ public function filter(array $rectors): array continue; } + $maxPhpVersion = $rector instanceof RelatedPolyfillInterface && $ceilingPhpVersion !== null + ? $ceilingPhpVersion + : $minProjectPhpVersion; + // does satisfy version? → include - if ($rector->provideMinPhpVersion() <= $minProjectPhpVersion) { + if ($rector->provideMinPhpVersion() <= $maxPhpVersion) { $activeRectors[] = $rector; } } return $activeRectors; } + + private function resolveCeilingPhpVersion(): ?int + { + if (! SimpleParameterProvider::hasParameter(Option::POLYFILL_CEILING_PHP_VERSION)) { + return null; + } + + $ceilingPhpVersion = SimpleParameterProvider::provideIntParameter(Option::POLYFILL_CEILING_PHP_VERSION); + if ($ceilingPhpVersion <= 0) { + return null; + } + + return $ceilingPhpVersion; + } } diff --git a/tests/Configuration/OnlyRuleResolverTest.php b/tests/Configuration/OnlyRuleResolverTest.php index 303fc83e238..3406daeef56 100644 --- a/tests/Configuration/OnlyRuleResolverTest.php +++ b/tests/Configuration/OnlyRuleResolverTest.php @@ -4,7 +4,6 @@ namespace Rector\Tests\Configuration; -use PHPStan\Reflection\ReflectionProvider; use Rector\Configuration\OnlyRuleResolver; use Rector\Contract\Rector\RectorInterface; use Rector\DeadCode\Rector\Assign\RemoveDoubleAssignRector; diff --git a/tests/VersionBonding/Fixture/PolyfillPhp83Rector.php b/tests/VersionBonding/Fixture/PolyfillPhp83Rector.php new file mode 100644 index 00000000000..0cd2a169709 --- /dev/null +++ b/tests/VersionBonding/Fixture/PolyfillPhp83Rector.php @@ -0,0 +1,41 @@ +phpVersionedFilter = new PhpVersionedFilter($phpVersionProvider, $polyfillPackagesProvider); + + SimpleParameterProvider::setParameter(Option::POLYFILL_PACKAGES, [PolyfillPackage::PHP_83]); + } + + protected function tearDown(): void + { + SimpleParameterProvider::setParameter(Option::POLYFILL_PACKAGES, []); + SimpleParameterProvider::setParameter(Option::POLYFILL_CEILING_PHP_VERSION, 0); } public function testRectorWithoutInterfaceIsIncluded(): void @@ -30,4 +43,32 @@ public function testRectorWithoutInterfaceIsIncluded(): void $this->assertCount(1, $filtered); $this->assertSame($noInterfaceRector, $filtered[0]); } + + public function testPolyfilledRectorIsIncluded(): void + { + $polyfillPhp83Rector = new PolyfillPhp83Rector(); + $filtered = $this->phpVersionedFilter->filter([$polyfillPhp83Rector]); + + $this->assertCount(1, $filtered); + $this->assertSame($polyfillPhp83Rector, $filtered[0]); + } + + public function testPolyfilledRectorAbovePickedPhpSetsVersionIsSkipped(): void + { + SimpleParameterProvider::setParameter(Option::POLYFILL_CEILING_PHP_VERSION, PhpVersion::PHP_82); + + $filtered = $this->phpVersionedFilter->filter([new PolyfillPhp83Rector()]); + $this->assertCount(0, $filtered); + } + + public function testPolyfilledRectorBelowPickedPhpSetsVersionIsIncluded(): void + { + SimpleParameterProvider::setParameter(Option::POLYFILL_CEILING_PHP_VERSION, PhpVersion::PHP_83); + + $polyfillPhp83Rector = new PolyfillPhp83Rector(); + $filtered = $this->phpVersionedFilter->filter([$polyfillPhp83Rector]); + + $this->assertCount(1, $filtered); + $this->assertSame($polyfillPhp83Rector, $filtered[0]); + } }