diff --git a/src/Set/ValueObject/ComposerTriggeredSet.php b/src/Set/ValueObject/ComposerTriggeredSet.php index 13725c12300..eac453f6de5 100644 --- a/src/Set/ValueObject/ComposerTriggeredSet.php +++ b/src/Set/ValueObject/ComposerTriggeredSet.php @@ -5,12 +5,14 @@ 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; /** * @api used by extensions + * @see \Rector\Tests\Set\ValueObject\ComposerTriggeredSetTest */ final readonly class ComposerTriggeredSet implements SetInterface { @@ -19,6 +21,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 +59,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([])); + } +}