Skip to content

Commit 5d2cf96

Browse files
committed
feat: separate version bonding for PHP requirement and deprecations
This will allow us to implement eager deprecation resolution for PHP version ranges. Test cases have been added to make sure the feature works correctly, but also that no rules in the PHP upgrade pipeline are misconfigured in an obvious way.
1 parent 3e92a56 commit 5d2cf96

12 files changed

Lines changed: 382 additions & 7 deletions

src/Config/RectorConfig.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,11 @@ public function treatClassesAsFinal(bool $treatClassesAsFinal = true): void
325325
SimpleParameterProvider::setParameter(Option::TREAT_CLASSES_AS_FINAL, $treatClassesAsFinal);
326326
}
327327

328+
public function eagerlyResolveDeprecations(bool $eagerlyResolveDeprecations = true): void
329+
{
330+
SimpleParameterProvider::setParameter(Option::EAGERLY_RESOLVE_DEPRECATIONS, $eagerlyResolveDeprecations);
331+
}
332+
328333
/**
329334
* @param string[] $extensions
330335
*/

src/Configuration/Option.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ final class Option
229229
*/
230230
public const string TREAT_CLASSES_AS_FINAL = 'treat_classes_as_final';
231231

232+
/**
233+
* @internal Use @see \Rector\Config\RectorConfig::eagerlyResolveDeprecations() method
234+
* @var string
235+
*/
236+
public const string EAGERLY_RESOLVE_DEPRECATIONS = 'eagerly_resolve_deprecations';
237+
232238
/**
233239
* @internal To report composer based loaded sets
234240
* @see \Rector\Configuration\RectorConfigBuilder::withComposerBased()

src/Configuration/RectorConfigBuilder.php

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

158158
private ?bool $isTreatClassesAsFinal = null;
159159

160+
private ?bool $isEagerlyResolvedDeprecations = null;
161+
160162
/**
161163
* @var RegisteredService[]
162164
*/
@@ -381,6 +383,10 @@ public function __invoke(RectorConfig $rectorConfig): void
381383
$rectorConfig->treatClassesAsFinal($this->isTreatClassesAsFinal);
382384
}
383385

386+
if ($this->isEagerlyResolvedDeprecations !== null) {
387+
$rectorConfig->eagerlyResolveDeprecations($this->isEagerlyResolvedDeprecations);
388+
}
389+
384390
if ($this->reportingRealPath !== null) {
385391
$rectorConfig->reportingRealPath($this->reportingRealPath);
386392
}
@@ -1215,6 +1221,12 @@ public function withTreatClassesAsFinal(bool $isTreatClassesAsFinal = true): sel
12151221
return $this;
12161222
}
12171223

1224+
public function withEagerlyResolvedDeprecations(bool $isEagerlyResolvedDeprecations = true): self
1225+
{
1226+
$this->isEagerlyResolvedDeprecations = $isEagerlyResolvedDeprecations;
1227+
return $this;
1228+
}
1229+
12181230
public function registerService(string $className, ?string $alias = null, ?string $tag = null): self
12191231
{
12201232
// BC layer since 2.2.9

src/Testing/PHPUnit/AbstractRectorTestCase.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public static function tearDownAfterClass(): void
6363
SimpleParameterProvider::setParameter(Option::NEW_LINE_ON_FLUENT_CALL, false);
6464

6565
SimpleParameterProvider::setParameter(Option::TREAT_CLASSES_AS_FINAL, false);
66+
67+
SimpleParameterProvider::setParameter(Option::EAGERLY_RESOLVE_DEPRECATIONS, false);
6668
}
6769

