Skip to content

Commit cdfd2b5

Browse files
authored
[dx] Deprecate SetList::PHP_* constants in favor of withPhpSets()/withPhpLevel(), introduce ruleWithConfigurationPhpVersionBound() (#8405)
1 parent fa3b728 commit cdfd2b5

10 files changed

Lines changed: 764 additions & 123 deletions

File tree

‎config/set/php-version-based.php‎

Lines changed: 597 additions & 0 deletions
Large diffs are not rendered by default.

‎phpstan.neon‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ parameters:
7676
paths:
7777
- src/Configuration/RectorConfigBuilder.php
7878

79+
# the deprecated SetList::PHP_* constants are still resolved internally by the level sets and their tests
80+
-
81+
identifier: classConstant.deprecated
82+
paths:
83+
- config/set/level
84+
- tests/Bridge/SetRectorsResolverTest.php
85+
7986
# the deprecated github/gitlab output formatters are still tested until removed in next minor version
8087
-
8188
identifier: new.deprecatedClass

‎src/Config/RectorConfig.php‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Rector\Contract\Rector\RectorInterface;
1717
use Rector\Enum\Config\Defaults;
1818
use Rector\Exception\ShouldNotHappenException;
19+
use Rector\Php\PhpVersionResolver\ComposerJsonPhpVersionResolver;
1920
use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver;
2021
use Rector\Validation\RectorConfigValidator;
2122
use Rector\ValueObject\Configuration\LevelOverflow;
@@ -277,6 +278,26 @@ public function ruleWithConfigurationComposerVersionBound(
277278
$this->ruleWithConfiguration($rectorClass, $configuration);
278279
}
279280

281+
/**
282+
* Register the rule configuration only if the target PHP version is at least $phpVersion.
283+
* Useful for a configuration valid from a specific PHP version, e.g. a function renamed in PHP 8.0.
284+
*
285+
* @param class-string<ConfigurableRectorInterface> $rectorClass
286+
* @param mixed[] $configuration
287+
* @param PhpVersion::* $phpVersion
288+
*/
289+
public function ruleWithConfigurationPhpVersionBound(
290+
string $rectorClass,
291+
array $configuration,
292+
int $phpVersion
293+
): void {
294+
if ($this->resolveTargetPhpVersion() < $phpVersion) {
295+
return;
296+
}
297+
298+
$this->ruleWithConfiguration($rectorClass, $configuration);
299+
}
300+
280301
/**
281302
* @param class-string<RectorInterface> $rectorClass
282303
*/
@@ -626,4 +647,22 @@ private function resolveInstalledPackageVersion(string $packageName): ?string
626647

627648
return $this->installedPackageResolver->resolvePackageVersion($packageName);
628649
}
650+
651+
private function resolveTargetPhpVersion(): int
652+
{
653+
// an explicitly picked withPhpSets(phpXX: true) version is the target for its sets,
654+
// even when it is above the project composer.json PHP version
655+
if (SimpleParameterProvider::hasParameter(Option::POLYFILL_CEILING_PHP_VERSION)) {
656+
$ceilingPhpVersion = SimpleParameterProvider::provideIntParameter(Option::POLYFILL_CEILING_PHP_VERSION);
657+
if ($ceilingPhpVersion > 0) {
658+
return $ceilingPhpVersion;
659+
}
660+
}
661+
662+
if (SimpleParameterProvider::hasParameter(Option::PHP_VERSION_FEATURES)) {
663+
return SimpleParameterProvider::provideIntParameter(Option::PHP_VERSION_FEATURES);
664+
}
665+
666+
return ComposerJsonPhpVersionResolver::resolve(getcwd() . '/composer.json') ?? PHP_VERSION_ID;
667+
}
629668
}

‎src/Configuration/PhpLevelSetResolver.php‎

Lines changed: 0 additions & 59 deletions
This file was deleted.

‎src/Configuration/RectorConfigBuilder.php‎

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace Rector\Configuration;
66

77
use Deprecated;
8+
use DrupalRector\Set\DrupalSetList;
9+
use Nette\Utils\Strings;
810
use PhpParser\NodeVisitor;
911
use Rector\Bridge\SetRectorsResolver;
1012
use Rector\Caching\Contract\ValueObject\Storage\CacheStorageInterface;
@@ -42,6 +44,11 @@ final class RectorConfigBuilder
4244
{
4345
private const int MAX_LEVEL_GAP = 10;
4446

47+
/**
48+
* Matches the deprecated per-version PHP set files, e.g. .../config/set/php82.php
49+
*/
50+
private const string DEPRECATED_PHP_SET_REGEX = '#/config/set/php\d+\.php$#';
51+
4552
/**
4653
* A level method and the set that contains the very same rules,
4754
* so they are never enabled both at once
@@ -172,6 +179,12 @@ final class RectorConfigBuilder
172179

173180
private ?int $pickedPhpSetsVersion = null;
174181

182+
/**
183+
* Only an explicitly picked withPhpSets(phpXX) version acts as a ceiling; the composer.json
184+
* fallback must not, so polyfilled rules can still be raised above the project PHP version
185+
*/
186+
private bool $isPhpSetsVersionPicked = false;
187+
175188
/**
176189
* @var LevelOverflow[]
177190
*/
@@ -184,7 +197,7 @@ public function __invoke(RectorConfig $rectorConfig): void
184197
$this->sets[] = SetList::PHP_POLYFILLS;
185198
}
186199

