Merge pull request #21 from dart-lang/fix-pre-release
Fix a pre-release-exclusion bug
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d50c768..53de841 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 1.3.4
+
+* Fix a bug where `VersionRange.allowsAll()`, `VersionRange.allowsAny()`, and
+ `VersionRange.difference()` would return incorrect results for pre-release
+ versions with the same base version number as release versions.
+
# 1.3.3
* Fix a bug where `VersionRange.difference()` with a union constraint that
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index efe105a..d793e7a 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -2,6 +2,9 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'package:collection/collection.dart';
+
+import 'version.dart';
import 'version_range.dart';
/// Returns whether [range1] is immediately next to, but not overlapping,
@@ -29,6 +32,10 @@
if (range1.max == null) return range2.max != null;
if (range2.max == null) return false;
+ // `<1.0.0-dev.1` allows `1.0.0-dev.0` which is higher than any versions
+ // allowed by `<1.0.0`.
+ if (disallowedByPreRelease(range2, range1.max)) return true;
+
var comparison = range1.max.compareTo(range2.max);
if (comparison == 1) return true;
if (comparison == -1) return false;
@@ -39,6 +46,7 @@
/// [range2].
bool strictlyLower(VersionRange range1, VersionRange range2) {
if (range1.max == null || range2.min == null) return false;
+ if (disallowedByPreRelease(range1, range2.min)) return true;
var comparison = range1.max.compareTo(range2.min);
if (comparison == -1) return true;
@@ -48,11 +56,43 @@
/// Returns whether [range1] allows only versions higher than those allowed by
/// [range2].
-bool strictlyHigher(VersionRange range1, VersionRange range2) {
- if (range1.min == null || range2.max == null) return false;
+bool strictlyHigher(VersionRange range1, VersionRange range2) =>
+ strictlyLower(range2, range1);
- var comparison = range1.min.compareTo(range2.max);
- if (comparison == 1) return true;
- if (comparison == -1) return false;
- return !range1.includeMin || !range2.includeMax;
+// Returns whether [other] is disallowed by [range] because we disallow
+// pre-release versions that have the same major, minor, and patch version as
+// the max of a range, but only if neither the max nor the min is a pre-release
+// of that version.
+//
+// This ensures that `^1.2.3` doesn't include `2.0.0-pre`, while also allowing
+// both `>=2.0.0-pre.2 <2.0.0` and `>=1.2.3 <2.0.0-pre.7` to match
+// `2.0.0-pre.5`.
+//
+// It's worth noting that this is different than [NPM's semantics][]. NPM
+// disallows **all** pre-release versions unless their major, minor, and
+// patch numbers match those of a prerelease min or max. This ensures that
+// no prerelease versions will ever be selected if the user doesn't
+// explicitly allow them.
+//
+// [NPM's semantics]: https://www.npmjs.org/doc/misc/semver.html#prerelease-tags
+//
+// Instead, we ensure that release versions will always be preferred over
+// prerelease versions by ordering the release versions first in
+// [Version.prioritize]. This means that constraints like `any` or
+// `>1.2.3` can still match prerelease versions if they're the only things
+// available.
+bool disallowedByPreRelease(VersionRange range, Version other) {
+ var maxIsReleaseOfOther = !range.includeMax &&
+ !range.max.isPreRelease &&
+ other.isPreRelease &&
+ _equalsWithoutPreRelease(other, range.max);
+ var minIsPreReleaseOfOther = range.min != null &&
+ range.min.isPreRelease &&
+ _equalsWithoutPreRelease(other, range.min);
+ return maxIsReleaseOfOther && !minIsPreReleaseOfOther;
}
+
+bool _equalsWithoutPreRelease(Version version1, Version version2) =>
+ version1.major == version2.major &&
+ version1.minor == version2.minor &&
+ version1.patch == version2.patch;
diff --git a/lib/src/version_range.dart b/lib/src/version_range.dart
index bce869b..b615c6f 100644
--- a/lib/src/version_range.dart
+++ b/lib/src/version_range.dart
@@ -90,44 +90,12 @@
if (max != null) {
if (other > max) return false;
if (!includeMax && other == max) return false;
-
- // Disallow pre-release versions that have the same major, minor, and
- // patch version as the max, but only if neither the max nor the min is a
- // pre-release of that version. This ensures that "^1.2.3" doesn't include
- // "2.0.0-pre", while also allowing both ">=2.0.0-pre.2 <2.0.0" and
- // ">=1.2.3 <2.0.0-pre.7" to match "2.0.0-pre.5".
- //
- // It's worth noting that this is different than [NPM's semantics][]. NPM
- // disallows **all** pre-release versions unless their major, minor, and
- // patch numbers match those of a prerelease min or max. This ensures that
- // no prerelease versions will ever be selected if the user doesn't
- // explicitly allow them.
- //
- // [NPM's semantics]: https://www.npmjs.org/doc/misc/semver.html#prerelease-tags
- //
- // Instead, we ensure that release versions will always be preferred over
- // prerelease versions by ordering the release versions first in
- // [Version.prioritize]. This means that constraints like "any" or
- // ">1.2.3" can still match prerelease versions if they're the only things
- // available.
- var maxIsReleaseOfOther = !includeMax &&
- !max.isPreRelease &&
- other.isPreRelease &&
- _equalsWithoutPreRelease(other, max);
- var minIsPreReleaseOfOther = min != null &&
- min.isPreRelease &&
- _equalsWithoutPreRelease(other, min);
- if (maxIsReleaseOfOther && !minIsPreReleaseOfOther) return false;
+ if (disallowedByPreRelease(this, other)) return false;
}
return true;
}
- bool _equalsWithoutPreRelease(Version version1, Version version2) =>
- version1.major == version2.major &&
- version1.minor == version2.minor &&
- version1.patch == version2.patch;
-
bool allowsAll(VersionConstraint other) {
if (other.isEmpty) return true;
if (other is Version) return allows(other);
@@ -137,19 +105,7 @@
}
if (other is VersionRange) {
- if (min != null) {
- if (other.min == null) return false;
- if (min > other.min) return false;
- if (min == other.min && !includeMin && other.includeMin) return false;
- }
-
- if (max != null) {
- if (other.max == null) return false;
- if (max < other.max) return false;
- if (max == other.max && !includeMax && other.includeMax) return false;
- }
-
- return true;
+ return !allowsLower(other, this) && !allowsHigher(other, this);
}
throw new ArgumentError('Unknown VersionConstraint type $other.');
diff --git a/test/version_range_test.dart b/test/version_range_test.dart
index cefd9d3..f011012 100644
--- a/test/version_range_test.dart
+++ b/test/version_range_test.dart
@@ -228,6 +228,50 @@
range.allowsAll(new VersionRange(min: v123, max: v234).union(v140)),
isFalse);
});
+
+ group('pre-release versions', () {
+ test('of inclusive min are excluded', () {
+ var range = new VersionRange(min: v123, includeMin: true);
+
+ expect(
+ range.allowsAll(new VersionConstraint.parse('>1.2.4-dev')), isTrue);
+ expect(range.allowsAll(new VersionConstraint.parse('>1.2.3-dev')),
+ isFalse);
+ });
+
+ test('of non-pre-release max are excluded', () {
+ var range = new VersionRange(max: v234);
+
+ expect(range.allowsAll(new VersionConstraint.parse('<2.3.3')), isTrue);
+ expect(range.allowsAll(new VersionConstraint.parse('<2.3.4-dev')),
+ isFalse);
+ });
+
+ test(
+ 'of non-pre-release max are included if min is a pre-release of the '
+ 'same version', () {
+ var range =
+ new VersionRange(min: new Version.parse('2.3.4-dev.0'), max: v234);
+
+ expect(
+ range.allowsAll(
+ new VersionConstraint.parse('>2.3.4-dev.0 <2.3.4-dev.1')),
+ isTrue);
+ });
+
+ test('of pre-release max are included', () {
+ var range = new VersionRange(max: new Version.parse('2.3.4-dev.2'));
+
+ expect(range.allowsAll(new VersionConstraint.parse('<2.3.4-dev.1')),
+ isTrue);
+ expect(range.allowsAll(new VersionConstraint.parse('<2.3.4-dev.2')),
+ isTrue);
+ expect(range.allowsAll(new VersionConstraint.parse('<=2.3.4-dev.2')),
+ isFalse);
+ expect(range.allowsAll(new VersionConstraint.parse('<2.3.4-dev.3')),
+ isFalse);
+ });
+ });
});
group('allowsAny()', () {
@@ -325,6 +369,52 @@
range.allowsAny(new VersionRange(min: v234, max: v300).union(v010)),
isFalse);
});
+
+ group('pre-release versions', () {
+ test('of inclusive min are excluded', () {
+ var range = new VersionRange(min: v123, includeMin: true);
+
+ expect(
+ range.allowsAny(new VersionConstraint.parse('<1.2.4-dev')), isTrue);
+ expect(range.allowsAny(new VersionConstraint.parse('<1.2.3-dev')),
+ isFalse);
+ });
+
+ test('of non-pre-release max are excluded', () {
+ var range = new VersionRange(max: v234);
+
+ expect(range.allowsAny(new VersionConstraint.parse('>2.3.3')), isTrue);
+ expect(range.allowsAny(new VersionConstraint.parse('>2.3.4-dev')),
+ isFalse);
+ });
+
+ test(
+ 'of non-pre-release max are included if min is a pre-release of the '
+ 'same version', () {
+ var range =
+ new VersionRange(min: new Version.parse('2.3.4-dev.0'), max: v234);
+
+ expect(range.allowsAny(new VersionConstraint.parse('>2.3.4-dev.1')),
+ isTrue);
+ expect(range.allowsAny(new VersionConstraint.parse('>2.3.4')), isFalse);
+
+ expect(range.allowsAny(new VersionConstraint.parse('<2.3.4-dev.1')),
+ isTrue);
+ expect(range.allowsAny(new VersionConstraint.parse('<2.3.4-dev')),
+ isFalse);
+ });
+
+ test('of pre-release max are included', () {
+ var range = new VersionConstraint.parse('<2.3.4-dev.2');
+
+ expect(range.allowsAny(new VersionConstraint.parse('>2.3.4-dev.1')),
+ isTrue);
+ expect(range.allowsAny(new VersionConstraint.parse('>2.3.4-dev.2')),
+ isFalse);
+ expect(range.allowsAny(new VersionConstraint.parse('>2.3.4-dev.3')),
+ isFalse);
+ });
+ });
});
group('intersect()', () {
@@ -614,6 +704,13 @@
[v003, new VersionRange(min: v010)])),
equals(VersionConstraint.empty));
});
+
+ test("with a range with a pre-release min, returns the original", () {
+ expect(
+ new VersionRange(max: v200)
+ .difference(new VersionConstraint.parse(">=2.0.0-dev")),
+ equals(new VersionRange(max: v200)));
+ });
});
test('isEmpty', () {