diff --git a/src/Intervals.php b/src/Intervals.php index d889d0ad..a4800c5d 100644 --- a/src/Intervals.php +++ b/src/Intervals.php @@ -150,16 +150,37 @@ public static function compactConstraint(ConstraintInterface $constraint) $constraints[] = $intervals['numeric'][0]->getStart(); $hasNumericMatchAll = true; } else { + // A bare != N matches all dev-* versions, so if the numeric part is the whole line minus some points, + // only compact it into unbounded != constraints when the branches already match all dev versions + $numeric = $intervals['numeric']; + $count = \count($numeric); + $skipNotEqual = false; + if (!$intervals['branches']['exclude'] && $count > 1 + && (string) $numeric[0]->getStart() === (string) Interval::fromZero() + && (string) $numeric[$count - 1]->getEnd() === (string) Interval::untilPositiveInfinity() + ) { + $skipNotEqual = true; + for ($i = 0; $i < $count - 1; $i++) { + if ($numeric[$i]->getEnd()->getOperator() !== '<' + || $numeric[$i + 1]->getStart()->getOperator() !== '>' + || $numeric[$i]->getEnd()->getVersion() !== $numeric[$i + 1]->getStart()->getVersion() + ) { + $skipNotEqual = false; + break; + } + } + } + $unEqualConstraints = array(); - for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) { - $interval = $intervals['numeric'][$i]; + for ($i = 0; $i < $count; $i++) { + $interval = $numeric[$i]; // if current interval ends with < N and next interval begins with > N we can swap this out for != N // but this needs to happen as a conjunctive expression together with the start of the current interval // and end of next interval, so [>=M, N, [>=M, !=N, getEnd()->getOperator() === '<' && $i+1 < $count) { - $nextInterval = $intervals['numeric'][$i+1]; + if (!$skipNotEqual && $interval->getEnd()->getOperator() === '<' && $i+1 < $count) { + $nextInterval = $numeric[$i+1]; if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') { // only add a start if we didn't already do so, can be skipped if we're looking at second // interval in [>=M, N, P, assertSame((string) $expected, (string) $new); } + /** + * @dataProvider compactDevSafetyProvider + * @param array $toCompact + * @param bool $conjunctive + */ + public function testCompactConstraintKeepsMatchedSet($toCompact, $conjunctive) + { + $parser = new VersionParser; + + $parts = array(); + foreach ($toCompact as $part) { + $parts[] = $parser->parseConstraints($part); + } + $constraint = new MultiConstraint($parts, $conjunctive); + + $compacted = Intervals::compactConstraint($constraint); + $dev = new Constraint('==', 'dev-foo'); + + $this->assertSame($constraint->matches($dev), $compacted->matches($dev), (string) $compacted); + $this->assertTrue(Intervals::isSubsetOf($compacted, $constraint), (string) $compacted); + $this->assertTrue(Intervals::isSubsetOf($constraint, $compacted), (string) $compacted); + } + + public static function compactDevSafetyProvider() + { + return array( + 'whole line minus one point, no dev' => array(array('> 1.0.0 != 2.0.0', '< 1.9.0'), false), + 'whole line minus two points, no dev' => array(array('> 1.0.0 != 2.0.0 != 3.0.0', '< 1.5'), false), + 'whole line minus one point, named branch' => array(array('> 1.0.0 != 2.0.0', '< 1.5', 'dev-bar'), false), + 'whole line minus two points, named branch' => array(array('> 1.0.0 != 2.0.0 != 3.0.0', '< 1.5', 'dev-bar'), false), + 'bounded != group' => array(array('> 1.0.0 != 2.0.0 < 3.0.0', '< 1.5', '>= 5.0'), false), + 'conjunctive !=, all dev' => array(array('!= 2.0', '!= 3.0'), true), + 'conjunctive != with excluded branch' => array(array('!= 2.0', '!= dev-foo'), true), + 'disjunctive != with named branch' => array(array('!= 2.0', 'dev-bar'), false), + ); + } + public static function compactProvider() { return array( @@ -114,6 +151,21 @@ public static function compactProvider() array('!= 1.0', '!= 1.0', '!= dev-foo', '1.0.5.*'), false ), + 'whole line minus one point without dev does not become a bare !=' => array( + '< 2.0.0.0-stable || > 2.0.0', + array('> 1.0.0 != 2.0.0', '< 1.9.0'), + false + ), + 'whole line minus two points without dev does not become a bare !=' => array( + '< 2.0.0.0-stable || > 2.0.0 < 3.0.0.0-stable || > 3.0.0', + array('> 1.0.0 != 2.0.0 != 3.0.0', '< 1.5'), + false + ), + 'whole line minus two points with named branch does not become a bare !=' => array( + '< 2.0.0.0-stable || > 2.0.0 < 3.0.0.0-stable || > 3.0.0 || dev-bar', + array('> 1.0.0 != 2.0.0 != 3.0.0', '< 1.5', 'dev-bar'), + false + ), 'conjunctive with complex negation' => array( '1.0.5.*', array('!= 1.0', '!= 1.0', '!= dev-foo', '1.0.5.*'),