Skip to content

Commit 65cf2be

Browse files
[VersionBonding] Cap polyfill rules by explicitly picked withPhpSets() version (#8315)
Co-authored-by: GitHub Action <actions@github.com>
1 parent a889536 commit 65cf2be

8 files changed

Lines changed: 130 additions & 4 deletions

File tree

‎bin/rector.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ public function loadIfExistsAndNotLoadedYet(string $filePath): void
147147
do {
148148
$errors[] = $throwable->getMessage();
149149
} while ($throwable = $throwable->getPrevious());
150+
150151
echo Json::encode([
151152
'fatal_errors' => $errors,
152153
]);

‎src/Configuration/OnlyRuleResolver.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Rector\Contract\Rector\RectorInterface;
88
use Rector\Exception\Configuration\RectorRuleNameAmbiguousException;
99
use Rector\Exception\Configuration\RectorRuleNotFoundException;
10+
use ReflectionClass;
1011

1112
/**
1213
* @see \Rector\Tests\Configuration\OnlyRuleResolverTest
@@ -106,7 +107,7 @@ private function isRectorRuleClass(string $className): bool
106107
return false;
107108
}
108109

109-
$reflectionClass = new \ReflectionClass($className);
110+
$reflectionClass = new ReflectionClass($className);
110111
if ($reflectionClass->isAbstract()) {
111112
return false;
112113
}

‎src/Configuration/Option.php‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ final class Option
4545
*/
4646
public const string POLYFILL_PACKAGES = 'polyfill_packages';
4747

48+
/**
49+
* PHP version explicitly picked in withPhpSets(), e.g. withPhpSets(php82: true).
50+
* Polyfilled rules above this version are skipped, as the version is an intended ceiling.
51+
*
52+
* @internal
53+
*/
54+
public const string POLYFILL_CEILING_PHP_VERSION = 'polyfill_ceiling_php_version';
55+
4856
/**
4957
* @internal Use @see \Rector\Config\RectorConfig::importNames() instead
5058
* @var string

‎src/Configuration/RectorConfigBuilder.php‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ final class RectorConfigBuilder
191191

192192
private ?bool $isWithPhpLevelUsed = null;
193193

194+
private ?int $pickedPhpSetsVersion = null;
195+
194196
/**
195197
* @var array<class-string<SetProviderInterface>,bool>
196198
*/
@@ -221,6 +223,13 @@ public function __invoke(RectorConfig $rectorConfig): void
221223
$this->sets[] = SetList::PHP_POLYFILLS;
222224
}
223225

226+
if ($this->pickedPhpSetsVersion !== null) {
227+
SimpleParameterProvider::setParameter(
228+
Option::POLYFILL_CEILING_PHP_VERSION,
229+
$this->pickedPhpSetsVersion
230+
);
231+
}
232+
224233
// merge sets together
225234
$this->sets = array_merge($this->sets, $this->groupLoadedSets);
226235

@@ -601,6 +610,9 @@ public function withPhpSets(
601610
return $this->addPhpLevelSets(ComposerJsonPhpVersionResolver::resolveFromCwdOrFail());
602611
}
603612

613+
// explicitly picked version is a ceiling, even for polyfilled rules
614+
$this->pickedPhpSetsVersion = $pickedPhpVersions[0];
615+
604616
return $this->addPhpLevelSets($pickedPhpVersions[0]);
605617
}
606618

‎src/VersionBonding/PhpVersionedFilter.php‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Rector\VersionBonding;
66

