Skip to content

Commit 006f215

Browse files
authored
[DX] Add version-based set loading based on installed package version (#6428)
* [config] Add version-based set loading, use composer semver * add Laravel enum * [config] Use more readable set map array
1 parent 129f93c commit 006f215

13 files changed

Lines changed: 178 additions & 120 deletions

File tree

phpunit.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
executionOrder="defects"
88
defaultTestSuite="main"
99
displayDetailsOnTestsThatTriggerWarnings="true"
10+
displayDetailsOnPhpunitDeprecations="true"
1011
>
1112
<testsuites>
1213
<testsuite name="main">

rector.php

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
use Rector\CodingStyle\Rector\String_\UseClassKeywordForClassNameResolutionRector;
66
use Rector\Config\RectorConfig;
77
use Rector\DeadCode\Rector\ConstFetch\RemovePhpVersionIdCheckRector;
8-
use Rector\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector;
98
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
109

1110
return RectorConfig::configure()
11+
->withComposerBased(twig: true)
1212
->withPreparedSets(
1313
deadCode: true,
1414
codeQuality: true,
@@ -20,10 +20,9 @@
2020
earlyReturn: true,
2121
naming: true,
2222
rectorPreset: true,
23-
// @experimental 2024-06
24-
// twig: true,
2523
phpunitCodeQuality: true
2624
)
25+
->withComposerBased(phpunit: true)
2726
->withPhpSets()
2827
->withPaths([
2928
__DIR__ . '/bin',
@@ -41,9 +40,7 @@
4140
StringClassNameToClassConstantRector::class,
4241
__DIR__ . '/bin/validate-phpstan-version.php',
4342
// tests
44-
'*/Fixture/*',
4543
'*/Fixture*',
46-
'*/Source/*',
4744
'*/Source*',
4845
'*/Expected/*',
4946

@@ -55,6 +52,4 @@
5552
__DIR__ . '/src/Configuration/RectorConfigBuilder.php',
5653
__DIR__ . '/src/Console/Notifier.php',
5754
],
58-
59-
RemoveUnusedPrivatePropertyRector::class => [__DIR__ . '/src/Configuration/RectorConfigBuilder.php'],
6055
]);

src/Bridge/SetProviderCollector.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Rector\Set\Contract\SetProviderInterface;
1111
use Rector\Set\SetProvider\CoreSetProvider;
1212
use Rector\Set\SetProvider\PHPSetProvider;
13+
use Rector\Set\ValueObject\ComposerTriggeredSet;
1314
use Rector\Symfony\Set\SetProvider\SymfonySetProvider;
1415
use Rector\Symfony\Set\SetProvider\TwigSetProvider;
1516

@@ -64,4 +65,15 @@ public function provideSets(): array
6465

6566
return $sets;
6667
}
68+
69+
/**
70+
* @return array<ComposerTriggeredSet>
71+
*/
72+
public function provideComposerTriggeredSets(): array
73+
{
74+
return array_filter(
75+
$this->provideSets(),
76+
fn (SetInterface $set): bool => $set instanceof ComposerTriggeredSet
77+
);
78+
}
6779
}

