Skip to content

Commit

Permalink
[DX] [Experimental] Add withPhpLevel() to raise PHP lele one rule at …
Browse files Browse the repository at this point in the history
…a time
  • Loading branch information
TomasVotruba committed Aug 27, 2024
1 parent b2b18ab commit 9a00924
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 18 deletions.
20 changes: 19 additions & 1 deletion src/Bridge/SetRectorsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Rector\Bridge;

use Rector\Config\RectorConfig;
use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\Contract\Rector\RectorInterface;
use ReflectionProperty;
use Webmozart\Assert\Assert;
Expand All @@ -16,6 +17,22 @@
*/
final class SetRectorsResolver
{
/**
* @return array<class-string<RectorInterface>>
*/
public function resolveFromFilePathForPhpLevel(string $configFilePath): array
{
$rectorClasses = $this->resolveFromFilePath($configFilePath);

$nonConfigurableRectorClasses = array_filter(
$rectorClasses,
fn (string $rectorClass): bool => ! is_a($rectorClass, ConfigurableRectorInterface::class, true)
);

// revert to start from the lowest level
return array_reverse($nonConfigurableRectorClasses);
}

/**
* @return array<class-string<RectorInterface>>
*/
Expand All @@ -34,7 +51,8 @@ public function resolveFromFilePath(string $configFilePath): array
$tags = $tagsReflectionProperty->getValue($rectorConfig);

$rectorClasses = $tags[RectorInterface::class] ?? [];
sort($rectorClasses);

// avoid sorting to keep original natural order for levels

return array_unique($rectorClasses);
}
Expand Down
5 changes: 5 additions & 0 deletions src/Configuration/Levels/LevelRulesResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ final class LevelRulesResolver
*/
public static function resolve(int $level, array $availableRules, string $methodName): array
{
Assert::positiveInteger($level);
Assert::allIsAOf($availableRules, RectorInterface::class);

$rulesCount = count($availableRules);

if ($availableRules === []) {
Expand All @@ -40,6 +43,8 @@ public static function resolve(int $level, array $availableRules, string $method
$levelRules[] = $availableRules[$i];
}

Assert::allIsAOf($levelRules, RectorInterface::class);

return $levelRules;
}
}
89 changes: 72 additions & 17 deletions src/Configuration/RectorConfigBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Nette\Utils\FileSystem;
use Rector\Bridge\SetProviderCollector;
use Rector\Bridge\SetRectorsResolver;
use Rector\Caching\Contract\ValueObject\Storage\CacheStorageInterface;
use Rector\Config\Level\CodeQualityLevel;
use Rector\Config\Level\DeadCodeLevel;
Expand Down Expand Up @@ -154,6 +155,10 @@ final class RectorConfigBuilder
*/
private array $groupLoadedSets = [];

private ?bool $isWithPhpSetsUsed = null;

private ?bool $isWithPhpLevelUsed = null;

