Skip to content

Commit df57a25

Browse files
committed
Fix a pre-release-exclusion bug
The special logic for excluding 1.0.0-dev from <1.0.0 was only implemented for VersionRange.allows(), not for any other methods. This brings the other methods in line with allows(). Closes flutter#20
1 parent dc16338 commit df57a25

File tree

4 files changed

+150
-33
lines changed

4 files changed

+150
-33
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 1.3.4
2+
3+
* Fix a bug where `VersionRange.allowsAll()`, `VersionRange.allowsAny()`, and
4+
`VersionRange.difference()` would return incorrect results for pre-release
5+
versions with the same base version number as release versions.
6+
17
# 1.3.3
28

39
* Fix a bug where `VersionRange.difference()` with a union constraint that

lib/src/utils.dart

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:collection/collection.dart';
6+
7+
import 'version.dart';
58
import 'version_range.dart';
69

710
/// Returns whether [range1] is immediately next to, but not overlapping,
@@ -29,6 +32,10 @@ bool allowsHigher(VersionRange range1, VersionRange range2) {
2932
if (range1.max == null) return range2.max != null;
3033
if (range2.max == null) return false;
3134

35+
// `<1.0.0-dev.1` allows `1.0.0-dev.0` which is higher than any versions
36+
// allowed by `<1.0.0`.
37+
if (disallowedByPreRelease(range2, range1.max)) return true;
38+
3239
var comparison = range1.max.compareTo(range2.max);
3340
if (comparison == 1) return true;
3441
if (comparison == -1) return false;
@@ -39,6 +46,7 @@ bool allowsHigher(VersionRange range1, VersionRange range2) {
3946
/// [range2].
4047
bool strictlyLower(VersionRange range1, VersionRange range2) {
4148
if (range1.max == null || range2.min == null) return false;
49+
if (disallowedByPreRelease(range1, range2.min)) return true;
4250

4351
var comparison = range1.max.compareTo(range2.min);
4452
if (comparison == -1) return true;
@@ -50,3 +58,41 @@ bool strictlyLower(VersionRange range1, VersionRange range2) {
5058
/// [range2].
5159
bool strictlyHigher(VersionRange range1, VersionRange range2) =>
5260
strictlyLower(range2, range1);
61+
62+
// Returns whether [other] is disallowed by [range] because we disallow
63+
// pre-release versions that have the same major, minor, and patch version as
64+
// the max of a range, but only if neither the max nor the min is a pre-release
65+
// of that version.
66+
//
67+
// This ensures that `^1.2.3` doesn't include `2.0.0-pre`, while also allowing
68+
// both `>=2.0.0-pre.2 <2.0.0` and `>=1.2.3 <2.0.0-pre.7` to match
69+
// `2.0.0-pre.5`.
70+
//
71+
// It's worth noting that this is different than [NPM's semantics][]. NPM
72+
// disallows **all** pre-release versions unless their major, minor, and
73+
// patch numbers match those of a prerelease min or max. This ensures that
74+
// no prerelease versions will ever be selected if the user doesn't
75+
// explicitly allow them.
76+
//
77+
// [NPM's semantics]: https://www.npmjs.org/doc/misc/semver.html#prerelease-tags
78+
//
79+
// Instead, we ensure that release versions will always be preferred over
80+
// prerelease versions by ordering the release versions first in
81+
// [Version.prioritize]. This means that constraints like `any` or
82+
// `>1.2.3` can still match prerelease versions if they're the only things
83+
// available.
84+
bool disallowedByPreRelease(VersionRange range, Version other) {
85+
var maxIsReleaseOfOther = !range.includeMax &&
86+
!range.max.isPreRelease &&
87+
other.isPreRelease &&
88+
_equalsWithoutPreRelease(other, range.max);
89+
var minIsPreReleaseOfOther = range.min != null &&
90+
range.min.isPreRelease &&
91+
_equalsWithoutPreRelease(other, range.min);
92+
return maxIsReleaseOfOther && !minIsPreReleaseOfOther;
93+
}
94+
95+
bool _equalsWithoutPreRelease(Version version1, Version version2) =>
96+
version1.major == version2.major &&
97+
version1.minor == version2.minor &&
98+
version1.patch == version2.patch;

lib/src/version_range.dart

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -90,44 +90,12 @@ class VersionRange implements Comparable<VersionRange>, VersionConstraint {
9090
if (max != null) {
9191
if (other > max) return false;
9292
if (!includeMax && other == max) return false;
93-
94-
// Disallow pre-release versions that have the same major, minor, and
95-
// patch version as the max, but only if neither the max nor the min is a
96-
// pre-release of that version. This ensures that "^1.2.3" doesn't include
97-
// "2.0.0-pre", while also allowing both ">=2.0.0-pre.2 <2.0.0" and
98-
// ">=1.2.3 <2.0.0-pre.7" to match "2.0.0-pre.5".
99-
//
100-
// It's worth noting that this is different than [NPM's semantics][]. NPM
101-
// disallows **all** pre-release versions unless their major, minor, and
102-
// patch numbers match those of a prerelease min or max. This ensures that
103-
// no prerelease versions will ever be selected if the user doesn't
104-
// explicitly allow them.
105-
//
106-
// [NPM's semantics]: https://www.npmjs.org/doc/misc/semver.html#prerelease-tags
107-
//
108-
// Instead, we ensure that release versions will always be preferred over
109-
// prerelease versions by ordering the release versions first in
110-
// [Version.prioritize]. This means that constraints like "any" or
111-
// ">1.2.3" can still match prerelease versions if they're the only things
112-
// available.
113-
var maxIsReleaseOfOther = !includeMax &&
114-
!max.isPreRelease &&
115-
other.isPreRelease &&
116-
_equalsWithoutPreRelease(other, max);
117-
var minIsPreReleaseOfOther = min != null &&
118-
min.isPreRelease &&
119-
_equalsWithoutPreRelease(other, min);
120-
if (maxIsReleaseOfOther && !minIsPreReleaseOfOther) return false;
93+
if (disallowedByPreRelease(this, other)) return false;
12194
}
12295

12396
return true;
12497
}
12598

126-
bool _equalsWithoutPreRelease(Version version1, Version version2) =>
127-
version1.major == version2.major &&
128-
version1.minor == version2.minor &&
129-
version1.patch == version2.patch;
130-
13199
bool allowsAll(VersionConstraint other) {
132100
if (other.isEmpty) return true;
133101
if (other is Version) return allows(other);

test/version_range_test.dart

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,50 @@ main() {
228228
range.allowsAll(new VersionRange(min: v123, max: v234).union(v140)),
229229
isFalse);
230230
});
231+
232+
group('pre-release versions', () {
233+
test('of inclusive min are excluded', () {
234+
var range = new VersionRange(min: v123, includeMin: true);
235+
236+
expect(
237+
range.allowsAll(new VersionConstraint.parse('>1.2.4-dev')), isTrue);
238+
expect(range.allowsAll(new VersionConstraint.parse('>1.2.3-dev')),
239+
isFalse);
240+
});
241+
242+
test('of non-pre-release max are excluded', () {
243+
var range = new VersionRange(max: v234);
244+
245+
expect(range.allowsAll(new VersionConstraint.parse('<2.3.3')), isTrue);
246+
expect(range.allowsAll(new VersionConstraint.parse('<2.3.4-dev')),
247+
isFalse);
248+
});
249+
250+
test(
251+
'of non-pre-release max are included if min is a pre-release of the '
252+
'same version', () {
253+
var range =
254+
new VersionRange(min: new Version.parse('2.3.4-dev.0'), max: v234);
255+
256+
expect(
257+
range.allowsAll(
258+
new VersionConstraint.parse('>2.3.4-dev.0 <2.3.4-dev.1')),
259+
isTrue);
260+
});
261+
262+
test('of pre-release max are included', () {
263+
var range = new VersionRange(max: new Version.parse('2.3.4-dev.2'));
264+
265+
expect(range.allowsAll(new VersionConstraint.parse('<2.3.4-dev.1')),
266+
isTrue);
267+
expect(range.allowsAll(new VersionConstraint.parse('<2.3.4-dev.2')),
268+
isTrue);
269+
expect(range.allowsAll(new VersionConstraint.parse('<=2.3.4-dev.2')),
270+
isFalse);
271+
expect(range.allowsAll(new VersionConstraint.parse('<2.3.4-dev.3')),
272+
isFalse);
273+
});
274+
});
231275
});
232276

233277
group('allowsAny()', () {
@@ -325,6 +369,52 @@ main() {
325369
range.allowsAny(new VersionRange(min: v234, max: v300).union(v010)),
326370
isFalse);
327371
});
372+
373+
group('pre-release versions', () {
374+
test('of inclusive min are excluded', () {
375+
var range = new VersionRange(min: v123, includeMin: true);
376+
377+
expect(
378+
range.allowsAny(new VersionConstraint.parse('<1.2.4-dev')), isTrue);
379+
expect(range.allowsAny(new VersionConstraint.parse('<1.2.3-dev')),
380+
isFalse);
381+
});
382+
383+
test('of non-pre-release max are excluded', () {
384+
var range = new VersionRange(max: v234);
385+
386+
expect(range.allowsAny(new VersionConstraint.parse('>2.3.3')), isTrue);
387+
expect(range.allowsAny(new VersionConstraint.parse('>2.3.4-dev')),
388+
isFalse);
389+
});
390+
391+
test(
392+
'of non-pre-release max are included if min is a pre-release of the '
393+
'same version', () {
394+
var range =
395+
new VersionRange(min: new Version.parse('2.3.4-dev.0'), max: v234);
396+
397+
expect(range.allowsAny(new VersionConstraint.parse('>2.3.4-dev.1')),
398+
isTrue);
399+
expect(range.allowsAny(new VersionConstraint.parse('>2.3.4')), isFalse);
400+
401+
expect(range.allowsAny(new VersionConstraint.parse('<2.3.4-dev.1')),
402+
isTrue);
403+
expect(range.allowsAny(new VersionConstraint.parse('<2.3.4-dev')),
404+
isFalse);
405+
});
406+
407+
test('of pre-release max are included', () {
408+
var range = new VersionConstraint.parse('<2.3.4-dev.2');
409+
410+
expect(range.allowsAny(new VersionConstraint.parse('>2.3.4-dev.1')),
411+
isTrue);
412+
expect(range.allowsAny(new VersionConstraint.parse('>2.3.4-dev.2')),
413+
isFalse);
414+
expect(range.allowsAny(new VersionConstraint.parse('>2.3.4-dev.3')),
415+
isFalse);
416+
});
417+
});
328418
});
329419

330420
group('intersect()', () {
@@ -614,6 +704,13 @@ main() {
614704
[v003, new VersionRange(min: v010)])),
615705
equals(VersionConstraint.empty));
616706
});
707+
708+
test("with a range with a pre-release min, returns the original", () {
709+
expect(
710+
new VersionRange(max: v200)
711+
.difference(new VersionConstraint.parse(">=2.0.0-dev")),
712+
equals(new VersionRange(max: v200)));
713+
});
617714
});
618715

619716
test('isEmpty', () {

0 commit comments

Comments
 (0)