diff --git a/src/Application/VersionResolver.php b/src/Application/VersionResolver.php index 5cfd648e9a1..4129314ac0f 100644 --- a/src/Application/VersionResolver.php +++ b/src/Application/VersionResolver.php @@ -19,12 +19,12 @@ final class VersionResolver * @api * @var string */ - public const PACKAGE_VERSION = 'd7bfc24bedc11374868f5a7ff3066eb847e80a8c'; + public const PACKAGE_VERSION = '684f23d165407e0461a48d2c1fd289834a492876'; /** * @api * @var string */ - public const RELEASE_DATE = '2024-10-12 17:38:13'; + public const RELEASE_DATE = '2024-10-12 17:53:31'; /** * @var int */ diff --git a/src/Bridge/SetRectorsResolver.php b/src/Bridge/SetRectorsResolver.php index dfd5634d321..c59cf19d932 100644 --- a/src/Bridge/SetRectorsResolver.php +++ b/src/Bridge/SetRectorsResolver.php @@ -5,31 +5,58 @@ use Rector\Config\RectorConfig; use Rector\Contract\Rector\RectorInterface; -use ReflectionProperty; use RectorPrefix202410\Webmozart\Assert\Assert; /** * @api * @experimental since 1.1.2 * Utils class to ease building bridges by 3rd-party tools + * + * @see \Rector\Tests\Bridge\SetRectorsResolverTest */ final class SetRectorsResolver { /** - * @return array> + * @param string[] $configFilePaths + * @return array|array, mixed[]>> */ - public function resolveFromFilePath(string $configFilePath) : array + public function resolveFromFilePathsIncludingConfiguration(array $configFilePaths) : array + { + Assert::allString($configFilePaths); + Assert::allFileExists($configFilePaths); + $combinedRectorRulesWithConfiguration = []; + foreach ($configFilePaths as $configFilePath) { + $rectorRulesWithConfiguration = $this->resolveFromFilePathIncludingConfiguration($configFilePath); + $combinedRectorRulesWithConfiguration = \array_merge($combinedRectorRulesWithConfiguration, $rectorRulesWithConfiguration); + } + return $combinedRectorRulesWithConfiguration; + } + /** + * @return array|array, mixed[]>> + */ + public function resolveFromFilePathIncludingConfiguration(string $configFilePath) : array + { + $rectorConfig = $this->loadRectorConfigFromFilePath($configFilePath); + $rectorClassesWithOptionalConfiguration = $rectorConfig->getRectorClasses(); + foreach ($rectorConfig->getRuleConfigurations() as $rectorClass => $configuration) { + // remove from non-configurable, if added again with better config + if (\in_array($rectorClass, $rectorClassesWithOptionalConfiguration)) { + $rectorRulePosition = \array_search($rectorClass, $rectorClassesWithOptionalConfiguration, \true); + if (\is_int($rectorRulePosition)) { + unset($rectorClassesWithOptionalConfiguration[$rectorRulePosition]); + } + } + $rectorClassesWithOptionalConfiguration[] = [$rectorClass => $configuration]; + } + // sort keys + return \array_values($rectorClassesWithOptionalConfiguration); + } + private function loadRectorConfigFromFilePath(string $configFilePath) : RectorConfig { Assert::fileExists($configFilePath); $rectorConfig = new RectorConfig(); /** @var callable $configCallable */ $configCallable = (require $configFilePath); $configCallable($rectorConfig); - // get tagged class-names - $tagsReflectionProperty = new ReflectionProperty($rectorConfig, 'tags'); - $tagsReflectionProperty->setAccessible(\true); - $tags = $tagsReflectionProperty->getValue($rectorConfig); - $rectorClasses = $tags[RectorInterface::class] ?? []; - \sort($rectorClasses); - return \array_unique($rectorClasses); + return $rectorConfig; } } diff --git a/src/Configuration/RectorConfigBuilder.php b/src/Configuration/RectorConfigBuilder.php index 8c4fab96dec..43ffc0af2c2 100644 --- a/src/Configuration/RectorConfigBuilder.php +++ b/src/Configuration/RectorConfigBuilder.php @@ -5,6 +5,7 @@ use RectorPrefix202410\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; @@ -186,6 +187,10 @@ final class RectorConfigBuilder * @var bool|null */ private $isWithPhpSetsUsed; + /** + * @var bool|null + */ + private $isWithPhpLevelUsed; public function __invoke(RectorConfig $rectorConfig) : void { // @experimental 2024-06 @@ -197,6 +202,9 @@ public function __invoke(RectorConfig $rectorConfig) : void // merge sets together $this->sets = \array_merge($this->sets, $this->groupLoadedSets); $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 rule 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.', \PHP_EOL)); } @@ -763,6 +771,33 @@ public function withTypeCoverageLevel(int $level) : self $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 + { + Assert::natural($level); + $this->isWithPhpLevelUsed = \true; + $phpVersion = ComposerJsonPhpVersionResolver::resolveFromCwdOrFail(); + $setRectorsResolver = new SetRectorsResolver(); + $setFilePaths = \Rector\Configuration\PhpLevelSetResolver::resolveFromPhpVersion($phpVersion); + $rectorRulesWithConfiguration = $setRectorsResolver->resolveFromFilePathsIncludingConfiguration($setFilePaths); + foreach ($rectorRulesWithConfiguration as $position => $rectorRuleWithConfiguration) { + // add rules untill level is reached + if ($position > $level) { + continue; + } + if (\is_string($rectorRuleWithConfiguration)) { + $this->rules[] = $rectorRuleWithConfiguration; + } elseif (\is_array($rectorRuleWithConfiguration)) { + foreach ($rectorRuleWithConfiguration as $rectorRule => $rectorRuleConfiguration) { + /** @var class-string $rectorRule */ + $this->withConfiguredRule($rectorRule, $rectorRuleConfiguration); + } + } + } + return $this; + } /** * @experimental Raise your code quality from the safest rules * to more affecting ones, one level at a time