Skip to content

Commit

Permalink
Merge branch '1.x' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Seldaek committed Sep 8, 2020
2 parents c6e35eb + 9787c20 commit c64053b
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 21 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

### [3.1.0] 2020-09-08

* Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 3.0.1
* Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package

### [3.0.1] 2020-09-08

* Fixed: handling of some invalid -dev versions which were seen as valid
Expand All @@ -26,6 +31,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
* Added `getUpperBound` and `getLowerBound` to ConstraintInterface. They return `Composer\Semver\Constraint\Bound` instances
* Added `MultiConstraint::create` to create the most-optimal form of ConstraintInterface from an array of constraint strings

### [1.6.0] 2020-09-08

* Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 1.5.2
* Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package

### [1.5.2] 2020-09-08

* Fixed: handling of some invalid -dev versions which were seen as valid
Expand Down Expand Up @@ -94,9 +104,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Namespace: `Composer\Test\Package\LinkConstraint` -> `Composer\Test\Semver\Constraint`
* Changed: code style using php-cs-fixer.

[3.1.0]: https://github.com/composer/semver/compare/3.0.1...3.1.0
[3.0.1]: https://github.com/composer/semver/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/composer/semver/compare/2.0.0...3.0.0
[2.0.0]: https://github.com/composer/semver/compare/1.5.1...2.0.0
[1.6.0]: https://github.com/composer/semver/compare/1.5.2...1.6.0
[1.5.2]: https://github.com/composer/semver/compare/1.5.1...1.5.2
[1.5.1]: https://github.com/composer/semver/compare/1.5.0...1.5.1
[1.5.0]: https://github.com/composer/semver/compare/1.4.2...1.5.0
Expand Down
41 changes: 30 additions & 11 deletions src/VersionParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class VersionParser
*/
private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';

/** @var array */
private static $stabilities = array('stable', 'RC', 'beta', 'alpha', 'dev');
/** @var string */
private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev';

/**
* Returns the stability of a version.
Expand Down Expand Up @@ -102,18 +102,21 @@ public static function normalizeStability($stability)
public function normalize($version, $fullVersion = null)
{
$version = trim($version);
$origVersion = $version;
if (null === $fullVersion) {
$fullVersion = $version;
}

// strip off aliasing
if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
// verify that the alias is a version without constraint
$this->normalize($match[2]);

$version = $match[1];
}

// strip off stability flag
if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) {
$version = substr($version, 0, strlen($version) - strlen($match[0]));
}

// normalize master/trunk/default branches to dev-name for BC with 1.x as these used to be valid constraints
if (\in_array($version, array('master', 'trunk', 'default'), true)) {
$version = 'dev-' . $version;
Expand Down Expand Up @@ -173,13 +176,13 @@ public function normalize($version, $fullVersion = null)
}

$extraMessage = '';
if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) {
if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))?$}', $fullVersion)) {
$extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
} elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) {
} elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))? +as +}', $fullVersion)) {
$extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
}

throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage);
throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage);
}

/**
Expand Down Expand Up @@ -248,7 +251,7 @@ public function parseConstraints($constraints)
{
$prettyConstraint = $constraints;

if (preg_match('{^([^,\s]*?)@(' . implode('|', self::$stabilities) . ')$}i', $constraints, $match)) {
if (preg_match('{^([^,\s]*?)@(?:' . self::$stabilitiesRegex . ')$}i', $constraints, $match)) {
$constraints = empty($match[1]) ? '*' : $match[1];
}

Expand Down Expand Up @@ -297,7 +300,7 @@ public function parseConstraints($constraints)
*/
private function parseConstraint($constraint)
{
if (preg_match('{^([^,\s]+?)@(' . implode('|', self::$stabilities) . ')$}i', $constraint, $match)) {
if (preg_match('{^([^,\s]+?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) {
$constraint = $match[1];
if ($match[2] !== 'stable') {
$stabilityModifier = $match[2];
Expand All @@ -312,7 +315,7 @@ private function parseConstraint($constraint)
return array(new MatchAllConstraint());
}

$versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?' . self::$modifierRegex . '(?:\+[^\s]+)?';
$versionRegex = 'v?(\d++)(?:\.(\d++|[xX*]))?(?:\.(\d++|[xX*]))?(?:\.(\d++|[xX*]))?' . self::$modifierRegex . '(?:\+[^\s]+)?';

// Tilde Range
//
Expand All @@ -338,6 +341,13 @@ private function parseConstraint($constraint)
$position = 1;
}

// make sure all Xs are converted to the 9999999 it represents
for ($i = $position; $i >= 0; $i--) {
if ($matches[$i] === 'x' || $matches[$i] === 'X' || $matches[$i] === '*') {
$matches[$i] = '9999999';
}
}

// Calculate the stability suffix
$stabilitySuffix = '';
if (empty($matches[5]) && empty($matches[7])) {
Expand Down Expand Up @@ -374,6 +384,11 @@ private function parseConstraint($constraint)
$position = 3;
}

// support ^0.x resolving to 0.9999 - 1.0-dev
if ($position === 2 && ($matches[2] === 'x' || $matches[2] === 'X' || $matches[2] === '*')) {
$position = 1;
}

// Calculate the stability suffix
$stabilitySuffix = '';
if (empty($matches[5]) && empty($matches[7])) {
Expand Down Expand Up @@ -445,6 +460,10 @@ private function parseConstraint($constraint)
$upperBound = new Constraint('<=', $highVersion);
} else {
$highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]);

// validate to version
$this->normalize($matches['to']);

$highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev';
$upperBound = new Constraint('<', $highVersion);
}
Expand Down
77 changes: 67 additions & 10 deletions tests/VersionParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ public function successfulNormalizedVersions()
'parses arbitrary/3' => array('dev-feature/foo', 'dev-feature/foo'),
'parses arbitrary/4' => array('dev-feature+issue-1', 'dev-feature+issue-1'),
'ignores aliases' => array('dev-master as 1.0.0', 'dev-master'),
'ignores aliases/2' => array('dev-load-varnish-only-when-used as ^2.0', 'dev-load-varnish-only-when-used'),
'ignores aliases/3' => array('dev-load-varnish-only-when-used@dev as ^2.0@dev', 'dev-load-varnish-only-when-used'),
'ignores stability' => array('1.0.0+foo@dev', '1.0.0.0'),
'ignores stability/2' => array('dev-load-varnish-only-when-used@stable', 'dev-load-varnish-only-when-used'),
'semver metadata/2' => array('1.0.0-beta.5+foo', '1.0.0.0-beta5'),
'semver metadata/3' => array('1.0.0+foo', '1.0.0.0'),
'semver metadata/4' => array('1.0.0-alpha.3.1+foo', '1.0.0.0-alpha3.1'),
Expand Down Expand Up @@ -133,27 +137,63 @@ public function failingNormalizedVersions()
'non-dev arbitrary' => array('feature-foo'),
'metadata w/ space' => array('1.0.0+foo bar'),
'maven style release' => array('1.0.1-SNAPSHOT'),
'Alias and caret' => array('1.0.0+foo as ^2.0'),
'Alias and tilde' => array('1.0.0+foo as ~2.0'),
'Alias and greater than' => array('1.0.0+foo as >2.0'),
'Alias and less than' => array('1.0.0+foo as <2.0'),
'dev with less than' => array('1.0.0<1.0.5-dev'),
'dev with less than/2' => array('1.0.0-dev<1.0.5-dev'),
'no version, no alias' => array(' as '),
'no version, only alias' => array(' as 1.2'),
);
}

