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. | [](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aoauth2) | [](https://pub.dev/packages/oauth2) |
| [package_config](pkgs/package_config/) | Support for reading and writing Dart Package Configuration files. | [](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apackage_config) | [](https://pub.dev/packages/package_config) |
| [pool](pkgs/pool/) | Manage a finite pool of resources. Useful for controlling concurrent file system or network requests. | [](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apool) | [](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. | [](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Apub_semver) | [](https://pub.dev/packages/pub_semver) |
| [source_map_stack_trace](pkgs/source_map_stack_trace/) | A package for applying source maps to stack traces. | [](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Asource_map_stack_trace) | [](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. | [](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics) | [](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 @@
+[](https://github.com/dart-lang/tools/actions/workflows/pub_semver.yaml)
+[](https://pub.dev/packages/pub_semver)
+[](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)
+ ])));
+ });
+ });
+}