From 6f0404c0d8bd7a6e072fa30aaa930b2de8bbb5cb Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 10 Aug 2026 13:25:20 +0200 Subject: [PATCH] Resolve the composer-based set of extension packages in withComposerBased() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `withComposerBased(phpunit: true)` and friends load a single set in which every rule is bound to the installed package version, which is what `ComposerPackageConstraintInterface` deprecated `SetProviderInterface` and `ComposerTriggeredSet` in favour of. The `laravel:` and `drupal:` toggles could not follow, because rector-src does not require those packages and cannot import their set list class. They were left on the deprecated set-group path, so an extension that has already migrated to a composer-based set has no way to be picked up by the toggle that is meant to load it. Resolve the extension's set list constant by name instead, so no dependency is needed: private const array EXTENSION_COMPOSER_BASED_SET_LISTS = [ SetGroup::LARAVEL => 'RectorLaravel\Set\LaravelSetList::COMPOSER_BASED', SetGroup::DRUPAL => 'DrupalRector\Set\DrupalSetList::COMPOSER_BASED', ]; When the constant is defined, the set is loaded like the bundled packages' ones. When it is not — the extension is absent, or has not migrated yet — the set group is registered exactly as before, so nothing changes for it. palantirnet/drupal-rector has `DrupalSetList::COMPOSER_BASED` ready on a branch: every rule is registered with the exact `drupal/core` version its deprecation was introduced in, so `withComposerBased(drupal: true)` starts loading it as soon as both are released. --- src/Configuration/RectorConfigBuilder.php | 31 +++++++- .../ExtensionComposerBasedSetTest.php | 73 +++++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 tests/Configuration/ExtensionComposerBasedSetTest.php diff --git a/src/Configuration/RectorConfigBuilder.php b/src/Configuration/RectorConfigBuilder.php index 8569aef678f..97411cb1c03 100644 --- a/src/Configuration/RectorConfigBuilder.php +++ b/src/Configuration/RectorConfigBuilder.php @@ -62,6 +62,17 @@ final class RectorConfigBuilder 'withCodingStyleLevel' => [SetList::CODING_STYLE, 'coding style'], ]; + /** + * The composer-based set of the extensions that rector-src does not require, so their set list class cannot be + * imported here. Resolved at run-time; an extension that ships no such set falls back to its set group. + * + * @var array + */ + private const array EXTENSION_COMPOSER_BASED_SET_LISTS = [ + SetGroup::LARAVEL => 'RectorLaravel\\Set\\LaravelSetList::COMPOSER_BASED', + SetGroup::DRUPAL => 'DrupalRector\\Set\\DrupalSetList::COMPOSER_BASED', + ]; + /** * @var string[] */ @@ -741,10 +752,24 @@ public function withComposerBased( SetGroup::DRUPAL => $drupal, ]; - foreach ($setMap as $setPath => $isEnabled) { - if ($isEnabled) { - $this->setGroups[] = $setPath; + foreach ($setMap as $setGroup => $isEnabled) { + if (! $isEnabled) { + continue; + } + + $setListConstant = self::EXTENSION_COMPOSER_BASED_SET_LISTS[$setGroup]; + if (defined($setListConstant)) { + $setFilePath = constant($setListConstant); + Assert::string($setFilePath); + + // single set, as every rule inside is bound to the installed package version on its own + $this->sets[] = $setFilePath; + continue; } + + // @deprecated fallback for extensions that still describe their sets as objects, + // instead of bonding the rules themselves + $this->setGroups[] = $setGroup; } if ($phpunit) { diff --git a/tests/Configuration/ExtensionComposerBasedSetTest.php b/tests/Configuration/ExtensionComposerBasedSetTest.php new file mode 100644 index 00000000000..de5873a81cc --- /dev/null +++ b/tests/Configuration/ExtensionComposerBasedSetTest.php @@ -0,0 +1,73 @@ +provideExtensionComposerBasedSetLists(); + + self::assertArrayHasKey(SetGroup::LARAVEL, $extensionSetLists); + self::assertArrayHasKey(SetGroup::DRUPAL, $extensionSetLists); + + foreach ($extensionSetLists as $setListConstant) { + self::assertMatchesRegularExpression('#^\w+(\\\\\w+)+::\w+$#', $setListConstant); + } + } + + /** + * The extension packages are not required by rector-src, so their constant is undefined here and the + * deprecated set group has to keep working. + */ + public function testFallsBackToTheSetGroupWhenTheExtensionIsNotInstalled(): void + { + foreach ($this->provideExtensionComposerBasedSetLists() as $setListConstant) { + self::assertFalse(defined($setListConstant), $setListConstant); + } + + $rectorConfigBuilder = new RectorConfigBuilder() + ->withComposerBased(laravel: true, drupal: true); + + self::assertSame([SetGroup::LARAVEL, SetGroup::DRUPAL], $this->readPrivateArray($rectorConfigBuilder, 'setGroups')); + self::assertSame([], $this->readPrivateArray($rectorConfigBuilder, 'sets')); + } + + /** + * @return array + */ + private function provideExtensionComposerBasedSetLists(): array + { + $extensionSetLists = new ReflectionClass(RectorConfigBuilder::class) + ->getConstant('EXTENSION_COMPOSER_BASED_SET_LISTS'); + + self::assertIsArray($extensionSetLists); + + return $extensionSetLists; + } + + /** + * @return mixed[] + */ + private function readPrivateArray(RectorConfigBuilder $rectorConfigBuilder, string $propertyName): array + { + $value = new ReflectionClass($rectorConfigBuilder) + ->getProperty($propertyName) + ->getValue($rectorConfigBuilder); + + self::assertIsArray($value); + + return $value; + } +}