7+
use Rector\Configuration\Option;
8+
use Rector\Configuration\Parameter\SimpleParameterProvider;
79
use Rector\Contract\Rector\RectorInterface;
810
use Rector\Php\PhpVersionProvider;
911
use Rector\Php\PolyfillPackagesProvider;
@@ -28,10 +30,13 @@ public function __construct(
2830
public function filter(array $rectors): array
2931
{
3032
$minProjectPhpVersion = $this->phpVersionProvider->provide();
33+
$ceilingPhpVersion = $this->resolveCeilingPhpVersion();
3134

3235
$activeRectors = [];
3336
foreach ($rectors as $rector) {
34-
if ($rector instanceof RelatedPolyfillInterface) {
37+
// polyfill package can raise the rule above the project PHP version,
38+
// but never above an explicitly picked withPhpSets() version
39+
if ($rector instanceof RelatedPolyfillInterface && $ceilingPhpVersion === null) {
3540
$polyfillPackageNames = $this->polyfillPackagesProvider->provide();
3641

3742
if (in_array($rector->providePolyfillPackage(), $polyfillPackageNames, true)) {
@@ -45,12 +50,30 @@ public function filter(array $rectors): array
4550
continue;
4651
}
4752

53+
$maxPhpVersion = $rector instanceof RelatedPolyfillInterface && $ceilingPhpVersion !== null
54+
? $ceilingPhpVersion
55+
: $minProjectPhpVersion;
56+
4857
// does satisfy version? → include
49-
if ($rector->provideMinPhpVersion() <= $minProjectPhpVersion) {
58+
if ($rector->provideMinPhpVersion() <= $maxPhpVersion) {
5059
$activeRectors[] = $rector;
5160
}
5261
}
5362

5463
return $activeRectors;
5564
}
65+
66+
private function resolveCeilingPhpVersion(): ?int
67+
{
68+
if (! SimpleParameterProvider::hasParameter(Option::POLYFILL_CEILING_PHP_VERSION)) {
69+
return null;
70+
}
71+
72+
$ceilingPhpVersion = SimpleParameterProvider::provideIntParameter(Option::POLYFILL_CEILING_PHP_VERSION);
73+
if ($ceilingPhpVersion <= 0) {
74+
return null;
75+
}
76+
77+
return $ceilingPhpVersion;
78+
}
5679
}

‎tests/Configuration/OnlyRuleResolverTest.php‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace Rector\Tests\Configuration;
66

7-
use PHPStan\Reflection\ReflectionProvider;
87
use Rector\Configuration\OnlyRuleResolver;
98
use Rector\Contract\Rector\RectorInterface;
109
use Rector\DeadCode\Rector\Assign\RemoveDoubleAssignRector;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\VersionBonding\Fixture;
6+
7+
use PhpParser\Node;
8+
use Rector\Rector\AbstractRector;
9+
use Rector\ValueObject\PhpVersion;
10+
use Rector\ValueObject\PolyfillPackage;
11+
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
12+
use Rector\VersionBonding\Contract\RelatedPolyfillInterface;
13+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
14+
15+
final class PolyfillPhp83Rector extends AbstractRector implements MinPhpVersionInterface, RelatedPolyfillInterface
16+
{
17+
public function getRuleDefinition(): RuleDefinition
18+
{
19+
return new RuleDefinition('Test rector bound to PHP 8.3 polyfill', []);
20+
}
21+
22+
public function getNodeTypes(): array
23+
{
24+
return [Node\Stmt\Class_::class];
25+
}
26+
27+
public function refactor(Node $node): ?Node
28+
{
29+
return null;
30+
}
31+
32+
public function provideMinPhpVersion(): int
33+
{
34+
return PhpVersion::PHP_83;
35+
}
36+
37+
public function providePolyfillPackage(): string
38+
{
39+
return PolyfillPackage::PHP_83;
40+
}
41+
}

‎tests/VersionBonding/PhpVersionedFilterTest.php‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
namespace Rector\Tests\VersionBonding;
66

77
use PHPUnit\Framework\TestCase;
8+
use Rector\Configuration\Option;
9+
use Rector\Configuration\Parameter\SimpleParameterProvider;
810
use Rector\Php\PhpVersionProvider;
911
use Rector\Php\PolyfillPackagesProvider;
1012
use Rector\Tests\VersionBonding\Fixture\NoInterfaceRector;
13+
use Rector\Tests\VersionBonding\Fixture\PolyfillPhp83Rector;
14+
use Rector\ValueObject\PhpVersion;
15+
use Rector\ValueObject\PolyfillPackage;
1116
use Rector\VersionBonding\PhpVersionedFilter;
1217

1318
final class PhpVersionedFilterTest extends TestCase
@@ -20,6 +25,14 @@ protected function setUp(): void
2025
$polyfillPackagesProvider = new PolyfillPackagesProvider();
2126

2227
$this->phpVersionedFilter = new PhpVersionedFilter($phpVersionProvider, $polyfillPackagesProvider);
28+
29+
SimpleParameterProvider::setParameter(Option::POLYFILL_PACKAGES, [PolyfillPackage::PHP_83]);
30+
}
31+
32+
protected function tearDown(): void
33+
{
34+
SimpleParameterProvider::setParameter(Option::POLYFILL_PACKAGES, []);
35+
SimpleParameterProvider::setParameter(Option::POLYFILL_CEILING_PHP_VERSION, 0);
2336
}
2437

2538
public function testRectorWithoutInterfaceIsIncluded(): void
@@ -30,4 +43,32 @@ public function testRectorWithoutInterfaceIsIncluded(): void
3043
$this->assertCount(1, $filtered);
3144
$this->assertSame($noInterfaceRector, $filtered[0]);
3245
}
46+
47+
public function testPolyfilledRectorIsIncluded(): void
48+
{
49+
$polyfillPhp83Rector = new PolyfillPhp83Rector();
50+
$filtered = $this->phpVersionedFilter->filter([$polyfillPhp83Rector]);
51+
52+
$this->assertCount(1, $filtered);
53+
$this->assertSame($polyfillPhp83Rector, $filtered[0]);
54+
}
55+
56+
public function testPolyfilledRectorAbovePickedPhpSetsVersionIsSkipped(): void
57+
{
58+
SimpleParameterProvider::setParameter(Option::POLYFILL_CEILING_PHP_VERSION, PhpVersion::PHP_82);
59+
60+
$filtered = $this->phpVersionedFilter->filter([new PolyfillPhp83Rector()]);
61+
$this->assertCount(0, $filtered);
62+
}
63+
64+
public function testPolyfilledRectorBelowPickedPhpSetsVersionIsIncluded(): void
65+
{
66+
SimpleParameterProvider::setParameter(Option::POLYFILL_CEILING_PHP_VERSION, PhpVersion::PHP_83);
67+
68+
$polyfillPhp83Rector = new PolyfillPhp83Rector();
69+
$filtered = $this->phpVersionedFilter->filter([$polyfillPhp83Rector]);
70+
71+
$this->assertCount(1, $filtered);
72+
$this->assertSame($polyfillPhp83Rector, $filtered[0]);
73+
}
3374
}

0 commit comments

Comments
 (0)