public function testNormalizeFailsWithExtraMessage()
/**
* @dataProvider failingNormalizedVersionsWithBadAlias
*/
public function testNormalizeFailsAndReportsAliasIssue($fullInput)
{
$this->doExpectException('UnexpectedValueException');
preg_match('{^([^,\s#]+)(?:#[^ ]+)? +as +([^,\s]+)$}', $fullInput, $match);
$parser = new VersionParser();
$parser->normalize('', ' as ');
$parser->normalize($match[1], $fullInput);
try {
$parser->normalize($match[2], $fullInput);
} catch (\UnexpectedValueException $e) {
$this->assertEquals('Invalid version string "'.$match[2].'" in "'.$fullInput.'", the alias must be an exact version', $e->getMessage());
}
}

public function testNormalizeFailsWithOtherExtraMessage()
public function failingNormalizedVersionsWithBadAlias()
{
$this->doExpectException('UnexpectedValueException');
return array(
'Alias and caret' => array('1.0.0+foo as ^2.0'),
'Alias and tilde' => array('1.0.0+foo as ~2.0'),
'Alias and greater than' => array('1.0.0+foo as >2.0'),
'Alias and less than' => array('1.0.0+foo as <2.0'),
'Bad alias with stability' => array('1.0.0+foo@dev as <2.0@dev'),
);
}

/**
* @dataProvider failingNormalizedVersionsWithBadAliasee
*/
public function testNormalizeFailsAndReportsAliaseeIssue($fullInput)
{
preg_match('{^([^,\s#]+)(?:#[^ ]+)? +as +([^,\s]+)$}', $fullInput, $match);
$parser = new VersionParser();
$parser->normalize('', ' as 123');
try {
$parser->normalize($match[1], $fullInput);
} catch (\UnexpectedValueException $e) {
$this->assertEquals('Invalid version string "'.$match[1].'" in "'.$fullInput.'", the alias source must be an exact version, if it is a branch name you should prefix it with dev-', $e->getMessage());
}
$parser->normalize($match[2], $fullInput);
}

public function failingNormalizedVersionsWithBadAliasee()
{
return array(
'Alias and caret' => array('^2.0 as 1.0.0+foo'),
'Alias and tilde' => array('~2.0 as 1.0.0+foo'),
'Alias and greater than' => array('>2.0 as 1.0.0+foo'),
'Alias and less than' => array('<2.0 as 1.0.0+foo'),
'Bad aliasee with stability' => array('<2.0@dev as 1.2.3@dev'),
);
}

