Skip to content

Commit 2aa8814

Browse files
committed
[Composer] Target lowest version for any distributed package type, only 'project' is an application
1 parent 9a5dde6 commit 2aa8814

6 files changed

Lines changed: 102 additions & 5 deletions

File tree

‎src/Composer/InstalledPackageResolver.php‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,16 @@ private function createInstalledPackages(array $packages): array
137137
/**
138138
* A library declares a compatibility range in its "composer.json"; the version-specific rules must target the
139139
* lowest declared version, not the one that happens to be installed locally.
140+
*
141+
* Anything but an application counts as a library: a missing "type" or an explicit "project" is an application,
142+
* every other declared type ("library", "symfony-bundle", ...) is a distributed package.
140143
*/
141144
private function isLibrary(): bool
142145
{
143146
$projectComposerJson = $this->loadProjectComposerJson();
147+
$type = $projectComposerJson['type'] ?? null;
144148

145-
return ($projectComposerJson['type'] ?? null) === 'library';
149+
return is_string($type) && $type !== 'project';
146150
}
147151

148152
/**
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"type": "project",
3+
"require": {
4+
"phpunit/phpunit": "^10.5 || ^11.0 || ^12.0",
5+
"symfony/console": "^7.0"
6+
},
7+
"require-dev": {
8+
"nette/utils": "^3.2"
9+
}
10+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"packages": [
3+
{
4+
"name": "phpunit/phpunit",
5+
"version": "12.1.0",
6+
"version_normalized": "12.1.0.0"
7+
},
8+
{
9+
"name": "symfony/console",
10+
"version": "v7.2.0",
11+
"version_normalized": "7.2.0.0"
12+
},
13+
{
14+
"name": "nette/utils",
15+
"version": "v3.2.0",
16+
"version_normalized": "3.2.0.0"
17+
},
18+
{
19+
"name": "webmozart/assert",
20+
"version": "1.11.0",
21+
"version_normalized": "1.11.0.0"
22+
}
23+
]
24+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"type": "symfony-bundle",
3+
"require": {
4+
"phpunit/phpunit": "^10.5 || ^11.0 || ^12.0",
5+
"symfony/console": "^7.0"
6+
},
7+
"require-dev": {
8+
"nette/utils": "^3.2"
9+
}
10+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"packages": [
3+
{
4+
"name": "phpunit/phpunit",
5+
"version": "12.1.0",
6+
"version_normalized": "12.1.0.0"
7+
},
8+
{
9+
"name": "symfony/console",
10+
"version": "v7.2.0",
11+
"version_normalized": "7.2.0.0"
12+
},
13+
{
14+
"name": "nette/utils",
15+
"version": "v3.2.0",
16+
"version_normalized": "3.2.0.0"
17+
},
18+
{
19+
"name": "webmozart/assert",
20+
"version": "1.11.0",
21+
"version_normalized": "1.11.0.0"
22+
}
23+
]
24+
}

‎tests/Composer/InstalledPackageResolverTest.php‎

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Rector\Tests\Composer;
66

7+
use PHPUnit\Framework\Attributes\DataProvider;
78
use PHPUnit\Framework\TestCase;
89
use Rector\Composer\InstalledPackageResolver;
910
use Rector\Composer\ValueObject\InstalledPackage;
@@ -47,11 +48,10 @@ public function testComposerJsonHasPriorityOverOutdatedInstalledJson(): void
4748
$this->assertSame('1.11.0.0', $installedPackageResolver->resolvePackageVersion('webmozart/assert'));
4849
}
4950

50-
public function testLibraryTargetsLowestDeclaredVersionEvenWhenInstalledSatisfies(): void
51+
#[DataProvider('provideLibraryTypeFixtureDirectory')]
52+
public function testLibraryTargetsLowestDeclaredVersionEvenWhenInstalledSatisfies(string $fixtureDirectory): void
5153
{
52-
$installedPackageResolver = new InstalledPackageResolver(
53-
__DIR__ . '/Fixture/InstalledPackageResolver/library_composer_json'
54-
);
54+
$installedPackageResolver = new InstalledPackageResolver($fixtureDirectory);
5555

5656
// installed 12.1.0 satisfies "^10.5 || ^11.0 || ^12.0", yet a library targets the lowest declared version
5757
$this->assertSame('10.5.0.0', $installedPackageResolver->resolvePackageVersion('phpunit/phpunit'));
@@ -66,6 +66,31 @@ public function testLibraryTargetsLowestDeclaredVersionEvenWhenInstalledSatisfie
6666
$this->assertSame('1.11.0.0', $installedPackageResolver->resolvePackageVersion('webmozart/assert'));
6767
}
6868

69+
/**
70+
* @return iterable<string, array{string}>
71+
*/
72+
public static function provideLibraryTypeFixtureDirectory(): iterable
73+
{
74+
// "library" is the default distributed type
75+
yield 'library' => [__DIR__ . '/Fixture/InstalledPackageResolver/library_composer_json'];
76+
77+
// any other non-"project" distributed type behaves the same way
78+
yield 'symfony-bundle' => [__DIR__ . '/Fixture/InstalledPackageResolver/symfony_bundle_composer_json'];
79+
}
80+
81+
public function testProjectTypeKeepsInstalledVersionOverDeclaredRange(): void
82+
{
83+
$installedPackageResolver = new InstalledPackageResolver(
84+
__DIR__ . '/Fixture/InstalledPackageResolver/project_composer_json'
85+
);
86+
87+
// an explicit "project" is an application, so the installed version stands even within a wider range
88+
$this->assertSame('12.1.0.0', $installedPackageResolver->resolvePackageVersion('phpunit/phpunit'));
89+
90+
// installed 7.2.0 satisfies "^7.0", kept as installed
91+
$this->assertSame('7.2.0.0', $installedPackageResolver->resolvePackageVersion('symfony/console'));
92+
}
93+
6994
public function testStandaloneComposerJsonResolvesVersionsWithoutVendor(): void
7095
{
7196
$installedPackageResolver = new InstalledPackageResolver(

0 commit comments

Comments
 (0)