Skip to content
Merged
597 changes: 597 additions & 0 deletions config/set/php-version-based.php

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ parameters:
paths:
- src/Configuration/RectorConfigBuilder.php

# the deprecated SetList::PHP_* constants are still resolved internally by the level sets and their tests
-
identifier: classConstant.deprecated
paths:
- config/set/level
- tests/Bridge/SetRectorsResolverTest.php

# the deprecated github/gitlab output formatters are still tested until removed in next minor version
-
identifier: new.deprecatedClass
Expand Down
39 changes: 39 additions & 0 deletions src/Config/RectorConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Rector\Contract\Rector\RectorInterface;
use Rector\Enum\Config\Defaults;
use Rector\Exception\ShouldNotHappenException;
use Rector\Php\PhpVersionResolver\ComposerJsonPhpVersionResolver;
use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver;
use Rector\Validation\RectorConfigValidator;
use Rector\ValueObject\Configuration\LevelOverflow;
Expand Down Expand Up @@ -277,6 +278,26 @@ public function ruleWithConfigurationComposerVersionBound(
$this->ruleWithConfiguration($rectorClass, $configuration);
}

/**
* Register the rule configuration only if the target PHP version is at least $phpVersion.
* Useful for a configuration valid from a specific PHP version, e.g. a function renamed in PHP 8.0.
*
* @param class-string<ConfigurableRectorInterface> $rectorClass
* @param mixed[] $configuration
* @param PhpVersion::* $phpVersion
*/
public function ruleWithConfigurationPhpVersionBound(
string $rectorClass,
array $configuration,
int $phpVersion
): void {
if ($this->resolveTargetPhpVersion() < $phpVersion) {
return;
}

$this->ruleWithConfiguration($rectorClass, $configuration);
}

/**
* @param class-string<RectorInterface> $rectorClass
*/
Expand Down Expand Up @@ -626,4 +647,22 @@ private function resolveInstalledPackageVersion(string $packageName): ?string

return $this->installedPackageResolver->resolvePackageVersion($packageName);
}

private function resolveTargetPhpVersion(): int
{
// an explicitly picked withPhpSets(phpXX: true) version is the target for its sets,
// even when it is above the project composer.json PHP version
if (SimpleParameterProvider::hasParameter(Option::POLYFILL_CEILING_PHP_VERSION)) {
$ceilingPhpVersion = SimpleParameterProvider::provideIntParameter(Option::POLYFILL_CEILING_PHP_VERSION);
if ($ceilingPhpVersion > 0) {
return $ceilingPhpVersion;
}
}

if (SimpleParameterProvider::hasParameter(Option::PHP_VERSION_FEATURES)) {
return SimpleParameterProvider::provideIntParameter(Option::PHP_VERSION_FEATURES);
}

return ComposerJsonPhpVersionResolver::resolve(getcwd() . '/composer.json') ?? PHP_VERSION_ID;
}
}
59 changes: 0 additions & 59 deletions src/Configuration/PhpLevelSetResolver.php

This file was deleted.

40 changes: 30 additions & 10 deletions src/Configuration/RectorConfigBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Rector\Configuration;

use Deprecated;
use DrupalRector\Set\DrupalSetList;
use Nette\Utils\Strings;
use PhpParser\NodeVisitor;
use Rector\Bridge\SetRectorsResolver;
use Rector\Caching\Contract\ValueObject\Storage\CacheStorageInterface;
Expand Down Expand Up @@ -42,6 +44,11 @@ final class RectorConfigBuilder
{
private const int MAX_LEVEL_GAP = 10;

/**
* Matches the deprecated per-version PHP set files, e.g. .../config/set/php82.php
*/
private const string DEPRECATED_PHP_SET_REGEX = '#/config/set/php\d+\.php$#';

/**
* A level method and the set that contains the very same rules,
* so they are never enabled both at once
Expand Down Expand Up @@ -172,6 +179,12 @@ final class RectorConfigBuilder

private ?int $pickedPhpSetsVersion = null;

/**
* Only an explicitly picked withPhpSets(phpXX) version acts as a ceiling; the composer.json
* fallback must not, so polyfilled rules can still be raised above the project PHP version
*/
private bool $isPhpSetsVersionPicked = false;

/**
* @var LevelOverflow[]
*/
Expand All @@ -184,7 +197,7 @@ public function __invoke(RectorConfig $rectorConfig): void
$this->sets[] = SetList::PHP_POLYFILLS;
}

