Change the pre-release range semantics.

This allows ">=1.2.3-dev <1.2.3" to work how it looks like it should
work, as opposed to matching no versions at all.

R=rnystrom@google.com

Review URL: https://codereview.chromium.org//1149543002
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 06f7caa..58190a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+# 1.2.1
+
+* Allow version ranges like `>=1.2.3-dev.1 <1.2.3` to match pre-release versions
+  of `1.2.3`. Previously, these didn't match, since the pre-release versions had
+  the same major, minor, and patch numbers as the max; now an exception has been
+  added if they also have the same major, minor, and patch numbers as the min
+  *and* the min is also a pre-release version.
+
 # 1.2.0
 
 * Add a `VersionConstraint.union()` method and a `new
diff --git a/README.md b/README.md
index 3f9558f..b359754 100644
--- a/README.md
+++ b/README.md
@@ -33,10 +33,12 @@
     unstable versions of `2.0.0`'s API, which is what pre-release versions
     represent.
 
-    To handle that, `<` version ranges to not allow pre-release versions of the
-    maximum unless the max is itself a pre-release. In other words, a `<2.0.0`
-    constraint will prohibit not just `2.0.0` but any pre-release of `2.0.0`.
-    However, `<2.0.0-beta` will exclude `2.0.0-beta` but allow `2.0.0-alpha`.
+    To handle that, `<` version ranges don't allow pre-release versions of the
+    maximum unless the max is itself a pre-release, or the min is a pre-release
+    of the same version. In other words, a `<2.0.0` constraint will prohibit not
+    just `2.0.0` but any pre-release of `2.0.0`. However, `<2.0.0-beta` will
+    exclude `2.0.0-beta` but allow `2.0.0-alpha`. Likewise, `>2.0.0-alpha
+    <2.0.0` will exclude `2.0.0-alpha` but allow `2.0.0-beta`.
 
  *  **Pre-release versions are avoided when possible.** The above case
     handles pre-release versions at the top of the range, but what about in
diff --git a/lib/src/version_range.dart b/lib/src/version_range.dart
index 7a18edf..5c53834 100644
--- a/lib/src/version_range.dart
+++ b/lib/src/version_range.dart
@@ -85,21 +85,42 @@
       if (other > max) return false;
       if (!includeMax && other == max) return false;
 
-      // If the max isn't itself a pre-release, don't allow any pre-release
-      // versions of the max.
+
+      // 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".
       //
-      // See: https://www.npmjs.org/doc/misc/semver.html
-      if (!includeMax &&
+      // 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 &&
-          other.major == max.major && other.minor == max.minor &&
-          other.patch == max.patch) {
-        return false;
-      }
+          _equalsWithoutPreRelease(other, max);
+      var minIsPreReleaseOfOther = min != null && min.isPreRelease &&
+          _equalsWithoutPreRelease(other, min);
+      if (maxIsReleaseOfOther && !minIsPreReleaseOfOther) 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);
diff --git a/test/version_range_test.dart b/test/version_range_test.dart
index ef35d80..6df6b31 100644
--- a/test/version_range_test.dart
+++ b/test/version_range_test.dart
@@ -105,6 +105,18 @@
           new Version.parse('2.3.4')));
     });
 
+    test('pre-release versions 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, allows(new Version.parse('2.3.4-dev.1')));
+      expect(range, doesNotAllow(
+          new Version.parse('2.3.3'),
+          new Version.parse('2.3.4-dev'),
+          new Version.parse('2.3.4')));
+    });
+
     test('pre-release versions of pre-release max are included', () {
       var range = new VersionRange(max: new Version.parse('2.3.4-dev.2'));