187-
if ($this->pickedPhpSetsVersion !== null) {
200+
if ($this->isPhpSetsVersionPicked && $this->pickedPhpSetsVersion !== null) {
188201
SimpleParameterProvider::setParameter(
189202
Option::POLYFILL_CEILING_PHP_VERSION,
190203
$this->pickedPhpSetsVersion
@@ -408,6 +421,14 @@ public function withRootFiles(): self
408421
*/
409422
public function withSets(array $sets): self
410423
{
424+
foreach ($sets as $set) {
425+
if (Strings::match($set, self::DEPRECATED_PHP_SET_REGEX) === null) {
426+
continue;
427+
}
428+
429+
Notifier::notifyDeprecatedPhpSet($set);
430+
}
431+
411432
$this->sets = array_merge($this->sets, $sets);
412433

413434
return $this;
@@ -551,13 +572,13 @@ public function withPhpSets(
551572
);
552573
}
553574

554-
// no version picked, resolve it from the project composer.json
575+
// no version picked, target the project composer.json PHP version
555576
if ($pickedPhpVersions === []) {
556577
return $this->addPhpLevelSets(ComposerJsonPhpVersionResolver::resolveFromCwdOrFail());
557578
}
558579

559580
// explicitly picked version is a ceiling, even for polyfilled rules
560-
$this->pickedPhpSetsVersion = $pickedPhpVersions[0];
581+
$this->isPhpSetsVersionPicked = true;
561582

562583
return $this->addPhpLevelSets($pickedPhpVersions[0]);
563584
}
@@ -688,7 +709,7 @@ public function withComposerBased(
688709

689710
if ($drupal && class_exists('DrupalRector\Set\DrupalSetList') && constant('DrupalRector\Set\DrupalSetList::COMPOSER_BASED')) {
690711
// waits on https://github.com/palantirnet/drupal-rector/pull/419/files#diff-c6bd4ee854830efc1363a7d99c1b6a2e7e64f2499a51e503174ab777de7e64e5
691-
$this->sets[] = \DrupalRector\Set\DrupalSetList::COMPOSER_BASED;
712+
$this->sets[] = DrupalSetList::COMPOSER_BASED;
692713
}
693714

694715
if ($phpunit) {
@@ -962,12 +983,10 @@ public function withPhpLevel(int $level): self
962983

963984
$this->isWithPhpLevelUsed = true;
964985

965-
$phpVersion = ComposerJsonPhpVersionResolver::resolveFromCwdOrFail();
966-
967986
$setRectorsResolver = new SetRectorsResolver();
968-
$setFilePaths = PhpLevelSetResolver::resolveFromPhpVersion($phpVersion);
969-
970-
$rectorRulesWithConfiguration = $setRectorsResolver->resolveFromFilePathsIncludingConfiguration($setFilePaths);
987+
$rectorRulesWithConfiguration = $setRectorsResolver->resolveFromFilePathIncludingConfiguration(
988+
SetList::PHP_VERSION_BASED_SET
989+
);
971990

972991
foreach ($rectorRulesWithConfiguration as $position => $rectorRuleWithConfiguration) {
973992
// add rules until level is reached
@@ -1143,8 +1162,9 @@ public function withSetProviders(): self
11431162
private function addPhpLevelSets(int $phpVersion): self
11441163
{
11451164
$this->isWithPhpSetsUsed = true;
1165+
$this->pickedPhpSetsVersion = $phpVersion;
11461166

1147-
$this->sets = array_merge($this->sets, PhpLevelSetResolver::resolveFromPhpVersion($phpVersion));
1167+
$this->sets[] = SetList::PHP_VERSION_BASED_SET;
11481168

11491169
return $this;
11501170
}

‎src/Console/Notifier.php‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ public static function notifyNotSuitableMethodForPHP74(string $calledMethod): vo
2828
sleep(3);
2929
}
3030

31+
public static function notifyDeprecatedPhpSet(string $set): void
32+
{
33+
$message = sprintf(
34+
'The per-version PHP set "%s" is deprecated. Use "withPhpSets()" or "withPhpLevel()" instead, '
35+
. 'they pick the rules by your PHP version automatically.',
36+
$set
37+
);
38+
39+
$symfonyStyle = new SymfonyStyle(new ArgvInput(), new ConsoleOutput());
40+
$symfonyStyle->warning($message);
41+
}
42+
3143
public static function errorWithPhpSetsNotSuitableForPHP74AndLower(): void
3244
{
3345
if (PHP_VERSION_ID >= 80000) {

‎src/Set/ValueObject/SetList.php‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,38 +35,89 @@ final class SetList
3535
*/
3636
public const string RECTOR_PRESET = __DIR__ . '/../../../config/set/rector-preset.php';
3737

38+
/**
39+
* @deprecated Use withPhpSets() or withPhpLevel() instead
40+
*/
3841
public const string PHP_52 = __DIR__ . '/../../../config/set/php52.php';
3942

43+
/**
44+
* @deprecated Use withPhpSets() or withPhpLevel() instead
45+
*/
4046
public const string PHP_53 = __DIR__ . '/../../../config/set/php53.php';
4147

48+
/**
49+
* @deprecated Use withPhpSets() or withPhpLevel() instead
50+
*/
4251
public const string PHP_54 = __DIR__ . '/../../../config/set/php54.php';
4352

53+
/**
54+
* @deprecated Use withPhpSets() or withPhpLevel() instead
55+
*/
4456
public const string PHP_55 = __DIR__ . '/../../../config/set/php55.php';
4557

58+
/**
59+
* @deprecated Use withPhpSets() or withPhpLevel() instead
60+
*/
4661
public const string PHP_56 = __DIR__ . '/../../../config/set/php56.php';
4762

63+
/**
64+
* @deprecated Use withPhpSets() or withPhpLevel() instead
65+
*/
4866
public const string PHP_70 = __DIR__ . '/../../../config/set/php70.php';
4967

68+
/**
69+
* @deprecated Use withPhpSets() or withPhpLevel() instead
70+
*/
5071
public const string PHP_71 = __DIR__ . '/../../../config/set/php71.php';
5172

73+
/**
74+
* @deprecated Use withPhpSets() or withPhpLevel() instead
75+
*/
5276
public const string PHP_72 = __DIR__ . '/../../../config/set/php72.php';
5377

78+
/**
79+
* @deprecated Use withPhpSets() or withPhpLevel() instead
80+
*/
5481
public const string PHP_73 = __DIR__ . '/../../../config/set/php73.php';
5582

83+
/**
84+
* @deprecated Use withPhpSets() or withPhpLevel() instead
85+
*/
5686
public const string PHP_74 = __DIR__ . '/../../../config/set/php74.php';
5787

88+
/**
89+
* @deprecated Use withPhpSets() or withPhpLevel() instead
90+
*/
5891
public const string PHP_80 = __DIR__ . '/../../../config/set/php80.php';
5992

93+
/**
94+
* @deprecated Use withPhpSets() or withPhpLevel() instead
95+
*/
6096
public const string PHP_81 = __DIR__ . '/../../../config/set/php81.php';
6197

98+
/**
99+
* @deprecated Use withPhpSets() or withPhpLevel() instead
100+
*/
62101
public const string PHP_82 = __DIR__ . '/../../../config/set/php82.php';
63102

103+
/**
104+
* @deprecated Use withPhpSets() or withPhpLevel() instead
105+
*/
64106
public const string PHP_83 = __DIR__ . '/../../../config/set/php83.php';
65107

108+
/**
109+
* @deprecated Use withPhpSets() or withPhpLevel() instead
110+
*/
66111
public const string PHP_84 = __DIR__ . '/../../../config/set/php84.php';
67112

113+
/**
114+
* @deprecated Use withPhpSets() or withPhpLevel() instead
115+
*/
68116
public const string PHP_85 = __DIR__ . '/../../../config/set/php85.php';
69117

118+
/**
119+
* @deprecated Use withPhpSets() or withPhpLevel() instead
120+
*/
70121
public const string PHP_86 = __DIR__ . '/../../../config/set/php86.php';
71122

72123
public const string PRIVATIZATION = __DIR__ . '/../../../config/set/privatization.php';
@@ -87,4 +138,9 @@ final class SetList
87138
public const string CARBON = __DIR__ . '/../../../config/set/datetime-to-carbon.php';
88139

89140
public const string BEHAT_ANNOTATIONS_TO_ATTRIBUTES = __DIR__ . '/../../../config/set/behat-annotations-to-attributes.php';
141+
142+
/**
143+
* All PHP version rules in one set; each rule gates itself by PHP version at runtime
144+
*/
145+
public const string PHP_VERSION_BASED_SET = __DIR__ . '/../../../config/set/php-version-based.php';
90146
}

‎src/VersionBonding/PhpVersionedFilter.php‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,15 @@ public function filter(array $rectors): array
5050
continue;
5151
}
5252

53-
$maxPhpVersion = $rector instanceof RelatedPolyfillInterface && $ceilingPhpVersion !== null
54-
? $ceilingPhpVersion
55-
: $minProjectPhpVersion;
53+
// an explicitly picked withPhpSets() version caps the whole set:
54+
// polyfilled rules up to the ceiling, the rest up to the lower of ceiling and project version
55+
if ($ceilingPhpVersion !== null) {
56+
$maxPhpVersion = $rector instanceof RelatedPolyfillInterface
57+
? $ceilingPhpVersion
58+
: min($ceilingPhpVersion, $minProjectPhpVersion);
59+
} else {
60+
$maxPhpVersion = $minProjectPhpVersion;
61+
}
5662

5763
// does satisfy version? → include
5864
if ($rector->provideMinPhpVersion() <= $maxPhpVersion) {

0 commit comments

Comments
 (0)