if ($this->pickedPhpSetsVersion !== null) {
if ($this->isPhpSetsVersionPicked && $this->pickedPhpSetsVersion !== null) {
SimpleParameterProvider::setParameter(
Option::POLYFILL_CEILING_PHP_VERSION,
$this->pickedPhpSetsVersion
Expand Down Expand Up @@ -408,6 +421,14 @@ public function withRootFiles(): self
*/
public function withSets(array $sets): self
{
foreach ($sets as $set) {
if (Strings::match($set, self::DEPRECATED_PHP_SET_REGEX) === null) {
continue;
}

Notifier::notifyDeprecatedPhpSet($set);
}

$this->sets = array_merge($this->sets, $sets);

return $this;
Expand Down Expand Up @@ -551,13 +572,13 @@ public function withPhpSets(
);
}

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

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

return $this->addPhpLevelSets($pickedPhpVersions[0]);
}
Expand Down Expand Up @@ -688,7 +709,7 @@ public function withComposerBased(

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

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

$this->isWithPhpLevelUsed = true;

$phpVersion = ComposerJsonPhpVersionResolver::resolveFromCwdOrFail();

$setRectorsResolver = new SetRectorsResolver();
$setFilePaths = PhpLevelSetResolver::resolveFromPhpVersion($phpVersion);

$rectorRulesWithConfiguration = $setRectorsResolver->resolveFromFilePathsIncludingConfiguration($setFilePaths);
$rectorRulesWithConfiguration = $setRectorsResolver->resolveFromFilePathIncludingConfiguration(
SetList::PHP_VERSION_BASED_SET
);

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

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

return $this;
}
Expand Down
12 changes: 12 additions & 0 deletions src/Console/Notifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ public static function notifyNotSuitableMethodForPHP74(string $calledMethod): vo
sleep(3);
}

public static function notifyDeprecatedPhpSet(string $set): void
{
$message = sprintf(
'The per-version PHP set "%s" is deprecated. Use "withPhpSets()" or "withPhpLevel()" instead, '
. 'they pick the rules by your PHP version automatically.',
$set
);

$symfonyStyle = new SymfonyStyle(new ArgvInput(), new ConsoleOutput());
$symfonyStyle->warning($message);
}

public static function errorWithPhpSetsNotSuitableForPHP74AndLower(): void
{
if (PHP_VERSION_ID >= 80000) {
Expand Down
56 changes: 56 additions & 0 deletions src/Set/ValueObject/SetList.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,38 +35,89 @@ final class SetList
*/
public const string RECTOR_PRESET = __DIR__ . '/../../../config/set/rector-preset.php';

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

public const string BEHAT_ANNOTATIONS_TO_ATTRIBUTES = __DIR__ . '/../../../config/set/behat-annotations-to-attributes.php';

/**
* All PHP version rules in one set; each rule gates itself by PHP version at runtime
*/
public const string PHP_VERSION_BASED_SET = __DIR__ . '/../../../config/set/php-version-based.php';
}
12 changes: 9 additions & 3 deletions src/VersionBonding/PhpVersionedFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,15 @@ public function filter(array $rectors): array
continue;
}

$maxPhpVersion = $rector instanceof RelatedPolyfillInterface && $ceilingPhpVersion !== null
? $ceilingPhpVersion
: $minProjectPhpVersion;
// an explicitly picked withPhpSets() version caps the whole set:
// polyfilled rules up to the ceiling, the rest up to the lower of ceiling and project version
if ($ceilingPhpVersion !== null) {
$maxPhpVersion = $rector instanceof RelatedPolyfillInterface
? $ceilingPhpVersion
: min($ceilingPhpVersion, $minProjectPhpVersion);
} else {
$maxPhpVersion = $minProjectPhpVersion;
}

// does satisfy version? → include
if ($rector->provideMinPhpVersion() <= $maxPhpVersion) {
Expand Down
Loading
Loading