public function __invoke(RectorConfig $rectorConfig): void
{
// @experimental 2024-06
Expand All @@ -169,6 +174,13 @@ public function __invoke(RectorConfig $rectorConfig): void

$uniqueSets = array_unique($this->sets);

if ($this->isWithPhpLevelUsed && $this->isWithPhpSetsUsed) {
throw new InvalidConfigurationException(sprintf(
'Your config uses "withPhp*()" and "withPhpLevel()" methods at the same time.%sPick one of them to avoid rules conflicts.',
PHP_EOL
));
}

if (in_array(SetList::TYPE_DECLARATION, $uniqueSets, true) && $this->isTypeCoverageLevelUsed === true) {
throw new InvalidConfigurationException(sprintf(
'Your config already enables type declarations set.%sRemove "->withTypeCoverageLevel()" as it only duplicates it, or remove type declaration set.',
Expand Down Expand Up @@ -490,6 +502,8 @@ public function withPhpSets(
bool $php53 = false,
bool $php84 = false, // place on later as BC break when used in php 7.x without named arg
): self {
$this->isWithPhpSetsUsed = true;

$pickedArguments = array_filter(func_get_args());
if ($pickedArguments !== []) {
Notifier::notifyWithPhpSetsNotSuitableForPHP80();
Expand All @@ -505,21 +519,9 @@ public function withPhpSets(
}

if ($pickedArguments === []) {
// use composer.json PHP version
$projectComposerJsonFilePath = getcwd() . '/composer.json';
if (file_exists($projectComposerJsonFilePath)) {
$projectPhpVersion = ProjectComposerJsonPhpVersionResolver::resolve($projectComposerJsonFilePath);
if (is_int($projectPhpVersion)) {
$this->sets[] = PhpLevelSetResolver::resolveFromPhpVersion($projectPhpVersion);

return $this;
}
}
$this->sets[] = $this->resolvePhpSetsFromComposerJsonPhpVersion();

throw new InvalidConfigurationException(sprintf(
'We could not find local "composer.json" to determine your PHP version.%sPlease, fill the PHP version set in withPhpSets() manually.',
PHP_EOL
));
return $this;
}

if ($php53) {
Expand Down Expand Up @@ -561,42 +563,56 @@ public function withPhpSets(
*/
public function withPhp53Sets(): self
{
$this->isWithPhpSetsUsed = true;

$this->sets[] = LevelSetList::UP_TO_PHP_53;
return $this;
}

public function withPhp54Sets(): self
{
$this->isWithPhpSetsUsed = true;

$this->sets[] = LevelSetList::UP_TO_PHP_54;
return $this;
}

public function withPhp55Sets(): self
{
$this->isWithPhpSetsUsed = true;

$this->sets[] = LevelSetList::UP_TO_PHP_55;
return $this;
}

public function withPhp56Sets(): self
{
$this->isWithPhpSetsUsed = true;

$this->sets[] = LevelSetList::UP_TO_PHP_56;
return $this;
}

public function withPhp70Sets(): self
{
$this->isWithPhpSetsUsed = true;

$this->sets[] = LevelSetList::UP_TO_PHP_70;
return $this;
}

public function withPhp71Sets(): self
{
$this->isWithPhpSetsUsed = true;

$this->sets[] = LevelSetList::UP_TO_PHP_71;
return $this;
}

public function withPhp72Sets(): self
{
$this->isWithPhpSetsUsed = true;

$this->sets[] = LevelSetList::UP_TO_PHP_72;
return $this;
}
Expand All @@ -609,6 +625,8 @@ public function withPhp73Sets(): self

public function withPhp74Sets(): self
{
$this->isWithPhpSetsUsed = true;

$this->sets[] = LevelSetList::UP_TO_PHP_74;
return $this;
}
Expand Down Expand Up @@ -877,7 +895,7 @@ public function withDeadCodeLevel(int $level): self
$levelRules = LevelRulesResolver::resolve(
$level,
DeadCodeLevel::RULES,
'RectorConfig::withDeadCodeLevel()'
'RectorConfig::' . __METHOD__ . '()'
);

$this->rules = array_merge($this->rules, $levelRules);
Expand All @@ -896,14 +914,34 @@ public function withTypeCoverageLevel(int $level): self
$levelRules = LevelRulesResolver::resolve(
$level,
TypeDeclarationLevel::RULES,
'RectorConfig::withTypeCoverageLevel()'
'RectorConfig::' . __METHOD__ . '()'
);

$this->rules = array_merge($this->rules, $levelRules);

return $this;
}

/**
* @experimental Since 1.2.5 Raise your PHP level from, one level at a time
*/
public function withPhpLevel(int $level): self
{
$this->isWithPhpLevelUsed = true;

$phpLevelSetFilePath = $this->resolvePhpSetsFromComposerJsonPhpVersion();

$setRectorsResolver = new SetRectorsResolver();
$phpRectorRules = $setRectorsResolver->resolveFromFilePathForPhpLevel($phpLevelSetFilePath);

$levelRules = LevelRulesResolver::resolve($level, $phpRectorRules, 'RectorConfig::' . __METHOD__);

// @todo this method conflicts with withPhpSets(), withPhpX(...)
$this->rules = array_merge($this->rules, $levelRules);

return $this;
}

/**
* @experimental Raise your code quality from the safest rules
* to more affecting ones, one level at a time
Expand All @@ -915,7 +953,7 @@ public function withCodeQualityLevel(int $level): self
$levelRules = LevelRulesResolver::resolve(
$level,
CodeQualityLevel::RULES,
'RectorConfig::withCodeQualityLevel()'
'RectorConfig::' . __METHOD__ . '()'
);

$this->rules = array_merge($this->rules, $levelRules);
Expand Down Expand Up @@ -993,4 +1031,21 @@ public function withRealPathReporting(bool $absolutePath = true): self

return $this;
}

private function resolvePhpSetsFromComposerJsonPhpVersion(): string
{
// use composer.json PHP version
$projectComposerJsonFilePath = getcwd() . '/composer.json';
if (file_exists($projectComposerJsonFilePath)) {
$projectPhpVersion = ProjectComposerJsonPhpVersionResolver::resolve($projectComposerJsonFilePath);
if (is_int($projectPhpVersion)) {
return PhpLevelSetResolver::resolveFromPhpVersion($projectPhpVersion);
}
}

throw new InvalidConfigurationException(sprintf(
'We could not find local "composer.json" to determine your PHP version.%sPlease, fill the PHP version set in withPhpSets() manually.',
PHP_EOL
));
}
}

0 comments on commit 9a00924

Please sign in to comment.