src/Composer/InstalledPackageResolver.php

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,43 +16,60 @@
1616
final class InstalledPackageResolver
1717
{
1818
/**
19-
* @var array<string, InstalledPackage[]>
19+
* @var InstalledPackage[]
2020
*/
2121
private array $resolvedInstalledPackages = [];
2222

23+
public function __construct(
24+
private readonly ?string $projectDirectory = null
25+
) {
26+
// fallback to root project directory
27+
if ($projectDirectory === null) {
28+
$projectDirectory = getcwd();
29+
}
30+
31+
Assert::directory($projectDirectory);
32+
}
33+
2334
/**
2435
* @return InstalledPackage[]
2536
*/
26-
public function resolve(string $projectDirectory): array
37+
public function resolve(): array
2738
{
2839
// cache
29-
if (isset($this->resolvedInstalledPackages[$projectDirectory])) {
30-
return $this->resolvedInstalledPackages[$projectDirectory];
40+
if ($this->resolvedInstalledPackages !== []) {
41+
return $this->resolvedInstalledPackages;
3142
}
3243

33-
Assert::directory($projectDirectory);
34-
35-
$installedPackagesFilePath = $projectDirectory . '/vendor/composer/installed.json';
44+
$installedPackagesFilePath = $this->projectDirectory . '/vendor/composer/installed.json';
3645
if (! file_exists($installedPackagesFilePath)) {
3746
throw new ShouldNotHappenException(
38-
'The installed package json not found. Make sure you run `composer update` and "vendor/composer/installed.json" file exists'
47+
'The installed package json not found. Make sure you run `composer update` and the "vendor/composer/installed.json" file exists'
3948
);
4049
}
4150

4251
$installedPackageFileContents = FileSystem::read($installedPackagesFilePath);
4352
$installedPackagesFilePath = Json::decode($installedPackageFileContents, true);
4453

54+
$installedPackages = $this->createInstalledPackages($installedPackagesFilePath['packages']);
55+
56+
$this->resolvedInstalledPackages = $installedPackages;
57+
58+
return $installedPackages;
59+
}
60+
61+
/**
62+
* @param mixed[] $packages
63+
* @return InstalledPackage[]
64+
*/
65+
private function createInstalledPackages(array $packages): array
66+
{
4567
$installedPackages = [];
4668

47-
foreach ($installedPackagesFilePath['packages'] as $installedPackage) {
48-
$installedPackages[] = new InstalledPackage(
49-
$installedPackage['name'],
50-
$installedPackage['version_normalized']
51-
);
69+
foreach ($packages as $package) {
70+
$installedPackages[] = new InstalledPackage($package['name'], $package['version_normalized']);
5271
}
5372

54-
$this->resolvedInstalledPackages[$projectDirectory] = $installedPackages;
55-
5673
return $installedPackages;
5774
}
5875
}

src/Configuration/RectorConfigBuilder.php

Lines changed: 38 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Rector\Bridge\SetProviderCollector;
99
use Rector\Bridge\SetRectorsResolver;
1010
use Rector\Caching\Contract\ValueObject\Storage\CacheStorageInterface;
11+
use Rector\Composer\InstalledPackageResolver;
1112
use Rector\Config\Level\CodeQualityLevel;
1213
use Rector\Config\Level\DeadCodeLevel;
1314
use Rector\Config\Level\TypeDeclarationLevel;
@@ -165,11 +166,8 @@ final class RectorConfigBuilder
165166

166167
public function __invoke(RectorConfig $rectorConfig): void
167168
{
168-
// @experimental 2024-06
169169
if ($this->setGroups !== []) {
170-
$setProviderCollector = $rectorConfig->make(SetProviderCollector::class);
171-
$setManager = new SetManager($setProviderCollector);
172-
170+
$setManager = new SetManager(new SetProviderCollector(), new InstalledPackageResolver(getcwd()));
173171
$this->groupLoadedSets = $setManager->matchBySetGroups($this->setGroups);
174172
}
175173

@@ -704,79 +702,48 @@ public function withPreparedSets(
704702
bool $doctrineCodeQuality = false,
705703
bool $symfonyCodeQuality = false,
706704
bool $symfonyConfigs = false,
707-
// composer based
708-
bool $twig = false,
709-
bool $phpunit = false,
710705
): self {
711706
Notifier::notifyNotSuitableMethodForPHP74(__METHOD__);
712707

713-
if ($deadCode) {
714-
$this->sets[] = SetList::DEAD_CODE;
715-
}
716-
717-
if ($codeQuality) {
718-
$this->sets[] = SetList::CODE_QUALITY;
719-
}
720-
721-
if ($codingStyle) {
722-
$this->sets[] = SetList::CODING_STYLE;
723-
}
724-
725-
if ($typeDeclarations) {
726-
$this->sets[] = SetList::TYPE_DECLARATION;
727-
}
728-
729-
if ($privatization) {
730-
$this->sets[] = SetList::PRIVATIZATION;
731-
}
732-
733-
if ($naming) {
734-
$this->sets[] = SetList::NAMING;
735-
}
736-
737-
if ($instanceOf) {
738-
$this->sets[] = SetList::INSTANCEOF;
739-
}
740-
741-
if ($earlyReturn) {
742-
$this->sets[] = SetList::EARLY_RETURN;
743-
}
744-
745-
if ($strictBooleans) {
746-
$this->sets[] = SetList::STRICT_BOOLEANS;
747-
}
748-
749-
if ($carbon) {
750-
$this->sets[] = SetList::CARBON;
751-
}
752-
753-
if ($rectorPreset) {
754-
$this->sets[] = SetList::RECTOR_PRESET;
755-
}
756-
757-
if ($phpunitCodeQuality) {
758-
$this->sets[] = PHPUnitSetList::PHPUNIT_CODE_QUALITY;
759-
}
760-
761-
if ($doctrineCodeQuality) {
762-
$this->sets[] = DoctrineSetList::DOCTRINE_CODE_QUALITY;
763-
}
764-
765-
if ($symfonyCodeQuality) {
766-
$this->sets[] = SymfonySetList::SYMFONY_CODE_QUALITY;
767-
}
768-
769-
if ($symfonyConfigs) {
770-
$this->sets[] = SymfonySetList::CONFIGS;
708+
$setMap = [
709+
$deadCode => SetList::DEAD_CODE,
710+
$codeQuality => SetList::CODE_QUALITY,
711+
$codingStyle => SetList::CODING_STYLE,
712+
$typeDeclarations => SetList::TYPE_DECLARATION,
713+
$privatization => SetList::PRIVATIZATION,
714+
$naming => SetList::NAMING,
715+
$instanceOf => SetList::INSTANCEOF,
716+
$earlyReturn => SetList::EARLY_RETURN,
717+
$strictBooleans => SetList::STRICT_BOOLEANS,
718+
$carbon => SetList::CARBON,
719+
$rectorPreset => SetList::RECTOR_PRESET,
720+
$phpunitCodeQuality => PHPUnitSetList::PHPUNIT_CODE_QUALITY,
721+
$doctrineCodeQuality => DoctrineSetList::DOCTRINE_CODE_QUALITY,
722+
$symfonyCodeQuality => SymfonySetList::SYMFONY_CODE_QUALITY,
723+
$symfonyConfigs => SymfonySetList::CONFIGS,
724+
];
725+
726+
foreach ($setMap as $isEnabled => $setPath) {
727+
if ($isEnabled) {
728+
$this->sets[] = $setPath;
729+
}
771730
}
772731

773-
// @experimental 2024-06
774-
if ($twig) {
775-
$this->setGroups[] = SetGroup::TWIG;
776-
}
732+
return $this;
733+
}
777734

778-
if ($phpunit) {
779-
$this->setGroups[] = SetGroup::PHPUNIT;
735+
public function withComposerBased(bool $twig = false, bool $doctrine = false, bool $phpunit = false): self
736+
{
737+
$setMap = [
738+
$twig => SetGroup::TWIG,
739+
$doctrine => SetGroup::DOCTRINE,
740+
$phpunit => SetGroup::PHPUNIT,
741+
];
742+
743+
foreach ($setMap as $isEnabled => $setPath) {
744+
if ($isEnabled) {
745+
$this->setGroups[] = $setPath;
746+
}
780747
}
781748

782749
return $this;

src/Set/Enum/SetGroup.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,35 @@ final class SetGroup
2020
public const PHP = 'php';
2121

2222
/**
23+
* Version-based set provider
2324
* @var string
2425
*/
2526
public const TWIG = 'twig';
2627

2728
/**
29+
* Version-based set provider
2830
* @var string
2931
*/
3032
public const PHPUNIT = 'phpunit';
3133

3234
/**
35+
* Version-based set provider
3336
* @var string
3437
*/
3538
public const DOCTRINE = 'doctrine';
3639

3740
/**
41+
* Version-based set provider
3842
* @var string
3943
*/
4044
public const SYMFONY = 'symfony';
4145

46+
/**
47+
* Version-based set provider
48+
* @var string
49+
*/
50+
public const LARAVEL = 'laravel';
51+
4252
/**
4353
* @var string
4454
*/

src/Set/SetManager.php

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Rector\Bridge\SetProviderCollector;
88
use Rector\Composer\InstalledPackageResolver;
9+
use Rector\Set\Enum\SetGroup;
910
use Rector\Set\ValueObject\ComposerTriggeredSet;
1011

1112
/**
@@ -14,7 +15,8 @@
1415
final readonly class SetManager
1516
{
1617
public function __construct(
17-
private SetProviderCollector $setProviderCollector
18+
private SetProviderCollector $setProviderCollector,
19+
private InstalledPackageResolver $installedPackageResolver,
1820
) {
1921
}
2022

@@ -25,40 +27,31 @@ public function matchComposerTriggered(string $groupName): array
2527
{
2628
$matchedSets = [];
2729

28-
foreach ($this->setProviderCollector->provideSets() as $set) {
29-
if (! $set instanceof ComposerTriggeredSet) {
30-
continue;
31-
}
32-
33-
if ($set->getGroupName() === $groupName) {
34-
$matchedSets[] = $set;
30+
foreach ($this->setProviderCollector->provideComposerTriggeredSets() as $composerTriggeredSet) {
31+
if ($composerTriggeredSet->getGroupName() === $groupName) {
32+
$matchedSets[] = $composerTriggeredSet;
3533
}
3634
}
3735

3836
return $matchedSets;
3937
}
4038

4139
/**
42-
* @param string[] $setGroups
40+
* @param SetGroup::*[] $setGroups
4341
* @return string[]
4442
*/
4543
public function matchBySetGroups(array $setGroups): array
4644
{
47-
$installedPackageResolver = new InstalledPackageResolver();
48-
$installedComposerPackages = $installedPackageResolver->resolve(getcwd());
49-
45+
$installedComposerPackages = $this->installedPackageResolver->resolve();
5046
$groupLoadedSets = [];
5147

5248
foreach ($setGroups as $setGroup) {
5349
$composerTriggeredSets = $this->matchComposerTriggered($setGroup);
5450

5551
foreach ($composerTriggeredSets as $composerTriggeredSet) {
5652
if ($composerTriggeredSet->matchInstalledPackages($installedComposerPackages)) {
57-
// @todo add debug note somewhere
58-
// echo sprintf('Loaded "%s" set as it meets the conditions', $composerTriggeredSet->getSetFilePath());
59-
6053
// it matched composer package + version requirements → load set
61-
$groupLoadedSets[] = $composerTriggeredSet->getSetFilePath();
54+
$groupLoadedSets[] = realpath($composerTriggeredSet->getSetFilePath());
6255
}
6356
}
6457
}

0 commit comments

Comments
 (0)