From 702439a659ef852620732a4fb9818d71efb80d6e Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 31 Jul 2026 21:34:15 +0200 Subject: [PATCH 1/2] [Set] Allow a version constraint in ComposerTriggeredSet, not only a bare major version --- src/Set/ValueObject/ComposerTriggeredSet.php | 23 ++++++- .../ValueObject/ComposerTriggeredSetTest.php | 61 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/Set/ValueObject/ComposerTriggeredSetTest.php diff --git a/src/Set/ValueObject/ComposerTriggeredSet.php b/src/Set/ValueObject/ComposerTriggeredSet.php index 13725c12300..9568870c2a9 100644 --- a/src/Set/ValueObject/ComposerTriggeredSet.php +++ b/src/Set/ValueObject/ComposerTriggeredSet.php @@ -5,6 +5,7 @@ namespace Rector\Set\ValueObject; use Composer\Semver\Semver; +use Nette\Utils\Strings; use Rector\Composer\ValueObject\InstalledPackage; use Rector\Set\Contract\SetInterface; use Webmozart\Assert\Assert; @@ -19,6 +20,13 @@ */ private const string PACKAGE_REGEX = '#^[a-z0-9-]+\/([a-z0-9-_]+|\*)$#'; + /** + * A bare "10.0" version, that is turned into a "^10.0" constraint + * + * @see https://regex101.com/r/vTJXPU/1 + */ + private const string BARE_VERSION_REGEX = '#^\d+(\.\d+)*$#'; + public function __construct( private string $groupName, private string $packageName, @@ -50,11 +58,24 @@ public function matchInstalledPackages(array $installedPackages): bool return false; } - return Semver::satisfies($package->getVersion(), '^' . $this->version); + return Semver::satisfies($package->getVersion(), $this->resolveVersionConstraint()); } public function getName(): string { return $this->packageName . ' ' . $this->version; } + + /** + * A bare version means "this major version", e.g. "10.0" is "^10.0". Anything else is used as is, + * to allow a set that spans multiple major versions, e.g. ">=10.0" or ">=10.0 <13.0". + */ + private function resolveVersionConstraint(): string + { + if (Strings::match($this->version, self::BARE_VERSION_REGEX) !== null) { + return '^' . $this->version; + } + + return $this->version; + } } diff --git a/tests/Set/ValueObject/ComposerTriggeredSetTest.php b/tests/Set/ValueObject/ComposerTriggeredSetTest.php new file mode 100644 index 00000000000..a735f876404 --- /dev/null +++ b/tests/Set/ValueObject/ComposerTriggeredSetTest.php @@ -0,0 +1,61 @@ + new InstalledPackage('phpunit/phpunit', $installedVersion), + ]; + + $this->assertSame($expectedMatch, $composerTriggeredSet->matchInstalledPackages($installedPackages)); + } + + public static function provideData(): Iterator + { + // a bare version keeps the "this major version" behaviour + yield ['10.0', '10.5.0.0', true]; + yield ['10.0', '11.0.0.0', false]; + yield ['10.0', '9.6.0.0', false]; + + // a constraint is used as is + yield ['>=10.0', '13.2.0.0', true]; + yield ['>=10.0', '9.6.0.0', false]; + yield ['>=10.0 <13.0', '12.5.0.0', true]; + yield ['>=10.0 <13.0', '13.0.0.0', false]; + } + + public function testSkipNotInstalledPackage(): void + { + $composerTriggeredSet = new ComposerTriggeredSet( + SetGroup::PHPUNIT, + 'phpunit/phpunit', + '>=10.0', + __FILE__ + ); + + $this->assertFalse($composerTriggeredSet->matchInstalledPackages([])); + } +} From 8cae9995a8395fec48b1aa7ae8c4be590c7c15fb Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 31 Jul 2026 19:36:02 +0000 Subject: [PATCH 2/2] [ci-review] Rector Rectify --- src/Set/ValueObject/ComposerTriggeredSet.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Set/ValueObject/ComposerTriggeredSet.php b/src/Set/ValueObject/ComposerTriggeredSet.php index 9568870c2a9..eac453f6de5 100644 --- a/src/Set/ValueObject/ComposerTriggeredSet.php +++ b/src/Set/ValueObject/ComposerTriggeredSet.php @@ -12,6 +12,7 @@ /** * @api used by extensions + * @see \Rector\Tests\Set\ValueObject\ComposerTriggeredSetTest */ final readonly class ComposerTriggeredSet implements SetInterface {