6870
protected function setUp(): void
@@ -182,8 +184,8 @@ protected function doTestFileExpectingWarningAboutRuleApplied(
182184
$testClass = static::class;
183185
$this->assertSame(
184186
PHP_EOL . 'WARNING: On fixture file "' . $fixtureName . '" for test "' . $testClass . '"' . PHP_EOL .
185-
'File not changed but some Rector rules applied:' . PHP_EOL .
186-
' * ' . $expectedRuleApplied . PHP_EOL,
187+
'File not changed but some Rector rules applied:' . PHP_EOL .
188+
' * ' . $expectedRuleApplied . PHP_EOL,
187189
$content
188190
);
189191
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\VersionBonding\Contract;
6+
7+
use Rector\ValueObject\PhpVersion;
8+
9+
/**
10+
* Can be implemented by @see \Rector\Contract\Rector\RectorInterface
11+
*
12+
* Rules that resolve deprecations can expose the PHP version where the
13+
* deprecation starts.
14+
*
15+
* Rules that do not meet this PHP version will be skipped when
16+
* Option::EAGERLY_RESOLVE_DEPRECATIONS is false (default).
17+
*
18+
* @see \Rector\VersionBonding\Contract\MinPhpVersionInterface
19+
*/
20+
interface DeprecatedAtVersionInterface
21+
{
22+
/**
23+
* @return PhpVersion::*
24+
*/
25+
public function provideDeprecatedAtVersion(): int;
26+
}

src/VersionBonding/Contract/MinPhpVersionInterface.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
* Can be implemented by @see \Rector\Contract\Rector\RectorInterface
1111
*
1212
* Rules that do not meet this PHP version will be skipped.
13+
*
14+
* Rules that also implement @see Rector\VersionBonding\Contract\DeprecatedAtVersionInterface
15+
* will prioritize the deprecation version over the minimum PHP version when
16+
* Option::EAGERLY_RESOLVE_DEPRECATIONS is false (default).
1317
*/
1418
interface MinPhpVersionInterface
1519
{

src/VersionBonding/PhpVersionedFilter.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
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;
12+
use Rector\VersionBonding\Contract\DeprecatedAtVersionInterface;
1013
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
1114
use Rector\VersionBonding\Contract\RelatedPolyfillInterface;
1215

@@ -28,6 +31,10 @@ public function __construct(
2831
public function filter(array $rectors): array
2932
{
3033
$minProjectPhpVersion = $this->phpVersionProvider->provide();
34+
$isEagerlyResolveDeprecations = SimpleParameterProvider::provideBoolParameter(
35+
Option::EAGERLY_RESOLVE_DEPRECATIONS,
36+
false
37+
);
3138

3239
$activeRectors = [];
3340
foreach ($rectors as $rector) {
@@ -40,13 +47,27 @@ public function filter(array $rectors): array
4047
}
4148
}
4249

43-
if (! $rector instanceof MinPhpVersionInterface) {
50+
if (
51+
! $rector instanceof MinPhpVersionInterface
52+
&& ! $rector instanceof DeprecatedAtVersionInterface
53+
) {
4454
$activeRectors[] = $rector;
4555
continue;
4656
}
4757

58+
$deprecationVersion = $rector instanceof DeprecatedAtVersionInterface
59+
? $rector->provideDeprecatedAtVersion()
60+
: null;
61+
$minPhpVersion = $rector instanceof MinPhpVersionInterface
62+
? $rector->provideMinPhpVersion()
63+
: null;
64+
65+
$requiredPhpVersion = (! $deprecationVersion || $isEagerlyResolveDeprecations)
66+
? $minPhpVersion
67+
: $deprecationVersion;
68+
4869
// does satisfy version? → include
49-
if ($rector->provideMinPhpVersion() <= $minProjectPhpVersion) {
70+
if ($requiredPhpVersion === null || $requiredPhpVersion <= $minProjectPhpVersion) {
5071
$activeRectors[] = $rector;
5172
}
5273
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\VersionBonding\Contract\DeprecatedAtVersionInterface;
10+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
11+
12+
final class DeprecatedAtVersionRector extends AbstractRector implements DeprecatedAtVersionInterface
13+
{
14+
public function __construct(
15+
private readonly int $deprecatedAtVersion,
16+
) {
17+
}
18+
19+
public function getRuleDefinition(): RuleDefinition
20+
{
21+
return new RuleDefinition('Test rector with deprecated-at PHP version', []);
22+
}
23+
24+
public function getNodeTypes(): array
25+
{
26+
return [Node\Stmt\Class_::class];
27+
}
28+
29+
public function refactor(Node $node): ?Node
30+
{
31+
return null;
32+
}
33+
34+
public function provideDeprecatedAtVersion(): int
35+
{
36+
return $this->deprecatedAtVersion;
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\VersionBonding\Contract\MinPhpVersionInterface;
10+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
11+
12+
final class MinPhpVersionRector extends AbstractRector implements MinPhpVersionInterface
13+
{
14+
public function __construct(
15+
private readonly int $minPhpVersion,
16+
) {
17+
}
18+
19+
public function getRuleDefinition(): RuleDefinition
20+
{
21+
return new RuleDefinition('Test rector with minimum PHP version', []);
22+
}
23+
24+
public function getNodeTypes(): array
25+
{
26+
return [Node\Stmt\Class_::class];
27+
}
28+
29+
public function refactor(Node $node): ?Node
30+
{
31+
return null;
32+
}
33+
34+
public function provideMinPhpVersion(): int
35+
{
36+
return $this->minPhpVersion;
37+
}
38+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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\VersionBonding\Contract\DeprecatedAtVersionInterface;
10+
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
11+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
12+
13+
final class MixedVersionBoundsRector extends AbstractRector implements MinPhpVersionInterface, DeprecatedAtVersionInterface
14+
{
15+
public function __construct(
16+
private readonly int $minPhpVersion,
17+
private readonly int $deprecatedAtVersion,
18+
) {}
19+
20+
public function getRuleDefinition(): RuleDefinition
21+
{
22+
return new RuleDefinition('Test rector with deprecation-aware versions', []);
23+
}
24+
25+
public function getNodeTypes(): array
26+
{
27+
return [Node\Stmt\Class_::class];
28+
}
29+
30+
public function refactor(Node $node): ?Node
31+
{
32+
return null;
33+
}
34+
35+
public function provideMinPhpVersion(): int
36+
{
37+
return $this->minPhpVersion;
38+
}
39+
40+
public function provideDeprecatedAtVersion(): int
41+
{
42+
return $this->deprecatedAtVersion;
43+
}
44+
}

0 commit comments

Comments
 (0)