Skip to content

Commit

Permalink
Updated Rector to commit 2dda748541be13dac8ef328011bc50233f2030bc
Browse files Browse the repository at this point in the history
rectorphp/rector-src@2dda748 [DX] Introduce set providers, to enable package + version based set registration (#5976)
  • Loading branch information
TomasVotruba committed Jun 20, 2024
1 parent 9d08250 commit 856c640
Show file tree
Hide file tree
Showing 19 changed files with 348 additions and 27 deletions.
4 changes: 2 additions & 2 deletions src/Application/VersionResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = 'f366a7e66228f35dc877e816b553c4f38332f801';
public const PACKAGE_VERSION = '2dda748541be13dac8ef328011bc50233f2030bc';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2024-06-20 13:21:54';
public const RELEASE_DATE = '2024-06-20 14:52:05';
/**
* @var int
*/
Expand Down
43 changes: 43 additions & 0 deletions src/Composer/InstalledPackageResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare (strict_types=1);
namespace Rector\Composer;

use RectorPrefix202406\Nette\Utils\FileSystem;
use RectorPrefix202406\Nette\Utils\Json;
use Rector\Composer\ValueObject\InstalledPackage;
use Rector\Exception\ShouldNotHappenException;
use RectorPrefix202406\Webmozart\Assert\Assert;
/**
* @see \Rector\Tests\Composer\InstalledPackageResolverTest
*/
final class InstalledPackageResolver
{
/**
* @var array<string, InstalledPackage[]>
*/
private $resolvedInstalledPackages = [];
/**
* @return InstalledPackage[]
*/
public function resolve(string $projectDirectory) : array
{
// cache
if (isset($this->resolvedInstalledPackages[$projectDirectory])) {
return $this->resolvedInstalledPackages[$projectDirectory];
}
Assert::directory($projectDirectory);
$installedPackagesFilePath = $projectDirectory . '/vendor/composer/installed.json';
if (!\file_exists($installedPackagesFilePath)) {
throw new ShouldNotHappenException('The installed package json not found. Make sure you run `composer update` and "vendor/composer/installed.json" file exists');
}
$installedPackageFileContents = FileSystem::read($installedPackagesFilePath);
$installedPackagesFilePath = Json::decode($installedPackageFileContents, \true);
$installedPackages = [];
foreach ($installedPackagesFilePath['packages'] as $installedPackage) {
$installedPackages[] = new InstalledPackage($installedPackage['name'], $installedPackage['version_normalized']);
}
$this->resolvedInstalledPackages[$projectDirectory] = $installedPackages;
return $installedPackages;
}
}
31 changes: 31 additions & 0 deletions src/Composer/ValueObject/InstalledPackage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare (strict_types=1);
namespace Rector\Composer\ValueObject;

final class InstalledPackage
{
/**
* @readonly
* @var string
*/
private $name;
/**
* @readonly
* @var string
*/
private $version;
public function __construct(string $name, string $version)
{
$this->name = $name;
$this->version = $version;
}
public function getName() : string
{
return $this->name;
}
public function getVersion() : string
{
return $this->version;
}
}
5 changes: 4 additions & 1 deletion src/Config/RectorConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Rector\Contract\Rector\RectorInterface;
use Rector\DependencyInjection\Laravel\ContainerMemento;
use Rector\Exception\ShouldNotHappenException;
use Rector\Set\SetProvider\TwigSetProvider;
use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver;
use Rector\Validation\RectorConfigValidator;
use Rector\ValueObject\PhpVersion;
Expand All @@ -37,7 +38,9 @@ final class RectorConfig extends Container
private $autotagInterfaces = [Command::class];
public static function configure() : RectorConfigBuilder
{
return new RectorConfigBuilder();
$rectorConfigBuilder = new RectorConfigBuilder();
$rectorConfigBuilder->withSetProviders([new TwigSetProvider()]);
return $rectorConfigBuilder;
}
/**
* @param string[] $paths
Expand Down
38 changes: 36 additions & 2 deletions src/Configuration/RectorConfigBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
use Rector\Contract\Rector\RectorInterface;
use Rector\Doctrine\Set\DoctrineSetList;
use Rector\Exception\Configuration\InvalidConfigurationException;
use Rector\Exception\ShouldNotHappenException;
use Rector\Php\PhpVersionResolver\ProjectComposerJsonPhpVersionResolver;
use Rector\PHPUnit\Set\PHPUnitSetList;
use Rector\Set\Contract\SetProviderInterface;
use Rector\Set\Enum\SetGroup;
use Rector\Set\SetManager;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
use Rector\Symfony\Set\FOSRestSetList;
Expand Down Expand Up @@ -161,8 +165,30 @@ final class RectorConfigBuilder
* @var RegisteredService[]
*/
private $registerServices = [];
/**
* @var SetProviderInterface[]
*/
private $setProviders = [];
/**
* @var array<SetGroup::*>
*/
private $setGroups = [];
/**
* @var string[]
*/
private $groupLoadedSets = [];
public function __invoke(RectorConfig $rectorConfig) : void
{
// @experimental 2024-06
if ($this->setGroups !== []) {
if ($this->setProviders === []) {
throw new ShouldNotHappenException(\sprintf('Register set providers first, as they are required for dynamic sets: "%s"', \implode('", "', $this->setGroups)));
}
$setManager = new SetManager($this->setProviders);
$this->groupLoadedSets = $setManager->matchBySetGroups($this->setGroups);
}
// merge sets together
$this->sets = \array_merge($this->sets, $this->groupLoadedSets);
$uniqueSets = \array_unique($this->sets);
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));
Expand Down Expand Up @@ -452,6 +478,14 @@ public function withPhp74Sets() : self
}
// there is no withPhp80Sets() and above,
// as we already use PHP 8.0 and should go with withPhpSets() instead
/**
* @param SetProviderInterface[] $setProviders
*/
public function withSetProviders(array $setProviders) : self
{
$this->setProviders = \array_merge($this->setProviders, $setProviders);
return $this;
}
public function withPreparedSets(
bool $deadCode = \false,
bool $codeQuality = \false,
Expand Down Expand Up @@ -502,9 +536,9 @@ public function withPreparedSets(
if ($rectorPreset) {
$this->sets[] = SetList::RECTOR_PRESET;
}
// @experimental 2024-06
if ($twig) {
// resolve sets based on composer.json versions
// @todo
$this->setGroups[] = SetGroup::TWIG;
}
return $this;
}
Expand Down
8 changes: 8 additions & 0 deletions src/Set/Contract/SetInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

declare (strict_types=1);
namespace Rector\Set\Contract;

interface SetInterface
{
}
12 changes: 12 additions & 0 deletions src/Set/Contract/SetProviderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare (strict_types=1);
namespace Rector\Set\Contract;

interface SetProviderInterface
{
/**
* @return SetInterface[]
*/
public function provide() : array;
}
9 changes: 9 additions & 0 deletions src/Set/Enum/SetGroup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare (strict_types=1);
namespace Rector\Set\Enum;

final class SetGroup
{
public const TWIG = 'twig';
}
68 changes: 68 additions & 0 deletions src/Set/SetManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare (strict_types=1);
namespace Rector\Set;

use Rector\Composer\InstalledPackageResolver;
use Rector\Set\Contract\SetProviderInterface;
use Rector\Set\ValueObject\ComposerTriggeredSet;
use RectorPrefix202406\Webmozart\Assert\Assert;
/**
* @see \Rector\Tests\Set\SetCollector\SetCollectorTest
*/
final class SetManager
{
/**
* @var SetProviderInterface[]
* @readonly
*/
private $setProviders;
/**
* @param SetProviderInterface[] $setProviders
*/
public function __construct(array $setProviders)
{
$this->setProviders = $setProviders;
Assert::allIsInstanceOf($setProviders, SetProviderInterface::class);
}
/**
* @return ComposerTriggeredSet[]
*/
public function matchComposerTriggered(string $groupName) : array
{
$matchedSets = [];
foreach ($this->setProviders as $setProvider) {
foreach ($setProvider->provide() as $set) {
if (!$set instanceof ComposerTriggeredSet) {
continue;
}
if ($set->getGroupName() === $groupName) {
$matchedSets[] = $set;
}
}
}
return $matchedSets;
}
/**
* @param string[] $setGroups
* @return string[]
*/
public function matchBySetGroups(array $setGroups) : array
{
$installedPackageResolver = new InstalledPackageResolver();
$installedComposerPackages = $installedPackageResolver->resolve(\getcwd());
$groupLoadedSets = [];
foreach ($setGroups as $setGroup) {
$composerTriggeredSets = $this->matchComposerTriggered($setGroup);
foreach ($composerTriggeredSets as $composerTriggeredSet) {
if ($composerTriggeredSet->matchInstalledPackages($installedComposerPackages)) {
// @todo add debug note somewhere
// echo sprintf('Loaded "%s" set as it meets the conditions', $composerTriggeredSet->getSetFilePath());
// it matched composer package + version requirements → load set
$groupLoadedSets[] = $composerTriggeredSet->getSetFilePath();
}
}
}
return $groupLoadedSets;
}
}
25 changes: 25 additions & 0 deletions src/Set/SetProvider/TwigSetProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare (strict_types=1);
namespace Rector\Set\SetProvider;

use Rector\Set\Contract\SetInterface;
use Rector\Set\Contract\SetProviderInterface;
use Rector\Set\Enum\SetGroup;
use Rector\Set\ValueObject\ComposerTriggeredSet;
use Rector\Symfony\Set\TwigSetList;
/**
* Temporary location, move to rector-symfony package once this is merged
* @experimental 2024-06
*/
final class TwigSetProvider implements SetProviderInterface
{
/**
* @return SetInterface[]
*/
public function provide() : array
{
// @todo temporary name to test, these will be located in rector-symfony, rector-doctrine, rector-phpunit packages
return [new ComposerTriggeredSet(SetGroup::TWIG, 'twig/twig', '1.12', TwigSetList::TWIG_112)];
}
}
69 changes: 69 additions & 0 deletions src/Set/ValueObject/ComposerTriggeredSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare (strict_types=1);
namespace Rector\Set\ValueObject;

use Rector\Composer\ValueObject\InstalledPackage;
use Rector\Set\Contract\SetInterface;
use RectorPrefix202406\Webmozart\Assert\Assert;
/**
* @api used by extensions
*/
final class ComposerTriggeredSet implements SetInterface
{
/**
* @readonly
* @var string
*/
private $groupName;
/**
* @readonly
* @var string
*/
private $packageName;
/**
* @readonly
* @var string
*/
private $version;
/**
* @readonly
* @var string
*/
private $setFilePath;
/**
* @var string
* @see https://regex101.com/r/xRjQ2X/1
*/
private const PACKAGE_REGEX = '#^[a-z0-9-]+\\/[a-z0-9-_]+$#';
public function __construct(string $groupName, string $packageName, string $version, string $setFilePath)
{
$this->groupName = $groupName;
$this->packageName = $packageName;
$this->version = $version;
$this->setFilePath = $setFilePath;
Assert::regex($this->packageName, self::PACKAGE_REGEX);
Assert::fileExists($setFilePath);
}
public function getGroupName() : string
{
return $this->groupName;
}
public function getSetFilePath() : string
{
return $this->setFilePath;
}
/**
* @param InstalledPackage[] $installedPackages
*/
public function matchInstalledPackages(array $installedPackages) : bool
{
foreach ($installedPackages as $installedPackage) {
if ($installedPackage->getName() !== $this->packageName) {
continue;
}
return \version_compare($installedPackage->getVersion(), $this->version) !== -1;
}
return \false;
}
}
Loading

0 comments on commit 856c640

Please sign in to comment.