Merge branch 'main' into merge-pub_semver-package
diff --git a/.github/ISSUE_TEMPLATE/pub_semver.md b/.github/ISSUE_TEMPLATE/pub_semver.md
new file mode 100644
index 0000000..c7db9b5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/pub_semver.md
@@ -0,0 +1,5 @@
+---
+name: "package:pub_semver"
+about: "Create a bug or file a feature request against package:pub_semver."
+labels: "package:pub_semver"
+---
\ No newline at end of file
diff --git a/.github/labeler.yml b/.github/labeler.yml
index c4d658f..09e5425 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -88,6 +88,10 @@
   - changed-files:
     - any-glob-to-any-file: 'pkgs/pool/**'
 
+'package:pub_semver':
+  - changed-files:
+    - any-glob-to-any-file: 'pkgs/pub_semver/**'
+
 'package:source_map_stack_trace':
   - changed-files:
     - any-glob-to-any-file: 'pkgs/source_map_stack_trace/**'
diff --git a/.github/workflows/pub_semver.yaml b/.github/workflows/pub_semver.yaml
new file mode 100644
index 0000000..ba0db18
--- /dev/null
+++ b/.github/workflows/pub_semver.yaml
@@ -0,0 +1,75 @@
+name: package:pub_semver
+
+on:
+  # Run on PRs and pushes to the default branch.
+  push:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/pub_semver.yaml'
+      - 'pkgs/pub_semver/**'
+  pull_request:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/pub_semver.yaml'
+      - 'pkgs/pub_semver/**'
+  schedule:
+    - cron: "0 0 * * 0"
+
+env:
+  PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+  run:
+    working-directory: pkgs/pub_semver/
+
+jobs:
+  # Check code formatting and static analysis on a single OS (linux)
+  # against Dart dev.
+  analyze:
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        sdk: [dev]
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+        with:
+          sdk: ${{ matrix.sdk }}
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: Check formatting
+        run: dart format --output=none --set-exit-if-changed .
+        if: always() && steps.install.outcome == 'success'
+      - name: Analyze code
+        run: dart analyze --fatal-infos
+        if: always() && steps.install.outcome == 'success'
+
+  # Run tests on a matrix consisting of two dimensions:
+  # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+  # 2. release channel: dev
+  test:
+    needs: analyze
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        # Add macos-latest and/or windows-latest if relevant for this package.
+        os: [ubuntu-latest]
+        sdk: [3.4, dev]
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+        with:
+          sdk: ${{ matrix.sdk }}
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: Run VM tests
+        run: dart test --platform vm
+        if: always() && steps.install.outcome == 'success'
+      - name: Run Chrome tests
+        run: dart test --platform chrome --compiler dart2js,dart2wasm
+        if: always() && steps.install.outcome == 'success'
diff --git a/README.md b/README.md
index 0b97b21..7846ba9 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@
 | [oauth2](pkgs/oauth2/) | A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. | [![package issues](https://img.shields.io/badge/package:oauth2-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aoauth2) | [![pub package](https://img.shields.io/pub/v/oauth2.svg)](https://pub.dev/packages/oauth2) |
 | [package_config](pkgs/package_config/) | Support for reading and writing Dart Package Configuration files. | [![package issues](https://img.shields.io/badge/package:package_config-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apackage_config) | [![pub package](https://img.shields.io/pub/v/package_config.svg)](https://pub.dev/packages/package_config) |
 | [pool](pkgs/pool/) | Manage a finite pool of resources. Useful for controlling concurrent file system or network requests. | [![package issues](https://img.shields.io/badge/package:pool-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apool) | [![pub package](https://img.shields.io/pub/v/pool.svg)](https://pub.dev/packages/pool) |
+| [pub_semver](pkgs/pub_semver/) | Versions and version constraints implementing pub's versioning policy. This is very similar to vanilla semver, with a few corner cases. | [![package issues](https://img.shields.io/badge/package:pub_semver-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apub_semver) | [![pub package](https://img.shields.io/pub/v/pub_semver.svg)](https://pub.dev/packages/pub_semver) |
 | [source_map_stack_trace](pkgs/source_map_stack_trace/) | A package for applying source maps to stack traces. | [![package issues](https://img.shields.io/badge/package:source_map_stack_trace-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Asource_map_stack_trace) | [![pub package](https://img.shields.io/pub/v/source_map_stack_trace.svg)](https://pub.dev/packages/source_map_stack_trace) |
 | [unified_analytics](pkgs/unified_analytics/) | A package for logging analytics for all Dart and Flutter related tooling to Google Analytics. | [![package issues](https://img.shields.io/badge/package:unified_analytics-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics) | [![pub package](https://img.shields.io/pub/v/unified_analytics.svg)](https://pub.dev/packages/unified_analytics) |
 
diff --git a/pkgs/pub_semver/.gitignore b/pkgs/pub_semver/.gitignore
new file mode 100644
index 0000000..49ce72d
--- /dev/null
+++ b/pkgs/pub_semver/.gitignore
@@ -0,0 +1,3 @@
+.dart_tool/
+.packages
+pubspec.lock
diff --git a/pkgs/pub_semver/CHANGELOG.md b/pkgs/pub_semver/CHANGELOG.md
new file mode 100644
index 0000000..a31fbb2
--- /dev/null
+++ b/pkgs/pub_semver/CHANGELOG.md
@@ -0,0 +1,177 @@
+## 2.1.5
+
+- Require Dart `3.4.0`.
+- Move to `dart-lang/tools` monorepo.
+
+## 2.1.4
+
+- Added topics to `pubspec.yaml`.
+
+## 2.1.3
+
+- Add type parameters to the signatures of the `Version.preRelease` and
+  `Version.build` fields (`List` ==> `List<Object>`).
+  [#74](https://github.com/dart-lang/pub_semver/pull/74).
+- Require Dart 2.17.
+
+## 2.1.2
+
+- Add markdown badges to the readme.
+
+## 2.1.1
+
+- Fixed the version parsing pattern to only accept dots between version
+  components.
+
+## 2.1.0
+
+- Added `Version.canonicalizedVersion` to help scrub leading zeros and highlight
+  that `Version.toString()` preserves leading zeros.
+- Annotated `Version` with `@sealed` to discourage users from implementing the
+  interface.
+
+## 2.0.0
+
+- Stable null safety release.
+- `Version.primary` now throws `StateError` if the `versions` argument is empty.
+
+## 1.4.4
+
+- Fix a bug of `VersionRange.union` where ranges bounded at infinity would get
+  combined wrongly.
+
+# 1.4.3
+
+- Update Dart SDK constraint to `>=2.0.0 <3.0.0`.
+- Update `package:collection` constraint to `^1.0.0`.
+
+## 1.4.2
+
+* Set max SDK version to `<3.0.0`.
+
+## 1.4.1
+
+* Fix a bug where there upper bound of a version range with a build identifier
+  could accidentally be rewritten.
+
+## 1.4.0
+
+* Add a `Version.firstPreRelease` getter that returns the first possible
+  pre-release of a version.
+
+* Add a `Version.isFirstPreRelease` getter that returns whether a version is the
+  first possible pre-release.
+
+* `new VersionRange()` with an exclusive maximum now replaces the maximum with
+  its first pre-release version. This matches the existing semantics, where an
+  exclusive maximum would exclude pre-release versions of that maximum.
+
+  Explicitly representing this by changing the maximum version ensures that all
+  operations behave correctly with respect to the special pre-release semantics.
+  In particular, it fixes bugs where, for example,
+  `(>=1.0.0 <2.0.0-dev).union(>=2.0.0-dev <2.0.0)` and
+  `(>=1.0.0 <3.0.0).difference(^1.0.0)` wouldn't include `2.0.0-dev`.
+
+* Add an `alwaysIncludeMaxPreRelease` parameter to `new VersionRange()`, which
+  disables the replacement described above and allows users to create ranges
+  that do include the pre-release versions of an exclusive max version.
+
+## 1.3.7
+
+* Fix more bugs with `VersionRange.intersect()`, `VersionRange.difference()`,
+  and `VersionRange.union()` involving version ranges with pre-release maximums.
+
+## 1.3.6
+
+* Fix a bug where constraints that only allowed pre-release versions would be
+  parsed as empty constraints.
+
+## 1.3.5
+
+* Fix a bug where `VersionRange.intersect()` would return incorrect results for
+  pre-release versions with the same base version number as release versions.
+
+## 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
+  covered the entire range would crash.
+
+## 1.3.2
+
+* Fix a checked-mode error in `VersionRange.difference()`.
+
+## 1.3.1
+
+* Fix a new strong mode error.
+
+## 1.3.0
+
+* Make the `VersionUnion` class public. This was previously used internally to
+  implement `new VersionConstraint.unionOf()` and `VersionConstraint.union()`.
+  Now it's public so you can use it too.
+
+* Added `VersionConstraint.difference()`. This returns a constraint matching all
+  versions matched by one constraint but not another.
+
+* Make `VersionRange` implement `Comparable<VersionRange>`. Ranges are ordered
+  first by lower bound, then by upper bound.
+
+## 1.2.4
+
+* Fix all remaining strong mode warnings.
+
+## 1.2.3
+
+* Addressed three strong mode warnings.
+
+## 1.2.2
+
+* Make the package analyze under strong mode and compile with the DDC (Dart Dev
+  Compiler). Fix two issues with a private subclass of `VersionConstraint`
+  having different types for overridden methods.
+
+## 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
+  VersionConstraint.unionOf()` constructor. These each return a constraint that
+  matches multiple existing constraints.
+
+* Add a `VersionConstraint.allowsAll()` method, which returns whether one
+  constraint is a superset of another.
+
+* Add a `VersionConstraint.allowsAny()` method, which returns whether one
+  constraint overlaps another.
+
+* `Version` now implements `VersionRange`.
+
+## 1.1.0
+
+* Add support for the `^` operator for compatible versions according to pub's
+  notion of compatibility. `^1.2.3` is equivalent to `>=1.2.3 <2.0.0`; `^0.1.2`
+  is equivalent to `>=0.1.2 <0.2.0`.
+
+* Add `Version.nextBreaking`, which returns the next version that introduces
+  breaking changes after a given version.
+
+* Add `new VersionConstraint.compatibleWith()`, which returns a range covering
+  all versions compatible with a given version.
+
+* Add a custom `VersionRange.hashCode` to make it properly hashable.
+
+## 1.0.0
+
+* Initial release.
diff --git a/pkgs/pub_semver/LICENSE b/pkgs/pub_semver/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/pub_semver/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, the Dart project authors. 
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google LLC nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/pub_semver/README.md b/pkgs/pub_semver/README.md
new file mode 100644
index 0000000..03c92a3
--- /dev/null
+++ b/pkgs/pub_semver/README.md
@@ -0,0 +1,107 @@
+[![Build Status](https://github.com/dart-lang/tools/actions/workflows/pub_semver.yaml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/pub_semver.yaml)
+[![pub package](https://img.shields.io/pub/v/pub_semver.svg)](https://pub.dev/packages/pub_semver)
+[![package publisher](https://img.shields.io/pub/publisher/pub_semver.svg)](https://pub.dev/packages/pub_semver/publisher)
+
+Handles version numbers and version constraints in the same way that [pub][]
+does.
+
+## Semantics
+
+The semantics here very closely follow the
+[Semantic Versioning spec version 2.0.0-rc.1][semver]. It differs from semver
+in a few corner cases:
+
+ *  **Version ordering does take build suffixes into account.** This is unlike
+    semver 2.0.0 but like earlier versions of semver. Version `1.2.3+1` is
+    considered a lower number than `1.2.3+2`.
+
+    Since a package may have published multiple versions that differ only by
+    build suffix, pub still has to pick one of them *somehow*. Semver leaves
+    that issue unresolved, so we just say that build numbers are sorted like
+    pre-release suffixes.
+
+ *  **Pre-release versions are excluded from most max ranges.** Let's say a
+    user is depending on "foo" with constraint `>=1.0.0 <2.0.0` and that "foo"
+    has published these versions:
+
+     *  `1.0.0`
+     *  `1.1.0`
+     *  `1.2.0`
+     *  `2.0.0-alpha`
+     *  `2.0.0-beta`
+     *  `2.0.0`
+     *  `2.1.0`
+
+    Versions `2.0.0` and `2.1.0` are excluded by the constraint since neither
+    matches `<2.0.0`. However, since semver specifies that pre-release versions
+    are lower than the non-prerelease version (i.e. `2.0.0-beta < 2.0.0`, then
+    the `<2.0.0` constraint does technically allow those.
+
+    But that's almost never what the user wants. If their package doesn't work
+    with foo `2.0.0`, it's certainly not likely to work with experimental,
+    unstable versions of `2.0.0`'s API, which is what pre-release versions
+    represent.
+
+    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
+    the middle? What if "foo" has these versions:
+
+     *  `1.0.0`
+     *  `1.2.0-alpha`
+     *  `1.2.0`
+     *  `1.3.0-experimental`
+
+    When a number of versions are valid, pub chooses the best one where "best"
+    usually means "highest numbered". That follows the user's intuition that,
+    all else being equal, they want the latest and greatest. Here, that would
+    mean `1.3.0-experimental`. However, most users don't want to use unstable
+    versions of their dependencies.
+
+    We want pre-releases to be explicitly opt-in so that package consumers
+    don't get unpleasant surprises and so that package maintainers are free to
+    put out pre-releases and get feedback without dragging all of their users
+    onto the bleeding edge.
+
+    To accommodate that, when pub is choosing a version, it uses *priority*
+    order which is different from strict comparison ordering. Any stable
+    version is considered higher priority than any unstable version. The above
+    versions, in priority order, are:
+
+     *  `1.2.0-alpha`
+     *  `1.3.0-experimental`
+     *  `1.0.0`
+     *  `1.2.0`
+
+    This ensures that users only end up with an unstable version when there are
+    no alternatives. Usually this means they've picked a constraint that
+    specifically selects that unstable version -- they've deliberately opted
+    into it.
+
+ *  **There is a notion of compatibility between pre-1.0.0 versions.** Semver
+    deems all pre-1.0.0 versions to be incompatible.  This means that the only
+    way to ensure compatibility when depending on a pre-1.0.0 package is to
+    pin the dependency to an exact version. Pinned version constraints prevent
+    automatic patch and pre-release updates. To avoid this situation, pub
+    defines the "next breaking" version as the version which increments the
+    major version if it's greater than zero, and the minor version otherwise,
+    resets subsequent digits to zero, and strips any pre-release or build
+    suffix.  For example, here are some versions along with their next breaking
+    ones:
+
+    `0.0.3` -> `0.1.0`
+    `0.7.2-alpha` -> `0.8.0`
+    `1.2.3` -> `2.0.0`
+
+    To make use of this, pub defines a "^" operator which yields a version
+    constraint greater than or equal to a given version, but less than its next
+    breaking one.
+
+[pub]: https://pub.dev
+[semver]: https://semver.org/spec/v2.0.0-rc.1.html
diff --git a/pkgs/pub_semver/analysis_options.yaml b/pkgs/pub_semver/analysis_options.yaml
new file mode 100644
index 0000000..76380a0
--- /dev/null
+++ b/pkgs/pub_semver/analysis_options.yaml
@@ -0,0 +1,31 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+  language:
+    strict-casts: true
+    strict-inference: true
+    strict-raw-types: true
+
+linter:
+  rules:
+    - avoid_bool_literals_in_conditional_expressions
+    - avoid_classes_with_only_static_members
+    - avoid_private_typedef_functions
+    - avoid_redundant_argument_values
+    - avoid_returning_this
+    - avoid_unused_constructor_parameters
+    - avoid_void_async
+    - cancel_subscriptions
+    - cascade_invocations
+    - join_return_with_assignment
+    - literal_only_boolean_expressions
+    - missing_whitespace_between_adjacent_strings
+    - no_adjacent_strings_in_list
+    - no_runtimeType_toString
+    - prefer_const_declarations
+    - prefer_expression_function_bodies
+    - unnecessary_await_in_return
+    - use_if_null_to_convert_nulls_to_bools
+    - use_raw_strings
+    - use_string_buffers
diff --git a/pkgs/pub_semver/example/example.dart b/pkgs/pub_semver/example/example.dart
new file mode 100644
index 0000000..890343c
--- /dev/null
+++ b/pkgs/pub_semver/example/example.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// 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:pub_semver/pub_semver.dart';
+
+void main() {
+  final range = VersionConstraint.parse('^2.0.0');
+
+  for (var version in [
+    Version.parse('1.2.3-pre'),
+    Version.parse('2.0.0+123'),
+    Version.parse('3.0.0-dev'),
+  ]) {
+    print('$version ${version.isPreRelease} ${range.allows(version)}');
+  }
+}
diff --git a/pkgs/pub_semver/lib/pub_semver.dart b/pkgs/pub_semver/lib/pub_semver.dart
new file mode 100644
index 0000000..4b6487c
--- /dev/null
+++ b/pkgs/pub_semver/lib/pub_semver.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+export 'src/version.dart';
+export 'src/version_constraint.dart';
+export 'src/version_range.dart' hide CompatibleWithVersionRange;
+export 'src/version_union.dart';
diff --git a/pkgs/pub_semver/lib/src/patterns.dart b/pkgs/pub_semver/lib/src/patterns.dart
new file mode 100644
index 0000000..03119ac
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/patterns.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Regex that matches a version number at the beginning of a string.
+final startVersion = RegExp(r'^' // Start at beginning.
+    r'(\d+)\.(\d+)\.(\d+)' // Version number.
+    r'(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Pre-release.
+    r'(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?'); // Build.
+
+/// Like [startVersion] but matches the entire string.
+final completeVersion = RegExp('${startVersion.pattern}\$');
+
+/// Parses a comparison operator ("<", ">", "<=", or ">=") at the beginning of
+/// a string.
+final startComparison = RegExp(r'^[<>]=?');
+
+/// The "compatible with" operator.
+const compatibleWithChar = '^';
diff --git a/pkgs/pub_semver/lib/src/utils.dart b/pkgs/pub_semver/lib/src/utils.dart
new file mode 100644
index 0000000..a9f714f
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/utils.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// 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 'version.dart';
+import 'version_range.dart';
+
+/// Returns whether [range1] is immediately next to, but not overlapping,
+/// [range2].
+bool areAdjacent(VersionRange range1, VersionRange range2) {
+  if (range1.max != range2.min) return false;
+
+  return (range1.includeMax && !range2.includeMin) ||
+      (!range1.includeMax && range2.includeMin);
+}
+
+/// Returns whether [range1] allows lower versions than [range2].
+bool allowsLower(VersionRange range1, VersionRange range2) {
+  if (range1.min == null) return range2.min != null;
+  if (range2.min == null) return false;
+
+  var comparison = range1.min!.compareTo(range2.min!);
+  if (comparison == -1) return true;
+  if (comparison == 1) return false;
+  return range1.includeMin && !range2.includeMin;
+}
+
+/// Returns whether [range1] allows higher versions than [range2].
+bool allowsHigher(VersionRange range1, VersionRange range2) {
+  if (range1.max == null) return range2.max != null;
+  if (range2.max == null) return false;
+
+  var comparison = range1.max!.compareTo(range2.max!);
+  if (comparison == 1) return true;
+  if (comparison == -1) return false;
+  return range1.includeMax && !range2.includeMax;
+}
+
+/// Returns whether [range1] allows only versions lower than those allowed by
+/// [range2].
+bool strictlyLower(VersionRange range1, VersionRange range2) {
+  if (range1.max == null || range2.min == null) return false;
+
+  var comparison = range1.max!.compareTo(range2.min!);
+  if (comparison == -1) return true;
+  if (comparison == 1) return false;
+  return !range1.includeMax || !range2.includeMin;
+}
+
+/// Returns whether [range1] allows only versions higher than those allowed by
+/// [range2].
+bool strictlyHigher(VersionRange range1, VersionRange range2) =>
+    strictlyLower(range2, range1);
+
+bool equalsWithoutPreRelease(Version version1, Version version2) =>
+    version1.major == version2.major &&
+    version1.minor == version2.minor &&
+    version1.patch == version2.patch;
diff --git a/pkgs/pub_semver/lib/src/version.dart b/pkgs/pub_semver/lib/src/version.dart
new file mode 100644
index 0000000..90f3d53
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/version.dart
@@ -0,0 +1,391 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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 'dart:math' as math;
+
+import 'package:collection/collection.dart';
+import 'package:meta/meta.dart' show sealed;
+
+import 'patterns.dart';
+import 'version_constraint.dart';
+import 'version_range.dart';
+
+/// The equality operator to use for comparing version components.
+const _equality = IterableEquality<Object>();
+
+/// A parsed semantic version number.
+@sealed
+class Version implements VersionConstraint, VersionRange {
+  /// No released version: i.e. "0.0.0".
+  static Version get none => Version(0, 0, 0);
+
+  /// Compares [a] and [b] to see which takes priority over the other.
+  ///
+  /// Returns `1` if [a] takes priority over [b] and `-1` if vice versa. If
+  /// [a] and [b] are equivalent, returns `0`.
+  ///
+  /// Unlike [compareTo], which *orders* versions, this determines which
+  /// version a user is likely to prefer. In particular, it prioritizes
+  /// pre-release versions lower than stable versions, regardless of their
+  /// version numbers. Pub uses this when determining which version to prefer
+  /// when a number of versions are allowed. In that case, it will always
+  /// choose a stable version when possible.
+  ///
+  /// When used to sort a list, orders in ascending priority so that the
+  /// highest priority version is *last* in the result.
+  static int prioritize(Version a, Version b) {
+    // Sort all prerelease versions after all normal versions. This way
+    // the solver will prefer stable packages over unstable ones.
+    if (a.isPreRelease && !b.isPreRelease) return -1;
+    if (!a.isPreRelease && b.isPreRelease) return 1;
+
+    return a.compareTo(b);
+  }
+
+  /// Like [prioritize], but lower version numbers are considered greater than
+  /// higher version numbers.
+  ///
+  /// This still considers prerelease versions to be lower than non-prerelease
+  /// versions. Pub uses this when downgrading -- it chooses the lowest version
+  /// but still excludes pre-release versions when possible.
+  static int antiprioritize(Version a, Version b) {
+    if (a.isPreRelease && !b.isPreRelease) return -1;
+    if (!a.isPreRelease && b.isPreRelease) return 1;
+
+    return b.compareTo(a);
+  }
+
+  /// The major version number: "1" in "1.2.3".
+  final int major;
+
+  /// The minor version number: "2" in "1.2.3".
+  final int minor;
+
+  /// The patch version number: "3" in "1.2.3".
+  final int patch;
+
+  /// The pre-release identifier: "foo" in "1.2.3-foo".
+  ///
+  /// This is split into a list of components, each of which may be either a
+  /// string or a non-negative integer. It may also be empty, indicating that
+  /// this version has no pre-release identifier.
+  final List<Object> preRelease;
+
+  /// The build identifier: "foo" in "1.2.3+foo".
+  ///
+  /// This is split into a list of components, each of which may be either a
+  /// string or a non-negative integer. It may also be empty, indicating that
+  /// this version has no build identifier.
+  final List<Object> build;
+
+  /// The original string representation of the version number.
+  ///
+  /// This preserves textual artifacts like leading zeros that may be left out
+  /// of the parsed version.
+  final String _text;
+
+  @override
+  Version get min => this;
+  @override
+  Version get max => this;
+  @override
+  bool get includeMin => true;
+  @override
+  bool get includeMax => true;
+
+  Version._(this.major, this.minor, this.patch, String? preRelease,
+      String? build, this._text)
+      : preRelease = preRelease == null ? <Object>[] : _splitParts(preRelease),
+        build = build == null ? [] : _splitParts(build) {
+    if (major < 0) throw ArgumentError('Major version must be non-negative.');
+    if (minor < 0) throw ArgumentError('Minor version must be non-negative.');
+    if (patch < 0) throw ArgumentError('Patch version must be non-negative.');
+  }
+
+  /// Creates a new [Version] object.
+  factory Version(int major, int minor, int patch,
+      {String? pre, String? build}) {
+    var text = '$major.$minor.$patch';
+    if (pre != null) text += '-$pre';
+    if (build != null) text += '+$build';
+
+    return Version._(major, minor, patch, pre, build, text);
+  }
+
+  /// Creates a new [Version] by parsing [text].
+  factory Version.parse(String text) {
+    final match = completeVersion.firstMatch(text);
+    if (match == null) {
+      throw FormatException('Could not parse "$text".');
+    }
+
+    try {
+      var major = int.parse(match[1]!);
+      var minor = int.parse(match[2]!);
+      var patch = int.parse(match[3]!);
+
+      var preRelease = match[5];
+      var build = match[8];
+
+      return Version._(major, minor, patch, preRelease, build, text);
+    } on FormatException {
+      throw FormatException('Could not parse "$text".');
+    }
+  }
+
+  /// Returns the primary version out of [versions].
+  ///
+  /// This is the highest-numbered stable (non-prerelease) version. If there
+  /// are no stable versions, it's just the highest-numbered version.
+  ///
+  /// If [versions] is empty, throws a [StateError].
+  static Version primary(List<Version> versions) {
+    var primary = versions.first;
+    for (var version in versions.skip(1)) {
+      if ((!version.isPreRelease && primary.isPreRelease) ||
+          (version.isPreRelease == primary.isPreRelease && version > primary)) {
+        primary = version;
+      }
+    }
+    return primary;
+  }
+
+  /// Splits a string of dot-delimited identifiers into their component parts.
+  ///
+  /// Identifiers that are numeric are converted to numbers.
+  static List<Object> _splitParts(String text) => text
+      .split('.')
+      .map((part) =>
+          // Return an integer part if possible, otherwise return the string
+          // as-is
+          int.tryParse(part) ?? part)
+      .toList();
+
+  @override
+  bool operator ==(Object other) =>
+      other is Version &&
+      major == other.major &&
+      minor == other.minor &&
+      patch == other.patch &&
+      _equality.equals(preRelease, other.preRelease) &&
+      _equality.equals(build, other.build);
+
+  @override
+  int get hashCode =>
+      major ^
+      minor ^
+      patch ^
+      _equality.hash(preRelease) ^
+      _equality.hash(build);
+
+  bool operator <(Version other) => compareTo(other) < 0;
+  bool operator >(Version other) => compareTo(other) > 0;
+  bool operator <=(Version other) => compareTo(other) <= 0;
+  bool operator >=(Version other) => compareTo(other) >= 0;
+
+  @override
+  bool get isAny => false;
+  @override
+  bool get isEmpty => false;
+
+  /// Whether or not this is a pre-release version.
+  bool get isPreRelease => preRelease.isNotEmpty;
+
+  /// Gets the next major version number that follows this one.
+  ///
+  /// If this version is a pre-release of a major version release (i.e. the
+  /// minor and patch versions are zero), then it just strips the pre-release
+  /// suffix. Otherwise, it increments the major version and resets the minor
+  /// and patch.
+  Version get nextMajor {
+    if (isPreRelease && minor == 0 && patch == 0) {
+      return Version(major, minor, patch);
+    }
+
+    return _incrementMajor();
+  }
+
+  /// Gets the next minor version number that follows this one.
+  ///
+  /// If this version is a pre-release of a minor version release (i.e. the
+  /// patch version is zero), then it just strips the pre-release suffix.
+  /// Otherwise, it increments the minor version and resets the patch.
+  Version get nextMinor {
+    if (isPreRelease && patch == 0) {
+      return Version(major, minor, patch);
+    }
+
+    return _incrementMinor();
+  }
+
+  /// Gets the next patch version number that follows this one.
+  ///
+  /// If this version is a pre-release, then it just strips the pre-release
+  /// suffix. Otherwise, it increments the patch version.
+  Version get nextPatch {
+    if (isPreRelease) {
+      return Version(major, minor, patch);
+    }
+
+    return _incrementPatch();
+  }
+
+  /// Gets the next breaking version number that follows this one.
+  ///
+  /// Increments [major] if it's greater than zero, otherwise [minor], resets
+  /// subsequent digits to zero, and strips any [preRelease] or [build]
+  /// suffix.
+  Version get nextBreaking {
+    if (major == 0) {
+      return _incrementMinor();
+    }
+
+    return _incrementMajor();
+  }
+
+  /// Returns the first possible pre-release of this version.
+  Version get firstPreRelease => Version(major, minor, patch, pre: '0');
+
+  /// Returns whether this is the first possible pre-release of its version.
+  bool get isFirstPreRelease => preRelease.length == 1 && preRelease.first == 0;
+
+  Version _incrementMajor() => Version(major + 1, 0, 0);
+  Version _incrementMinor() => Version(major, minor + 1, 0);
+  Version _incrementPatch() => Version(major, minor, patch + 1);
+
+  /// Tests if [other] matches this version exactly.
+  @override
+  bool allows(Version other) => this == other;
+
+  @override
+  bool allowsAll(VersionConstraint other) => other.isEmpty || other == this;
+
+  @override
+  bool allowsAny(VersionConstraint other) => other.allows(this);
+
+  @override
+  VersionConstraint intersect(VersionConstraint other) =>
+      other.allows(this) ? this : VersionConstraint.empty;
+
+  @override
+  VersionConstraint union(VersionConstraint other) {
+    if (other.allows(this)) return other;
+
+    if (other is VersionRange) {
+      if (other.min == this) {
+        return VersionRange(
+            min: other.min,
+            max: other.max,
+            includeMin: true,
+            includeMax: other.includeMax,
+            alwaysIncludeMaxPreRelease: true);
+      }
+
+      if (other.max == this) {
+        return VersionRange(
+            min: other.min,
+            max: other.max,
+            includeMin: other.includeMin,
+            includeMax: true,
+            alwaysIncludeMaxPreRelease: true);
+      }
+    }
+
+    return VersionConstraint.unionOf([this, other]);
+  }
+
+  @override
+  VersionConstraint difference(VersionConstraint other) =>
+      other.allows(this) ? VersionConstraint.empty : this;
+
+  @override
+  int compareTo(VersionRange other) {
+    if (other is Version) {
+      if (major != other.major) return major.compareTo(other.major);
+      if (minor != other.minor) return minor.compareTo(other.minor);
+      if (patch != other.patch) return patch.compareTo(other.patch);
+
+      // Pre-releases always come before no pre-release string.
+      if (!isPreRelease && other.isPreRelease) return 1;
+      if (!other.isPreRelease && isPreRelease) return -1;
+
+      var comparison = _compareLists(preRelease, other.preRelease);
+      if (comparison != 0) return comparison;
+
+      // Builds always come after no build string.
+      if (build.isEmpty && other.build.isNotEmpty) return -1;
+      if (other.build.isEmpty && build.isNotEmpty) return 1;
+      return _compareLists(build, other.build);
+    } else {
+      return -other.compareTo(this);
+    }
+  }
+
+  /// Get non-canonical string representation of this [Version].
+  ///
+  /// If created with [Version.parse], the string from which the version was
+  /// parsed is returned. Unlike the [canonicalizedVersion] this preserves
+  /// artifacts such as leading zeros.
+  @override
+  String toString() => _text;
+
+  /// Get a canonicalized string representation of this [Version].
+  ///
+  /// Unlike [Version.toString()] this always returns a canonical string
+  /// representation of this [Version].
+  ///
+  /// **Example**
+  /// ```dart
+  /// final v = Version.parse('01.02.03-01.dev+pre.02');
+  ///
+  /// assert(v.toString() == '01.02.03-01.dev+pre.02');
+  /// assert(v.canonicalizedVersion == '1.2.3-1.dev+pre.2');
+  /// assert(Version.parse(v.canonicalizedVersion) == v);
+  /// ```
+  String get canonicalizedVersion => Version(
+        major,
+        minor,
+        patch,
+        pre: preRelease.isNotEmpty ? preRelease.join('.') : null,
+        build: build.isNotEmpty ? build.join('.') : null,
+      ).toString();
+
+  /// Compares a dot-separated component of two versions.
+  ///
+  /// This is used for the pre-release and build version parts. This follows
+  /// Rule 12 of the Semantic Versioning spec (v2.0.0-rc.1).
+  int _compareLists(List<Object> a, List<Object> b) {
+    for (var i = 0; i < math.max(a.length, b.length); i++) {
+      var aPart = (i < a.length) ? a[i] : null;
+      var bPart = (i < b.length) ? b[i] : null;
+
+      if (aPart == bPart) continue;
+
+      // Missing parts come before present ones.
+      if (aPart == null) return -1;
+      if (bPart == null) return 1;
+
+      if (aPart is num) {
+        if (bPart is num) {
+          // Compare two numbers.
+          return aPart.compareTo(bPart);
+        } else {
+          // Numbers come before strings.
+          return -1;
+        }
+      } else {
+        if (bPart is num) {
+          // Strings come after numbers.
+          return 1;
+        } else {
+          // Compare two strings.
+          return (aPart as String).compareTo(bPart as String);
+        }
+      }
+    }
+
+    // The lists are entirely equal.
+    return 0;
+  }
+}
diff --git a/pkgs/pub_semver/lib/src/version_constraint.dart b/pkgs/pub_semver/lib/src/version_constraint.dart
new file mode 100644
index 0000000..948118e
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/version_constraint.dart
@@ -0,0 +1,287 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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 'patterns.dart';
+import 'utils.dart';
+import 'version.dart';
+import 'version_range.dart';
+import 'version_union.dart';
+
+/// A [VersionConstraint] is a predicate that can determine whether a given
+/// version is valid or not.
+///
+/// For example, a ">= 2.0.0" constraint allows any version that is "2.0.0" or
+/// greater. Version objects themselves implement this to match a specific
+/// version.
+abstract class VersionConstraint {
+  /// A [VersionConstraint] that allows all versions.
+  static VersionConstraint any = VersionRange();
+
+  /// A [VersionConstraint] that allows no versions -- the empty set.
+  static VersionConstraint empty = const _EmptyVersion();
+
+  /// Parses a version constraint.
+  ///
+  /// This string is one of:
+  ///
+  ///   * "any". [any] version.
+  ///   * "^" followed by a version string. Versions compatible with
+  ///     ([VersionConstraint.compatibleWith]) the version.
+  ///   * a series of version parts. Each part can be one of:
+  ///     * A version string like `1.2.3`. In other words, anything that can be
+  ///       parsed by [Version.parse()].
+  ///     * A comparison operator (`<`, `>`, `<=`, or `>=`) followed by a
+  ///       version string.
+  ///
+  /// Whitespace is ignored.
+  ///
+  /// Examples:
+  ///
+  ///     any
+  ///     ^0.7.2
+  ///     ^1.0.0-alpha
+  ///     1.2.3-alpha
+  ///     <=5.1.4
+  ///     >2.0.4 <= 2.4.6
+  factory VersionConstraint.parse(String text) {
+    var originalText = text;
+
+    void skipWhitespace() {
+      text = text.trim();
+    }
+
+    skipWhitespace();
+
+    // Handle the "any" constraint.
+    if (text == 'any') return any;
+
+    // Try to parse and consume a version number.
+    Version? matchVersion() {
+      var version = startVersion.firstMatch(text);
+      if (version == null) return null;
+
+      text = text.substring(version.end);
+      return Version.parse(version[0]!);
+    }
+
+    // Try to parse and consume a comparison operator followed by a version.
+    VersionRange? matchComparison() {
+      var comparison = startComparison.firstMatch(text);
+      if (comparison == null) return null;
+
+      var op = comparison[0]!;
+      text = text.substring(comparison.end);
+      skipWhitespace();
+
+      var version = matchVersion();
+      if (version == null) {
+        throw FormatException('Expected version number after "$op" in '
+            '"$originalText", got "$text".');
+      }
+
+      return switch (op) {
+        '<=' => VersionRange(max: version, includeMax: true),
+        '<' => VersionRange(max: version, alwaysIncludeMaxPreRelease: true),
+        '>=' => VersionRange(min: version, includeMin: true),
+        '>' => VersionRange(min: version),
+        _ => throw UnsupportedError(op),
+      };
+    }
+
+    // Try to parse the "^" operator followed by a version.
+    VersionConstraint? matchCompatibleWith() {
+      if (!text.startsWith(compatibleWithChar)) return null;
+
+      text = text.substring(compatibleWithChar.length);
+      skipWhitespace();
+
+      var version = matchVersion();
+      if (version == null) {
+        throw FormatException('Expected version number after '
+            '"$compatibleWithChar" in "$originalText", got "$text".');
+      }
+
+      if (text.isNotEmpty) {
+        throw FormatException('Cannot include other constraints with '
+            '"$compatibleWithChar" constraint in "$originalText".');
+      }
+
+      return VersionConstraint.compatibleWith(version);
+    }
+
+    var compatibleWith = matchCompatibleWith();
+    if (compatibleWith != null) return compatibleWith;
+
+    Version? min;
+    var includeMin = false;
+    Version? max;
+    var includeMax = false;
+
+    for (;;) {
+      skipWhitespace();
+
+      if (text.isEmpty) break;
+
+      var newRange = matchVersion() ?? matchComparison();
+      if (newRange == null) {
+        throw FormatException('Could not parse version "$originalText". '
+            'Unknown text at "$text".');
+      }
+
+      if (newRange.min != null) {
+        if (min == null || newRange.min! > min) {
+          min = newRange.min;
+          includeMin = newRange.includeMin;
+        } else if (newRange.min == min && !newRange.includeMin) {
+          includeMin = false;
+        }
+      }
+
+      if (newRange.max != null) {
+        if (max == null || newRange.max! < max) {
+          max = newRange.max;
+          includeMax = newRange.includeMax;
+        } else if (newRange.max == max && !newRange.includeMax) {
+          includeMax = false;
+        }
+      }
+    }
+
+    if (min == null && max == null) {
+      throw const FormatException('Cannot parse an empty string.');
+    }
+
+    if (min != null && max != null) {
+      if (min > max) return VersionConstraint.empty;
+      if (min == max) {
+        if (includeMin && includeMax) return min;
+        return VersionConstraint.empty;
+      }
+    }
+
+    return VersionRange(
+        min: min, includeMin: includeMin, max: max, includeMax: includeMax);
+  }
+
+  /// Creates a version constraint which allows all versions that are
+  /// backward compatible with [version].
+  ///
+  /// Versions are considered backward compatible with [version] if they
+  /// are greater than or equal to [version], but less than the next breaking
+  /// version ([Version.nextBreaking]) of [version].
+  factory VersionConstraint.compatibleWith(Version version) =>
+      CompatibleWithVersionRange(version);
+
+  /// Creates a new version constraint that is the intersection of
+  /// [constraints].
+  ///
+  /// It only allows versions that all of those constraints allow. If
+  /// constraints is empty, then it returns a VersionConstraint that allows
+  /// all versions.
+  factory VersionConstraint.intersection(
+      Iterable<VersionConstraint> constraints) {
+    var constraint = VersionRange();
+    for (var other in constraints) {
+      constraint = constraint.intersect(other) as VersionRange;
+    }
+    return constraint;
+  }
+
+  /// Creates a new version constraint that is the union of [constraints].
+  ///
+  /// It allows any versions that any of those constraints allows. If
+  /// [constraints] is empty, this returns a constraint that allows no versions.
+  factory VersionConstraint.unionOf(Iterable<VersionConstraint> constraints) {
+    var flattened = constraints.expand((constraint) {
+      if (constraint.isEmpty) return <VersionRange>[];
+      if (constraint is VersionUnion) return constraint.ranges;
+      if (constraint is VersionRange) return [constraint];
+      throw ArgumentError('Unknown VersionConstraint type $constraint.');
+    }).toList();
+
+    if (flattened.isEmpty) return VersionConstraint.empty;
+
+    if (flattened.any((constraint) => constraint.isAny)) {
+      return VersionConstraint.any;
+    }
+
+    flattened.sort();
+
+    var merged = <VersionRange>[];
+    for (var constraint in flattened) {
+      // Merge this constraint with the previous one, but only if they touch.
+      if (merged.isEmpty ||
+          (!merged.last.allowsAny(constraint) &&
+              !areAdjacent(merged.last, constraint))) {
+        merged.add(constraint);
+      } else {
+        merged[merged.length - 1] =
+            merged.last.union(constraint) as VersionRange;
+      }
+    }
+
+    if (merged.length == 1) return merged.single;
+    return VersionUnion.fromRanges(merged);
+  }
+
+  /// Returns `true` if this constraint allows no versions.
+  bool get isEmpty;
+
+  /// Returns `true` if this constraint allows all versions.
+  bool get isAny;
+
+  /// Returns `true` if this constraint allows [version].
+  bool allows(Version version);
+
+  /// Returns `true` if this constraint allows all the versions that [other]
+  /// allows.
+  bool allowsAll(VersionConstraint other);
+
+  /// Returns `true` if this constraint allows any of the versions that [other]
+  /// allows.
+  bool allowsAny(VersionConstraint other);
+
+  /// Returns a [VersionConstraint] that only allows [Version]s allowed by both
+  /// this and [other].
+  VersionConstraint intersect(VersionConstraint other);
+
+  /// Returns a [VersionConstraint] that allows [Version]s allowed by either
+  /// this or [other].
+  VersionConstraint union(VersionConstraint other);
+
+  /// Returns a [VersionConstraint] that allows [Version]s allowed by this but
+  /// not [other].
+  VersionConstraint difference(VersionConstraint other);
+}
+
+class _EmptyVersion implements VersionConstraint {
+  const _EmptyVersion();
+
+  @override
+  bool get isEmpty => true;
+
+  @override
+  bool get isAny => false;
+
+  @override
+  bool allows(Version other) => false;
+
+  @override
+  bool allowsAll(VersionConstraint other) => other.isEmpty;
+
+  @override
+  bool allowsAny(VersionConstraint other) => false;
+
+  @override
+  VersionConstraint intersect(VersionConstraint other) => this;
+
+  @override
+  VersionConstraint union(VersionConstraint other) => other;
+
+  @override
+  VersionConstraint difference(VersionConstraint other) => this;
+
+  @override
+  String toString() => '<empty>';
+}
diff --git a/pkgs/pub_semver/lib/src/version_range.dart b/pkgs/pub_semver/lib/src/version_range.dart
new file mode 100644
index 0000000..6f2ed54
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/version_range.dart
@@ -0,0 +1,476 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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 'utils.dart';
+import 'version.dart';
+import 'version_constraint.dart';
+import 'version_union.dart';
+
+/// Constrains versions to a fall within a given range.
+///
+/// If there is a minimum, then this only allows versions that are at that
+/// minimum or greater. If there is a maximum, then only versions less than
+/// that are allowed. In other words, this allows `>= min, < max`.
+///
+/// Version ranges are ordered first by their lower bounds, then by their upper
+/// bounds. For example, `>=1.0.0 <2.0.0` is before `>=1.5.0 <2.0.0` is before
+/// `>=1.5.0 <3.0.0`.
+class VersionRange implements Comparable<VersionRange>, VersionConstraint {
+  /// The minimum end of the range.
+  ///
+  /// If [includeMin] is `true`, this will be the minimum allowed version.
+  /// Otherwise, it will be the highest version below the range that is not
+  /// allowed.
+  ///
+  /// This may be `null` in which case the range has no minimum end and allows
+  /// any version less than the maximum.
+  final Version? min;
+
+  /// The maximum end of the range.
+  ///
+  /// If [includeMax] is `true`, this will be the maximum allowed version.
+  /// Otherwise, it will be the lowest version above the range that is not
+  /// allowed.
+  ///
+  /// This may be `null` in which case the range has no maximum end and allows
+  /// any version greater than the minimum.
+  final Version? max;
+
+  /// If `true` then [min] is allowed by the range.
+  final bool includeMin;
+
+  /// If `true`, then [max] is allowed by the range.
+  final bool includeMax;
+
+  /// Creates a new version range from [min] to [max], either inclusive or
+  /// exclusive.
+  ///
+  /// If it is an error if [min] is greater than [max].
+  ///
+  /// Either [max] or [min] may be omitted to not clamp the range at that end.
+  /// If both are omitted, the range allows all versions.
+  ///
+  /// If [includeMin] is `true`, then the minimum end of the range is inclusive.
+  /// Likewise, passing [includeMax] as `true` makes the upper end inclusive.
+  ///
+  /// If [alwaysIncludeMaxPreRelease] is `true`, this will always include
+  /// pre-release versions of an exclusive [max]. Otherwise, it will use the
+  /// default behavior for pre-release versions of [max].
+  factory VersionRange(
+      {Version? min,
+      Version? max,
+      bool includeMin = false,
+      bool includeMax = false,
+      bool alwaysIncludeMaxPreRelease = false}) {
+    if (min != null && max != null && min > max) {
+      throw ArgumentError(
+          'Minimum version ("$min") must be less than maximum ("$max").');
+    }
+
+    if (!alwaysIncludeMaxPreRelease &&
+        !includeMax &&
+        max != null &&
+        !max.isPreRelease &&
+        max.build.isEmpty &&
+        (min == null ||
+            !min.isPreRelease ||
+            !equalsWithoutPreRelease(min, max))) {
+      max = max.firstPreRelease;
+    }
+
+    return VersionRange._(min, max, includeMin, includeMax);
+  }
+
+  VersionRange._(this.min, this.max, this.includeMin, this.includeMax);
+
+  @override
+  bool operator ==(Object other) {
+    if (other is! VersionRange) return false;
+
+    return min == other.min &&
+        max == other.max &&
+        includeMin == other.includeMin &&
+        includeMax == other.includeMax;
+  }
+
+  @override
+  int get hashCode =>
+      min.hashCode ^
+      (max.hashCode * 3) ^
+      (includeMin.hashCode * 5) ^
+      (includeMax.hashCode * 7);
+
+  @override
+  bool get isEmpty => false;
+
+  @override
+  bool get isAny => min == null && max == null;
+
+  /// Tests if [other] falls within this version range.
+  @override
+  bool allows(Version other) {
+    if (min != null) {
+      if (other < min!) return false;
+      if (!includeMin && other == min) return false;
+    }
+
+    if (max != null) {
+      if (other > max!) return false;
+      if (!includeMax && other == max) return false;
+    }
+
+    return true;
+  }
+
+  @override
+  bool allowsAll(VersionConstraint other) {
+    if (other.isEmpty) return true;
+    if (other is Version) return allows(other);
+
+    if (other is VersionUnion) {
+      return other.ranges.every(allowsAll);
+    }
+
+    if (other is VersionRange) {
+      return !allowsLower(other, this) && !allowsHigher(other, this);
+    }
+
+    throw ArgumentError('Unknown VersionConstraint type $other.');
+  }
+
+  @override
+  bool allowsAny(VersionConstraint other) {
+    if (other.isEmpty) return false;
+    if (other is Version) return allows(other);
+
+    if (other is VersionUnion) {
+      return other.ranges.any(allowsAny);
+    }
+
+    if (other is VersionRange) {
+      return !strictlyLower(other, this) && !strictlyHigher(other, this);
+    }
+
+    throw ArgumentError('Unknown VersionConstraint type $other.');
+  }
+
+  @override
+  VersionConstraint intersect(VersionConstraint other) {
+    if (other.isEmpty) return other;
+    if (other is VersionUnion) return other.intersect(this);
+
+    // A range and a Version just yields the version if it's in the range.
+    if (other is Version) {
+      return allows(other) ? other : VersionConstraint.empty;
+    }
+
+    if (other is VersionRange) {
+      // Intersect the two ranges.
+      Version? intersectMin;
+      bool intersectIncludeMin;
+      if (allowsLower(this, other)) {
+        if (strictlyLower(this, other)) return VersionConstraint.empty;
+        intersectMin = other.min;
+        intersectIncludeMin = other.includeMin;
+      } else {
+        if (strictlyLower(other, this)) return VersionConstraint.empty;
+        intersectMin = min;
+        intersectIncludeMin = includeMin;
+      }
+
+      Version? intersectMax;
+      bool intersectIncludeMax;
+      if (allowsHigher(this, other)) {
+        intersectMax = other.max;
+        intersectIncludeMax = other.includeMax;
+      } else {
+        intersectMax = max;
+        intersectIncludeMax = includeMax;
+      }
+
+      if (intersectMin == null && intersectMax == null) {
+        // Open range.
+        return VersionRange();
+      }
+
+      // If the range is just a single version.
+      if (intersectMin == intersectMax) {
+        // Because we already verified that the lower range isn't strictly
+        // lower, there must be some overlap.
+        assert(intersectIncludeMin && intersectIncludeMax);
+        return intersectMin!;
+      }
+
+      // If we got here, there is an actual range.
+      return VersionRange(
+          min: intersectMin,
+          max: intersectMax,
+          includeMin: intersectIncludeMin,
+          includeMax: intersectIncludeMax,
+          alwaysIncludeMaxPreRelease: true);
+    }
+
+    throw ArgumentError('Unknown VersionConstraint type $other.');
+  }
+
+  @override
+  VersionConstraint union(VersionConstraint other) {
+    if (other is Version) {
+      if (allows(other)) return this;
+
+      if (other == min) {
+        return VersionRange(
+            min: min,
+            max: max,
+            includeMin: true,
+            includeMax: includeMax,
+            alwaysIncludeMaxPreRelease: true);
+      }
+
+      if (other == max) {
+        return VersionRange(
+            min: min,
+            max: max,
+            includeMin: includeMin,
+            includeMax: true,
+            alwaysIncludeMaxPreRelease: true);
+      }
+
+      return VersionConstraint.unionOf([this, other]);
+    }
+
+    if (other is VersionRange) {
+      // If the two ranges don't overlap, we won't be able to create a single
+      // VersionRange for both of them.
+      var edgesTouch = (max != null &&
+              max == other.min &&
+              (includeMax || other.includeMin)) ||
+          (min != null && min == other.max && (includeMin || other.includeMax));
+      if (!edgesTouch && !allowsAny(other)) {
+        return VersionConstraint.unionOf([this, other]);
+      }
+
+      Version? unionMin;
+      bool unionIncludeMin;
+      if (allowsLower(this, other)) {
+        unionMin = min;
+        unionIncludeMin = includeMin;
+      } else {
+        unionMin = other.min;
+        unionIncludeMin = other.includeMin;
+      }
+
+      Version? unionMax;
+      bool unionIncludeMax;
+      if (allowsHigher(this, other)) {
+        unionMax = max;
+        unionIncludeMax = includeMax;
+      } else {
+        unionMax = other.max;
+        unionIncludeMax = other.includeMax;
+      }
+
+      return VersionRange(
+          min: unionMin,
+          max: unionMax,
+          includeMin: unionIncludeMin,
+          includeMax: unionIncludeMax,
+          alwaysIncludeMaxPreRelease: true);
+    }
+
+    return VersionConstraint.unionOf([this, other]);
+  }
+
+  @override
+  VersionConstraint difference(VersionConstraint other) {
+    if (other.isEmpty) return this;
+
+    if (other is Version) {
+      if (!allows(other)) return this;
+
+      if (other == min) {
+        if (!includeMin) return this;
+        return VersionRange(
+            min: min,
+            max: max,
+            includeMax: includeMax,
+            alwaysIncludeMaxPreRelease: true);
+      }
+
+      if (other == max) {
+        if (!includeMax) return this;
+        return VersionRange(
+            min: min,
+            max: max,
+            includeMin: includeMin,
+            alwaysIncludeMaxPreRelease: true);
+      }
+
+      return VersionUnion.fromRanges([
+        VersionRange(
+            min: min,
+            max: other,
+            includeMin: includeMin,
+            alwaysIncludeMaxPreRelease: true),
+        VersionRange(
+            min: other,
+            max: max,
+            includeMax: includeMax,
+            alwaysIncludeMaxPreRelease: true)
+      ]);
+    } else if (other is VersionRange) {
+      if (!allowsAny(other)) return this;
+
+      VersionRange? before;
+      if (!allowsLower(this, other)) {
+        before = null;
+      } else if (min == other.min) {
+        assert(includeMin && !other.includeMin);
+        assert(min != null);
+        before = min;
+      } else {
+        before = VersionRange(
+            min: min,
+            max: other.min,
+            includeMin: includeMin,
+            includeMax: !other.includeMin,
+            alwaysIncludeMaxPreRelease: true);
+      }
+
+      VersionRange? after;
+      if (!allowsHigher(this, other)) {
+        after = null;
+      } else if (max == other.max) {
+        assert(includeMax && !other.includeMax);
+        assert(max != null);
+        after = max;
+      } else {
+        after = VersionRange(
+            min: other.max,
+            max: max,
+            includeMin: !other.includeMax,
+            includeMax: includeMax,
+            alwaysIncludeMaxPreRelease: true);
+      }
+
+      if (before == null && after == null) return VersionConstraint.empty;
+      if (before == null) return after!;
+      if (after == null) return before;
+      return VersionUnion.fromRanges([before, after]);
+    } else if (other is VersionUnion) {
+      var ranges = <VersionRange>[];
+      var current = this;
+
+      for (var range in other.ranges) {
+        // Skip any ranges that are strictly lower than [current].
+        if (strictlyLower(range, current)) continue;
+
+        // If we reach a range strictly higher than [current], no more ranges
+        // will be relevant so we can bail early.
+        if (strictlyHigher(range, current)) break;
+
+        var difference = current.difference(range);
+        if (difference.isEmpty) {
+          return VersionConstraint.empty;
+        } else if (difference is VersionUnion) {
+          // If [range] split [current] in half, we only need to continue
+          // checking future ranges against the latter half.
+          assert(difference.ranges.length == 2);
+          ranges.add(difference.ranges.first);
+          current = difference.ranges.last;
+        } else {
+          current = difference as VersionRange;
+        }
+      }
+
+      if (ranges.isEmpty) return current;
+      return VersionUnion.fromRanges(ranges..add(current));
+    }
+
+    throw ArgumentError('Unknown VersionConstraint type $other.');
+  }
+
+  @override
+  int compareTo(VersionRange other) {
+    if (min == null) {
+      if (other.min == null) return _compareMax(other);
+      return -1;
+    } else if (other.min == null) {
+      return 1;
+    }
+
+    var result = min!.compareTo(other.min!);
+    if (result != 0) return result;
+    if (includeMin != other.includeMin) return includeMin ? -1 : 1;
+
+    return _compareMax(other);
+  }
+
+  /// Compares the maximum values of `this` and [other].
+  int _compareMax(VersionRange other) {
+    if (max == null) {
+      if (other.max == null) return 0;
+      return 1;
+    } else if (other.max == null) {
+      return -1;
+    }
+
+    var result = max!.compareTo(other.max!);
+    if (result != 0) return result;
+    if (includeMax != other.includeMax) return includeMax ? 1 : -1;
+    return 0;
+  }
+
+  @override
+  String toString() {
+    var buffer = StringBuffer();
+
+    final min = this.min;
+    if (min != null) {
+      buffer
+        ..write(includeMin ? '>=' : '>')
+        ..write(min);
+    }
+
+    final max = this.max;
+
+    if (max != null) {
+      if (min != null) buffer.write(' ');
+      if (includeMax) {
+        buffer
+          ..write('<=')
+          ..write(max);
+      } else {
+        buffer.write('<');
+        if (max.isFirstPreRelease) {
+          // Since `"<$max"` would parse the same as `"<$max-0"`, we just emit
+          // `<$max` to avoid confusing "-0" suffixes.
+          buffer.write('${max.major}.${max.minor}.${max.patch}');
+        } else {
+          buffer.write(max);
+
+          // If `">=$min <$max"` would parse as `">=$min <$max-0"`, add `-*` to
+          // indicate that actually does allow pre-release versions.
+          var minIsPreReleaseOfMax = min != null &&
+              min.isPreRelease &&
+              equalsWithoutPreRelease(min, max);
+          if (!max.isPreRelease && max.build.isEmpty && !minIsPreReleaseOfMax) {
+            buffer.write('-∞');
+          }
+        }
+      }
+    }
+
+    if (min == null && max == null) buffer.write('any');
+    return buffer.toString();
+  }
+}
+
+class CompatibleWithVersionRange extends VersionRange {
+  CompatibleWithVersionRange(Version version)
+      : super._(version, version.nextBreaking.firstPreRelease, true, false);
+
+  @override
+  String toString() => '^$min';
+}
diff --git a/pkgs/pub_semver/lib/src/version_union.dart b/pkgs/pub_semver/lib/src/version_union.dart
new file mode 100644
index 0000000..844d3b8
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/version_union.dart
@@ -0,0 +1,224 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// 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 'utils.dart';
+import 'version.dart';
+import 'version_constraint.dart';
+import 'version_range.dart';
+
+/// A version constraint representing a union of multiple disjoint version
+/// ranges.
+///
+/// An instance of this will only be created if the version can't be represented
+/// as a non-compound value.
+class VersionUnion implements VersionConstraint {
+  /// The constraints that compose this union.
+  ///
+  /// This list has two invariants:
+  ///
+  /// * Its contents are sorted using the standard ordering of [VersionRange]s.
+  /// * Its contents are disjoint and non-adjacent. In other words, for any two
+  ///   constraints next to each other in the list, there's some version between
+  ///   those constraints that they don't match.
+  final List<VersionRange> ranges;
+
+  @override
+  bool get isEmpty => false;
+
+  @override
+  bool get isAny => false;
+
+  /// Creates a union from a list of ranges with no pre-processing.
+  ///
+  /// It's up to the caller to ensure that the invariants described in [ranges]
+  /// are maintained. They are not verified by this constructor. To
+  /// automatically ensure that they're maintained, use
+  /// [VersionConstraint.unionOf] instead.
+  VersionUnion.fromRanges(this.ranges);
+
+  @override
+  bool allows(Version version) =>
+      ranges.any((constraint) => constraint.allows(version));
+
+  @override
+  bool allowsAll(VersionConstraint other) {
+    var ourRanges = ranges.iterator;
+    var theirRanges = _rangesFor(other).iterator;
+
+    // Because both lists of ranges are ordered by minimum version, we can
+    // safely move through them linearly here.
+    var ourRangesMoved = ourRanges.moveNext();
+    var theirRangesMoved = theirRanges.moveNext();
+    while (ourRangesMoved && theirRangesMoved) {
+      if (ourRanges.current.allowsAll(theirRanges.current)) {
+        theirRangesMoved = theirRanges.moveNext();
+      } else {
+        ourRangesMoved = ourRanges.moveNext();
+      }
+    }
+
+    // If our ranges have allowed all of their ranges, we'll have consumed all
+    // of them.
+    return !theirRangesMoved;
+  }
+
+  @override
+  bool allowsAny(VersionConstraint other) {
+    var ourRanges = ranges.iterator;
+    var theirRanges = _rangesFor(other).iterator;
+
+    // Because both lists of ranges are ordered by minimum version, we can
+    // safely move through them linearly here.
+    var ourRangesMoved = ourRanges.moveNext();
+    var theirRangesMoved = theirRanges.moveNext();
+    while (ourRangesMoved && theirRangesMoved) {
+      if (ourRanges.current.allowsAny(theirRanges.current)) {
+        return true;
+      }
+
+      // Move the constraint with the lower max value forward. This ensures that
+      // we keep both lists in sync as much as possible.
+      if (allowsHigher(theirRanges.current, ourRanges.current)) {
+        ourRangesMoved = ourRanges.moveNext();
+      } else {
+        theirRangesMoved = theirRanges.moveNext();
+      }
+    }
+
+    return false;
+  }
+
+  @override
+  VersionConstraint intersect(VersionConstraint other) {
+    var ourRanges = ranges.iterator;
+    var theirRanges = _rangesFor(other).iterator;
+
+    // Because both lists of ranges are ordered by minimum version, we can
+    // safely move through them linearly here.
+    var newRanges = <VersionRange>[];
+    var ourRangesMoved = ourRanges.moveNext();
+    var theirRangesMoved = theirRanges.moveNext();
+    while (ourRangesMoved && theirRangesMoved) {
+      var intersection = ourRanges.current.intersect(theirRanges.current);
+
+      if (!intersection.isEmpty) newRanges.add(intersection as VersionRange);
+
+      // Move the constraint with the lower max value forward. This ensures that
+      // we keep both lists in sync as much as possible, and that large ranges
+      // have a chance to match multiple small ranges that they contain.
+      if (allowsHigher(theirRanges.current, ourRanges.current)) {
+        ourRangesMoved = ourRanges.moveNext();
+      } else {
+        theirRangesMoved = theirRanges.moveNext();
+      }
+    }
+
+    if (newRanges.isEmpty) return VersionConstraint.empty;
+    if (newRanges.length == 1) return newRanges.single;
+
+    return VersionUnion.fromRanges(newRanges);
+  }
+
+  @override
+  VersionConstraint difference(VersionConstraint other) {
+    var ourRanges = ranges.iterator;
+    var theirRanges = _rangesFor(other).iterator;
+
+    var newRanges = <VersionRange>[];
+    ourRanges.moveNext();
+    theirRanges.moveNext();
+    var current = ourRanges.current;
+
+    bool theirNextRange() {
+      if (theirRanges.moveNext()) return true;
+
+      // If there are no more of their ranges, none of the rest of our ranges
+      // need to be subtracted so we can add them as-is.
+      newRanges.add(current);
+      while (ourRanges.moveNext()) {
+        newRanges.add(ourRanges.current);
+      }
+      return false;
+    }
+
+    bool ourNextRange({bool includeCurrent = true}) {
+      if (includeCurrent) newRanges.add(current);
+      if (!ourRanges.moveNext()) return false;
+      current = ourRanges.current;
+      return true;
+    }
+
+    for (;;) {
+      // If the current ranges are disjoint, move the lowest one forward.
+      if (strictlyLower(theirRanges.current, current)) {
+        if (!theirNextRange()) break;
+        continue;
+      }
+
+      if (strictlyHigher(theirRanges.current, current)) {
+        if (!ourNextRange()) break;
+        continue;
+      }
+
+      // If we're here, we know [theirRanges.current] overlaps [current].
+      var difference = current.difference(theirRanges.current);
+      if (difference is VersionUnion) {
+        // If their range split [current] in half, we only need to continue
+        // checking future ranges against the latter half.
+        assert(difference.ranges.length == 2);
+        newRanges.add(difference.ranges.first);
+        current = difference.ranges.last;
+
+        // Since their range split [current], it definitely doesn't allow higher
+        // versions, so we should move their ranges forward.
+        if (!theirNextRange()) break;
+      } else if (difference.isEmpty) {
+        if (!ourNextRange(includeCurrent: false)) break;
+      } else {
+        current = difference as VersionRange;
+
+        // Move the constraint with the lower max value forward. This ensures
+        // that we keep both lists in sync as much as possible, and that large
+        // ranges have a chance to subtract or be subtracted by multiple small
+        // ranges that they contain.
+        if (allowsHigher(current, theirRanges.current)) {
+          if (!theirNextRange()) break;
+        } else {
+          if (!ourNextRange()) break;
+        }
+      }
+    }
+
+    if (newRanges.isEmpty) return VersionConstraint.empty;
+    if (newRanges.length == 1) return newRanges.single;
+    return VersionUnion.fromRanges(newRanges);
+  }
+
+  /// Returns [constraint] as a list of ranges.
+  ///
+  /// This is used to normalize ranges of various types.
+  List<VersionRange> _rangesFor(VersionConstraint constraint) {
+    if (constraint.isEmpty) return [];
+    if (constraint is VersionUnion) return constraint.ranges;
+    if (constraint is VersionRange) return [constraint];
+    throw ArgumentError('Unknown VersionConstraint type $constraint.');
+  }
+
+  @override
+  VersionConstraint union(VersionConstraint other) =>
+      VersionConstraint.unionOf([this, other]);
+
+  @override
+  bool operator ==(Object other) =>
+      other is VersionUnion &&
+      const ListEquality<VersionRange>().equals(ranges, other.ranges);
+
+  @override
+  int get hashCode => const ListEquality<VersionRange>().hash(ranges);
+
+  @override
+  String toString() => ranges.join(' or ');
+}
diff --git a/pkgs/pub_semver/pubspec.yaml b/pkgs/pub_semver/pubspec.yaml
new file mode 100644
index 0000000..290fb92
--- /dev/null
+++ b/pkgs/pub_semver/pubspec.yaml
@@ -0,0 +1,20 @@
+name: pub_semver
+version: 2.1.5
+description: >-
+ Versions and version constraints implementing pub's versioning policy. This
+ is very similar to vanilla semver, with a few corner cases.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/pub_semver
+topics:
+ - dart-pub
+ - semver
+
+environment:
+  sdk: ^3.4.0
+
+dependencies:
+  collection: ^1.15.0
+  meta: ^1.3.0
+
+dev_dependencies:
+  dart_flutter_team_lints: ^3.0.0
+  test: ^1.16.0
diff --git a/pkgs/pub_semver/test/utils.dart b/pkgs/pub_semver/test/utils.dart
new file mode 100644
index 0000000..bd7aa8f
--- /dev/null
+++ b/pkgs/pub_semver/test/utils.dart
@@ -0,0 +1,123 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+/// Some stock example versions to use in tests.
+final v003 = Version.parse('0.0.3');
+final v010 = Version.parse('0.1.0');
+final v072 = Version.parse('0.7.2');
+final v080 = Version.parse('0.8.0');
+final v114 = Version.parse('1.1.4');
+final v123 = Version.parse('1.2.3');
+final v124 = Version.parse('1.2.4');
+final v130 = Version.parse('1.3.0');
+final v140 = Version.parse('1.4.0');
+final v200 = Version.parse('2.0.0');
+final v201 = Version.parse('2.0.1');
+final v234 = Version.parse('2.3.4');
+final v250 = Version.parse('2.5.0');
+final v300 = Version.parse('3.0.0');
+
+/// A range that allows pre-release versions of its max version.
+final includeMaxPreReleaseRange =
+    VersionRange(max: v200, alwaysIncludeMaxPreRelease: true);
+
+/// A [Matcher] that tests if a [VersionConstraint] allows or does not allow a
+/// given list of [Version]s.
+class _VersionConstraintMatcher implements Matcher {
+  final List<Version> _expected;
+  final bool _allow;
+
+  _VersionConstraintMatcher(this._expected, this._allow);
+
+  @override
+  bool matches(dynamic item, Map<dynamic, dynamic> matchState) =>
+      (item is VersionConstraint) &&
+      _expected.every((version) => item.allows(version) == _allow);
+
+  @override
+  Description describe(Description description) {
+    description.addAll(' ${_allow ? "allows" : "does not allow"} versions ',
+        ', ', '', _expected);
+    return description;
+  }
+
+  @override
+  Description describeMismatch(dynamic item, Description mismatchDescription,
+      Map<dynamic, dynamic> matchState, bool verbose) {
+    if (item is! VersionConstraint) {
+      mismatchDescription.add('was not a VersionConstraint');
+      return mismatchDescription;
+    }
+
+    var first = true;
+    for (var version in _expected) {
+      if (item.allows(version) != _allow) {
+        if (first) {
+          if (_allow) {
+            mismatchDescription.addDescriptionOf(item).add(' did not allow ');
+          } else {
+            mismatchDescription.addDescriptionOf(item).add(' allowed ');
+          }
+        } else {
+          mismatchDescription.add(' and ');
+        }
+        first = false;
+
+        mismatchDescription.add(version.toString());
+      }
+    }
+
+    return mismatchDescription;
+  }
+}
+
+/// Gets a [Matcher] that validates that a [VersionConstraint] allows all
+/// given versions.
+Matcher allows(Version v1,
+    [Version? v2,
+    Version? v3,
+    Version? v4,
+    Version? v5,
+    Version? v6,
+    Version? v7,
+    Version? v8]) {
+  var versions = _makeVersionList(v1, v2, v3, v4, v5, v6, v7, v8);
+  return _VersionConstraintMatcher(versions, true);
+}
+
+/// Gets a [Matcher] that validates that a [VersionConstraint] allows none of
+/// the given versions.
+Matcher doesNotAllow(Version v1,
+    [Version? v2,
+    Version? v3,
+    Version? v4,
+    Version? v5,
+    Version? v6,
+    Version? v7,
+    Version? v8]) {
+  var versions = _makeVersionList(v1, v2, v3, v4, v5, v6, v7, v8);
+  return _VersionConstraintMatcher(versions, false);
+}
+
+List<Version> _makeVersionList(Version v1,
+    [Version? v2,
+    Version? v3,
+    Version? v4,
+    Version? v5,
+    Version? v6,
+    Version? v7,
+    Version? v8]) {
+  var versions = [v1];
+  if (v2 != null) versions.add(v2);
+  if (v3 != null) versions.add(v3);
+  if (v4 != null) versions.add(v4);
+  if (v5 != null) versions.add(v5);
+  if (v6 != null) versions.add(v6);
+  if (v7 != null) versions.add(v7);
+  if (v8 != null) versions.add(v8);
+  return versions;
+}
diff --git a/pkgs/pub_semver/test/version_constraint_test.dart b/pkgs/pub_semver/test/version_constraint_test.dart
new file mode 100644
index 0000000..4fbcbe0
--- /dev/null
+++ b/pkgs/pub_semver/test/version_constraint_test.dart
@@ -0,0 +1,185 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+  test('any', () {
+    expect(VersionConstraint.any.isAny, isTrue);
+    expect(
+        VersionConstraint.any,
+        allows(Version.parse('0.0.0-blah'), Version.parse('1.2.3'),
+            Version.parse('12345.678.90')));
+  });
+
+  test('empty', () {
+    expect(VersionConstraint.empty.isEmpty, isTrue);
+    expect(VersionConstraint.empty.isAny, isFalse);
+    expect(
+        VersionConstraint.empty,
+        doesNotAllow(Version.parse('0.0.0-blah'), Version.parse('1.2.3'),
+            Version.parse('12345.678.90')));
+  });
+
+  group('parse()', () {
+    test('parses an exact version', () {
+      var constraint = VersionConstraint.parse('1.2.3-alpha');
+
+      expect(constraint is Version, isTrue);
+      expect(constraint, equals(Version(1, 2, 3, pre: 'alpha')));
+    });
+
+    test('parses "any"', () {
+      var constraint = VersionConstraint.parse('any');
+
+      expect(
+          constraint,
+          allows(Version.parse('0.0.0'), Version.parse('1.2.3'),
+              Version.parse('12345.678.90')));
+    });
+
+    test('parses a ">" minimum version', () {
+      var constraint = VersionConstraint.parse('>1.2.3');
+
+      expect(constraint,
+          allows(Version.parse('1.2.3+foo'), Version.parse('1.2.4')));
+      expect(
+          constraint,
+          doesNotAllow(Version.parse('1.2.1'), Version.parse('1.2.3-build'),
+              Version.parse('1.2.3')));
+    });
+
+    test('parses a ">=" minimum version', () {
+      var constraint = VersionConstraint.parse('>=1.2.3');
+
+      expect(
+          constraint,
+          allows(Version.parse('1.2.3'), Version.parse('1.2.3+foo'),
+              Version.parse('1.2.4')));
+      expect(constraint,
+          doesNotAllow(Version.parse('1.2.1'), Version.parse('1.2.3-build')));
+    });
+
+    test('parses a "<" maximum version', () {
+      var constraint = VersionConstraint.parse('<1.2.3');
+
+      expect(constraint,
+          allows(Version.parse('1.2.1'), Version.parse('1.2.2+foo')));
+      expect(
+          constraint,
+          doesNotAllow(Version.parse('1.2.3'), Version.parse('1.2.3+foo'),
+              Version.parse('1.2.4')));
+    });
+
+    test('parses a "<=" maximum version', () {
+      var constraint = VersionConstraint.parse('<=1.2.3');
+
+      expect(
+          constraint,
+          allows(Version.parse('1.2.1'), Version.parse('1.2.3-build'),
+              Version.parse('1.2.3')));
+      expect(constraint,
+          doesNotAllow(Version.parse('1.2.3+foo'), Version.parse('1.2.4')));
+    });
+
+    test('parses a series of space-separated constraints', () {
+      var constraint = VersionConstraint.parse('>1.0.0 >=1.2.3 <1.3.0');
+
+      expect(
+          constraint, allows(Version.parse('1.2.3'), Version.parse('1.2.5')));
+      expect(
+          constraint,
+          doesNotAllow(Version.parse('1.2.3-pre'), Version.parse('1.3.0'),
+              Version.parse('3.4.5')));
+    });
+
+    test('parses a pre-release-only constraint', () {
+      var constraint = VersionConstraint.parse('>=1.0.0-dev.2 <1.0.0');
+      expect(constraint,
+          allows(Version.parse('1.0.0-dev.2'), Version.parse('1.0.0-dev.3')));
+      expect(constraint,
+          doesNotAllow(Version.parse('1.0.0-dev.1'), Version.parse('1.0.0')));
+    });
+
+    test('ignores whitespace around comparison operators', () {
+      var constraint = VersionConstraint.parse(' >1.0.0>=1.2.3 < 1.3.0');
+
+      expect(
+          constraint, allows(Version.parse('1.2.3'), Version.parse('1.2.5')));
+      expect(
+          constraint,
+          doesNotAllow(Version.parse('1.2.3-pre'), Version.parse('1.3.0'),
+              Version.parse('3.4.5')));
+    });
+
+    test('does not allow "any" to be mixed with other constraints', () {
+      expect(() => VersionConstraint.parse('any 1.0.0'), throwsFormatException);
+    });
+
+    test('parses a "^" version', () {
+      expect(VersionConstraint.parse('^0.0.3'),
+          equals(VersionConstraint.compatibleWith(v003)));
+
+      expect(VersionConstraint.parse('^0.7.2'),
+          equals(VersionConstraint.compatibleWith(v072)));
+
+      expect(VersionConstraint.parse('^1.2.3'),
+          equals(VersionConstraint.compatibleWith(v123)));
+
+      var min = Version.parse('0.7.2-pre+1');
+      expect(VersionConstraint.parse('^0.7.2-pre+1'),
+          equals(VersionConstraint.compatibleWith(min)));
+    });
+
+    test('does not allow "^" to be mixed with other constraints', () {
+      expect(() => VersionConstraint.parse('>=1.2.3 ^1.0.0'),
+          throwsFormatException);
+      expect(() => VersionConstraint.parse('^1.0.0 <1.2.3'),
+          throwsFormatException);
+    });
+
+    test('ignores whitespace around "^"', () {
+      var constraint = VersionConstraint.parse(' ^ 1.2.3 ');
+
+      expect(constraint, equals(VersionConstraint.compatibleWith(v123)));
+    });
+
+    test('throws FormatException on a bad string', () {
+      var bad = [
+        '', '   ', // Empty string.
+        'foo', // Bad text.
+        '>foo', // Bad text after operator.
+        '^foo', // Bad text after "^".
+        '1.0.0 foo', '1.0.0foo', // Bad text after version.
+        'anything', // Bad text after "any".
+        '<>1.0.0', // Multiple operators.
+        '1.0.0<' // Trailing operator.
+      ];
+
+      for (var text in bad) {
+        expect(() => VersionConstraint.parse(text), throwsFormatException);
+      }
+    });
+  });
+
+  group('compatibleWith()', () {
+    test('returns the range of compatible versions', () {
+      var constraint = VersionConstraint.compatibleWith(v072);
+
+      expect(
+          constraint,
+          equals(VersionRange(
+              min: v072, includeMin: true, max: v072.nextBreaking)));
+    });
+
+    test('toString() uses "^"', () {
+      var constraint = VersionConstraint.compatibleWith(v072);
+
+      expect(constraint.toString(), equals('^0.7.2'));
+    });
+  });
+}
diff --git a/pkgs/pub_semver/test/version_range_test.dart b/pkgs/pub_semver/test/version_range_test.dart
new file mode 100644
index 0000000..5978df0
--- /dev/null
+++ b/pkgs/pub_semver/test/version_range_test.dart
@@ -0,0 +1,998 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+  group('constructor', () {
+    test('takes a min and max', () {
+      var range = VersionRange(min: v123, max: v124);
+      expect(range.isAny, isFalse);
+      expect(range.min, equals(v123));
+      expect(range.max, equals(v124.firstPreRelease));
+    });
+
+    group("doesn't make the max a pre-release if", () {
+      test("it's already a pre-release", () {
+        expect(VersionRange(max: Version.parse('1.2.4-pre')).max,
+            equals(Version.parse('1.2.4-pre')));
+      });
+
+      test('includeMax is true', () {
+        expect(VersionRange(max: v124, includeMax: true).max, equals(v124));
+      });
+
+      test('min is a prerelease of max', () {
+        expect(VersionRange(min: Version.parse('1.2.4-pre'), max: v124).max,
+            equals(v124));
+      });
+
+      test('max has a build identifier', () {
+        expect(VersionRange(max: Version.parse('1.2.4+1')).max,
+            equals(Version.parse('1.2.4+1')));
+      });
+    });
+
+    test('allows omitting max', () {
+      var range = VersionRange(min: v123);
+      expect(range.isAny, isFalse);
+      expect(range.min, equals(v123));
+      expect(range.max, isNull);
+    });
+
+    test('allows omitting min and max', () {
+      var range = VersionRange();
+      expect(range.isAny, isTrue);
+      expect(range.min, isNull);
+      expect(range.max, isNull);
+    });
+
+    test('takes includeMin', () {
+      var range = VersionRange(min: v123, includeMin: true);
+      expect(range.includeMin, isTrue);
+    });
+
+    test('includeMin defaults to false if omitted', () {
+      var range = VersionRange(min: v123);
+      expect(range.includeMin, isFalse);
+    });
+
+    test('takes includeMax', () {
+      var range = VersionRange(max: v123, includeMax: true);
+      expect(range.includeMax, isTrue);
+    });
+
+    test('includeMax defaults to false if omitted', () {
+      var range = VersionRange(max: v123);
+      expect(range.includeMax, isFalse);
+    });
+
+    test('throws if min > max', () {
+      expect(() => VersionRange(min: v124, max: v123), throwsArgumentError);
+    });
+  });
+
+  group('allows()', () {
+    test('version must be greater than min', () {
+      var range = VersionRange(min: v123);
+
+      expect(range, allows(Version.parse('1.3.3'), Version.parse('2.3.3')));
+      expect(
+          range, doesNotAllow(Version.parse('1.2.2'), Version.parse('1.2.3')));
+    });
+
+    test('version must be min or greater if includeMin', () {
+      var range = VersionRange(min: v123, includeMin: true);
+
+      expect(
+          range,
+          allows(Version.parse('1.2.3'), Version.parse('1.3.3'),
+              Version.parse('2.3.3')));
+      expect(range, doesNotAllow(Version.parse('1.2.2')));
+    });
+
+    test('pre-release versions of inclusive min are excluded', () {
+      var range = VersionRange(min: v123, includeMin: true);
+
+      expect(range, allows(Version.parse('1.2.4-dev')));
+      expect(range, doesNotAllow(Version.parse('1.2.3-dev')));
+    });
+
+    test('version must be less than max', () {
+      var range = VersionRange(max: v234);
+
+      expect(range, allows(Version.parse('2.3.3')));
+      expect(
+          range, doesNotAllow(Version.parse('2.3.4'), Version.parse('2.4.3')));
+    });
+
+    test('pre-release versions of non-pre-release max are excluded', () {
+      var range = VersionRange(max: v234);
+
+      expect(range, allows(Version.parse('2.3.3')));
+      expect(range,
+          doesNotAllow(Version.parse('2.3.4-dev'), 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 = VersionRange(min: Version.parse('2.3.4-dev.0'), max: v234);
+
+      expect(range, allows(Version.parse('2.3.4-dev.1')));
+      expect(
+          range,
+          doesNotAllow(Version.parse('2.3.3'), Version.parse('2.3.4-dev'),
+              Version.parse('2.3.4')));
+    });
+
+    test('pre-release versions of pre-release max are included', () {
+      var range = VersionRange(max: Version.parse('2.3.4-dev.2'));
+
+      expect(range, allows(Version.parse('2.3.4-dev.1')));
+      expect(
+          range,
+          doesNotAllow(
+              Version.parse('2.3.4-dev.2'), Version.parse('2.3.4-dev.3')));
+    });
+
+    test('version must be max or less if includeMax', () {
+      var range = VersionRange(min: v123, max: v234, includeMax: true);
+
+      expect(
+          range,
+          allows(
+              Version.parse('2.3.3'),
+              Version.parse('2.3.4'),
+              // Pre-releases of the max are allowed.
+              Version.parse('2.3.4-dev')));
+      expect(range, doesNotAllow(Version.parse('2.4.3')));
+    });
+
+    test('has no min if one was not set', () {
+      var range = VersionRange(max: v123);
+
+      expect(range, allows(Version.parse('0.0.0')));
+      expect(range, doesNotAllow(Version.parse('1.2.3')));
+    });
+
+    test('has no max if one was not set', () {
+      var range = VersionRange(min: v123);
+
+      expect(range, allows(Version.parse('1.3.3'), Version.parse('999.3.3')));
+      expect(range, doesNotAllow(Version.parse('1.2.3')));
+    });
+
+    test('allows any version if there is no min or max', () {
+      var range = VersionRange();
+
+      expect(range, allows(Version.parse('0.0.0'), Version.parse('999.99.9')));
+    });
+
+    test('allows pre-releases of the max with includeMaxPreRelease', () {
+      expect(includeMaxPreReleaseRange, allows(Version.parse('2.0.0-dev')));
+    });
+  });
+
+  group('allowsAll()', () {
+    test('allows an empty constraint', () {
+      expect(
+          VersionRange(min: v123, max: v250).allowsAll(VersionConstraint.empty),
+          isTrue);
+    });
+
+    test('allows allowed versions', () {
+      var range = VersionRange(min: v123, max: v250, includeMax: true);
+      expect(range.allowsAll(v123), isFalse);
+      expect(range.allowsAll(v124), isTrue);
+      expect(range.allowsAll(v250), isTrue);
+      expect(range.allowsAll(v300), isFalse);
+    });
+
+    test('with no min', () {
+      var range = VersionRange(max: v250);
+      expect(range.allowsAll(VersionRange(min: v080, max: v140)), isTrue);
+      expect(range.allowsAll(VersionRange(min: v080, max: v300)), isFalse);
+      expect(range.allowsAll(VersionRange(max: v140)), isTrue);
+      expect(range.allowsAll(VersionRange(max: v300)), isFalse);
+      expect(range.allowsAll(range), isTrue);
+      expect(range.allowsAll(VersionConstraint.any), isFalse);
+    });
+
+    test('with no max', () {
+      var range = VersionRange(min: v010);
+      expect(range.allowsAll(VersionRange(min: v080, max: v140)), isTrue);
+      expect(range.allowsAll(VersionRange(min: v003, max: v140)), isFalse);
+      expect(range.allowsAll(VersionRange(min: v080)), isTrue);
+      expect(range.allowsAll(VersionRange(min: v003)), isFalse);
+      expect(range.allowsAll(range), isTrue);
+      expect(range.allowsAll(VersionConstraint.any), isFalse);
+    });
+
+    test('with a min and max', () {
+      var range = VersionRange(min: v010, max: v250);
+      expect(range.allowsAll(VersionRange(min: v080, max: v140)), isTrue);
+      expect(range.allowsAll(VersionRange(min: v080, max: v300)), isFalse);
+      expect(range.allowsAll(VersionRange(min: v003, max: v140)), isFalse);
+      expect(range.allowsAll(VersionRange(min: v080)), isFalse);
+      expect(range.allowsAll(VersionRange(max: v140)), isFalse);
+      expect(range.allowsAll(range), isTrue);
+    });
+
+    test("allows a bordering range that's not more inclusive", () {
+      var exclusive = VersionRange(min: v010, max: v250);
+      var inclusive = VersionRange(
+          min: v010, includeMin: true, max: v250, includeMax: true);
+      expect(inclusive.allowsAll(exclusive), isTrue);
+      expect(inclusive.allowsAll(inclusive), isTrue);
+      expect(exclusive.allowsAll(inclusive), isFalse);
+      expect(exclusive.allowsAll(exclusive), isTrue);
+    });
+
+    test('allows unions that are completely contained', () {
+      var range = VersionRange(min: v114, max: v200);
+      expect(range.allowsAll(VersionRange(min: v123, max: v124).union(v140)),
+          isTrue);
+      expect(range.allowsAll(VersionRange(min: v010, max: v124).union(v140)),
+          isFalse);
+      expect(range.allowsAll(VersionRange(min: v123, max: v234).union(v140)),
+          isFalse);
+    });
+
+    group('pre-release versions', () {
+      test('of inclusive min are excluded', () {
+        var range = VersionRange(min: v123, includeMin: true);
+
+        expect(range.allowsAll(VersionConstraint.parse('>1.2.4-dev')), isTrue);
+        expect(range.allowsAll(VersionConstraint.parse('>1.2.3-dev')), isFalse);
+      });
+
+      test('of non-pre-release max are excluded', () {
+        var range = VersionRange(max: v234);
+
+        expect(range.allowsAll(VersionConstraint.parse('<2.3.3')), isTrue);
+        expect(range.allowsAll(VersionConstraint.parse('<2.3.4-dev')), isFalse);
+      });
+
+      test('of non-pre-release max are included with includeMaxPreRelease', () {
+        expect(
+            includeMaxPreReleaseRange
+                .allowsAll(VersionConstraint.parse('<2.0.0-dev')),
+            isTrue);
+      });
+
+      test(
+          'of non-pre-release max are included if min is a pre-release of the '
+          'same version', () {
+        var range = VersionRange(min: Version.parse('2.3.4-dev.0'), max: v234);
+
+        expect(
+            range.allowsAll(
+                VersionConstraint.parse('>2.3.4-dev.0 <2.3.4-dev.1')),
+            isTrue);
+      });
+
+      test('of pre-release max are included', () {
+        var range = VersionRange(max: Version.parse('2.3.4-dev.2'));
+
+        expect(
+            range.allowsAll(VersionConstraint.parse('<2.3.4-dev.1')), isTrue);
+        expect(
+            range.allowsAll(VersionConstraint.parse('<2.3.4-dev.2')), isTrue);
+        expect(
+            range.allowsAll(VersionConstraint.parse('<=2.3.4-dev.2')), isFalse);
+        expect(
+            range.allowsAll(VersionConstraint.parse('<2.3.4-dev.3')), isFalse);
+      });
+    });
+  });
+
+  group('allowsAny()', () {
+    test('disallows an empty constraint', () {
+      expect(
+          VersionRange(min: v123, max: v250).allowsAny(VersionConstraint.empty),
+          isFalse);
+    });
+
+    test('allows allowed versions', () {
+      var range = VersionRange(min: v123, max: v250, includeMax: true);
+      expect(range.allowsAny(v123), isFalse);
+      expect(range.allowsAny(v124), isTrue);
+      expect(range.allowsAny(v250), isTrue);
+      expect(range.allowsAny(v300), isFalse);
+    });
+
+    test('with no min', () {
+      var range = VersionRange(max: v200);
+      expect(range.allowsAny(VersionRange(min: v140, max: v300)), isTrue);
+      expect(range.allowsAny(VersionRange(min: v234, max: v300)), isFalse);
+      expect(range.allowsAny(VersionRange(min: v140)), isTrue);
+      expect(range.allowsAny(VersionRange(min: v234)), isFalse);
+      expect(range.allowsAny(range), isTrue);
+    });
+
+    test('with no max', () {
+      var range = VersionRange(min: v072);
+      expect(range.allowsAny(VersionRange(min: v003, max: v140)), isTrue);
+      expect(range.allowsAny(VersionRange(min: v003, max: v010)), isFalse);
+      expect(range.allowsAny(VersionRange(max: v080)), isTrue);
+      expect(range.allowsAny(VersionRange(max: v003)), isFalse);
+      expect(range.allowsAny(range), isTrue);
+    });
+
+    test('with a min and max', () {
+      var range = VersionRange(min: v072, max: v200);
+      expect(range.allowsAny(VersionRange(min: v003, max: v140)), isTrue);
+      expect(range.allowsAny(VersionRange(min: v140, max: v300)), isTrue);
+      expect(range.allowsAny(VersionRange(min: v003, max: v010)), isFalse);
+      expect(range.allowsAny(VersionRange(min: v234, max: v300)), isFalse);
+      expect(range.allowsAny(VersionRange(max: v010)), isFalse);
+      expect(range.allowsAny(VersionRange(min: v234)), isFalse);
+      expect(range.allowsAny(range), isTrue);
+    });
+
+    test('allows a bordering range when both are inclusive', () {
+      expect(
+          VersionRange(max: v250).allowsAny(VersionRange(min: v250)), isFalse);
+
+      expect(
+          VersionRange(max: v250, includeMax: true)
+              .allowsAny(VersionRange(min: v250)),
+          isFalse);
+
+      expect(
+          VersionRange(max: v250)
+              .allowsAny(VersionRange(min: v250, includeMin: true)),
+          isFalse);
+
+      expect(
+          VersionRange(max: v250, includeMax: true)
+              .allowsAny(VersionRange(min: v250, includeMin: true)),
+          isTrue);
+
+      expect(
+          VersionRange(min: v250).allowsAny(VersionRange(max: v250)), isFalse);
+
+      expect(
+          VersionRange(min: v250, includeMin: true)
+              .allowsAny(VersionRange(max: v250)),
+          isFalse);
+
+      expect(
+          VersionRange(min: v250)
+              .allowsAny(VersionRange(max: v250, includeMax: true)),
+          isFalse);
+
+      expect(
+          VersionRange(min: v250, includeMin: true)
+              .allowsAny(VersionRange(max: v250, includeMax: true)),
+          isTrue);
+    });
+
+    test('allows unions that are partially contained', () {
+      var range = VersionRange(min: v114, max: v200);
+      expect(range.allowsAny(VersionRange(min: v010, max: v080).union(v140)),
+          isTrue);
+      expect(range.allowsAny(VersionRange(min: v123, max: v234).union(v300)),
+          isTrue);
+      expect(range.allowsAny(VersionRange(min: v234, max: v300).union(v010)),
+          isFalse);
+    });
+
+    group('pre-release versions', () {
+      test('of inclusive min are excluded', () {
+        var range = VersionRange(min: v123, includeMin: true);
+
+        expect(range.allowsAny(VersionConstraint.parse('<1.2.4-dev')), isTrue);
+        expect(range.allowsAny(VersionConstraint.parse('<1.2.3-dev')), isFalse);
+      });
+
+      test('of non-pre-release max are excluded', () {
+        var range = VersionRange(max: v234);
+
+        expect(range.allowsAny(VersionConstraint.parse('>2.3.3')), isTrue);
+        expect(range.allowsAny(VersionConstraint.parse('>2.3.4-dev')), isFalse);
+      });
+
+      test('of non-pre-release max are included with includeMaxPreRelease', () {
+        expect(
+            includeMaxPreReleaseRange
+                .allowsAny(VersionConstraint.parse('>2.0.0-dev')),
+            isTrue);
+      });
+
+      test(
+          'of non-pre-release max are included if min is a pre-release of the '
+          'same version', () {
+        var range = VersionRange(min: Version.parse('2.3.4-dev.0'), max: v234);
+
+        expect(
+            range.allowsAny(VersionConstraint.parse('>2.3.4-dev.1')), isTrue);
+        expect(range.allowsAny(VersionConstraint.parse('>2.3.4')), isFalse);
+
+        expect(
+            range.allowsAny(VersionConstraint.parse('<2.3.4-dev.1')), isTrue);
+        expect(range.allowsAny(VersionConstraint.parse('<2.3.4-dev')), isFalse);
+      });
+
+      test('of pre-release max are included', () {
+        var range = VersionConstraint.parse('<2.3.4-dev.2');
+
+        expect(
+            range.allowsAny(VersionConstraint.parse('>2.3.4-dev.1')), isTrue);
+        expect(
+            range.allowsAny(VersionConstraint.parse('>2.3.4-dev.2')), isFalse);
+        expect(
+            range.allowsAny(VersionConstraint.parse('>2.3.4-dev.3')), isFalse);
+      });
+    });
+  });
+
+  group('intersect()', () {
+    test('two overlapping ranges', () {
+      expect(
+          VersionRange(min: v123, max: v250)
+              .intersect(VersionRange(min: v200, max: v300)),
+          equals(VersionRange(min: v200, max: v250)));
+    });
+
+    test('a non-overlapping range allows no versions', () {
+      var a = VersionRange(min: v114, max: v124);
+      var b = VersionRange(min: v200, max: v250);
+      expect(a.intersect(b).isEmpty, isTrue);
+    });
+
+    test('adjacent ranges allow no versions if exclusive', () {
+      var a = VersionRange(min: v114, max: v124);
+      var b = VersionRange(min: v124, max: v200);
+      expect(a.intersect(b).isEmpty, isTrue);
+    });
+
+    test('adjacent ranges allow version if inclusive', () {
+      var a = VersionRange(min: v114, max: v124, includeMax: true);
+      var b = VersionRange(min: v124, max: v200, includeMin: true);
+      expect(a.intersect(b), equals(v124));
+    });
+
+    test('with an open range', () {
+      var open = VersionRange();
+      var a = VersionRange(min: v114, max: v124);
+      expect(open.intersect(open), equals(open));
+      expect(a.intersect(open), equals(a));
+    });
+
+    test('returns the version if the range allows it', () {
+      expect(VersionRange(min: v114, max: v124).intersect(v123), equals(v123));
+      expect(
+          VersionRange(min: v123, max: v124).intersect(v114).isEmpty, isTrue);
+    });
+
+    test('with a range with a pre-release min, returns an empty constraint',
+        () {
+      expect(
+          VersionRange(max: v200)
+              .intersect(VersionConstraint.parse('>=2.0.0-dev')),
+          equals(VersionConstraint.empty));
+    });
+
+    test('with a range with a pre-release max, returns the original', () {
+      expect(
+          VersionRange(max: v200)
+              .intersect(VersionConstraint.parse('<2.0.0-dev')),
+          equals(VersionRange(max: v200)));
+    });
+
+    group('with includeMaxPreRelease', () {
+      test('preserves includeMaxPreRelease if the max version is included', () {
+        expect(
+            includeMaxPreReleaseRange
+                .intersect(VersionConstraint.parse('<1.0.0')),
+            equals(VersionConstraint.parse('<1.0.0')));
+        expect(
+            includeMaxPreReleaseRange
+                .intersect(VersionConstraint.parse('<2.0.0')),
+            equals(VersionConstraint.parse('<2.0.0')));
+        expect(includeMaxPreReleaseRange.intersect(includeMaxPreReleaseRange),
+            equals(includeMaxPreReleaseRange));
+        expect(
+            includeMaxPreReleaseRange
+                .intersect(VersionConstraint.parse('<3.0.0')),
+            equals(includeMaxPreReleaseRange));
+        expect(
+            includeMaxPreReleaseRange
+                .intersect(VersionConstraint.parse('>1.1.4')),
+            equals(VersionRange(
+                min: v114, max: v200, alwaysIncludeMaxPreRelease: true)));
+      });
+
+      test(
+          'and a range with a pre-release min, returns '
+          'an intersection', () {
+        expect(
+            includeMaxPreReleaseRange
+                .intersect(VersionConstraint.parse('>=2.0.0-dev')),
+            equals(VersionConstraint.parse('>=2.0.0-dev <2.0.0')));
+      });
+
+      test(
+          'and a range with a pre-release max, returns '
+          'the narrower constraint', () {
+        expect(
+            includeMaxPreReleaseRange
+                .intersect(VersionConstraint.parse('<2.0.0-dev')),
+            equals(VersionConstraint.parse('<2.0.0-dev')));
+      });
+    });
+  });
+
+  group('union()', () {
+    test('with a version returns the range if it contains the version', () {
+      var range = VersionRange(min: v114, max: v124);
+      expect(range.union(v123), equals(range));
+    });
+
+    test('with a version on the edge of the range, expands the range', () {
+      expect(
+          VersionRange(min: v114, max: v124, alwaysIncludeMaxPreRelease: true)
+              .union(v124),
+          equals(VersionRange(min: v114, max: v124, includeMax: true)));
+      expect(VersionRange(min: v114, max: v124).union(v114),
+          equals(VersionRange(min: v114, max: v124, includeMin: true)));
+    });
+
+    test(
+        'with a version allows both the range and the version if the range '
+        "doesn't contain the version", () {
+      var result = VersionRange(min: v003, max: v114).union(v124);
+      expect(result, allows(v010));
+      expect(result, doesNotAllow(v123));
+      expect(result, allows(v124));
+    });
+
+    test('returns a VersionUnion for a disjoint range', () {
+      var result = VersionRange(min: v003, max: v114)
+          .union(VersionRange(min: v130, max: v200));
+      expect(result, allows(v080));
+      expect(result, doesNotAllow(v123));
+      expect(result, allows(v140));
+    });
+
+    test('returns a VersionUnion for a disjoint range with infinite end', () {
+      void isVersionUnion(VersionConstraint constraint) {
+        expect(constraint, allows(v080));
+        expect(constraint, doesNotAllow(v123));
+        expect(constraint, allows(v140));
+      }
+
+      for (final includeAMin in [true, false]) {
+        for (final includeAMax in [true, false]) {
+          for (final includeBMin in [true, false]) {
+            for (final includeBMax in [true, false]) {
+              final a = VersionRange(
+                  min: v130, includeMin: includeAMin, includeMax: includeAMax);
+              final b = VersionRange(
+                  max: v114, includeMin: includeBMin, includeMax: includeBMax);
+              isVersionUnion(a.union(b));
+              isVersionUnion(b.union(a));
+            }
+          }
+        }
+      }
+    });
+
+    test('considers open ranges disjoint', () {
+      var result = VersionRange(min: v003, max: v114)
+          .union(VersionRange(min: v114, max: v200));
+      expect(result, allows(v080));
+      expect(result, doesNotAllow(v114));
+      expect(result, allows(v140));
+
+      result = VersionRange(min: v114, max: v200)
+          .union(VersionRange(min: v003, max: v114));
+      expect(result, allows(v080));
+      expect(result, doesNotAllow(v114));
+      expect(result, allows(v140));
+    });
+
+    test('returns a merged range for an overlapping range', () {
+      var result = VersionRange(min: v003, max: v114)
+          .union(VersionRange(min: v080, max: v200));
+      expect(result, equals(VersionRange(min: v003, max: v200)));
+    });
+
+    test('considers closed ranges overlapping', () {
+      var result = VersionRange(min: v003, max: v114, includeMax: true)
+          .union(VersionRange(min: v114, max: v200));
+      expect(result, equals(VersionRange(min: v003, max: v200)));
+
+      result =
+          VersionRange(min: v003, max: v114, alwaysIncludeMaxPreRelease: true)
+              .union(VersionRange(min: v114, max: v200, includeMin: true));
+      expect(result, equals(VersionRange(min: v003, max: v200)));
+
+      result = VersionRange(min: v114, max: v200)
+          .union(VersionRange(min: v003, max: v114, includeMax: true));
+      expect(result, equals(VersionRange(min: v003, max: v200)));
+
+      result = VersionRange(min: v114, max: v200, includeMin: true).union(
+          VersionRange(min: v003, max: v114, alwaysIncludeMaxPreRelease: true));
+      expect(result, equals(VersionRange(min: v003, max: v200)));
+    });
+
+    test('includes edges if either range does', () {
+      var result = VersionRange(min: v003, max: v114, includeMin: true)
+          .union(VersionRange(min: v003, max: v114, includeMax: true));
+      expect(
+          result,
+          equals(VersionRange(
+              min: v003, max: v114, includeMin: true, includeMax: true)));
+    });
+
+    test('with a range with a pre-release min, returns a constraint with a gap',
+        () {
+      var result =
+          VersionRange(max: v200).union(VersionConstraint.parse('>=2.0.0-dev'));
+      expect(result, allows(v140));
+      expect(result, doesNotAllow(Version.parse('2.0.0-alpha')));
+      expect(result, allows(Version.parse('2.0.0-dev')));
+      expect(result, allows(Version.parse('2.0.0-dev.1')));
+      expect(result, allows(Version.parse('2.0.0')));
+    });
+
+    test('with a range with a pre-release max, returns the larger constraint',
+        () {
+      expect(
+          VersionRange(max: v200).union(VersionConstraint.parse('<2.0.0-dev')),
+          equals(VersionConstraint.parse('<2.0.0-dev')));
+    });
+
+    group('with includeMaxPreRelease', () {
+      test('adds includeMaxPreRelease if the max version is included', () {
+        expect(
+            includeMaxPreReleaseRange.union(VersionConstraint.parse('<1.0.0')),
+            equals(includeMaxPreReleaseRange));
+        expect(includeMaxPreReleaseRange.union(includeMaxPreReleaseRange),
+            equals(includeMaxPreReleaseRange));
+        expect(
+            includeMaxPreReleaseRange.union(VersionConstraint.parse('<2.0.0')),
+            equals(includeMaxPreReleaseRange));
+        expect(
+            includeMaxPreReleaseRange.union(VersionConstraint.parse('<3.0.0')),
+            equals(VersionConstraint.parse('<3.0.0')));
+      });
+
+      test('and a range with a pre-release min, returns any', () {
+        expect(
+            includeMaxPreReleaseRange
+                .union(VersionConstraint.parse('>=2.0.0-dev')),
+            equals(VersionConstraint.any));
+      });
+
+      test('and a range with a pre-release max, returns the original', () {
+        expect(
+            includeMaxPreReleaseRange
+                .union(VersionConstraint.parse('<2.0.0-dev')),
+            equals(includeMaxPreReleaseRange));
+      });
+    });
+  });
+
+  group('difference()', () {
+    test('with an empty range returns the original range', () {
+      expect(
+          VersionRange(min: v003, max: v114)
+              .difference(VersionConstraint.empty),
+          equals(VersionRange(min: v003, max: v114)));
+    });
+
+    test('with a version outside the range returns the original range', () {
+      expect(VersionRange(min: v003, max: v114).difference(v200),
+          equals(VersionRange(min: v003, max: v114)));
+    });
+
+    test('with a version in the range splits the range', () {
+      expect(
+          VersionRange(min: v003, max: v114).difference(v072),
+          equals(VersionConstraint.unionOf([
+            VersionRange(
+                min: v003, max: v072, alwaysIncludeMaxPreRelease: true),
+            VersionRange(min: v072, max: v114)
+          ])));
+    });
+
+    test('with the max version makes the max exclusive', () {
+      expect(
+          VersionRange(min: v003, max: v114, includeMax: true).difference(v114),
+          equals(VersionRange(
+              min: v003, max: v114, alwaysIncludeMaxPreRelease: true)));
+    });
+
+    test('with the min version makes the min exclusive', () {
+      expect(
+          VersionRange(min: v003, max: v114, includeMin: true).difference(v003),
+          equals(VersionRange(min: v003, max: v114)));
+    });
+
+    test('with a disjoint range returns the original', () {
+      expect(
+          VersionRange(min: v003, max: v114)
+              .difference(VersionRange(min: v123, max: v140)),
+          equals(VersionRange(min: v003, max: v114)));
+    });
+
+    test('with an adjacent range returns the original', () {
+      expect(
+          VersionRange(min: v003, max: v114, includeMax: true)
+              .difference(VersionRange(min: v114, max: v140)),
+          equals(VersionRange(min: v003, max: v114, includeMax: true)));
+    });
+
+    test('with a range at the beginning cuts off the beginning of the range',
+        () {
+      expect(
+          VersionRange(min: v080, max: v130)
+              .difference(VersionRange(min: v010, max: v114)),
+          equals(VersionConstraint.parse('>=1.1.4-0 <1.3.0')));
+      expect(
+          VersionRange(min: v080, max: v130)
+              .difference(VersionRange(max: v114)),
+          equals(VersionConstraint.parse('>=1.1.4-0 <1.3.0')));
+      expect(
+          VersionRange(min: v080, max: v130)
+              .difference(VersionRange(min: v010, max: v114, includeMax: true)),
+          equals(VersionRange(min: v114, max: v130)));
+      expect(
+          VersionRange(min: v080, max: v130, includeMin: true)
+              .difference(VersionRange(min: v010, max: v080, includeMax: true)),
+          equals(VersionRange(min: v080, max: v130)));
+      expect(
+          VersionRange(min: v080, max: v130, includeMax: true)
+              .difference(VersionRange(min: v080, max: v130)),
+          equals(VersionConstraint.parse('>=1.3.0-0 <=1.3.0')));
+    });
+
+    test('with a range at the end cuts off the end of the range', () {
+      expect(
+          VersionRange(min: v080, max: v130)
+              .difference(VersionRange(min: v114, max: v140)),
+          equals(VersionRange(min: v080, max: v114, includeMax: true)));
+      expect(
+          VersionRange(min: v080, max: v130)
+              .difference(VersionRange(min: v114)),
+          equals(VersionRange(min: v080, max: v114, includeMax: true)));
+      expect(
+          VersionRange(min: v080, max: v130)
+              .difference(VersionRange(min: v114, max: v140, includeMin: true)),
+          equals(VersionRange(
+              min: v080, max: v114, alwaysIncludeMaxPreRelease: true)));
+      expect(
+          VersionRange(min: v080, max: v130, includeMax: true)
+              .difference(VersionRange(min: v130, max: v140, includeMin: true)),
+          equals(VersionRange(
+              min: v080, max: v130, alwaysIncludeMaxPreRelease: true)));
+      expect(
+          VersionRange(min: v080, max: v130, includeMin: true)
+              .difference(VersionRange(min: v080, max: v130)),
+          equals(v080));
+    });
+
+    test('with a range in the middle cuts the range in half', () {
+      expect(
+          VersionRange(min: v003, max: v130)
+              .difference(VersionRange(min: v072, max: v114)),
+          equals(VersionConstraint.unionOf([
+            VersionRange(min: v003, max: v072, includeMax: true),
+            VersionConstraint.parse('>=1.1.4-0 <1.3.0')
+          ])));
+    });
+
+    test('with a totally covering range returns empty', () {
+      expect(
+          VersionRange(min: v114, max: v200)
+              .difference(VersionRange(min: v072, max: v300)),
+          isEmpty);
+      expect(
+          VersionRange(min: v003, max: v114)
+              .difference(VersionRange(min: v003, max: v114)),
+          isEmpty);
+      expect(
+          VersionRange(min: v003, max: v114, includeMin: true, includeMax: true)
+              .difference(VersionRange(
+                  min: v003, max: v114, includeMin: true, includeMax: true)),
+          isEmpty);
+    });
+
+    test(
+        "with a version union that doesn't cover the range, returns the "
+        'original', () {
+      expect(
+          VersionRange(min: v114, max: v140)
+              .difference(VersionConstraint.unionOf([v010, v200])),
+          equals(VersionRange(min: v114, max: v140)));
+    });
+
+    test('with a version union that intersects the ends, chops them off', () {
+      expect(
+          VersionRange(min: v114, max: v140).difference(
+              VersionConstraint.unionOf([
+            VersionRange(min: v080, max: v123),
+            VersionRange(min: v130, max: v200)
+          ])),
+          equals(VersionConstraint.parse('>=1.2.3-0 <=1.3.0')));
+    });
+
+    test('with a version union that intersects the middle, chops it up', () {
+      expect(
+          VersionRange(min: v114, max: v140)
+              .difference(VersionConstraint.unionOf([v123, v124, v130])),
+          equals(VersionConstraint.unionOf([
+            VersionRange(
+                min: v114, max: v123, alwaysIncludeMaxPreRelease: true),
+            VersionRange(
+                min: v123, max: v124, alwaysIncludeMaxPreRelease: true),
+            VersionRange(
+                min: v124, max: v130, alwaysIncludeMaxPreRelease: true),
+            VersionRange(min: v130, max: v140)
+          ])));
+    });
+
+    test('with a version union that covers the whole range, returns empty', () {
+      expect(
+          VersionRange(min: v114, max: v140).difference(
+              VersionConstraint.unionOf([v003, VersionRange(min: v010)])),
+          equals(VersionConstraint.empty));
+    });
+
+    test('with a range with a pre-release min, returns the original', () {
+      expect(
+          VersionRange(max: v200)
+              .difference(VersionConstraint.parse('>=2.0.0-dev')),
+          equals(VersionRange(max: v200)));
+    });
+
+    test('with a range with a pre-release max, returns null', () {
+      expect(
+          VersionRange(max: v200)
+              .difference(VersionConstraint.parse('<2.0.0-dev')),
+          equals(VersionConstraint.empty));
+    });
+
+    group('with includeMaxPreRelease', () {
+      group('for the minuend', () {
+        test('preserves includeMaxPreRelease if the max version is included',
+            () {
+          expect(
+              includeMaxPreReleaseRange
+                  .difference(VersionConstraint.parse('<1.0.0')),
+              equals(VersionRange(
+                  min: Version.parse('1.0.0-0'),
+                  max: v200,
+                  includeMin: true,
+                  alwaysIncludeMaxPreRelease: true)));
+          expect(
+              includeMaxPreReleaseRange
+                  .difference(VersionConstraint.parse('<2.0.0')),
+              equals(VersionRange(
+                  min: v200.firstPreRelease,
+                  max: v200,
+                  includeMin: true,
+                  alwaysIncludeMaxPreRelease: true)));
+          expect(
+              includeMaxPreReleaseRange.difference(includeMaxPreReleaseRange),
+              equals(VersionConstraint.empty));
+          expect(
+              includeMaxPreReleaseRange
+                  .difference(VersionConstraint.parse('<3.0.0')),
+              equals(VersionConstraint.empty));
+        });
+
+        test('with a range with a pre-release min, adjusts the max', () {
+          expect(
+              includeMaxPreReleaseRange
+                  .difference(VersionConstraint.parse('>=2.0.0-dev')),
+              equals(VersionConstraint.parse('<2.0.0-dev')));
+        });
+
+        test('with a range with a pre-release max, adjusts the min', () {
+          expect(
+              includeMaxPreReleaseRange
+                  .difference(VersionConstraint.parse('<2.0.0-dev')),
+              equals(VersionConstraint.parse('>=2.0.0-dev <2.0.0')));
+        });
+      });
+
+      group('for the subtrahend', () {
+        group("doesn't create a pre-release minimum", () {
+          test('when cutting off the bottom', () {
+            expect(
+                VersionConstraint.parse('<3.0.0')
+                    .difference(includeMaxPreReleaseRange),
+                equals(VersionRange(min: v200, max: v300, includeMin: true)));
+          });
+
+          test('with splitting down the middle', () {
+            expect(
+                VersionConstraint.parse('<4.0.0').difference(VersionRange(
+                    min: v200,
+                    max: v300,
+                    includeMin: true,
+                    alwaysIncludeMaxPreRelease: true)),
+                equals(VersionConstraint.unionOf([
+                  VersionRange(max: v200, alwaysIncludeMaxPreRelease: true),
+                  VersionConstraint.parse('>=3.0.0 <4.0.0')
+                ])));
+          });
+
+          test('can leave a single version', () {
+            expect(
+                VersionConstraint.parse('<=2.0.0')
+                    .difference(includeMaxPreReleaseRange),
+                equals(v200));
+          });
+        });
+      });
+    });
+  });
+
+  test('isEmpty', () {
+    expect(VersionRange().isEmpty, isFalse);
+    expect(VersionRange(min: v123, max: v124).isEmpty, isFalse);
+  });
+
+  group('compareTo()', () {
+    test('orders by minimum first', () {
+      _expectComparesSmaller(VersionRange(min: v003, max: v080),
+          VersionRange(min: v010, max: v072));
+      _expectComparesSmaller(VersionRange(min: v003, max: v080),
+          VersionRange(min: v010, max: v080));
+      _expectComparesSmaller(VersionRange(min: v003, max: v080),
+          VersionRange(min: v010, max: v114));
+    });
+
+    test('orders by maximum second', () {
+      _expectComparesSmaller(VersionRange(min: v003, max: v010),
+          VersionRange(min: v003, max: v072));
+    });
+
+    test('includeMin comes before !includeMin', () {
+      _expectComparesSmaller(
+          VersionRange(min: v003, max: v080, includeMin: true),
+          VersionRange(min: v003, max: v080));
+    });
+
+    test('includeMax comes after !includeMax', () {
+      _expectComparesSmaller(VersionRange(min: v003, max: v080),
+          VersionRange(min: v003, max: v080, includeMax: true));
+    });
+
+    test('includeMaxPreRelease comes after !includeMaxPreRelease', () {
+      _expectComparesSmaller(
+          VersionRange(max: v200), includeMaxPreReleaseRange);
+    });
+
+    test('no minimum comes before small minimum', () {
+      _expectComparesSmaller(
+          VersionRange(max: v010), VersionRange(min: v003, max: v010));
+      _expectComparesSmaller(VersionRange(max: v010, includeMin: true),
+          VersionRange(min: v003, max: v010));
+    });
+
+    test('no maximium comes after large maximum', () {
+      _expectComparesSmaller(
+          VersionRange(min: v003, max: v300), VersionRange(min: v003));
+      _expectComparesSmaller(VersionRange(min: v003, max: v300),
+          VersionRange(min: v003, includeMax: true));
+    });
+  });
+}
+
+void _expectComparesSmaller(VersionRange smaller, VersionRange larger) {
+  expect(smaller.compareTo(larger), lessThan(0),
+      reason: 'expected $smaller to sort below $larger');
+  expect(larger.compareTo(smaller), greaterThan(0),
+      reason: 'expected $larger to sort above $smaller');
+}
diff --git a/pkgs/pub_semver/test/version_test.dart b/pkgs/pub_semver/test/version_test.dart
new file mode 100644
index 0000000..d7f1197
--- /dev/null
+++ b/pkgs/pub_semver/test/version_test.dart
@@ -0,0 +1,411 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+  test('none', () {
+    expect(Version.none.toString(), equals('0.0.0'));
+  });
+
+  test('prioritize()', () {
+    // A correctly sorted list of versions in order of increasing priority.
+    var versions = [
+      '1.0.0-alpha',
+      '2.0.0-alpha',
+      '1.0.0',
+      '1.0.0+build',
+      '1.0.1',
+      '1.1.0',
+      '2.0.0'
+    ];
+
+    // Ensure that every pair of versions is prioritized in the order that it
+    // appears in the list.
+    for (var i = 0; i < versions.length; i++) {
+      for (var j = 0; j < versions.length; j++) {
+        var a = Version.parse(versions[i]);
+        var b = Version.parse(versions[j]);
+        expect(Version.prioritize(a, b), equals(i.compareTo(j)));
+      }
+    }
+  });
+
+  test('antiprioritize()', () {
+    // A correctly sorted list of versions in order of increasing antipriority.
+    var versions = [
+      '2.0.0-alpha',
+      '1.0.0-alpha',
+      '2.0.0',
+      '1.1.0',
+      '1.0.1',
+      '1.0.0+build',
+      '1.0.0'
+    ];
+
+    // Ensure that every pair of versions is prioritized in the order that it
+    // appears in the list.
+    for (var i = 0; i < versions.length; i++) {
+      for (var j = 0; j < versions.length; j++) {
+        var a = Version.parse(versions[i]);
+        var b = Version.parse(versions[j]);
+        expect(Version.antiprioritize(a, b), equals(i.compareTo(j)));
+      }
+    }
+  });
+
+  group('constructor', () {
+    test('throws on negative numbers', () {
+      expect(() => Version(-1, 1, 1), throwsArgumentError);
+      expect(() => Version(1, -1, 1), throwsArgumentError);
+      expect(() => Version(1, 1, -1), throwsArgumentError);
+    });
+  });
+
+  group('comparison', () {
+    // A correctly sorted list of versions.
+    var versions = [
+      '1.0.0-alpha',
+      '1.0.0-alpha.1',
+      '1.0.0-beta.2',
+      '1.0.0-beta.11',
+      '1.0.0-rc.1',
+      '1.0.0-rc.1+build.1',
+      '1.0.0',
+      '1.0.0+0.3.7',
+      '1.3.7+build',
+      '1.3.7+build.2.b8f12d7',
+      '1.3.7+build.11.e0f985a',
+      '2.0.0',
+      '2.1.0',
+      '2.2.0',
+      '2.11.0',
+      '2.11.1'
+    ];
+
+    test('compareTo()', () {
+      // Ensure that every pair of versions compares in the order that it
+      // appears in the list.
+      for (var i = 0; i < versions.length; i++) {
+        for (var j = 0; j < versions.length; j++) {
+          var a = Version.parse(versions[i]);
+          var b = Version.parse(versions[j]);
+          expect(a.compareTo(b), equals(i.compareTo(j)));
+        }
+      }
+    });
+
+    test('operators', () {
+      for (var i = 0; i < versions.length; i++) {
+        for (var j = 0; j < versions.length; j++) {
+          var a = Version.parse(versions[i]);
+          var b = Version.parse(versions[j]);
+          expect(a < b, equals(i < j));
+          expect(a > b, equals(i > j));
+          expect(a <= b, equals(i <= j));
+          expect(a >= b, equals(i >= j));
+          expect(a == b, equals(i == j));
+          expect(a != b, equals(i != j));
+        }
+      }
+    });
+
+    test('equality', () {
+      expect(Version.parse('01.2.3'), equals(Version.parse('1.2.3')));
+      expect(Version.parse('1.02.3'), equals(Version.parse('1.2.3')));
+      expect(Version.parse('1.2.03'), equals(Version.parse('1.2.3')));
+      expect(Version.parse('1.2.3-01'), equals(Version.parse('1.2.3-1')));
+      expect(Version.parse('1.2.3+01'), equals(Version.parse('1.2.3+1')));
+    });
+  });
+
+  test('allows()', () {
+    expect(v123, allows(v123));
+    expect(
+        v123,
+        doesNotAllow(
+            Version.parse('2.2.3'),
+            Version.parse('1.3.3'),
+            Version.parse('1.2.4'),
+            Version.parse('1.2.3-dev'),
+            Version.parse('1.2.3+build')));
+  });
+
+  test('allowsAll()', () {
+    expect(v123.allowsAll(v123), isTrue);
+    expect(v123.allowsAll(v003), isFalse);
+    expect(v123.allowsAll(VersionRange(min: v114, max: v124)), isFalse);
+    expect(v123.allowsAll(VersionConstraint.any), isFalse);
+    expect(v123.allowsAll(VersionConstraint.empty), isTrue);
+  });
+
+  test('allowsAny()', () {
+    expect(v123.allowsAny(v123), isTrue);
+    expect(v123.allowsAny(v003), isFalse);
+    expect(v123.allowsAny(VersionRange(min: v114, max: v124)), isTrue);
+    expect(v123.allowsAny(VersionConstraint.any), isTrue);
+    expect(v123.allowsAny(VersionConstraint.empty), isFalse);
+  });
+
+  test('intersect()', () {
+    // Intersecting the same version returns the version.
+    expect(v123.intersect(v123), equals(v123));
+
+    // Intersecting a different version allows no versions.
+    expect(v123.intersect(v114).isEmpty, isTrue);
+
+    // Intersecting a range returns the version if the range allows it.
+    expect(v123.intersect(VersionRange(min: v114, max: v124)), equals(v123));
+
+    // Intersecting a range allows no versions if the range doesn't allow it.
+    expect(v114.intersect(VersionRange(min: v123, max: v124)).isEmpty, isTrue);
+  });
+
+  group('union()', () {
+    test('with the same version returns the version', () {
+      expect(v123.union(v123), equals(v123));
+    });
+
+    test('with a different version returns a version that matches both', () {
+      var result = v123.union(v080);
+      expect(result, allows(v123));
+      expect(result, allows(v080));
+
+      // Nothing in between should match.
+      expect(result, doesNotAllow(v114));
+    });
+
+    test('with a range returns the range if it contains the version', () {
+      var range = VersionRange(min: v114, max: v124);
+      expect(v123.union(range), equals(range));
+    });
+
+    test('with a range with the version on the edge, expands the range', () {
+      expect(
+          v124.union(VersionRange(
+              min: v114, max: v124, alwaysIncludeMaxPreRelease: true)),
+          equals(VersionRange(min: v114, max: v124, includeMax: true)));
+      expect(
+          v124.firstPreRelease.union(VersionRange(min: v114, max: v124)),
+          equals(VersionRange(
+              min: v114, max: v124.firstPreRelease, includeMax: true)));
+      expect(v114.union(VersionRange(min: v114, max: v124)),
+          equals(VersionRange(min: v114, max: v124, includeMin: true)));
+    });
+
+    test(
+        'with a range allows both the range and the version if the range '
+        "doesn't contain the version", () {
+      var result = v123.union(VersionRange(min: v003, max: v114));
+      expect(result, allows(v123));
+      expect(result, allows(v010));
+    });
+  });
+
+  group('difference()', () {
+    test('with the same version returns an empty constraint', () {
+      expect(v123.difference(v123), isEmpty);
+    });
+
+    test('with a different version returns the original version', () {
+      expect(v123.difference(v080), equals(v123));
+    });
+
+    test('returns an empty constraint with a range that contains the version',
+        () {
+      expect(v123.difference(VersionRange(min: v114, max: v124)), isEmpty);
+    });
+
+    test("returns the version constraint with a range that doesn't contain it",
+        () {
+      expect(v123.difference(VersionRange(min: v140, max: v300)), equals(v123));
+    });
+  });
+
+  test('isEmpty', () {
+    expect(v123.isEmpty, isFalse);
+  });
+
+  test('nextMajor', () {
+    expect(v123.nextMajor, equals(v200));
+    expect(v114.nextMajor, equals(v200));
+    expect(v200.nextMajor, equals(v300));
+
+    // Ignores pre-release if not on a major version.
+    expect(Version.parse('1.2.3-dev').nextMajor, equals(v200));
+
+    // Just removes it if on a major version.
+    expect(Version.parse('2.0.0-dev').nextMajor, equals(v200));
+
+    // Strips build suffix.
+    expect(Version.parse('1.2.3+patch').nextMajor, equals(v200));
+  });
+
+  test('nextMinor', () {
+    expect(v123.nextMinor, equals(v130));
+    expect(v130.nextMinor, equals(v140));
+
+    // Ignores pre-release if not on a minor version.
+    expect(Version.parse('1.2.3-dev').nextMinor, equals(v130));
+
+    // Just removes it if on a minor version.
+    expect(Version.parse('1.3.0-dev').nextMinor, equals(v130));
+
+    // Strips build suffix.
+    expect(Version.parse('1.2.3+patch').nextMinor, equals(v130));
+  });
+
+  test('nextPatch', () {
+    expect(v123.nextPatch, equals(v124));
+    expect(v200.nextPatch, equals(v201));
+
+    // Just removes pre-release version if present.
+    expect(Version.parse('1.2.4-dev').nextPatch, equals(v124));
+
+    // Strips build suffix.
+    expect(Version.parse('1.2.3+patch').nextPatch, equals(v124));
+  });
+
+  test('nextBreaking', () {
+    expect(v123.nextBreaking, equals(v200));
+    expect(v072.nextBreaking, equals(v080));
+    expect(v003.nextBreaking, equals(v010));
+
+    // Removes pre-release version if present.
+    expect(Version.parse('1.2.3-dev').nextBreaking, equals(v200));
+
+    // Strips build suffix.
+    expect(Version.parse('1.2.3+patch').nextBreaking, equals(v200));
+  });
+
+  test('parse()', () {
+    expect(Version.parse('0.0.0'), equals(Version(0, 0, 0)));
+    expect(Version.parse('12.34.56'), equals(Version(12, 34, 56)));
+
+    expect(Version.parse('1.2.3-alpha.1'),
+        equals(Version(1, 2, 3, pre: 'alpha.1')));
+    expect(Version.parse('1.2.3-x.7.z-92'),
+        equals(Version(1, 2, 3, pre: 'x.7.z-92')));
+
+    expect(Version.parse('1.2.3+build.1'),
+        equals(Version(1, 2, 3, build: 'build.1')));
+    expect(Version.parse('1.2.3+x.7.z-92'),
+        equals(Version(1, 2, 3, build: 'x.7.z-92')));
+
+    expect(Version.parse('1.0.0-rc-1+build-1'),
+        equals(Version(1, 0, 0, pre: 'rc-1', build: 'build-1')));
+
+    expect(() => Version.parse('1.0'), throwsFormatException);
+    expect(() => Version.parse('1a2b3'), throwsFormatException);
+    expect(() => Version.parse('1.2.3.4'), throwsFormatException);
+    expect(() => Version.parse('1234'), throwsFormatException);
+    expect(() => Version.parse('-2.3.4'), throwsFormatException);
+    expect(() => Version.parse('1.3-pre'), throwsFormatException);
+    expect(() => Version.parse('1.3+build'), throwsFormatException);
+    expect(() => Version.parse('1.3+bu?!3ild'), throwsFormatException);
+  });
+
+  group('toString()', () {
+    test('returns the version string', () {
+      expect(Version(0, 0, 0).toString(), equals('0.0.0'));
+      expect(Version(12, 34, 56).toString(), equals('12.34.56'));
+
+      expect(
+          Version(1, 2, 3, pre: 'alpha.1').toString(), equals('1.2.3-alpha.1'));
+      expect(Version(1, 2, 3, pre: 'x.7.z-92').toString(),
+          equals('1.2.3-x.7.z-92'));
+
+      expect(Version(1, 2, 3, build: 'build.1').toString(),
+          equals('1.2.3+build.1'));
+      expect(Version(1, 2, 3, pre: 'pre', build: 'bui').toString(),
+          equals('1.2.3-pre+bui'));
+    });
+
+    test('preserves leading zeroes', () {
+      expect(Version.parse('001.02.0003-01.dev+pre.002').toString(),
+          equals('001.02.0003-01.dev+pre.002'));
+    });
+  });
+
+  group('canonicalizedVersion', () {
+    test('returns version string', () {
+      expect(Version(0, 0, 0).canonicalizedVersion, equals('0.0.0'));
+      expect(Version(12, 34, 56).canonicalizedVersion, equals('12.34.56'));
+
+      expect(Version(1, 2, 3, pre: 'alpha.1').canonicalizedVersion,
+          equals('1.2.3-alpha.1'));
+      expect(Version(1, 2, 3, pre: 'x.7.z-92').canonicalizedVersion,
+          equals('1.2.3-x.7.z-92'));
+
+      expect(Version(1, 2, 3, build: 'build.1').canonicalizedVersion,
+          equals('1.2.3+build.1'));
+      expect(Version(1, 2, 3, pre: 'pre', build: 'bui').canonicalizedVersion,
+          equals('1.2.3-pre+bui'));
+    });
+
+    test('discards leading zeroes', () {
+      expect(Version.parse('001.02.0003-01.dev+pre.002').canonicalizedVersion,
+          equals('1.2.3-1.dev+pre.2'));
+    });
+
+    test('example from documentation', () {
+      final v = Version.parse('01.02.03-01.dev+pre.02');
+
+      assert(v.toString() == '01.02.03-01.dev+pre.02');
+      assert(v.canonicalizedVersion == '1.2.3-1.dev+pre.2');
+      assert(Version.parse(v.canonicalizedVersion) == v);
+    });
+  });
+
+  group('primary', () {
+    test('single', () {
+      expect(
+        _primary([
+          '1.2.3',
+        ]).toString(),
+        '1.2.3',
+      );
+    });
+
+    test('normal', () {
+      expect(
+        _primary([
+          '1.2.3',
+          '1.2.2',
+        ]).toString(),
+        '1.2.3',
+      );
+    });
+
+    test('all prerelease', () {
+      expect(
+        _primary([
+          '1.2.2-dev.1',
+          '1.2.2-dev.2',
+        ]).toString(),
+        '1.2.2-dev.2',
+      );
+    });
+
+    test('later prerelease', () {
+      expect(
+        _primary([
+          '1.2.3',
+          '1.2.3-dev',
+        ]).toString(),
+        '1.2.3',
+      );
+    });
+
+    test('empty', () {
+      expect(() => Version.primary([]), throwsStateError);
+    });
+  });
+}
+
+Version _primary(List<String> input) =>
+    Version.primary(input.map(Version.parse).toList());
diff --git a/pkgs/pub_semver/test/version_union_test.dart b/pkgs/pub_semver/test/version_union_test.dart
new file mode 100644
index 0000000..857f10e
--- /dev/null
+++ b/pkgs/pub_semver/test/version_union_test.dart
@@ -0,0 +1,482 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// 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:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+  group('factory', () {
+    test('ignores empty constraints', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionConstraint.empty,
+            VersionConstraint.empty,
+            v123,
+            VersionConstraint.empty
+          ]),
+          equals(v123));
+
+      expect(
+          VersionConstraint.unionOf(
+              [VersionConstraint.empty, VersionConstraint.empty]),
+          isEmpty);
+    });
+
+    test('returns an empty constraint for an empty list', () {
+      expect(VersionConstraint.unionOf([]), isEmpty);
+    });
+
+    test('any constraints override everything', () {
+      expect(
+          VersionConstraint.unionOf([
+            v123,
+            VersionConstraint.any,
+            v200,
+            VersionRange(min: v234, max: v250)
+          ]),
+          equals(VersionConstraint.any));
+    });
+
+    test('flattens other unions', () {
+      expect(
+          VersionConstraint.unionOf([
+            v072,
+            VersionConstraint.unionOf([v123, v124]),
+            v250
+          ]),
+          equals(VersionConstraint.unionOf([v072, v123, v124, v250])));
+    });
+
+    test('returns a single merged range as-is', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v080, max: v140),
+            VersionRange(min: v123, max: v200)
+          ]),
+          equals(VersionRange(min: v080, max: v200)));
+    });
+  });
+
+  group('equality', () {
+    test("doesn't depend on original order", () {
+      expect(
+          VersionConstraint.unionOf([
+            v250,
+            VersionRange(min: v201, max: v234),
+            v124,
+            v072,
+            VersionRange(min: v080, max: v114),
+            v123
+          ]),
+          equals(VersionConstraint.unionOf([
+            v072,
+            VersionRange(min: v080, max: v114),
+            v123,
+            v124,
+            VersionRange(min: v201, max: v234),
+            v250
+          ])));
+    });
+
+    test('merges overlapping ranges', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v003, max: v072),
+            VersionRange(min: v010, max: v080),
+            VersionRange(min: v114, max: v124),
+            VersionRange(min: v123, max: v130)
+          ]),
+          equals(VersionConstraint.unionOf([
+            VersionRange(min: v003, max: v080),
+            VersionRange(min: v114, max: v130)
+          ])));
+    });
+
+    test('merges adjacent ranges', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v003, max: v072, includeMax: true),
+            VersionRange(min: v072, max: v080),
+            VersionRange(
+                min: v114, max: v124, alwaysIncludeMaxPreRelease: true),
+            VersionRange(min: v124, max: v130, includeMin: true),
+            VersionRange(min: v130.firstPreRelease, max: v200, includeMin: true)
+          ]),
+          equals(VersionConstraint.unionOf([
+            VersionRange(min: v003, max: v080),
+            VersionRange(min: v114, max: v200)
+          ])));
+    });
+
+    test("doesn't merge not-quite-adjacent ranges", () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v114, max: v124),
+            VersionRange(min: v124, max: v130, includeMin: true)
+          ]),
+          isNot(equals(VersionRange(min: v114, max: v130))));
+
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v003, max: v072),
+            VersionRange(min: v072, max: v080)
+          ]),
+          isNot(equals(VersionRange(min: v003, max: v080))));
+    });
+
+    test('merges version numbers into ranges', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v003, max: v072),
+            v010,
+            VersionRange(min: v114, max: v124),
+            v123
+          ]),
+          equals(VersionConstraint.unionOf([
+            VersionRange(min: v003, max: v072),
+            VersionRange(min: v114, max: v124)
+          ])));
+    });
+
+    test('merges adjacent version numbers into ranges', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(
+                min: v003, max: v072, alwaysIncludeMaxPreRelease: true),
+            v072,
+            v114,
+            VersionRange(min: v114, max: v124),
+            v124.firstPreRelease
+          ]),
+          equals(VersionConstraint.unionOf([
+            VersionRange(min: v003, max: v072, includeMax: true),
+            VersionRange(
+                min: v114,
+                max: v124.firstPreRelease,
+                includeMin: true,
+                includeMax: true)
+          ])));
+    });
+
+    test("doesn't merge not-quite-adjacent version numbers into ranges", () {
+      expect(
+          VersionConstraint.unionOf([VersionRange(min: v003, max: v072), v072]),
+          isNot(equals(VersionRange(min: v003, max: v072, includeMax: true))));
+    });
+  });
+
+  test('isEmpty returns false', () {
+    expect(
+        VersionConstraint.unionOf([
+          VersionRange(min: v003, max: v080),
+          VersionRange(min: v123, max: v130),
+        ]),
+        isNot(isEmpty));
+  });
+
+  test('isAny returns false', () {
+    expect(
+        VersionConstraint.unionOf([
+          VersionRange(min: v003, max: v080),
+          VersionRange(min: v123, max: v130),
+        ]).isAny,
+        isFalse);
+  });
+
+  test('allows() allows anything the components allow', () {
+    var union = VersionConstraint.unionOf([
+      VersionRange(min: v003, max: v080),
+      VersionRange(min: v123, max: v130),
+      v200
+    ]);
+
+    expect(union, allows(v010));
+    expect(union, doesNotAllow(v080));
+    expect(union, allows(v124));
+    expect(union, doesNotAllow(v140));
+    expect(union, allows(v200));
+  });
+
+  group('allowsAll()', () {
+    test('for a version, returns true if any component allows the version', () {
+      var union = VersionConstraint.unionOf([
+        VersionRange(min: v003, max: v080),
+        VersionRange(min: v123, max: v130),
+        v200
+      ]);
+
+      expect(union.allowsAll(v010), isTrue);
+      expect(union.allowsAll(v080), isFalse);
+      expect(union.allowsAll(v124), isTrue);
+      expect(union.allowsAll(v140), isFalse);
+      expect(union.allowsAll(v200), isTrue);
+    });
+
+    test(
+        'for a version range, returns true if any component allows the whole '
+        'range', () {
+      var union = VersionConstraint.unionOf([
+        VersionRange(min: v003, max: v080),
+        VersionRange(min: v123, max: v130)
+      ]);
+
+      expect(union.allowsAll(VersionRange(min: v003, max: v080)), isTrue);
+      expect(union.allowsAll(VersionRange(min: v010, max: v072)), isTrue);
+      expect(union.allowsAll(VersionRange(min: v010, max: v124)), isFalse);
+    });
+
+    group('for a union,', () {
+      var union = VersionConstraint.unionOf([
+        VersionRange(min: v003, max: v080),
+        VersionRange(min: v123, max: v130)
+      ]);
+
+      test('returns true if every constraint matches a different constraint',
+          () {
+        expect(
+            union.allowsAll(VersionConstraint.unionOf([
+              VersionRange(min: v010, max: v072),
+              VersionRange(min: v124, max: v130)
+            ])),
+            isTrue);
+      });
+
+      test('returns true if every constraint matches the same constraint', () {
+        expect(
+            union.allowsAll(VersionConstraint.unionOf([
+              VersionRange(min: v003, max: v010),
+              VersionRange(min: v072, max: v080)
+            ])),
+            isTrue);
+      });
+
+      test("returns false if there's an unmatched constraint", () {
+        expect(
+            union.allowsAll(VersionConstraint.unionOf([
+              VersionRange(min: v010, max: v072),
+              VersionRange(min: v124, max: v130),
+              VersionRange(min: v140, max: v200)
+            ])),
+            isFalse);
+      });
+
+      test("returns false if a constraint isn't fully matched", () {
+        expect(
+            union.allowsAll(VersionConstraint.unionOf([
+              VersionRange(min: v010, max: v114),
+              VersionRange(min: v124, max: v130)
+            ])),
+            isFalse);
+      });
+    });
+  });
+
+  group('allowsAny()', () {
+    test('for a version, returns true if any component allows the version', () {
+      var union = VersionConstraint.unionOf([
+        VersionRange(min: v003, max: v080),
+        VersionRange(min: v123, max: v130),
+        v200
+      ]);
+
+      expect(union.allowsAny(v010), isTrue);
+      expect(union.allowsAny(v080), isFalse);
+      expect(union.allowsAny(v124), isTrue);
+      expect(union.allowsAny(v140), isFalse);
+      expect(union.allowsAny(v200), isTrue);
+    });
+
+    test(
+        'for a version range, returns true if any component allows part of '
+        'the range', () {
+      var union =
+          VersionConstraint.unionOf([VersionRange(min: v003, max: v080), v123]);
+
+      expect(union.allowsAny(VersionRange(min: v010, max: v114)), isTrue);
+      expect(union.allowsAny(VersionRange(min: v114, max: v124)), isTrue);
+      expect(union.allowsAny(VersionRange(min: v124, max: v130)), isFalse);
+    });
+
+    group('for a union,', () {
+      var union = VersionConstraint.unionOf([
+        VersionRange(min: v010, max: v080),
+        VersionRange(min: v123, max: v130)
+      ]);
+
+      test('returns true if any constraint matches', () {
+        expect(
+            union.allowsAny(VersionConstraint.unionOf(
+                [v072, VersionRange(min: v200, max: v300)])),
+            isTrue);
+
+        expect(
+            union.allowsAny(VersionConstraint.unionOf(
+                [v003, VersionRange(min: v124, max: v300)])),
+            isTrue);
+      });
+
+      test('returns false if no constraint matches', () {
+        expect(
+            union.allowsAny(VersionConstraint.unionOf([
+              v003,
+              VersionRange(min: v130, max: v140),
+              VersionRange(min: v140, max: v200)
+            ])),
+            isFalse);
+      });
+    });
+  });
+
+  group('intersect()', () {
+    test('with an overlapping version, returns that version', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v010, max: v080),
+            VersionRange(min: v123, max: v140)
+          ]).intersect(v072),
+          equals(v072));
+    });
+
+    test('with a non-overlapping version, returns an empty constraint', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v010, max: v080),
+            VersionRange(min: v123, max: v140)
+          ]).intersect(v300),
+          isEmpty);
+    });
+
+    test('with an overlapping range, returns that range', () {
+      var range = VersionRange(min: v072, max: v080);
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v010, max: v080),
+            VersionRange(min: v123, max: v140)
+          ]).intersect(range),
+          equals(range));
+    });
+
+    test('with a non-overlapping range, returns an empty constraint', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v010, max: v080),
+            VersionRange(min: v123, max: v140)
+          ]).intersect(VersionRange(min: v080, max: v123)),
+          isEmpty);
+    });
+
+    test('with a parially-overlapping range, returns the overlapping parts',
+        () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v010, max: v080),
+            VersionRange(min: v123, max: v140)
+          ]).intersect(VersionRange(min: v072, max: v130)),
+          equals(VersionConstraint.unionOf([
+            VersionRange(min: v072, max: v080),
+            VersionRange(min: v123, max: v130)
+          ])));
+    });
+
+    group('for a union,', () {
+      var union = VersionConstraint.unionOf([
+        VersionRange(min: v003, max: v080),
+        VersionRange(min: v123, max: v130)
+      ]);
+
+      test('returns the overlapping parts', () {
+        expect(
+            union.intersect(VersionConstraint.unionOf([
+              v010,
+              VersionRange(min: v072, max: v124),
+              VersionRange(min: v124, max: v130)
+            ])),
+            equals(VersionConstraint.unionOf([
+              v010,
+              VersionRange(min: v072, max: v080),
+              VersionRange(min: v123, max: v124),
+              VersionRange(min: v124, max: v130)
+            ])));
+      });
+
+      test("drops parts that don't match", () {
+        expect(
+            union.intersect(VersionConstraint.unionOf([
+              v003,
+              VersionRange(min: v072, max: v080),
+              VersionRange(min: v080, max: v123)
+            ])),
+            equals(VersionRange(min: v072, max: v080)));
+      });
+    });
+  });
+
+  group('difference()', () {
+    test("ignores ranges that don't intersect", () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v072, max: v080),
+            VersionRange(min: v123, max: v130)
+          ]).difference(VersionConstraint.unionOf([
+            VersionRange(min: v003, max: v010),
+            VersionRange(min: v080, max: v123),
+            VersionRange(min: v140)
+          ])),
+          equals(VersionConstraint.unionOf([
+            VersionRange(min: v072, max: v080),
+            VersionRange(min: v123, max: v130)
+          ])));
+    });
+
+    test('removes overlapping portions', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v010, max: v080),
+            VersionRange(min: v123, max: v130)
+          ]).difference(VersionConstraint.unionOf(
+              [VersionRange(min: v003, max: v072), VersionRange(min: v124)])),
+          equals(VersionConstraint.unionOf([
+            VersionRange(
+                min: v072.firstPreRelease, max: v080, includeMin: true),
+            VersionRange(min: v123, max: v124, includeMax: true)
+          ])));
+    });
+
+    test('removes multiple portions from the same range', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v010, max: v114),
+            VersionRange(min: v130, max: v200)
+          ]).difference(VersionConstraint.unionOf([v072, v080])),
+          equals(VersionConstraint.unionOf([
+            VersionRange(
+                min: v010, max: v072, alwaysIncludeMaxPreRelease: true),
+            VersionRange(
+                min: v072, max: v080, alwaysIncludeMaxPreRelease: true),
+            VersionRange(min: v080, max: v114),
+            VersionRange(min: v130, max: v200)
+          ])));
+    });
+
+    test('removes the same range from multiple ranges', () {
+      expect(
+          VersionConstraint.unionOf([
+            VersionRange(min: v010, max: v072),
+            VersionRange(min: v080, max: v123),
+            VersionRange(min: v124, max: v130),
+            VersionRange(min: v200, max: v234),
+            VersionRange(min: v250, max: v300)
+          ]).difference(VersionRange(min: v114, max: v201)),
+          equals(VersionConstraint.unionOf([
+            VersionRange(min: v010, max: v072),
+            VersionRange(min: v080, max: v114, includeMax: true),
+            VersionRange(
+                min: v201.firstPreRelease, max: v234, includeMin: true),
+            VersionRange(min: v250, max: v300)
+          ])));
+    });
+  });
+}