/**
Expand Down Expand Up @@ -190,6 +230,8 @@ public function testParseConstraintsIgnoresStabilityFlag()
$parser = new VersionParser();

$this->assertSame((string) new Constraint('=', '1.0.0.0'), (string) $parser->parseConstraints('1.0@dev'));
$this->assertSame((string) new Constraint('=', 'dev-load-varnish-only-when-used'), (string) $parser->parseConstraints('dev-load-varnish-only-when-used as ^2.0@dev'));
$this->assertSame((string) new Constraint('=', 'dev-load-varnish-only-when-used'), (string) $parser->parseConstraints('dev-load-varnish-only-when-used@dev as ^2.0@dev'));
}

public function testParseConstraintsIgnoresReferenceOnDevVersion()
Expand Down Expand Up @@ -351,6 +393,11 @@ public function tildeConstraints()
array('~201903.0-beta', new Constraint('>=', '201903.0-beta'), new Constraint('<', '201904.0.0.0-dev')),
array('~201903.0-stable', new Constraint('>=', '201903.0'), new Constraint('<', '201904.0.0.0-dev')),
array('~201903.205830.1-stable', new Constraint('>=', '201903.205830.1'), new Constraint('<', '201903.205831.0.0-dev')),
array('~2.x.x.x-dev', new Constraint('>=', '2.9999999.9999999.9999999-dev'), new Constraint('<', '2.9999999.10000000.0-dev')),
array('~2.0.3.x-dev', new Constraint('>=', '2.0.3.9999999-dev'), new Constraint('<', '2.0.4.0-dev')),
array('~2.0.x-dev', new Constraint('>=', '2.0.9999999.9999999-dev'), new Constraint('<', '2.1.0.0-dev')),
array('~2.x-dev', new Constraint('>=', '2.9999999.9999999.9999999-dev'), new Constraint('<', '3.0.0.0-dev')),
array('~0.x-dev', new Constraint('>=', '0.9999999.9999999.9999999-dev'), new Constraint('<', '1.0.0.0-dev')),
);
}

Expand Down Expand Up @@ -396,6 +443,11 @@ public function caretConstraints()
array('^201903.0', new Constraint('>=', '201903.0-dev'), new Constraint('<', '201904.0.0.0-dev')),
array('^201903.0-beta', new Constraint('>=', '201903.0-beta'), new Constraint('<', '201904.0.0.0-dev')),
array('^201903.205830.1-stable', new Constraint('>=', '201903.205830.1'), new Constraint('<', '201904.0.0.0-dev')),
array('^2.x.x.x-dev', new Constraint('>=', '2.9999999.9999999.9999999-dev'), new Constraint('<', '3.0.0.0-dev')),
array('^2.0.3.x-dev', new Constraint('>=', '2.0.3.9999999-dev'), new Constraint('<', '3.0.0.0-dev')),
array('^2.0.x-dev', new Constraint('>=', '2.0.9999999.9999999-dev'), new Constraint('<', '3.0.0.0-dev')),
array('^2.x-dev', new Constraint('>=', '2.9999999.9999999.9999999-dev'), new Constraint('<', '3.0.0.0-dev')),
array('^0.x-dev', new Constraint('>=', '0.9999999.9999999.9999999-dev'), new Constraint('<', '1.0.0.0-dev')),
);
}

Expand Down Expand Up @@ -434,6 +486,11 @@ public function hyphenConstraints()
array('1 - 2.1', new Constraint('>=', '1.0.0.0-dev'), new Constraint('<', '2.2.0.0-dev')),
array('1.2 - 2.1.0', new Constraint('>=', '1.2.0.0-dev'), new Constraint('<=', '2.1.0.0')),
array('1.3 - 2.1.3', new Constraint('>=', '1.3.0.0-dev'), new Constraint('<=', '2.1.3.0')),
array('2.x.x.x-dev - 3.x.x.x-dev', new Constraint('>=', '2.9999999.9999999.9999999-dev'), new Constraint('<=', '3.9999999.9999999.9999999-dev')),
array('2.0.3.x-dev - 3.0.3.x-dev', new Constraint('>=', '2.0.3.9999999-dev'), new Constraint('<=', '3.0.3.9999999-dev')),
array('2.0.x-dev - 3.0.x-dev', new Constraint('>=', '2.0.9999999.9999999-dev'), new Constraint('<=', '3.0.9999999.9999999-dev')),
array('2.x-dev - 3.x-dev', new Constraint('>=', '2.9999999.9999999.9999999-dev'), new Constraint('<=', '3.9999999.9999999.9999999-dev')),
array('0.x-dev - 1.x-dev', new Constraint('>=', '0.9999999.9999999.9999999-dev'), new Constraint('<=', '1.9999999.9999999.9999999-dev')),
);
}

Expand Down

0 comments on commit c64053b

Please sign in to comment.