├── .github ├── dependabot.yaml └── workflows │ └── test-package.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example └── example.dart ├── lib ├── pub_semver.dart └── src │ ├── patterns.dart │ ├── utils.dart │ ├── version.dart │ ├── version_constraint.dart │ ├── version_range.dart │ └── version_union.dart ├── pubspec.yaml └── test ├── utils.dart ├── version_constraint_test.dart ├── version_range_test.dart ├── version_test.dart └── version_union_test.dart /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file. 2 | version: 2 3 | 4 | updates: 5 | - package-ecosystem: github-actions 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | labels: 10 | - autosubmit 11 | groups: 12 | github-actions: 13 | patterns: 14 | - "*" 15 | -------------------------------------------------------------------------------- /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | # Run on PRs and pushes to the default branch. 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | schedule: 10 | - cron: "0 0 * * 0" 11 | 12 | env: 13 | PUB_ENVIRONMENT: bot.github 14 | 15 | jobs: 16 | # Check code formatting and static analysis on a single OS (linux) 17 | # against Dart dev. 18 | analyze: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | sdk: [dev] 24 | steps: 25 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 26 | - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 27 | with: 28 | sdk: ${{ matrix.sdk }} 29 | - id: install 30 | name: Install dependencies 31 | run: dart pub get 32 | - name: Check formatting 33 | run: dart format --output=none --set-exit-if-changed . 34 | if: always() && steps.install.outcome == 'success' 35 | - name: Analyze code 36 | run: dart analyze --fatal-infos 37 | if: always() && steps.install.outcome == 'success' 38 | 39 | # Run tests on a matrix consisting of two dimensions: 40 | # 1. OS: ubuntu-latest, (macos-latest, windows-latest) 41 | # 2. release channel: dev 42 | test: 43 | needs: analyze 44 | runs-on: ${{ matrix.os }} 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | # Add macos-latest and/or windows-latest if relevant for this package. 49 | os: [ubuntu-latest] 50 | sdk: [3.4, dev] 51 | steps: 52 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 53 | - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 54 | with: 55 | sdk: ${{ matrix.sdk }} 56 | - id: install 57 | name: Install dependencies 58 | run: dart pub get 59 | - name: Run VM tests 60 | run: dart test --platform vm 61 | if: always() && steps.install.outcome == 'success' 62 | - name: Run Chrome tests 63 | run: dart test --platform chrome --compiler dart2js,dart2wasm 64 | if: always() && steps.install.outcome == 'success' 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dart_tool/ 2 | .packages 3 | pubspec.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.1.5-wip 2 | 3 | - Require Dart `3.4.0`. 4 | 5 | ## 2.1.4 6 | 7 | - Added topics to `pubspec.yaml`. 8 | 9 | ## 2.1.3 10 | 11 | - Add type parameters to the signatures of the `Version.preRelease` and 12 | `Version.build` fields (`List` ==> `List`). 13 | [#74](https://github.com/dart-lang/pub_semver/pull/74). 14 | - Require Dart 2.17. 15 | 16 | ## 2.1.2 17 | 18 | - Add markdown badges to the readme. 19 | 20 | ## 2.1.1 21 | 22 | - Fixed the version parsing pattern to only accept dots between version 23 | components. 24 | 25 | ## 2.1.0 26 | 27 | - Added `Version.canonicalizedVersion` to help scrub leading zeros and highlight 28 | that `Version.toString()` preserves leading zeros. 29 | - Annotated `Version` with `@sealed` to discourage users from implementing the 30 | interface. 31 | 32 | ## 2.0.0 33 | 34 | - Stable null safety release. 35 | - `Version.primary` now throws `StateError` if the `versions` argument is empty. 36 | 37 | ## 1.4.4 38 | 39 | - Fix a bug of `VersionRange.union` where ranges bounded at infinity would get 40 | combined wrongly. 41 | 42 | # 1.4.3 43 | 44 | - Update Dart SDK constraint to `>=2.0.0 <3.0.0`. 45 | - Update `package:collection` constraint to `^1.0.0`. 46 | 47 | ## 1.4.2 48 | 49 | * Set max SDK version to `<3.0.0`. 50 | 51 | ## 1.4.1 52 | 53 | * Fix a bug where there upper bound of a version range with a build identifier 54 | could accidentally be rewritten. 55 | 56 | ## 1.4.0 57 | 58 | * Add a `Version.firstPreRelease` getter that returns the first possible 59 | pre-release of a version. 60 | 61 | * Add a `Version.isFirstPreRelease` getter that returns whether a version is the 62 | first possible pre-release. 63 | 64 | * `new VersionRange()` with an exclusive maximum now replaces the maximum with 65 | its first pre-release version. This matches the existing semantics, where an 66 | exclusive maximum would exclude pre-release versions of that maximum. 67 | 68 | Explicitly representing this by changing the maximum version ensures that all 69 | operations behave correctly with respect to the special pre-release semantics. 70 | In particular, it fixes bugs where, for example, 71 | `(>=1.0.0 <2.0.0-dev).union(>=2.0.0-dev <2.0.0)` and 72 | `(>=1.0.0 <3.0.0).difference(^1.0.0)` wouldn't include `2.0.0-dev`. 73 | 74 | * Add an `alwaysIncludeMaxPreRelease` parameter to `new VersionRange()`, which 75 | disables the replacement described above and allows users to create ranges 76 | that do include the pre-release versions of an exclusive max version. 77 | 78 | ## 1.3.7 79 | 80 | * Fix more bugs with `VersionRange.intersect()`, `VersionRange.difference()`, 81 | and `VersionRange.union()` involving version ranges with pre-release maximums. 82 | 83 | ## 1.3.6 84 | 85 | * Fix a bug where constraints that only allowed pre-release versions would be 86 | parsed as empty constraints. 87 | 88 | ## 1.3.5 89 | 90 | * Fix a bug where `VersionRange.intersect()` would return incorrect results for 91 | pre-release versions with the same base version number as release versions. 92 | 93 | ## 1.3.4 94 | 95 | * Fix a bug where `VersionRange.allowsAll()`, `VersionRange.allowsAny()`, and 96 | `VersionRange.difference()` would return incorrect results for pre-release 97 | versions with the same base version number as release versions. 98 | 99 | ## 1.3.3 100 | 101 | * Fix a bug where `VersionRange.difference()` with a union constraint that 102 | covered the entire range would crash. 103 | 104 | ## 1.3.2 105 | 106 | * Fix a checked-mode error in `VersionRange.difference()`. 107 | 108 | ## 1.3.1 109 | 110 | * Fix a new strong mode error. 111 | 112 | ## 1.3.0 113 | 114 | * Make the `VersionUnion` class public. This was previously used internally to 115 | implement `new VersionConstraint.unionOf()` and `VersionConstraint.union()`. 116 | Now it's public so you can use it too. 117 | 118 | * Added `VersionConstraint.difference()`. This returns a constraint matching all 119 | versions matched by one constraint but not another. 120 | 121 | * Make `VersionRange` implement `Comparable`. Ranges are ordered 122 | first by lower bound, then by upper bound. 123 | 124 | ## 1.2.4 125 | 126 | * Fix all remaining strong mode warnings. 127 | 128 | ## 1.2.3 129 | 130 | * Addressed three strong mode warnings. 131 | 132 | ## 1.2.2 133 | 134 | * Make the package analyze under strong mode and compile with the DDC (Dart Dev 135 | Compiler). Fix two issues with a private subclass of `VersionConstraint` 136 | having different types for overridden methods. 137 | 138 | ## 1.2.1 139 | 140 | * Allow version ranges like `>=1.2.3-dev.1 <1.2.3` to match pre-release versions 141 | of `1.2.3`. Previously, these didn't match, since the pre-release versions had 142 | the same major, minor, and patch numbers as the max; now an exception has been 143 | added if they also have the same major, minor, and patch numbers as the min 144 | *and* the min is also a pre-release version. 145 | 146 | ## 1.2.0 147 | 148 | * Add a `VersionConstraint.union()` method and a `new 149 | VersionConstraint.unionOf()` constructor. These each return a constraint that 150 | matches multiple existing constraints. 151 | 152 | * Add a `VersionConstraint.allowsAll()` method, which returns whether one 153 | constraint is a superset of another. 154 | 155 | * Add a `VersionConstraint.allowsAny()` method, which returns whether one 156 | constraint overlaps another. 157 | 158 | * `Version` now implements `VersionRange`. 159 | 160 | ## 1.1.0 161 | 162 | * Add support for the `^` operator for compatible versions according to pub's 163 | notion of compatibility. `^1.2.3` is equivalent to `>=1.2.3 <2.0.0`; `^0.1.2` 164 | is equivalent to `>=0.1.2 <0.2.0`. 165 | 166 | * Add `Version.nextBreaking`, which returns the next version that introduces 167 | breaking changes after a given version. 168 | 169 | * Add `new VersionConstraint.compatibleWith()`, which returns a range covering 170 | all versions compatible with a given version. 171 | 172 | * Add a custom `VersionRange.hashCode` to make it properly hashable. 173 | 174 | ## 1.0.0 175 | 176 | * Initial release. 177 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > This repo has moved to https://github.com/dart-lang/tools/tree/main/pkgs/pub_semver 3 | 4 | [![Dart CI](https://github.com/dart-lang/pub_semver/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/pub_semver/actions/workflows/test-package.yml) 5 | [![Pub](https://img.shields.io/pub/v/pub_semver.svg)](https://pub.dev/packages/pub_semver) 6 | [![package publisher](https://img.shields.io/pub/publisher/pub_semver.svg)](https://pub.dev/packages/pub_semver/publisher) 7 | 8 | Handles version numbers and version constraints in the same way that [pub][] 9 | does. 10 | 11 | ## Semantics 12 | 13 | The semantics here very closely follow the 14 | [Semantic Versioning spec version 2.0.0-rc.1][semver]. It differs from semver 15 | in a few corner cases: 16 | 17 | * **Version ordering does take build suffixes into account.** This is unlike 18 | semver 2.0.0 but like earlier versions of semver. Version `1.2.3+1` is 19 | considered a lower number than `1.2.3+2`. 20 | 21 | Since a package may have published multiple versions that differ only by 22 | build suffix, pub still has to pick one of them *somehow*. Semver leaves 23 | that issue unresolved, so we just say that build numbers are sorted like 24 | pre-release suffixes. 25 | 26 | * **Pre-release versions are excluded from most max ranges.** Let's say a 27 | user is depending on "foo" with constraint `>=1.0.0 <2.0.0` and that "foo" 28 | has published these versions: 29 | 30 | * `1.0.0` 31 | * `1.1.0` 32 | * `1.2.0` 33 | * `2.0.0-alpha` 34 | * `2.0.0-beta` 35 | * `2.0.0` 36 | * `2.1.0` 37 | 38 | Versions `2.0.0` and `2.1.0` are excluded by the constraint since neither 39 | matches `<2.0.0`. However, since semver specifies that pre-release versions 40 | are lower than the non-prerelease version (i.e. `2.0.0-beta < 2.0.0`, then 41 | the `<2.0.0` constraint does technically allow those. 42 | 43 | But that's almost never what the user wants. If their package doesn't work 44 | with foo `2.0.0`, it's certainly not likely to work with experimental, 45 | unstable versions of `2.0.0`'s API, which is what pre-release versions 46 | represent. 47 | 48 | To handle that, `<` version ranges don't allow pre-release versions of the 49 | maximum unless the max is itself a pre-release, or the min is a pre-release 50 | of the same version. In other words, a `<2.0.0` constraint will prohibit not 51 | just `2.0.0` but any pre-release of `2.0.0`. However, `<2.0.0-beta` will 52 | exclude `2.0.0-beta` but allow `2.0.0-alpha`. Likewise, `>2.0.0-alpha 53 | <2.0.0` will exclude `2.0.0-alpha` but allow `2.0.0-beta`. 54 | 55 | * **Pre-release versions are avoided when possible.** The above case 56 | handles pre-release versions at the top of the range, but what about in 57 | the middle? What if "foo" has these versions: 58 | 59 | * `1.0.0` 60 | * `1.2.0-alpha` 61 | * `1.2.0` 62 | * `1.3.0-experimental` 63 | 64 | When a number of versions are valid, pub chooses the best one where "best" 65 | usually means "highest numbered". That follows the user's intuition that, 66 | all else being equal, they want the latest and greatest. Here, that would 67 | mean `1.3.0-experimental`. However, most users don't want to use unstable 68 | versions of their dependencies. 69 | 70 | We want pre-releases to be explicitly opt-in so that package consumers 71 | don't get unpleasant surprises and so that package maintainers are free to 72 | put out pre-releases and get feedback without dragging all of their users 73 | onto the bleeding edge. 74 | 75 | To accommodate that, when pub is choosing a version, it uses *priority* 76 | order which is different from strict comparison ordering. Any stable 77 | version is considered higher priority than any unstable version. The above 78 | versions, in priority order, are: 79 | 80 | * `1.2.0-alpha` 81 | * `1.3.0-experimental` 82 | * `1.0.0` 83 | * `1.2.0` 84 | 85 | This ensures that users only end up with an unstable version when there are 86 | no alternatives. Usually this means they've picked a constraint that 87 | specifically selects that unstable version -- they've deliberately opted 88 | into it. 89 | 90 | * **There is a notion of compatibility between pre-1.0.0 versions.** Semver 91 | deems all pre-1.0.0 versions to be incompatible. This means that the only 92 | way to ensure compatibility when depending on a pre-1.0.0 package is to 93 | pin the dependency to an exact version. Pinned version constraints prevent 94 | automatic patch and pre-release updates. To avoid this situation, pub 95 | defines the "next breaking" version as the version which increments the 96 | major version if it's greater than zero, and the minor version otherwise, 97 | resets subsequent digits to zero, and strips any pre-release or build 98 | suffix. For example, here are some versions along with their next breaking 99 | ones: 100 | 101 | `0.0.3` -> `0.1.0` 102 | `0.7.2-alpha` -> `0.8.0` 103 | `1.2.3` -> `2.0.0` 104 | 105 | To make use of this, pub defines a "^" operator which yields a version 106 | constraint greater than or equal to a given version, but less than its next 107 | breaking one. 108 | 109 | [pub]: https://pub.dev 110 | [semver]: https://semver.org/spec/v2.0.0-rc.1.html 111 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # https://dart.dev/guides/language/analysis-options 2 | include: package:dart_flutter_team_lints/analysis_options.yaml 3 | 4 | analyzer: 5 | language: 6 | strict-casts: true 7 | strict-inference: true 8 | strict-raw-types: true 9 | 10 | linter: 11 | rules: 12 | - avoid_bool_literals_in_conditional_expressions 13 | - avoid_classes_with_only_static_members 14 | - avoid_private_typedef_functions 15 | - avoid_redundant_argument_values 16 | - avoid_returning_this 17 | - avoid_unused_constructor_parameters 18 | - avoid_void_async 19 | - cancel_subscriptions 20 | - cascade_invocations 21 | - join_return_with_assignment 22 | - literal_only_boolean_expressions 23 | - missing_whitespace_between_adjacent_strings 24 | - no_adjacent_strings_in_list 25 | - no_runtimeType_toString 26 | - prefer_const_declarations 27 | - prefer_expression_function_bodies 28 | - unnecessary_await_in_return 29 | - use_if_null_to_convert_nulls_to_bools 30 | - use_raw_strings 31 | - use_string_buffers 32 | -------------------------------------------------------------------------------- /example/example.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:pub_semver/pub_semver.dart'; 6 | 7 | void main() { 8 | final range = VersionConstraint.parse('^2.0.0'); 9 | 10 | for (var version in [ 11 | Version.parse('1.2.3-pre'), 12 | Version.parse('2.0.0+123'), 13 | Version.parse('3.0.0-dev'), 14 | ]) { 15 | print('$version ${version.isPreRelease} ${range.allows(version)}'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/pub_semver.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'src/version.dart'; 6 | export 'src/version_constraint.dart'; 7 | export 'src/version_range.dart' hide CompatibleWithVersionRange; 8 | export 'src/version_union.dart'; 9 | -------------------------------------------------------------------------------- /lib/src/patterns.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Regex that matches a version number at the beginning of a string. 6 | final startVersion = RegExp(r'^' // Start at beginning. 7 | r'(\d+)\.(\d+)\.(\d+)' // Version number. 8 | r'(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Pre-release. 9 | r'(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?'); // Build. 10 | 11 | /// Like [startVersion] but matches the entire string. 12 | final completeVersion = RegExp('${startVersion.pattern}\$'); 13 | 14 | /// Parses a comparison operator ("<", ">", "<=", or ">=") at the beginning of 15 | /// a string. 16 | final startComparison = RegExp(r'^[<>]=?'); 17 | 18 | /// The "compatible with" operator. 19 | const compatibleWithChar = '^'; 20 | -------------------------------------------------------------------------------- /lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'version.dart'; 6 | import 'version_range.dart'; 7 | 8 | /// Returns whether [range1] is immediately next to, but not overlapping, 9 | /// [range2]. 10 | bool areAdjacent(VersionRange range1, VersionRange range2) { 11 | if (range1.max != range2.min) return false; 12 | 13 | return (range1.includeMax && !range2.includeMin) || 14 | (!range1.includeMax && range2.includeMin); 15 | } 16 | 17 | /// Returns whether [range1] allows lower versions than [range2]. 18 | bool allowsLower(VersionRange range1, VersionRange range2) { 19 | if (range1.min == null) return range2.min != null; 20 | if (range2.min == null) return false; 21 | 22 | var comparison = range1.min!.compareTo(range2.min!); 23 | if (comparison == -1) return true; 24 | if (comparison == 1) return false; 25 | return range1.includeMin && !range2.includeMin; 26 | } 27 | 28 | /// Returns whether [range1] allows higher versions than [range2]. 29 | bool allowsHigher(VersionRange range1, VersionRange range2) { 30 | if (range1.max == null) return range2.max != null; 31 | if (range2.max == null) return false; 32 | 33 | var comparison = range1.max!.compareTo(range2.max!); 34 | if (comparison == 1) return true; 35 | if (comparison == -1) return false; 36 | return range1.includeMax && !range2.includeMax; 37 | } 38 | 39 | /// Returns whether [range1] allows only versions lower than those allowed by 40 | /// [range2]. 41 | bool strictlyLower(VersionRange range1, VersionRange range2) { 42 | if (range1.max == null || range2.min == null) return false; 43 | 44 | var comparison = range1.max!.compareTo(range2.min!); 45 | if (comparison == -1) return true; 46 | if (comparison == 1) return false; 47 | return !range1.includeMax || !range2.includeMin; 48 | } 49 | 50 | /// Returns whether [range1] allows only versions higher than those allowed by 51 | /// [range2]. 52 | bool strictlyHigher(VersionRange range1, VersionRange range2) => 53 | strictlyLower(range2, range1); 54 | 55 | bool equalsWithoutPreRelease(Version version1, Version version2) => 56 | version1.major == version2.major && 57 | version1.minor == version2.minor && 58 | version1.patch == version2.patch; 59 | -------------------------------------------------------------------------------- /lib/src/version.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:math' as math; 6 | 7 | import 'package:collection/collection.dart'; 8 | import 'package:meta/meta.dart' show sealed; 9 | 10 | import 'patterns.dart'; 11 | import 'version_constraint.dart'; 12 | import 'version_range.dart'; 13 | 14 | /// The equality operator to use for comparing version components. 15 | const _equality = IterableEquality(); 16 | 17 | /// A parsed semantic version number. 18 | @sealed 19 | class Version implements VersionConstraint, VersionRange { 20 | /// No released version: i.e. "0.0.0". 21 | static Version get none => Version(0, 0, 0); 22 | 23 | /// Compares [a] and [b] to see which takes priority over the other. 24 | /// 25 | /// Returns `1` if [a] takes priority over [b] and `-1` if vice versa. If 26 | /// [a] and [b] are equivalent, returns `0`. 27 | /// 28 | /// Unlike [compareTo], which *orders* versions, this determines which 29 | /// version a user is likely to prefer. In particular, it prioritizes 30 | /// pre-release versions lower than stable versions, regardless of their 31 | /// version numbers. Pub uses this when determining which version to prefer 32 | /// when a number of versions are allowed. In that case, it will always 33 | /// choose a stable version when possible. 34 | /// 35 | /// When used to sort a list, orders in ascending priority so that the 36 | /// highest priority version is *last* in the result. 37 | static int prioritize(Version a, Version b) { 38 | // Sort all prerelease versions after all normal versions. This way 39 | // the solver will prefer stable packages over unstable ones. 40 | if (a.isPreRelease && !b.isPreRelease) return -1; 41 | if (!a.isPreRelease && b.isPreRelease) return 1; 42 | 43 | return a.compareTo(b); 44 | } 45 | 46 | /// Like [prioritize], but lower version numbers are considered greater than 47 | /// higher version numbers. 48 | /// 49 | /// This still considers prerelease versions to be lower than non-prerelease 50 | /// versions. Pub uses this when downgrading -- it chooses the lowest version 51 | /// but still excludes pre-release versions when possible. 52 | static int antiprioritize(Version a, Version b) { 53 | if (a.isPreRelease && !b.isPreRelease) return -1; 54 | if (!a.isPreRelease && b.isPreRelease) return 1; 55 | 56 | return b.compareTo(a); 57 | } 58 | 59 | /// The major version number: "1" in "1.2.3". 60 | final int major; 61 | 62 | /// The minor version number: "2" in "1.2.3". 63 | final int minor; 64 | 65 | /// The patch version number: "3" in "1.2.3". 66 | final int patch; 67 | 68 | /// The pre-release identifier: "foo" in "1.2.3-foo". 69 | /// 70 | /// This is split into a list of components, each of which may be either a 71 | /// string or a non-negative integer. It may also be empty, indicating that 72 | /// this version has no pre-release identifier. 73 | final List preRelease; 74 | 75 | /// The build identifier: "foo" in "1.2.3+foo". 76 | /// 77 | /// This is split into a list of components, each of which may be either a 78 | /// string or a non-negative integer. It may also be empty, indicating that 79 | /// this version has no build identifier. 80 | final List build; 81 | 82 | /// The original string representation of the version number. 83 | /// 84 | /// This preserves textual artifacts like leading zeros that may be left out 85 | /// of the parsed version. 86 | final String _text; 87 | 88 | @override 89 | Version get min => this; 90 | @override 91 | Version get max => this; 92 | @override 93 | bool get includeMin => true; 94 | @override 95 | bool get includeMax => true; 96 | 97 | Version._(this.major, this.minor, this.patch, String? preRelease, 98 | String? build, this._text) 99 | : preRelease = preRelease == null ? [] : _splitParts(preRelease), 100 | build = build == null ? [] : _splitParts(build) { 101 | if (major < 0) throw ArgumentError('Major version must be non-negative.'); 102 | if (minor < 0) throw ArgumentError('Minor version must be non-negative.'); 103 | if (patch < 0) throw ArgumentError('Patch version must be non-negative.'); 104 | } 105 | 106 | /// Creates a new [Version] object. 107 | factory Version(int major, int minor, int patch, 108 | {String? pre, String? build}) { 109 | var text = '$major.$minor.$patch'; 110 | if (pre != null) text += '-$pre'; 111 | if (build != null) text += '+$build'; 112 | 113 | return Version._(major, minor, patch, pre, build, text); 114 | } 115 | 116 | /// Creates a new [Version] by parsing [text]. 117 | factory Version.parse(String text) { 118 | final match = completeVersion.firstMatch(text); 119 | if (match == null) { 120 | throw FormatException('Could not parse "$text".'); 121 | } 122 | 123 | try { 124 | var major = int.parse(match[1]!); 125 | var minor = int.parse(match[2]!); 126 | var patch = int.parse(match[3]!); 127 | 128 | var preRelease = match[5]; 129 | var build = match[8]; 130 | 131 | return Version._(major, minor, patch, preRelease, build, text); 132 | } on FormatException { 133 | throw FormatException('Could not parse "$text".'); 134 | } 135 | } 136 | 137 | /// Returns the primary version out of [versions]. 138 | /// 139 | /// This is the highest-numbered stable (non-prerelease) version. If there 140 | /// are no stable versions, it's just the highest-numbered version. 141 | /// 142 | /// If [versions] is empty, throws a [StateError]. 143 | static Version primary(List versions) { 144 | var primary = versions.first; 145 | for (var version in versions.skip(1)) { 146 | if ((!version.isPreRelease && primary.isPreRelease) || 147 | (version.isPreRelease == primary.isPreRelease && version > primary)) { 148 | primary = version; 149 | } 150 | } 151 | return primary; 152 | } 153 | 154 | /// Splits a string of dot-delimited identifiers into their component parts. 155 | /// 156 | /// Identifiers that are numeric are converted to numbers. 157 | static List _splitParts(String text) => text 158 | .split('.') 159 | .map((part) => 160 | // Return an integer part if possible, otherwise return the string 161 | // as-is 162 | int.tryParse(part) ?? part) 163 | .toList(); 164 | 165 | @override 166 | bool operator ==(Object other) => 167 | other is Version && 168 | major == other.major && 169 | minor == other.minor && 170 | patch == other.patch && 171 | _equality.equals(preRelease, other.preRelease) && 172 | _equality.equals(build, other.build); 173 | 174 | @override 175 | int get hashCode => 176 | major ^ 177 | minor ^ 178 | patch ^ 179 | _equality.hash(preRelease) ^ 180 | _equality.hash(build); 181 | 182 | bool operator <(Version other) => compareTo(other) < 0; 183 | bool operator >(Version other) => compareTo(other) > 0; 184 | bool operator <=(Version other) => compareTo(other) <= 0; 185 | bool operator >=(Version other) => compareTo(other) >= 0; 186 | 187 | @override 188 | bool get isAny => false; 189 | @override 190 | bool get isEmpty => false; 191 | 192 | /// Whether or not this is a pre-release version. 193 | bool get isPreRelease => preRelease.isNotEmpty; 194 | 195 | /// Gets the next major version number that follows this one. 196 | /// 197 | /// If this version is a pre-release of a major version release (i.e. the 198 | /// minor and patch versions are zero), then it just strips the pre-release 199 | /// suffix. Otherwise, it increments the major version and resets the minor 200 | /// and patch. 201 | Version get nextMajor { 202 | if (isPreRelease && minor == 0 && patch == 0) { 203 | return Version(major, minor, patch); 204 | } 205 | 206 | return _incrementMajor(); 207 | } 208 | 209 | /// Gets the next minor version number that follows this one. 210 | /// 211 | /// If this version is a pre-release of a minor version release (i.e. the 212 | /// patch version is zero), then it just strips the pre-release suffix. 213 | /// Otherwise, it increments the minor version and resets the patch. 214 | Version get nextMinor { 215 | if (isPreRelease && patch == 0) { 216 | return Version(major, minor, patch); 217 | } 218 | 219 | return _incrementMinor(); 220 | } 221 | 222 | /// Gets the next patch version number that follows this one. 223 | /// 224 | /// If this version is a pre-release, then it just strips the pre-release 225 | /// suffix. Otherwise, it increments the patch version. 226 | Version get nextPatch { 227 | if (isPreRelease) { 228 | return Version(major, minor, patch); 229 | } 230 | 231 | return _incrementPatch(); 232 | } 233 | 234 | /// Gets the next breaking version number that follows this one. 235 | /// 236 | /// Increments [major] if it's greater than zero, otherwise [minor], resets 237 | /// subsequent digits to zero, and strips any [preRelease] or [build] 238 | /// suffix. 239 | Version get nextBreaking { 240 | if (major == 0) { 241 | return _incrementMinor(); 242 | } 243 | 244 | return _incrementMajor(); 245 | } 246 | 247 | /// Returns the first possible pre-release of this version. 248 | Version get firstPreRelease => Version(major, minor, patch, pre: '0'); 249 | 250 | /// Returns whether this is the first possible pre-release of its version. 251 | bool get isFirstPreRelease => preRelease.length == 1 && preRelease.first == 0; 252 | 253 | Version _incrementMajor() => Version(major + 1, 0, 0); 254 | Version _incrementMinor() => Version(major, minor + 1, 0); 255 | Version _incrementPatch() => Version(major, minor, patch + 1); 256 | 257 | /// Tests if [other] matches this version exactly. 258 | @override 259 | bool allows(Version other) => this == other; 260 | 261 | @override 262 | bool allowsAll(VersionConstraint other) => other.isEmpty || other == this; 263 | 264 | @override 265 | bool allowsAny(VersionConstraint other) => other.allows(this); 266 | 267 | @override 268 | VersionConstraint intersect(VersionConstraint other) => 269 | other.allows(this) ? this : VersionConstraint.empty; 270 | 271 | @override 272 | VersionConstraint union(VersionConstraint other) { 273 | if (other.allows(this)) return other; 274 | 275 | if (other is VersionRange) { 276 | if (other.min == this) { 277 | return VersionRange( 278 | min: other.min, 279 | max: other.max, 280 | includeMin: true, 281 | includeMax: other.includeMax, 282 | alwaysIncludeMaxPreRelease: true); 283 | } 284 | 285 | if (other.max == this) { 286 | return VersionRange( 287 | min: other.min, 288 | max: other.max, 289 | includeMin: other.includeMin, 290 | includeMax: true, 291 | alwaysIncludeMaxPreRelease: true); 292 | } 293 | } 294 | 295 | return VersionConstraint.unionOf([this, other]); 296 | } 297 | 298 | @override 299 | VersionConstraint difference(VersionConstraint other) => 300 | other.allows(this) ? VersionConstraint.empty : this; 301 | 302 | @override 303 | int compareTo(VersionRange other) { 304 | if (other is Version) { 305 | if (major != other.major) return major.compareTo(other.major); 306 | if (minor != other.minor) return minor.compareTo(other.minor); 307 | if (patch != other.patch) return patch.compareTo(other.patch); 308 | 309 | // Pre-releases always come before no pre-release string. 310 | if (!isPreRelease && other.isPreRelease) return 1; 311 | if (!other.isPreRelease && isPreRelease) return -1; 312 | 313 | var comparison = _compareLists(preRelease, other.preRelease); 314 | if (comparison != 0) return comparison; 315 | 316 | // Builds always come after no build string. 317 | if (build.isEmpty && other.build.isNotEmpty) return -1; 318 | if (other.build.isEmpty && build.isNotEmpty) return 1; 319 | return _compareLists(build, other.build); 320 | } else { 321 | return -other.compareTo(this); 322 | } 323 | } 324 | 325 | /// Get non-canonical string representation of this [Version]. 326 | /// 327 | /// If created with [Version.parse], the string from which the version was 328 | /// parsed is returned. Unlike the [canonicalizedVersion] this preserves 329 | /// artifacts such as leading zeros. 330 | @override 331 | String toString() => _text; 332 | 333 | /// Get a canonicalized string representation of this [Version]. 334 | /// 335 | /// Unlike [Version.toString()] this always returns a canonical string 336 | /// representation of this [Version]. 337 | /// 338 | /// **Example** 339 | /// ```dart 340 | /// final v = Version.parse('01.02.03-01.dev+pre.02'); 341 | /// 342 | /// assert(v.toString() == '01.02.03-01.dev+pre.02'); 343 | /// assert(v.canonicalizedVersion == '1.2.3-1.dev+pre.2'); 344 | /// assert(Version.parse(v.canonicalizedVersion) == v); 345 | /// ``` 346 | String get canonicalizedVersion => Version( 347 | major, 348 | minor, 349 | patch, 350 | pre: preRelease.isNotEmpty ? preRelease.join('.') : null, 351 | build: build.isNotEmpty ? build.join('.') : null, 352 | ).toString(); 353 | 354 | /// Compares a dot-separated component of two versions. 355 | /// 356 | /// This is used for the pre-release and build version parts. This follows 357 | /// Rule 12 of the Semantic Versioning spec (v2.0.0-rc.1). 358 | int _compareLists(List a, List b) { 359 | for (var i = 0; i < math.max(a.length, b.length); i++) { 360 | var aPart = (i < a.length) ? a[i] : null; 361 | var bPart = (i < b.length) ? b[i] : null; 362 | 363 | if (aPart == bPart) continue; 364 | 365 | // Missing parts come before present ones. 366 | if (aPart == null) return -1; 367 | if (bPart == null) return 1; 368 | 369 | if (aPart is num) { 370 | if (bPart is num) { 371 | // Compare two numbers. 372 | return aPart.compareTo(bPart); 373 | } else { 374 | // Numbers come before strings. 375 | return -1; 376 | } 377 | } else { 378 | if (bPart is num) { 379 | // Strings come after numbers. 380 | return 1; 381 | } else { 382 | // Compare two strings. 383 | return (aPart as String).compareTo(bPart as String); 384 | } 385 | } 386 | } 387 | 388 | // The lists are entirely equal. 389 | return 0; 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /lib/src/version_constraint.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'patterns.dart'; 6 | import 'utils.dart'; 7 | import 'version.dart'; 8 | import 'version_range.dart'; 9 | import 'version_union.dart'; 10 | 11 | /// A [VersionConstraint] is a predicate that can determine whether a given 12 | /// version is valid or not. 13 | /// 14 | /// For example, a ">= 2.0.0" constraint allows any version that is "2.0.0" or 15 | /// greater. Version objects themselves implement this to match a specific 16 | /// version. 17 | abstract class VersionConstraint { 18 | /// A [VersionConstraint] that allows all versions. 19 | static VersionConstraint any = VersionRange(); 20 | 21 | /// A [VersionConstraint] that allows no versions -- the empty set. 22 | static VersionConstraint empty = const _EmptyVersion(); 23 | 24 | /// Parses a version constraint. 25 | /// 26 | /// This string is one of: 27 | /// 28 | /// * "any". [any] version. 29 | /// * "^" followed by a version string. Versions compatible with 30 | /// ([VersionConstraint.compatibleWith]) the version. 31 | /// * a series of version parts. Each part can be one of: 32 | /// * A version string like `1.2.3`. In other words, anything that can be 33 | /// parsed by [Version.parse()]. 34 | /// * A comparison operator (`<`, `>`, `<=`, or `>=`) followed by a 35 | /// version string. 36 | /// 37 | /// Whitespace is ignored. 38 | /// 39 | /// Examples: 40 | /// 41 | /// any 42 | /// ^0.7.2 43 | /// ^1.0.0-alpha 44 | /// 1.2.3-alpha 45 | /// <=5.1.4 46 | /// >2.0.4 <= 2.4.6 47 | factory VersionConstraint.parse(String text) { 48 | var originalText = text; 49 | 50 | void skipWhitespace() { 51 | text = text.trim(); 52 | } 53 | 54 | skipWhitespace(); 55 | 56 | // Handle the "any" constraint. 57 | if (text == 'any') return any; 58 | 59 | // Try to parse and consume a version number. 60 | Version? matchVersion() { 61 | var version = startVersion.firstMatch(text); 62 | if (version == null) return null; 63 | 64 | text = text.substring(version.end); 65 | return Version.parse(version[0]!); 66 | } 67 | 68 | // Try to parse and consume a comparison operator followed by a version. 69 | VersionRange? matchComparison() { 70 | var comparison = startComparison.firstMatch(text); 71 | if (comparison == null) return null; 72 | 73 | var op = comparison[0]!; 74 | text = text.substring(comparison.end); 75 | skipWhitespace(); 76 | 77 | var version = matchVersion(); 78 | if (version == null) { 79 | throw FormatException('Expected version number after "$op" in ' 80 | '"$originalText", got "$text".'); 81 | } 82 | 83 | return switch (op) { 84 | '<=' => VersionRange(max: version, includeMax: true), 85 | '<' => VersionRange(max: version, alwaysIncludeMaxPreRelease: true), 86 | '>=' => VersionRange(min: version, includeMin: true), 87 | '>' => VersionRange(min: version), 88 | _ => throw UnsupportedError(op), 89 | }; 90 | } 91 | 92 | // Try to parse the "^" operator followed by a version. 93 | VersionConstraint? matchCompatibleWith() { 94 | if (!text.startsWith(compatibleWithChar)) return null; 95 | 96 | text = text.substring(compatibleWithChar.length); 97 | skipWhitespace(); 98 | 99 | var version = matchVersion(); 100 | if (version == null) { 101 | throw FormatException('Expected version number after ' 102 | '"$compatibleWithChar" in "$originalText", got "$text".'); 103 | } 104 | 105 | if (text.isNotEmpty) { 106 | throw FormatException('Cannot include other constraints with ' 107 | '"$compatibleWithChar" constraint in "$originalText".'); 108 | } 109 | 110 | return VersionConstraint.compatibleWith(version); 111 | } 112 | 113 | var compatibleWith = matchCompatibleWith(); 114 | if (compatibleWith != null) return compatibleWith; 115 | 116 | Version? min; 117 | var includeMin = false; 118 | Version? max; 119 | var includeMax = false; 120 | 121 | for (;;) { 122 | skipWhitespace(); 123 | 124 | if (text.isEmpty) break; 125 | 126 | var newRange = matchVersion() ?? matchComparison(); 127 | if (newRange == null) { 128 | throw FormatException('Could not parse version "$originalText". ' 129 | 'Unknown text at "$text".'); 130 | } 131 | 132 | if (newRange.min != null) { 133 | if (min == null || newRange.min! > min) { 134 | min = newRange.min; 135 | includeMin = newRange.includeMin; 136 | } else if (newRange.min == min && !newRange.includeMin) { 137 | includeMin = false; 138 | } 139 | } 140 | 141 | if (newRange.max != null) { 142 | if (max == null || newRange.max! < max) { 143 | max = newRange.max; 144 | includeMax = newRange.includeMax; 145 | } else if (newRange.max == max && !newRange.includeMax) { 146 | includeMax = false; 147 | } 148 | } 149 | } 150 | 151 | if (min == null && max == null) { 152 | throw const FormatException('Cannot parse an empty string.'); 153 | } 154 | 155 | if (min != null && max != null) { 156 | if (min > max) return VersionConstraint.empty; 157 | if (min == max) { 158 | if (includeMin && includeMax) return min; 159 | return VersionConstraint.empty; 160 | } 161 | } 162 | 163 | return VersionRange( 164 | min: min, includeMin: includeMin, max: max, includeMax: includeMax); 165 | } 166 | 167 | /// Creates a version constraint which allows all versions that are 168 | /// backward compatible with [version]. 169 | /// 170 | /// Versions are considered backward compatible with [version] if they 171 | /// are greater than or equal to [version], but less than the next breaking 172 | /// version ([Version.nextBreaking]) of [version]. 173 | factory VersionConstraint.compatibleWith(Version version) => 174 | CompatibleWithVersionRange(version); 175 | 176 | /// Creates a new version constraint that is the intersection of 177 | /// [constraints]. 178 | /// 179 | /// It only allows versions that all of those constraints allow. If 180 | /// constraints is empty, then it returns a VersionConstraint that allows 181 | /// all versions. 182 | factory VersionConstraint.intersection( 183 | Iterable constraints) { 184 | var constraint = VersionRange(); 185 | for (var other in constraints) { 186 | constraint = constraint.intersect(other) as VersionRange; 187 | } 188 | return constraint; 189 | } 190 | 191 | /// Creates a new version constraint that is the union of [constraints]. 192 | /// 193 | /// It allows any versions that any of those constraints allows. If 194 | /// [constraints] is empty, this returns a constraint that allows no versions. 195 | factory VersionConstraint.unionOf(Iterable constraints) { 196 | var flattened = constraints.expand((constraint) { 197 | if (constraint.isEmpty) return []; 198 | if (constraint is VersionUnion) return constraint.ranges; 199 | if (constraint is VersionRange) return [constraint]; 200 | throw ArgumentError('Unknown VersionConstraint type $constraint.'); 201 | }).toList(); 202 | 203 | if (flattened.isEmpty) return VersionConstraint.empty; 204 | 205 | if (flattened.any((constraint) => constraint.isAny)) { 206 | return VersionConstraint.any; 207 | } 208 | 209 | flattened.sort(); 210 | 211 | var merged = []; 212 | for (var constraint in flattened) { 213 | // Merge this constraint with the previous one, but only if they touch. 214 | if (merged.isEmpty || 215 | (!merged.last.allowsAny(constraint) && 216 | !areAdjacent(merged.last, constraint))) { 217 | merged.add(constraint); 218 | } else { 219 | merged[merged.length - 1] = 220 | merged.last.union(constraint) as VersionRange; 221 | } 222 | } 223 | 224 | if (merged.length == 1) return merged.single; 225 | return VersionUnion.fromRanges(merged); 226 | } 227 | 228 | /// Returns `true` if this constraint allows no versions. 229 | bool get isEmpty; 230 | 231 | /// Returns `true` if this constraint allows all versions. 232 | bool get isAny; 233 | 234 | /// Returns `true` if this constraint allows [version]. 235 | bool allows(Version version); 236 | 237 | /// Returns `true` if this constraint allows all the versions that [other] 238 | /// allows. 239 | bool allowsAll(VersionConstraint other); 240 | 241 | /// Returns `true` if this constraint allows any of the versions that [other] 242 | /// allows. 243 | bool allowsAny(VersionConstraint other); 244 | 245 | /// Returns a [VersionConstraint] that only allows [Version]s allowed by both 246 | /// this and [other]. 247 | VersionConstraint intersect(VersionConstraint other); 248 | 249 | /// Returns a [VersionConstraint] that allows [Version]s allowed by either 250 | /// this or [other]. 251 | VersionConstraint union(VersionConstraint other); 252 | 253 | /// Returns a [VersionConstraint] that allows [Version]s allowed by this but 254 | /// not [other]. 255 | VersionConstraint difference(VersionConstraint other); 256 | } 257 | 258 | class _EmptyVersion implements VersionConstraint { 259 | const _EmptyVersion(); 260 | 261 | @override 262 | bool get isEmpty => true; 263 | 264 | @override 265 | bool get isAny => false; 266 | 267 | @override 268 | bool allows(Version other) => false; 269 | 270 | @override 271 | bool allowsAll(VersionConstraint other) => other.isEmpty; 272 | 273 | @override 274 | bool allowsAny(VersionConstraint other) => false; 275 | 276 | @override 277 | VersionConstraint intersect(VersionConstraint other) => this; 278 | 279 | @override 280 | VersionConstraint union(VersionConstraint other) => other; 281 | 282 | @override 283 | VersionConstraint difference(VersionConstraint other) => this; 284 | 285 | @override 286 | String toString() => ''; 287 | } 288 | -------------------------------------------------------------------------------- /lib/src/version_range.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'utils.dart'; 6 | import 'version.dart'; 7 | import 'version_constraint.dart'; 8 | import 'version_union.dart'; 9 | 10 | /// Constrains versions to a fall within a given range. 11 | /// 12 | /// If there is a minimum, then this only allows versions that are at that 13 | /// minimum or greater. If there is a maximum, then only versions less than 14 | /// that are allowed. In other words, this allows `>= min, < max`. 15 | /// 16 | /// Version ranges are ordered first by their lower bounds, then by their upper 17 | /// bounds. For example, `>=1.0.0 <2.0.0` is before `>=1.5.0 <2.0.0` is before 18 | /// `>=1.5.0 <3.0.0`. 19 | class VersionRange implements Comparable, VersionConstraint { 20 | /// The minimum end of the range. 21 | /// 22 | /// If [includeMin] is `true`, this will be the minimum allowed version. 23 | /// Otherwise, it will be the highest version below the range that is not 24 | /// allowed. 25 | /// 26 | /// This may be `null` in which case the range has no minimum end and allows 27 | /// any version less than the maximum. 28 | final Version? min; 29 | 30 | /// The maximum end of the range. 31 | /// 32 | /// If [includeMax] is `true`, this will be the maximum allowed version. 33 | /// Otherwise, it will be the lowest version above the range that is not 34 | /// allowed. 35 | /// 36 | /// This may be `null` in which case the range has no maximum end and allows 37 | /// any version greater than the minimum. 38 | final Version? max; 39 | 40 | /// If `true` then [min] is allowed by the range. 41 | final bool includeMin; 42 | 43 | /// If `true`, then [max] is allowed by the range. 44 | final bool includeMax; 45 | 46 | /// Creates a new version range from [min] to [max], either inclusive or 47 | /// exclusive. 48 | /// 49 | /// If it is an error if [min] is greater than [max]. 50 | /// 51 | /// Either [max] or [min] may be omitted to not clamp the range at that end. 52 | /// If both are omitted, the range allows all versions. 53 | /// 54 | /// If [includeMin] is `true`, then the minimum end of the range is inclusive. 55 | /// Likewise, passing [includeMax] as `true` makes the upper end inclusive. 56 | /// 57 | /// If [alwaysIncludeMaxPreRelease] is `true`, this will always include 58 | /// pre-release versions of an exclusive [max]. Otherwise, it will use the 59 | /// default behavior for pre-release versions of [max]. 60 | factory VersionRange( 61 | {Version? min, 62 | Version? max, 63 | bool includeMin = false, 64 | bool includeMax = false, 65 | bool alwaysIncludeMaxPreRelease = false}) { 66 | if (min != null && max != null && min > max) { 67 | throw ArgumentError( 68 | 'Minimum version ("$min") must be less than maximum ("$max").'); 69 | } 70 | 71 | if (!alwaysIncludeMaxPreRelease && 72 | !includeMax && 73 | max != null && 74 | !max.isPreRelease && 75 | max.build.isEmpty && 76 | (min == null || 77 | !min.isPreRelease || 78 | !equalsWithoutPreRelease(min, max))) { 79 | max = max.firstPreRelease; 80 | } 81 | 82 | return VersionRange._(min, max, includeMin, includeMax); 83 | } 84 | 85 | VersionRange._(this.min, this.max, this.includeMin, this.includeMax); 86 | 87 | @override 88 | bool operator ==(Object other) { 89 | if (other is! VersionRange) return false; 90 | 91 | return min == other.min && 92 | max == other.max && 93 | includeMin == other.includeMin && 94 | includeMax == other.includeMax; 95 | } 96 | 97 | @override 98 | int get hashCode => 99 | min.hashCode ^ 100 | (max.hashCode * 3) ^ 101 | (includeMin.hashCode * 5) ^ 102 | (includeMax.hashCode * 7); 103 | 104 | @override 105 | bool get isEmpty => false; 106 | 107 | @override 108 | bool get isAny => min == null && max == null; 109 | 110 | /// Tests if [other] falls within this version range. 111 | @override 112 | bool allows(Version other) { 113 | if (min != null) { 114 | if (other < min!) return false; 115 | if (!includeMin && other == min) return false; 116 | } 117 | 118 | if (max != null) { 119 | if (other > max!) return false; 120 | if (!includeMax && other == max) return false; 121 | } 122 | 123 | return true; 124 | } 125 | 126 | @override 127 | bool allowsAll(VersionConstraint other) { 128 | if (other.isEmpty) return true; 129 | if (other is Version) return allows(other); 130 | 131 | if (other is VersionUnion) { 132 | return other.ranges.every(allowsAll); 133 | } 134 | 135 | if (other is VersionRange) { 136 | return !allowsLower(other, this) && !allowsHigher(other, this); 137 | } 138 | 139 | throw ArgumentError('Unknown VersionConstraint type $other.'); 140 | } 141 | 142 | @override 143 | bool allowsAny(VersionConstraint other) { 144 | if (other.isEmpty) return false; 145 | if (other is Version) return allows(other); 146 | 147 | if (other is VersionUnion) { 148 | return other.ranges.any(allowsAny); 149 | } 150 | 151 | if (other is VersionRange) { 152 | return !strictlyLower(other, this) && !strictlyHigher(other, this); 153 | } 154 | 155 | throw ArgumentError('Unknown VersionConstraint type $other.'); 156 | } 157 | 158 | @override 159 | VersionConstraint intersect(VersionConstraint other) { 160 | if (other.isEmpty) return other; 161 | if (other is VersionUnion) return other.intersect(this); 162 | 163 | // A range and a Version just yields the version if it's in the range. 164 | if (other is Version) { 165 | return allows(other) ? other : VersionConstraint.empty; 166 | } 167 | 168 | if (other is VersionRange) { 169 | // Intersect the two ranges. 170 | Version? intersectMin; 171 | bool intersectIncludeMin; 172 | if (allowsLower(this, other)) { 173 | if (strictlyLower(this, other)) return VersionConstraint.empty; 174 | intersectMin = other.min; 175 | intersectIncludeMin = other.includeMin; 176 | } else { 177 | if (strictlyLower(other, this)) return VersionConstraint.empty; 178 | intersectMin = min; 179 | intersectIncludeMin = includeMin; 180 | } 181 | 182 | Version? intersectMax; 183 | bool intersectIncludeMax; 184 | if (allowsHigher(this, other)) { 185 | intersectMax = other.max; 186 | intersectIncludeMax = other.includeMax; 187 | } else { 188 | intersectMax = max; 189 | intersectIncludeMax = includeMax; 190 | } 191 | 192 | if (intersectMin == null && intersectMax == null) { 193 | // Open range. 194 | return VersionRange(); 195 | } 196 | 197 | // If the range is just a single version. 198 | if (intersectMin == intersectMax) { 199 | // Because we already verified that the lower range isn't strictly 200 | // lower, there must be some overlap. 201 | assert(intersectIncludeMin && intersectIncludeMax); 202 | return intersectMin!; 203 | } 204 | 205 | // If we got here, there is an actual range. 206 | return VersionRange( 207 | min: intersectMin, 208 | max: intersectMax, 209 | includeMin: intersectIncludeMin, 210 | includeMax: intersectIncludeMax, 211 | alwaysIncludeMaxPreRelease: true); 212 | } 213 | 214 | throw ArgumentError('Unknown VersionConstraint type $other.'); 215 | } 216 | 217 | @override 218 | VersionConstraint union(VersionConstraint other) { 219 | if (other is Version) { 220 | if (allows(other)) return this; 221 | 222 | if (other == min) { 223 | return VersionRange( 224 | min: min, 225 | max: max, 226 | includeMin: true, 227 | includeMax: includeMax, 228 | alwaysIncludeMaxPreRelease: true); 229 | } 230 | 231 | if (other == max) { 232 | return VersionRange( 233 | min: min, 234 | max: max, 235 | includeMin: includeMin, 236 | includeMax: true, 237 | alwaysIncludeMaxPreRelease: true); 238 | } 239 | 240 | return VersionConstraint.unionOf([this, other]); 241 | } 242 | 243 | if (other is VersionRange) { 244 | // If the two ranges don't overlap, we won't be able to create a single 245 | // VersionRange for both of them. 246 | var edgesTouch = (max != null && 247 | max == other.min && 248 | (includeMax || other.includeMin)) || 249 | (min != null && min == other.max && (includeMin || other.includeMax)); 250 | if (!edgesTouch && !allowsAny(other)) { 251 | return VersionConstraint.unionOf([this, other]); 252 | } 253 | 254 | Version? unionMin; 255 | bool unionIncludeMin; 256 | if (allowsLower(this, other)) { 257 | unionMin = min; 258 | unionIncludeMin = includeMin; 259 | } else { 260 | unionMin = other.min; 261 | unionIncludeMin = other.includeMin; 262 | } 263 | 264 | Version? unionMax; 265 | bool unionIncludeMax; 266 | if (allowsHigher(this, other)) { 267 | unionMax = max; 268 | unionIncludeMax = includeMax; 269 | } else { 270 | unionMax = other.max; 271 | unionIncludeMax = other.includeMax; 272 | } 273 | 274 | return VersionRange( 275 | min: unionMin, 276 | max: unionMax, 277 | includeMin: unionIncludeMin, 278 | includeMax: unionIncludeMax, 279 | alwaysIncludeMaxPreRelease: true); 280 | } 281 | 282 | return VersionConstraint.unionOf([this, other]); 283 | } 284 | 285 | @override 286 | VersionConstraint difference(VersionConstraint other) { 287 | if (other.isEmpty) return this; 288 | 289 | if (other is Version) { 290 | if (!allows(other)) return this; 291 | 292 | if (other == min) { 293 | if (!includeMin) return this; 294 | return VersionRange( 295 | min: min, 296 | max: max, 297 | includeMax: includeMax, 298 | alwaysIncludeMaxPreRelease: true); 299 | } 300 | 301 | if (other == max) { 302 | if (!includeMax) return this; 303 | return VersionRange( 304 | min: min, 305 | max: max, 306 | includeMin: includeMin, 307 | alwaysIncludeMaxPreRelease: true); 308 | } 309 | 310 | return VersionUnion.fromRanges([ 311 | VersionRange( 312 | min: min, 313 | max: other, 314 | includeMin: includeMin, 315 | alwaysIncludeMaxPreRelease: true), 316 | VersionRange( 317 | min: other, 318 | max: max, 319 | includeMax: includeMax, 320 | alwaysIncludeMaxPreRelease: true) 321 | ]); 322 | } else if (other is VersionRange) { 323 | if (!allowsAny(other)) return this; 324 | 325 | VersionRange? before; 326 | if (!allowsLower(this, other)) { 327 | before = null; 328 | } else if (min == other.min) { 329 | assert(includeMin && !other.includeMin); 330 | assert(min != null); 331 | before = min; 332 | } else { 333 | before = VersionRange( 334 | min: min, 335 | max: other.min, 336 | includeMin: includeMin, 337 | includeMax: !other.includeMin, 338 | alwaysIncludeMaxPreRelease: true); 339 | } 340 | 341 | VersionRange? after; 342 | if (!allowsHigher(this, other)) { 343 | after = null; 344 | } else if (max == other.max) { 345 | assert(includeMax && !other.includeMax); 346 | assert(max != null); 347 | after = max; 348 | } else { 349 | after = VersionRange( 350 | min: other.max, 351 | max: max, 352 | includeMin: !other.includeMax, 353 | includeMax: includeMax, 354 | alwaysIncludeMaxPreRelease: true); 355 | } 356 | 357 | if (before == null && after == null) return VersionConstraint.empty; 358 | if (before == null) return after!; 359 | if (after == null) return before; 360 | return VersionUnion.fromRanges([before, after]); 361 | } else if (other is VersionUnion) { 362 | var ranges = []; 363 | var current = this; 364 | 365 | for (var range in other.ranges) { 366 | // Skip any ranges that are strictly lower than [current]. 367 | if (strictlyLower(range, current)) continue; 368 | 369 | // If we reach a range strictly higher than [current], no more ranges 370 | // will be relevant so we can bail early. 371 | if (strictlyHigher(range, current)) break; 372 | 373 | var difference = current.difference(range); 374 | if (difference.isEmpty) { 375 | return VersionConstraint.empty; 376 | } else if (difference is VersionUnion) { 377 | // If [range] split [current] in half, we only need to continue 378 | // checking future ranges against the latter half. 379 | assert(difference.ranges.length == 2); 380 | ranges.add(difference.ranges.first); 381 | current = difference.ranges.last; 382 | } else { 383 | current = difference as VersionRange; 384 | } 385 | } 386 | 387 | if (ranges.isEmpty) return current; 388 | return VersionUnion.fromRanges(ranges..add(current)); 389 | } 390 | 391 | throw ArgumentError('Unknown VersionConstraint type $other.'); 392 | } 393 | 394 | @override 395 | int compareTo(VersionRange other) { 396 | if (min == null) { 397 | if (other.min == null) return _compareMax(other); 398 | return -1; 399 | } else if (other.min == null) { 400 | return 1; 401 | } 402 | 403 | var result = min!.compareTo(other.min!); 404 | if (result != 0) return result; 405 | if (includeMin != other.includeMin) return includeMin ? -1 : 1; 406 | 407 | return _compareMax(other); 408 | } 409 | 410 | /// Compares the maximum values of `this` and [other]. 411 | int _compareMax(VersionRange other) { 412 | if (max == null) { 413 | if (other.max == null) return 0; 414 | return 1; 415 | } else if (other.max == null) { 416 | return -1; 417 | } 418 | 419 | var result = max!.compareTo(other.max!); 420 | if (result != 0) return result; 421 | if (includeMax != other.includeMax) return includeMax ? 1 : -1; 422 | return 0; 423 | } 424 | 425 | @override 426 | String toString() { 427 | var buffer = StringBuffer(); 428 | 429 | final min = this.min; 430 | if (min != null) { 431 | buffer 432 | ..write(includeMin ? '>=' : '>') 433 | ..write(min); 434 | } 435 | 436 | final max = this.max; 437 | 438 | if (max != null) { 439 | if (min != null) buffer.write(' '); 440 | if (includeMax) { 441 | buffer 442 | ..write('<=') 443 | ..write(max); 444 | } else { 445 | buffer.write('<'); 446 | if (max.isFirstPreRelease) { 447 | // Since `"<$max"` would parse the same as `"<$max-0"`, we just emit 448 | // `<$max` to avoid confusing "-0" suffixes. 449 | buffer.write('${max.major}.${max.minor}.${max.patch}'); 450 | } else { 451 | buffer.write(max); 452 | 453 | // If `">=$min <$max"` would parse as `">=$min <$max-0"`, add `-*` to 454 | // indicate that actually does allow pre-release versions. 455 | var minIsPreReleaseOfMax = min != null && 456 | min.isPreRelease && 457 | equalsWithoutPreRelease(min, max); 458 | if (!max.isPreRelease && max.build.isEmpty && !minIsPreReleaseOfMax) { 459 | buffer.write('-∞'); 460 | } 461 | } 462 | } 463 | } 464 | 465 | if (min == null && max == null) buffer.write('any'); 466 | return buffer.toString(); 467 | } 468 | } 469 | 470 | class CompatibleWithVersionRange extends VersionRange { 471 | CompatibleWithVersionRange(Version version) 472 | : super._(version, version.nextBreaking.firstPreRelease, true, false); 473 | 474 | @override 475 | String toString() => '^$min'; 476 | } 477 | -------------------------------------------------------------------------------- /lib/src/version_union.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:collection/collection.dart'; 6 | 7 | import 'utils.dart'; 8 | import 'version.dart'; 9 | import 'version_constraint.dart'; 10 | import 'version_range.dart'; 11 | 12 | /// A version constraint representing a union of multiple disjoint version 13 | /// ranges. 14 | /// 15 | /// An instance of this will only be created if the version can't be represented 16 | /// as a non-compound value. 17 | class VersionUnion implements VersionConstraint { 18 | /// The constraints that compose this union. 19 | /// 20 | /// This list has two invariants: 21 | /// 22 | /// * Its contents are sorted using the standard ordering of [VersionRange]s. 23 | /// * Its contents are disjoint and non-adjacent. In other words, for any two 24 | /// constraints next to each other in the list, there's some version between 25 | /// those constraints that they don't match. 26 | final List ranges; 27 | 28 | @override 29 | bool get isEmpty => false; 30 | 31 | @override 32 | bool get isAny => false; 33 | 34 | /// Creates a union from a list of ranges with no pre-processing. 35 | /// 36 | /// It's up to the caller to ensure that the invariants described in [ranges] 37 | /// are maintained. They are not verified by this constructor. To 38 | /// automatically ensure that they're maintained, use 39 | /// [VersionConstraint.unionOf] instead. 40 | VersionUnion.fromRanges(this.ranges); 41 | 42 | @override 43 | bool allows(Version version) => 44 | ranges.any((constraint) => constraint.allows(version)); 45 | 46 | @override 47 | bool allowsAll(VersionConstraint other) { 48 | var ourRanges = ranges.iterator; 49 | var theirRanges = _rangesFor(other).iterator; 50 | 51 | // Because both lists of ranges are ordered by minimum version, we can 52 | // safely move through them linearly here. 53 | var ourRangesMoved = ourRanges.moveNext(); 54 | var theirRangesMoved = theirRanges.moveNext(); 55 | while (ourRangesMoved && theirRangesMoved) { 56 | if (ourRanges.current.allowsAll(theirRanges.current)) { 57 | theirRangesMoved = theirRanges.moveNext(); 58 | } else { 59 | ourRangesMoved = ourRanges.moveNext(); 60 | } 61 | } 62 | 63 | // If our ranges have allowed all of their ranges, we'll have consumed all 64 | // of them. 65 | return !theirRangesMoved; 66 | } 67 | 68 | @override 69 | bool allowsAny(VersionConstraint other) { 70 | var ourRanges = ranges.iterator; 71 | var theirRanges = _rangesFor(other).iterator; 72 | 73 | // Because both lists of ranges are ordered by minimum version, we can 74 | // safely move through them linearly here. 75 | var ourRangesMoved = ourRanges.moveNext(); 76 | var theirRangesMoved = theirRanges.moveNext(); 77 | while (ourRangesMoved && theirRangesMoved) { 78 | if (ourRanges.current.allowsAny(theirRanges.current)) { 79 | return true; 80 | } 81 | 82 | // Move the constraint with the lower max value forward. This ensures that 83 | // we keep both lists in sync as much as possible. 84 | if (allowsHigher(theirRanges.current, ourRanges.current)) { 85 | ourRangesMoved = ourRanges.moveNext(); 86 | } else { 87 | theirRangesMoved = theirRanges.moveNext(); 88 | } 89 | } 90 | 91 | return false; 92 | } 93 | 94 | @override 95 | VersionConstraint intersect(VersionConstraint other) { 96 | var ourRanges = ranges.iterator; 97 | var theirRanges = _rangesFor(other).iterator; 98 | 99 | // Because both lists of ranges are ordered by minimum version, we can 100 | // safely move through them linearly here. 101 | var newRanges = []; 102 | var ourRangesMoved = ourRanges.moveNext(); 103 | var theirRangesMoved = theirRanges.moveNext(); 104 | while (ourRangesMoved && theirRangesMoved) { 105 | var intersection = ourRanges.current.intersect(theirRanges.current); 106 | 107 | if (!intersection.isEmpty) newRanges.add(intersection as VersionRange); 108 | 109 | // Move the constraint with the lower max value forward. This ensures that 110 | // we keep both lists in sync as much as possible, and that large ranges 111 | // have a chance to match multiple small ranges that they contain. 112 | if (allowsHigher(theirRanges.current, ourRanges.current)) { 113 | ourRangesMoved = ourRanges.moveNext(); 114 | } else { 115 | theirRangesMoved = theirRanges.moveNext(); 116 | } 117 | } 118 | 119 | if (newRanges.isEmpty) return VersionConstraint.empty; 120 | if (newRanges.length == 1) return newRanges.single; 121 | 122 | return VersionUnion.fromRanges(newRanges); 123 | } 124 | 125 | @override 126 | VersionConstraint difference(VersionConstraint other) { 127 | var ourRanges = ranges.iterator; 128 | var theirRanges = _rangesFor(other).iterator; 129 | 130 | var newRanges = []; 131 | ourRanges.moveNext(); 132 | theirRanges.moveNext(); 133 | var current = ourRanges.current; 134 | 135 | bool theirNextRange() { 136 | if (theirRanges.moveNext()) return true; 137 | 138 | // If there are no more of their ranges, none of the rest of our ranges 139 | // need to be subtracted so we can add them as-is. 140 | newRanges.add(current); 141 | while (ourRanges.moveNext()) { 142 | newRanges.add(ourRanges.current); 143 | } 144 | return false; 145 | } 146 | 147 | bool ourNextRange({bool includeCurrent = true}) { 148 | if (includeCurrent) newRanges.add(current); 149 | if (!ourRanges.moveNext()) return false; 150 | current = ourRanges.current; 151 | return true; 152 | } 153 | 154 | for (;;) { 155 | // If the current ranges are disjoint, move the lowest one forward. 156 | if (strictlyLower(theirRanges.current, current)) { 157 | if (!theirNextRange()) break; 158 | continue; 159 | } 160 | 161 | if (strictlyHigher(theirRanges.current, current)) { 162 | if (!ourNextRange()) break; 163 | continue; 164 | } 165 | 166 | // If we're here, we know [theirRanges.current] overlaps [current]. 167 | var difference = current.difference(theirRanges.current); 168 | if (difference is VersionUnion) { 169 | // If their range split [current] in half, we only need to continue 170 | // checking future ranges against the latter half. 171 | assert(difference.ranges.length == 2); 172 | newRanges.add(difference.ranges.first); 173 | current = difference.ranges.last; 174 | 175 | // Since their range split [current], it definitely doesn't allow higher 176 | // versions, so we should move their ranges forward. 177 | if (!theirNextRange()) break; 178 | } else if (difference.isEmpty) { 179 | if (!ourNextRange(includeCurrent: false)) break; 180 | } else { 181 | current = difference as VersionRange; 182 | 183 | // Move the constraint with the lower max value forward. This ensures 184 | // that we keep both lists in sync as much as possible, and that large 185 | // ranges have a chance to subtract or be subtracted by multiple small 186 | // ranges that they contain. 187 | if (allowsHigher(current, theirRanges.current)) { 188 | if (!theirNextRange()) break; 189 | } else { 190 | if (!ourNextRange()) break; 191 | } 192 | } 193 | } 194 | 195 | if (newRanges.isEmpty) return VersionConstraint.empty; 196 | if (newRanges.length == 1) return newRanges.single; 197 | return VersionUnion.fromRanges(newRanges); 198 | } 199 | 200 | /// Returns [constraint] as a list of ranges. 201 | /// 202 | /// This is used to normalize ranges of various types. 203 | List _rangesFor(VersionConstraint constraint) { 204 | if (constraint.isEmpty) return []; 205 | if (constraint is VersionUnion) return constraint.ranges; 206 | if (constraint is VersionRange) return [constraint]; 207 | throw ArgumentError('Unknown VersionConstraint type $constraint.'); 208 | } 209 | 210 | @override 211 | VersionConstraint union(VersionConstraint other) => 212 | VersionConstraint.unionOf([this, other]); 213 | 214 | @override 215 | bool operator ==(Object other) => 216 | other is VersionUnion && 217 | const ListEquality().equals(ranges, other.ranges); 218 | 219 | @override 220 | int get hashCode => const ListEquality().hash(ranges); 221 | 222 | @override 223 | String toString() => ranges.join(' or '); 224 | } 225 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pub_semver 2 | version: 2.1.5-wip 3 | description: >- 4 | Versions and version constraints implementing pub's versioning policy. This 5 | is very similar to vanilla semver, with a few corner cases. 6 | repository: https://github.com/dart-lang/pub_semver 7 | topics: 8 | - dart-pub 9 | - semver 10 | 11 | environment: 12 | sdk: ^3.4.0 13 | 14 | dependencies: 15 | collection: ^1.15.0 16 | meta: ^1.3.0 17 | 18 | dev_dependencies: 19 | dart_flutter_team_lints: ^3.0.0 20 | test: ^1.16.0 21 | -------------------------------------------------------------------------------- /test/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:pub_semver/pub_semver.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | /// Some stock example versions to use in tests. 9 | final v003 = Version.parse('0.0.3'); 10 | final v010 = Version.parse('0.1.0'); 11 | final v072 = Version.parse('0.7.2'); 12 | final v080 = Version.parse('0.8.0'); 13 | final v114 = Version.parse('1.1.4'); 14 | final v123 = Version.parse('1.2.3'); 15 | final v124 = Version.parse('1.2.4'); 16 | final v130 = Version.parse('1.3.0'); 17 | final v140 = Version.parse('1.4.0'); 18 | final v200 = Version.parse('2.0.0'); 19 | final v201 = Version.parse('2.0.1'); 20 | final v234 = Version.parse('2.3.4'); 21 | final v250 = Version.parse('2.5.0'); 22 | final v300 = Version.parse('3.0.0'); 23 | 24 | /// A range that allows pre-release versions of its max version. 25 | final includeMaxPreReleaseRange = 26 | VersionRange(max: v200, alwaysIncludeMaxPreRelease: true); 27 | 28 | /// A [Matcher] that tests if a [VersionConstraint] allows or does not allow a 29 | /// given list of [Version]s. 30 | class _VersionConstraintMatcher implements Matcher { 31 | final List _expected; 32 | final bool _allow; 33 | 34 | _VersionConstraintMatcher(this._expected, this._allow); 35 | 36 | @override 37 | bool matches(dynamic item, Map matchState) => 38 | (item is VersionConstraint) && 39 | _expected.every((version) => item.allows(version) == _allow); 40 | 41 | @override 42 | Description describe(Description description) { 43 | description.addAll(' ${_allow ? "allows" : "does not allow"} versions ', 44 | ', ', '', _expected); 45 | return description; 46 | } 47 | 48 | @override 49 | Description describeMismatch(dynamic item, Description mismatchDescription, 50 | Map matchState, bool verbose) { 51 | if (item is! VersionConstraint) { 52 | mismatchDescription.add('was not a VersionConstraint'); 53 | return mismatchDescription; 54 | } 55 | 56 | var first = true; 57 | for (var version in _expected) { 58 | if (item.allows(version) != _allow) { 59 | if (first) { 60 | if (_allow) { 61 | mismatchDescription.addDescriptionOf(item).add(' did not allow '); 62 | } else { 63 | mismatchDescription.addDescriptionOf(item).add(' allowed '); 64 | } 65 | } else { 66 | mismatchDescription.add(' and '); 67 | } 68 | first = false; 69 | 70 | mismatchDescription.add(version.toString()); 71 | } 72 | } 73 | 74 | return mismatchDescription; 75 | } 76 | } 77 | 78 | /// Gets a [Matcher] that validates that a [VersionConstraint] allows all 79 | /// given versions. 80 | Matcher allows(Version v1, 81 | [Version? v2, 82 | Version? v3, 83 | Version? v4, 84 | Version? v5, 85 | Version? v6, 86 | Version? v7, 87 | Version? v8]) { 88 | var versions = _makeVersionList(v1, v2, v3, v4, v5, v6, v7, v8); 89 | return _VersionConstraintMatcher(versions, true); 90 | } 91 | 92 | /// Gets a [Matcher] that validates that a [VersionConstraint] allows none of 93 | /// the given versions. 94 | Matcher doesNotAllow(Version v1, 95 | [Version? v2, 96 | Version? v3, 97 | Version? v4, 98 | Version? v5, 99 | Version? v6, 100 | Version? v7, 101 | Version? v8]) { 102 | var versions = _makeVersionList(v1, v2, v3, v4, v5, v6, v7, v8); 103 | return _VersionConstraintMatcher(versions, false); 104 | } 105 | 106 | List _makeVersionList(Version v1, 107 | [Version? v2, 108 | Version? v3, 109 | Version? v4, 110 | Version? v5, 111 | Version? v6, 112 | Version? v7, 113 | Version? v8]) { 114 | var versions = [v1]; 115 | if (v2 != null) versions.add(v2); 116 | if (v3 != null) versions.add(v3); 117 | if (v4 != null) versions.add(v4); 118 | if (v5 != null) versions.add(v5); 119 | if (v6 != null) versions.add(v6); 120 | if (v7 != null) versions.add(v7); 121 | if (v8 != null) versions.add(v8); 122 | return versions; 123 | } 124 | -------------------------------------------------------------------------------- /test/version_constraint_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:pub_semver/pub_semver.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | void main() { 11 | test('any', () { 12 | expect(VersionConstraint.any.isAny, isTrue); 13 | expect( 14 | VersionConstraint.any, 15 | allows(Version.parse('0.0.0-blah'), Version.parse('1.2.3'), 16 | Version.parse('12345.678.90'))); 17 | }); 18 | 19 | test('empty', () { 20 | expect(VersionConstraint.empty.isEmpty, isTrue); 21 | expect(VersionConstraint.empty.isAny, isFalse); 22 | expect( 23 | VersionConstraint.empty, 24 | doesNotAllow(Version.parse('0.0.0-blah'), Version.parse('1.2.3'), 25 | Version.parse('12345.678.90'))); 26 | }); 27 | 28 | group('parse()', () { 29 | test('parses an exact version', () { 30 | var constraint = VersionConstraint.parse('1.2.3-alpha'); 31 | 32 | expect(constraint is Version, isTrue); 33 | expect(constraint, equals(Version(1, 2, 3, pre: 'alpha'))); 34 | }); 35 | 36 | test('parses "any"', () { 37 | var constraint = VersionConstraint.parse('any'); 38 | 39 | expect( 40 | constraint, 41 | allows(Version.parse('0.0.0'), Version.parse('1.2.3'), 42 | Version.parse('12345.678.90'))); 43 | }); 44 | 45 | test('parses a ">" minimum version', () { 46 | var constraint = VersionConstraint.parse('>1.2.3'); 47 | 48 | expect(constraint, 49 | allows(Version.parse('1.2.3+foo'), Version.parse('1.2.4'))); 50 | expect( 51 | constraint, 52 | doesNotAllow(Version.parse('1.2.1'), Version.parse('1.2.3-build'), 53 | Version.parse('1.2.3'))); 54 | }); 55 | 56 | test('parses a ">=" minimum version', () { 57 | var constraint = VersionConstraint.parse('>=1.2.3'); 58 | 59 | expect( 60 | constraint, 61 | allows(Version.parse('1.2.3'), Version.parse('1.2.3+foo'), 62 | Version.parse('1.2.4'))); 63 | expect(constraint, 64 | doesNotAllow(Version.parse('1.2.1'), Version.parse('1.2.3-build'))); 65 | }); 66 | 67 | test('parses a "<" maximum version', () { 68 | var constraint = VersionConstraint.parse('<1.2.3'); 69 | 70 | expect(constraint, 71 | allows(Version.parse('1.2.1'), Version.parse('1.2.2+foo'))); 72 | expect( 73 | constraint, 74 | doesNotAllow(Version.parse('1.2.3'), Version.parse('1.2.3+foo'), 75 | Version.parse('1.2.4'))); 76 | }); 77 | 78 | test('parses a "<=" maximum version', () { 79 | var constraint = VersionConstraint.parse('<=1.2.3'); 80 | 81 | expect( 82 | constraint, 83 | allows(Version.parse('1.2.1'), Version.parse('1.2.3-build'), 84 | Version.parse('1.2.3'))); 85 | expect(constraint, 86 | doesNotAllow(Version.parse('1.2.3+foo'), Version.parse('1.2.4'))); 87 | }); 88 | 89 | test('parses a series of space-separated constraints', () { 90 | var constraint = VersionConstraint.parse('>1.0.0 >=1.2.3 <1.3.0'); 91 | 92 | expect( 93 | constraint, allows(Version.parse('1.2.3'), Version.parse('1.2.5'))); 94 | expect( 95 | constraint, 96 | doesNotAllow(Version.parse('1.2.3-pre'), Version.parse('1.3.0'), 97 | Version.parse('3.4.5'))); 98 | }); 99 | 100 | test('parses a pre-release-only constraint', () { 101 | var constraint = VersionConstraint.parse('>=1.0.0-dev.2 <1.0.0'); 102 | expect(constraint, 103 | allows(Version.parse('1.0.0-dev.2'), Version.parse('1.0.0-dev.3'))); 104 | expect(constraint, 105 | doesNotAllow(Version.parse('1.0.0-dev.1'), Version.parse('1.0.0'))); 106 | }); 107 | 108 | test('ignores whitespace around comparison operators', () { 109 | var constraint = VersionConstraint.parse(' >1.0.0>=1.2.3 < 1.3.0'); 110 | 111 | expect( 112 | constraint, allows(Version.parse('1.2.3'), Version.parse('1.2.5'))); 113 | expect( 114 | constraint, 115 | doesNotAllow(Version.parse('1.2.3-pre'), Version.parse('1.3.0'), 116 | Version.parse('3.4.5'))); 117 | }); 118 | 119 | test('does not allow "any" to be mixed with other constraints', () { 120 | expect(() => VersionConstraint.parse('any 1.0.0'), throwsFormatException); 121 | }); 122 | 123 | test('parses a "^" version', () { 124 | expect(VersionConstraint.parse('^0.0.3'), 125 | equals(VersionConstraint.compatibleWith(v003))); 126 | 127 | expect(VersionConstraint.parse('^0.7.2'), 128 | equals(VersionConstraint.compatibleWith(v072))); 129 | 130 | expect(VersionConstraint.parse('^1.2.3'), 131 | equals(VersionConstraint.compatibleWith(v123))); 132 | 133 | var min = Version.parse('0.7.2-pre+1'); 134 | expect(VersionConstraint.parse('^0.7.2-pre+1'), 135 | equals(VersionConstraint.compatibleWith(min))); 136 | }); 137 | 138 | test('does not allow "^" to be mixed with other constraints', () { 139 | expect(() => VersionConstraint.parse('>=1.2.3 ^1.0.0'), 140 | throwsFormatException); 141 | expect(() => VersionConstraint.parse('^1.0.0 <1.2.3'), 142 | throwsFormatException); 143 | }); 144 | 145 | test('ignores whitespace around "^"', () { 146 | var constraint = VersionConstraint.parse(' ^ 1.2.3 '); 147 | 148 | expect(constraint, equals(VersionConstraint.compatibleWith(v123))); 149 | }); 150 | 151 | test('throws FormatException on a bad string', () { 152 | var bad = [ 153 | '', ' ', // Empty string. 154 | 'foo', // Bad text. 155 | '>foo', // Bad text after operator. 156 | '^foo', // Bad text after "^". 157 | '1.0.0 foo', '1.0.0foo', // Bad text after version. 158 | 'anything', // Bad text after "any". 159 | '<>1.0.0', // Multiple operators. 160 | '1.0.0<' // Trailing operator. 161 | ]; 162 | 163 | for (var text in bad) { 164 | expect(() => VersionConstraint.parse(text), throwsFormatException); 165 | } 166 | }); 167 | }); 168 | 169 | group('compatibleWith()', () { 170 | test('returns the range of compatible versions', () { 171 | var constraint = VersionConstraint.compatibleWith(v072); 172 | 173 | expect( 174 | constraint, 175 | equals(VersionRange( 176 | min: v072, includeMin: true, max: v072.nextBreaking))); 177 | }); 178 | 179 | test('toString() uses "^"', () { 180 | var constraint = VersionConstraint.compatibleWith(v072); 181 | 182 | expect(constraint.toString(), equals('^0.7.2')); 183 | }); 184 | }); 185 | } 186 | -------------------------------------------------------------------------------- /test/version_range_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:pub_semver/pub_semver.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | void main() { 11 | group('constructor', () { 12 | test('takes a min and max', () { 13 | var range = VersionRange(min: v123, max: v124); 14 | expect(range.isAny, isFalse); 15 | expect(range.min, equals(v123)); 16 | expect(range.max, equals(v124.firstPreRelease)); 17 | }); 18 | 19 | group("doesn't make the max a pre-release if", () { 20 | test("it's already a pre-release", () { 21 | expect(VersionRange(max: Version.parse('1.2.4-pre')).max, 22 | equals(Version.parse('1.2.4-pre'))); 23 | }); 24 | 25 | test('includeMax is true', () { 26 | expect(VersionRange(max: v124, includeMax: true).max, equals(v124)); 27 | }); 28 | 29 | test('min is a prerelease of max', () { 30 | expect(VersionRange(min: Version.parse('1.2.4-pre'), max: v124).max, 31 | equals(v124)); 32 | }); 33 | 34 | test('max has a build identifier', () { 35 | expect(VersionRange(max: Version.parse('1.2.4+1')).max, 36 | equals(Version.parse('1.2.4+1'))); 37 | }); 38 | }); 39 | 40 | test('allows omitting max', () { 41 | var range = VersionRange(min: v123); 42 | expect(range.isAny, isFalse); 43 | expect(range.min, equals(v123)); 44 | expect(range.max, isNull); 45 | }); 46 | 47 | test('allows omitting min and max', () { 48 | var range = VersionRange(); 49 | expect(range.isAny, isTrue); 50 | expect(range.min, isNull); 51 | expect(range.max, isNull); 52 | }); 53 | 54 | test('takes includeMin', () { 55 | var range = VersionRange(min: v123, includeMin: true); 56 | expect(range.includeMin, isTrue); 57 | }); 58 | 59 | test('includeMin defaults to false if omitted', () { 60 | var range = VersionRange(min: v123); 61 | expect(range.includeMin, isFalse); 62 | }); 63 | 64 | test('takes includeMax', () { 65 | var range = VersionRange(max: v123, includeMax: true); 66 | expect(range.includeMax, isTrue); 67 | }); 68 | 69 | test('includeMax defaults to false if omitted', () { 70 | var range = VersionRange(max: v123); 71 | expect(range.includeMax, isFalse); 72 | }); 73 | 74 | test('throws if min > max', () { 75 | expect(() => VersionRange(min: v124, max: v123), throwsArgumentError); 76 | }); 77 | }); 78 | 79 | group('allows()', () { 80 | test('version must be greater than min', () { 81 | var range = VersionRange(min: v123); 82 | 83 | expect(range, allows(Version.parse('1.3.3'), Version.parse('2.3.3'))); 84 | expect( 85 | range, doesNotAllow(Version.parse('1.2.2'), Version.parse('1.2.3'))); 86 | }); 87 | 88 | test('version must be min or greater if includeMin', () { 89 | var range = VersionRange(min: v123, includeMin: true); 90 | 91 | expect( 92 | range, 93 | allows(Version.parse('1.2.3'), Version.parse('1.3.3'), 94 | Version.parse('2.3.3'))); 95 | expect(range, doesNotAllow(Version.parse('1.2.2'))); 96 | }); 97 | 98 | test('pre-release versions of inclusive min are excluded', () { 99 | var range = VersionRange(min: v123, includeMin: true); 100 | 101 | expect(range, allows(Version.parse('1.2.4-dev'))); 102 | expect(range, doesNotAllow(Version.parse('1.2.3-dev'))); 103 | }); 104 | 105 | test('version must be less than max', () { 106 | var range = VersionRange(max: v234); 107 | 108 | expect(range, allows(Version.parse('2.3.3'))); 109 | expect( 110 | range, doesNotAllow(Version.parse('2.3.4'), Version.parse('2.4.3'))); 111 | }); 112 | 113 | test('pre-release versions of non-pre-release max are excluded', () { 114 | var range = VersionRange(max: v234); 115 | 116 | expect(range, allows(Version.parse('2.3.3'))); 117 | expect(range, 118 | doesNotAllow(Version.parse('2.3.4-dev'), Version.parse('2.3.4'))); 119 | }); 120 | 121 | test( 122 | 'pre-release versions of non-pre-release max are included if min is a ' 123 | 'pre-release of the same version', () { 124 | var range = VersionRange(min: Version.parse('2.3.4-dev.0'), max: v234); 125 | 126 | expect(range, allows(Version.parse('2.3.4-dev.1'))); 127 | expect( 128 | range, 129 | doesNotAllow(Version.parse('2.3.3'), Version.parse('2.3.4-dev'), 130 | Version.parse('2.3.4'))); 131 | }); 132 | 133 | test('pre-release versions of pre-release max are included', () { 134 | var range = VersionRange(max: Version.parse('2.3.4-dev.2')); 135 | 136 | expect(range, allows(Version.parse('2.3.4-dev.1'))); 137 | expect( 138 | range, 139 | doesNotAllow( 140 | Version.parse('2.3.4-dev.2'), Version.parse('2.3.4-dev.3'))); 141 | }); 142 | 143 | test('version must be max or less if includeMax', () { 144 | var range = VersionRange(min: v123, max: v234, includeMax: true); 145 | 146 | expect( 147 | range, 148 | allows( 149 | Version.parse('2.3.3'), 150 | Version.parse('2.3.4'), 151 | // Pre-releases of the max are allowed. 152 | Version.parse('2.3.4-dev'))); 153 | expect(range, doesNotAllow(Version.parse('2.4.3'))); 154 | }); 155 | 156 | test('has no min if one was not set', () { 157 | var range = VersionRange(max: v123); 158 | 159 | expect(range, allows(Version.parse('0.0.0'))); 160 | expect(range, doesNotAllow(Version.parse('1.2.3'))); 161 | }); 162 | 163 | test('has no max if one was not set', () { 164 | var range = VersionRange(min: v123); 165 | 166 | expect(range, allows(Version.parse('1.3.3'), Version.parse('999.3.3'))); 167 | expect(range, doesNotAllow(Version.parse('1.2.3'))); 168 | }); 169 | 170 | test('allows any version if there is no min or max', () { 171 | var range = VersionRange(); 172 | 173 | expect(range, allows(Version.parse('0.0.0'), Version.parse('999.99.9'))); 174 | }); 175 | 176 | test('allows pre-releases of the max with includeMaxPreRelease', () { 177 | expect(includeMaxPreReleaseRange, allows(Version.parse('2.0.0-dev'))); 178 | }); 179 | }); 180 | 181 | group('allowsAll()', () { 182 | test('allows an empty constraint', () { 183 | expect( 184 | VersionRange(min: v123, max: v250).allowsAll(VersionConstraint.empty), 185 | isTrue); 186 | }); 187 | 188 | test('allows allowed versions', () { 189 | var range = VersionRange(min: v123, max: v250, includeMax: true); 190 | expect(range.allowsAll(v123), isFalse); 191 | expect(range.allowsAll(v124), isTrue); 192 | expect(range.allowsAll(v250), isTrue); 193 | expect(range.allowsAll(v300), isFalse); 194 | }); 195 | 196 | test('with no min', () { 197 | var range = VersionRange(max: v250); 198 | expect(range.allowsAll(VersionRange(min: v080, max: v140)), isTrue); 199 | expect(range.allowsAll(VersionRange(min: v080, max: v300)), isFalse); 200 | expect(range.allowsAll(VersionRange(max: v140)), isTrue); 201 | expect(range.allowsAll(VersionRange(max: v300)), isFalse); 202 | expect(range.allowsAll(range), isTrue); 203 | expect(range.allowsAll(VersionConstraint.any), isFalse); 204 | }); 205 | 206 | test('with no max', () { 207 | var range = VersionRange(min: v010); 208 | expect(range.allowsAll(VersionRange(min: v080, max: v140)), isTrue); 209 | expect(range.allowsAll(VersionRange(min: v003, max: v140)), isFalse); 210 | expect(range.allowsAll(VersionRange(min: v080)), isTrue); 211 | expect(range.allowsAll(VersionRange(min: v003)), isFalse); 212 | expect(range.allowsAll(range), isTrue); 213 | expect(range.allowsAll(VersionConstraint.any), isFalse); 214 | }); 215 | 216 | test('with a min and max', () { 217 | var range = VersionRange(min: v010, max: v250); 218 | expect(range.allowsAll(VersionRange(min: v080, max: v140)), isTrue); 219 | expect(range.allowsAll(VersionRange(min: v080, max: v300)), isFalse); 220 | expect(range.allowsAll(VersionRange(min: v003, max: v140)), isFalse); 221 | expect(range.allowsAll(VersionRange(min: v080)), isFalse); 222 | expect(range.allowsAll(VersionRange(max: v140)), isFalse); 223 | expect(range.allowsAll(range), isTrue); 224 | }); 225 | 226 | test("allows a bordering range that's not more inclusive", () { 227 | var exclusive = VersionRange(min: v010, max: v250); 228 | var inclusive = VersionRange( 229 | min: v010, includeMin: true, max: v250, includeMax: true); 230 | expect(inclusive.allowsAll(exclusive), isTrue); 231 | expect(inclusive.allowsAll(inclusive), isTrue); 232 | expect(exclusive.allowsAll(inclusive), isFalse); 233 | expect(exclusive.allowsAll(exclusive), isTrue); 234 | }); 235 | 236 | test('allows unions that are completely contained', () { 237 | var range = VersionRange(min: v114, max: v200); 238 | expect(range.allowsAll(VersionRange(min: v123, max: v124).union(v140)), 239 | isTrue); 240 | expect(range.allowsAll(VersionRange(min: v010, max: v124).union(v140)), 241 | isFalse); 242 | expect(range.allowsAll(VersionRange(min: v123, max: v234).union(v140)), 243 | isFalse); 244 | }); 245 | 246 | group('pre-release versions', () { 247 | test('of inclusive min are excluded', () { 248 | var range = VersionRange(min: v123, includeMin: true); 249 | 250 | expect(range.allowsAll(VersionConstraint.parse('>1.2.4-dev')), isTrue); 251 | expect(range.allowsAll(VersionConstraint.parse('>1.2.3-dev')), isFalse); 252 | }); 253 | 254 | test('of non-pre-release max are excluded', () { 255 | var range = VersionRange(max: v234); 256 | 257 | expect(range.allowsAll(VersionConstraint.parse('<2.3.3')), isTrue); 258 | expect(range.allowsAll(VersionConstraint.parse('<2.3.4-dev')), isFalse); 259 | }); 260 | 261 | test('of non-pre-release max are included with includeMaxPreRelease', () { 262 | expect( 263 | includeMaxPreReleaseRange 264 | .allowsAll(VersionConstraint.parse('<2.0.0-dev')), 265 | isTrue); 266 | }); 267 | 268 | test( 269 | 'of non-pre-release max are included if min is a pre-release of the ' 270 | 'same version', () { 271 | var range = VersionRange(min: Version.parse('2.3.4-dev.0'), max: v234); 272 | 273 | expect( 274 | range.allowsAll( 275 | VersionConstraint.parse('>2.3.4-dev.0 <2.3.4-dev.1')), 276 | isTrue); 277 | }); 278 | 279 | test('of pre-release max are included', () { 280 | var range = VersionRange(max: Version.parse('2.3.4-dev.2')); 281 | 282 | expect( 283 | range.allowsAll(VersionConstraint.parse('<2.3.4-dev.1')), isTrue); 284 | expect( 285 | range.allowsAll(VersionConstraint.parse('<2.3.4-dev.2')), isTrue); 286 | expect( 287 | range.allowsAll(VersionConstraint.parse('<=2.3.4-dev.2')), isFalse); 288 | expect( 289 | range.allowsAll(VersionConstraint.parse('<2.3.4-dev.3')), isFalse); 290 | }); 291 | }); 292 | }); 293 | 294 | group('allowsAny()', () { 295 | test('disallows an empty constraint', () { 296 | expect( 297 | VersionRange(min: v123, max: v250).allowsAny(VersionConstraint.empty), 298 | isFalse); 299 | }); 300 | 301 | test('allows allowed versions', () { 302 | var range = VersionRange(min: v123, max: v250, includeMax: true); 303 | expect(range.allowsAny(v123), isFalse); 304 | expect(range.allowsAny(v124), isTrue); 305 | expect(range.allowsAny(v250), isTrue); 306 | expect(range.allowsAny(v300), isFalse); 307 | }); 308 | 309 | test('with no min', () { 310 | var range = VersionRange(max: v200); 311 | expect(range.allowsAny(VersionRange(min: v140, max: v300)), isTrue); 312 | expect(range.allowsAny(VersionRange(min: v234, max: v300)), isFalse); 313 | expect(range.allowsAny(VersionRange(min: v140)), isTrue); 314 | expect(range.allowsAny(VersionRange(min: v234)), isFalse); 315 | expect(range.allowsAny(range), isTrue); 316 | }); 317 | 318 | test('with no max', () { 319 | var range = VersionRange(min: v072); 320 | expect(range.allowsAny(VersionRange(min: v003, max: v140)), isTrue); 321 | expect(range.allowsAny(VersionRange(min: v003, max: v010)), isFalse); 322 | expect(range.allowsAny(VersionRange(max: v080)), isTrue); 323 | expect(range.allowsAny(VersionRange(max: v003)), isFalse); 324 | expect(range.allowsAny(range), isTrue); 325 | }); 326 | 327 | test('with a min and max', () { 328 | var range = VersionRange(min: v072, max: v200); 329 | expect(range.allowsAny(VersionRange(min: v003, max: v140)), isTrue); 330 | expect(range.allowsAny(VersionRange(min: v140, max: v300)), isTrue); 331 | expect(range.allowsAny(VersionRange(min: v003, max: v010)), isFalse); 332 | expect(range.allowsAny(VersionRange(min: v234, max: v300)), isFalse); 333 | expect(range.allowsAny(VersionRange(max: v010)), isFalse); 334 | expect(range.allowsAny(VersionRange(min: v234)), isFalse); 335 | expect(range.allowsAny(range), isTrue); 336 | }); 337 | 338 | test('allows a bordering range when both are inclusive', () { 339 | expect( 340 | VersionRange(max: v250).allowsAny(VersionRange(min: v250)), isFalse); 341 | 342 | expect( 343 | VersionRange(max: v250, includeMax: true) 344 | .allowsAny(VersionRange(min: v250)), 345 | isFalse); 346 | 347 | expect( 348 | VersionRange(max: v250) 349 | .allowsAny(VersionRange(min: v250, includeMin: true)), 350 | isFalse); 351 | 352 | expect( 353 | VersionRange(max: v250, includeMax: true) 354 | .allowsAny(VersionRange(min: v250, includeMin: true)), 355 | isTrue); 356 | 357 | expect( 358 | VersionRange(min: v250).allowsAny(VersionRange(max: v250)), isFalse); 359 | 360 | expect( 361 | VersionRange(min: v250, includeMin: true) 362 | .allowsAny(VersionRange(max: v250)), 363 | isFalse); 364 | 365 | expect( 366 | VersionRange(min: v250) 367 | .allowsAny(VersionRange(max: v250, includeMax: true)), 368 | isFalse); 369 | 370 | expect( 371 | VersionRange(min: v250, includeMin: true) 372 | .allowsAny(VersionRange(max: v250, includeMax: true)), 373 | isTrue); 374 | }); 375 | 376 | test('allows unions that are partially contained', () { 377 | var range = VersionRange(min: v114, max: v200); 378 | expect(range.allowsAny(VersionRange(min: v010, max: v080).union(v140)), 379 | isTrue); 380 | expect(range.allowsAny(VersionRange(min: v123, max: v234).union(v300)), 381 | isTrue); 382 | expect(range.allowsAny(VersionRange(min: v234, max: v300).union(v010)), 383 | isFalse); 384 | }); 385 | 386 | group('pre-release versions', () { 387 | test('of inclusive min are excluded', () { 388 | var range = VersionRange(min: v123, includeMin: true); 389 | 390 | expect(range.allowsAny(VersionConstraint.parse('<1.2.4-dev')), isTrue); 391 | expect(range.allowsAny(VersionConstraint.parse('<1.2.3-dev')), isFalse); 392 | }); 393 | 394 | test('of non-pre-release max are excluded', () { 395 | var range = VersionRange(max: v234); 396 | 397 | expect(range.allowsAny(VersionConstraint.parse('>2.3.3')), isTrue); 398 | expect(range.allowsAny(VersionConstraint.parse('>2.3.4-dev')), isFalse); 399 | }); 400 | 401 | test('of non-pre-release max are included with includeMaxPreRelease', () { 402 | expect( 403 | includeMaxPreReleaseRange 404 | .allowsAny(VersionConstraint.parse('>2.0.0-dev')), 405 | isTrue); 406 | }); 407 | 408 | test( 409 | 'of non-pre-release max are included if min is a pre-release of the ' 410 | 'same version', () { 411 | var range = VersionRange(min: Version.parse('2.3.4-dev.0'), max: v234); 412 | 413 | expect( 414 | range.allowsAny(VersionConstraint.parse('>2.3.4-dev.1')), isTrue); 415 | expect(range.allowsAny(VersionConstraint.parse('>2.3.4')), isFalse); 416 | 417 | expect( 418 | range.allowsAny(VersionConstraint.parse('<2.3.4-dev.1')), isTrue); 419 | expect(range.allowsAny(VersionConstraint.parse('<2.3.4-dev')), isFalse); 420 | }); 421 | 422 | test('of pre-release max are included', () { 423 | var range = VersionConstraint.parse('<2.3.4-dev.2'); 424 | 425 | expect( 426 | range.allowsAny(VersionConstraint.parse('>2.3.4-dev.1')), isTrue); 427 | expect( 428 | range.allowsAny(VersionConstraint.parse('>2.3.4-dev.2')), isFalse); 429 | expect( 430 | range.allowsAny(VersionConstraint.parse('>2.3.4-dev.3')), isFalse); 431 | }); 432 | }); 433 | }); 434 | 435 | group('intersect()', () { 436 | test('two overlapping ranges', () { 437 | expect( 438 | VersionRange(min: v123, max: v250) 439 | .intersect(VersionRange(min: v200, max: v300)), 440 | equals(VersionRange(min: v200, max: v250))); 441 | }); 442 | 443 | test('a non-overlapping range allows no versions', () { 444 | var a = VersionRange(min: v114, max: v124); 445 | var b = VersionRange(min: v200, max: v250); 446 | expect(a.intersect(b).isEmpty, isTrue); 447 | }); 448 | 449 | test('adjacent ranges allow no versions if exclusive', () { 450 | var a = VersionRange(min: v114, max: v124); 451 | var b = VersionRange(min: v124, max: v200); 452 | expect(a.intersect(b).isEmpty, isTrue); 453 | }); 454 | 455 | test('adjacent ranges allow version if inclusive', () { 456 | var a = VersionRange(min: v114, max: v124, includeMax: true); 457 | var b = VersionRange(min: v124, max: v200, includeMin: true); 458 | expect(a.intersect(b), equals(v124)); 459 | }); 460 | 461 | test('with an open range', () { 462 | var open = VersionRange(); 463 | var a = VersionRange(min: v114, max: v124); 464 | expect(open.intersect(open), equals(open)); 465 | expect(a.intersect(open), equals(a)); 466 | }); 467 | 468 | test('returns the version if the range allows it', () { 469 | expect(VersionRange(min: v114, max: v124).intersect(v123), equals(v123)); 470 | expect( 471 | VersionRange(min: v123, max: v124).intersect(v114).isEmpty, isTrue); 472 | }); 473 | 474 | test('with a range with a pre-release min, returns an empty constraint', 475 | () { 476 | expect( 477 | VersionRange(max: v200) 478 | .intersect(VersionConstraint.parse('>=2.0.0-dev')), 479 | equals(VersionConstraint.empty)); 480 | }); 481 | 482 | test('with a range with a pre-release max, returns the original', () { 483 | expect( 484 | VersionRange(max: v200) 485 | .intersect(VersionConstraint.parse('<2.0.0-dev')), 486 | equals(VersionRange(max: v200))); 487 | }); 488 | 489 | group('with includeMaxPreRelease', () { 490 | test('preserves includeMaxPreRelease if the max version is included', () { 491 | expect( 492 | includeMaxPreReleaseRange 493 | .intersect(VersionConstraint.parse('<1.0.0')), 494 | equals(VersionConstraint.parse('<1.0.0'))); 495 | expect( 496 | includeMaxPreReleaseRange 497 | .intersect(VersionConstraint.parse('<2.0.0')), 498 | equals(VersionConstraint.parse('<2.0.0'))); 499 | expect(includeMaxPreReleaseRange.intersect(includeMaxPreReleaseRange), 500 | equals(includeMaxPreReleaseRange)); 501 | expect( 502 | includeMaxPreReleaseRange 503 | .intersect(VersionConstraint.parse('<3.0.0')), 504 | equals(includeMaxPreReleaseRange)); 505 | expect( 506 | includeMaxPreReleaseRange 507 | .intersect(VersionConstraint.parse('>1.1.4')), 508 | equals(VersionRange( 509 | min: v114, max: v200, alwaysIncludeMaxPreRelease: true))); 510 | }); 511 | 512 | test( 513 | 'and a range with a pre-release min, returns ' 514 | 'an intersection', () { 515 | expect( 516 | includeMaxPreReleaseRange 517 | .intersect(VersionConstraint.parse('>=2.0.0-dev')), 518 | equals(VersionConstraint.parse('>=2.0.0-dev <2.0.0'))); 519 | }); 520 | 521 | test( 522 | 'and a range with a pre-release max, returns ' 523 | 'the narrower constraint', () { 524 | expect( 525 | includeMaxPreReleaseRange 526 | .intersect(VersionConstraint.parse('<2.0.0-dev')), 527 | equals(VersionConstraint.parse('<2.0.0-dev'))); 528 | }); 529 | }); 530 | }); 531 | 532 | group('union()', () { 533 | test('with a version returns the range if it contains the version', () { 534 | var range = VersionRange(min: v114, max: v124); 535 | expect(range.union(v123), equals(range)); 536 | }); 537 | 538 | test('with a version on the edge of the range, expands the range', () { 539 | expect( 540 | VersionRange(min: v114, max: v124, alwaysIncludeMaxPreRelease: true) 541 | .union(v124), 542 | equals(VersionRange(min: v114, max: v124, includeMax: true))); 543 | expect(VersionRange(min: v114, max: v124).union(v114), 544 | equals(VersionRange(min: v114, max: v124, includeMin: true))); 545 | }); 546 | 547 | test( 548 | 'with a version allows both the range and the version if the range ' 549 | "doesn't contain the version", () { 550 | var result = VersionRange(min: v003, max: v114).union(v124); 551 | expect(result, allows(v010)); 552 | expect(result, doesNotAllow(v123)); 553 | expect(result, allows(v124)); 554 | }); 555 | 556 | test('returns a VersionUnion for a disjoint range', () { 557 | var result = VersionRange(min: v003, max: v114) 558 | .union(VersionRange(min: v130, max: v200)); 559 | expect(result, allows(v080)); 560 | expect(result, doesNotAllow(v123)); 561 | expect(result, allows(v140)); 562 | }); 563 | 564 | test('returns a VersionUnion for a disjoint range with infinite end', () { 565 | void isVersionUnion(VersionConstraint constraint) { 566 | expect(constraint, allows(v080)); 567 | expect(constraint, doesNotAllow(v123)); 568 | expect(constraint, allows(v140)); 569 | } 570 | 571 | for (final includeAMin in [true, false]) { 572 | for (final includeAMax in [true, false]) { 573 | for (final includeBMin in [true, false]) { 574 | for (final includeBMax in [true, false]) { 575 | final a = VersionRange( 576 | min: v130, includeMin: includeAMin, includeMax: includeAMax); 577 | final b = VersionRange( 578 | max: v114, includeMin: includeBMin, includeMax: includeBMax); 579 | isVersionUnion(a.union(b)); 580 | isVersionUnion(b.union(a)); 581 | } 582 | } 583 | } 584 | } 585 | }); 586 | 587 | test('considers open ranges disjoint', () { 588 | var result = VersionRange(min: v003, max: v114) 589 | .union(VersionRange(min: v114, max: v200)); 590 | expect(result, allows(v080)); 591 | expect(result, doesNotAllow(v114)); 592 | expect(result, allows(v140)); 593 | 594 | result = VersionRange(min: v114, max: v200) 595 | .union(VersionRange(min: v003, max: v114)); 596 | expect(result, allows(v080)); 597 | expect(result, doesNotAllow(v114)); 598 | expect(result, allows(v140)); 599 | }); 600 | 601 | test('returns a merged range for an overlapping range', () { 602 | var result = VersionRange(min: v003, max: v114) 603 | .union(VersionRange(min: v080, max: v200)); 604 | expect(result, equals(VersionRange(min: v003, max: v200))); 605 | }); 606 | 607 | test('considers closed ranges overlapping', () { 608 | var result = VersionRange(min: v003, max: v114, includeMax: true) 609 | .union(VersionRange(min: v114, max: v200)); 610 | expect(result, equals(VersionRange(min: v003, max: v200))); 611 | 612 | result = 613 | VersionRange(min: v003, max: v114, alwaysIncludeMaxPreRelease: true) 614 | .union(VersionRange(min: v114, max: v200, includeMin: true)); 615 | expect(result, equals(VersionRange(min: v003, max: v200))); 616 | 617 | result = VersionRange(min: v114, max: v200) 618 | .union(VersionRange(min: v003, max: v114, includeMax: true)); 619 | expect(result, equals(VersionRange(min: v003, max: v200))); 620 | 621 | result = VersionRange(min: v114, max: v200, includeMin: true).union( 622 | VersionRange(min: v003, max: v114, alwaysIncludeMaxPreRelease: true)); 623 | expect(result, equals(VersionRange(min: v003, max: v200))); 624 | }); 625 | 626 | test('includes edges if either range does', () { 627 | var result = VersionRange(min: v003, max: v114, includeMin: true) 628 | .union(VersionRange(min: v003, max: v114, includeMax: true)); 629 | expect( 630 | result, 631 | equals(VersionRange( 632 | min: v003, max: v114, includeMin: true, includeMax: true))); 633 | }); 634 | 635 | test('with a range with a pre-release min, returns a constraint with a gap', 636 | () { 637 | var result = 638 | VersionRange(max: v200).union(VersionConstraint.parse('>=2.0.0-dev')); 639 | expect(result, allows(v140)); 640 | expect(result, doesNotAllow(Version.parse('2.0.0-alpha'))); 641 | expect(result, allows(Version.parse('2.0.0-dev'))); 642 | expect(result, allows(Version.parse('2.0.0-dev.1'))); 643 | expect(result, allows(Version.parse('2.0.0'))); 644 | }); 645 | 646 | test('with a range with a pre-release max, returns the larger constraint', 647 | () { 648 | expect( 649 | VersionRange(max: v200).union(VersionConstraint.parse('<2.0.0-dev')), 650 | equals(VersionConstraint.parse('<2.0.0-dev'))); 651 | }); 652 | 653 | group('with includeMaxPreRelease', () { 654 | test('adds includeMaxPreRelease if the max version is included', () { 655 | expect( 656 | includeMaxPreReleaseRange.union(VersionConstraint.parse('<1.0.0')), 657 | equals(includeMaxPreReleaseRange)); 658 | expect(includeMaxPreReleaseRange.union(includeMaxPreReleaseRange), 659 | equals(includeMaxPreReleaseRange)); 660 | expect( 661 | includeMaxPreReleaseRange.union(VersionConstraint.parse('<2.0.0')), 662 | equals(includeMaxPreReleaseRange)); 663 | expect( 664 | includeMaxPreReleaseRange.union(VersionConstraint.parse('<3.0.0')), 665 | equals(VersionConstraint.parse('<3.0.0'))); 666 | }); 667 | 668 | test('and a range with a pre-release min, returns any', () { 669 | expect( 670 | includeMaxPreReleaseRange 671 | .union(VersionConstraint.parse('>=2.0.0-dev')), 672 | equals(VersionConstraint.any)); 673 | }); 674 | 675 | test('and a range with a pre-release max, returns the original', () { 676 | expect( 677 | includeMaxPreReleaseRange 678 | .union(VersionConstraint.parse('<2.0.0-dev')), 679 | equals(includeMaxPreReleaseRange)); 680 | }); 681 | }); 682 | }); 683 | 684 | group('difference()', () { 685 | test('with an empty range returns the original range', () { 686 | expect( 687 | VersionRange(min: v003, max: v114) 688 | .difference(VersionConstraint.empty), 689 | equals(VersionRange(min: v003, max: v114))); 690 | }); 691 | 692 | test('with a version outside the range returns the original range', () { 693 | expect(VersionRange(min: v003, max: v114).difference(v200), 694 | equals(VersionRange(min: v003, max: v114))); 695 | }); 696 | 697 | test('with a version in the range splits the range', () { 698 | expect( 699 | VersionRange(min: v003, max: v114).difference(v072), 700 | equals(VersionConstraint.unionOf([ 701 | VersionRange( 702 | min: v003, max: v072, alwaysIncludeMaxPreRelease: true), 703 | VersionRange(min: v072, max: v114) 704 | ]))); 705 | }); 706 | 707 | test('with the max version makes the max exclusive', () { 708 | expect( 709 | VersionRange(min: v003, max: v114, includeMax: true).difference(v114), 710 | equals(VersionRange( 711 | min: v003, max: v114, alwaysIncludeMaxPreRelease: true))); 712 | }); 713 | 714 | test('with the min version makes the min exclusive', () { 715 | expect( 716 | VersionRange(min: v003, max: v114, includeMin: true).difference(v003), 717 | equals(VersionRange(min: v003, max: v114))); 718 | }); 719 | 720 | test('with a disjoint range returns the original', () { 721 | expect( 722 | VersionRange(min: v003, max: v114) 723 | .difference(VersionRange(min: v123, max: v140)), 724 | equals(VersionRange(min: v003, max: v114))); 725 | }); 726 | 727 | test('with an adjacent range returns the original', () { 728 | expect( 729 | VersionRange(min: v003, max: v114, includeMax: true) 730 | .difference(VersionRange(min: v114, max: v140)), 731 | equals(VersionRange(min: v003, max: v114, includeMax: true))); 732 | }); 733 | 734 | test('with a range at the beginning cuts off the beginning of the range', 735 | () { 736 | expect( 737 | VersionRange(min: v080, max: v130) 738 | .difference(VersionRange(min: v010, max: v114)), 739 | equals(VersionConstraint.parse('>=1.1.4-0 <1.3.0'))); 740 | expect( 741 | VersionRange(min: v080, max: v130) 742 | .difference(VersionRange(max: v114)), 743 | equals(VersionConstraint.parse('>=1.1.4-0 <1.3.0'))); 744 | expect( 745 | VersionRange(min: v080, max: v130) 746 | .difference(VersionRange(min: v010, max: v114, includeMax: true)), 747 | equals(VersionRange(min: v114, max: v130))); 748 | expect( 749 | VersionRange(min: v080, max: v130, includeMin: true) 750 | .difference(VersionRange(min: v010, max: v080, includeMax: true)), 751 | equals(VersionRange(min: v080, max: v130))); 752 | expect( 753 | VersionRange(min: v080, max: v130, includeMax: true) 754 | .difference(VersionRange(min: v080, max: v130)), 755 | equals(VersionConstraint.parse('>=1.3.0-0 <=1.3.0'))); 756 | }); 757 | 758 | test('with a range at the end cuts off the end of the range', () { 759 | expect( 760 | VersionRange(min: v080, max: v130) 761 | .difference(VersionRange(min: v114, max: v140)), 762 | equals(VersionRange(min: v080, max: v114, includeMax: true))); 763 | expect( 764 | VersionRange(min: v080, max: v130) 765 | .difference(VersionRange(min: v114)), 766 | equals(VersionRange(min: v080, max: v114, includeMax: true))); 767 | expect( 768 | VersionRange(min: v080, max: v130) 769 | .difference(VersionRange(min: v114, max: v140, includeMin: true)), 770 | equals(VersionRange( 771 | min: v080, max: v114, alwaysIncludeMaxPreRelease: true))); 772 | expect( 773 | VersionRange(min: v080, max: v130, includeMax: true) 774 | .difference(VersionRange(min: v130, max: v140, includeMin: true)), 775 | equals(VersionRange( 776 | min: v080, max: v130, alwaysIncludeMaxPreRelease: true))); 777 | expect( 778 | VersionRange(min: v080, max: v130, includeMin: true) 779 | .difference(VersionRange(min: v080, max: v130)), 780 | equals(v080)); 781 | }); 782 | 783 | test('with a range in the middle cuts the range in half', () { 784 | expect( 785 | VersionRange(min: v003, max: v130) 786 | .difference(VersionRange(min: v072, max: v114)), 787 | equals(VersionConstraint.unionOf([ 788 | VersionRange(min: v003, max: v072, includeMax: true), 789 | VersionConstraint.parse('>=1.1.4-0 <1.3.0') 790 | ]))); 791 | }); 792 | 793 | test('with a totally covering range returns empty', () { 794 | expect( 795 | VersionRange(min: v114, max: v200) 796 | .difference(VersionRange(min: v072, max: v300)), 797 | isEmpty); 798 | expect( 799 | VersionRange(min: v003, max: v114) 800 | .difference(VersionRange(min: v003, max: v114)), 801 | isEmpty); 802 | expect( 803 | VersionRange(min: v003, max: v114, includeMin: true, includeMax: true) 804 | .difference(VersionRange( 805 | min: v003, max: v114, includeMin: true, includeMax: true)), 806 | isEmpty); 807 | }); 808 | 809 | test( 810 | "with a version union that doesn't cover the range, returns the " 811 | 'original', () { 812 | expect( 813 | VersionRange(min: v114, max: v140) 814 | .difference(VersionConstraint.unionOf([v010, v200])), 815 | equals(VersionRange(min: v114, max: v140))); 816 | }); 817 | 818 | test('with a version union that intersects the ends, chops them off', () { 819 | expect( 820 | VersionRange(min: v114, max: v140).difference( 821 | VersionConstraint.unionOf([ 822 | VersionRange(min: v080, max: v123), 823 | VersionRange(min: v130, max: v200) 824 | ])), 825 | equals(VersionConstraint.parse('>=1.2.3-0 <=1.3.0'))); 826 | }); 827 | 828 | test('with a version union that intersects the middle, chops it up', () { 829 | expect( 830 | VersionRange(min: v114, max: v140) 831 | .difference(VersionConstraint.unionOf([v123, v124, v130])), 832 | equals(VersionConstraint.unionOf([ 833 | VersionRange( 834 | min: v114, max: v123, alwaysIncludeMaxPreRelease: true), 835 | VersionRange( 836 | min: v123, max: v124, alwaysIncludeMaxPreRelease: true), 837 | VersionRange( 838 | min: v124, max: v130, alwaysIncludeMaxPreRelease: true), 839 | VersionRange(min: v130, max: v140) 840 | ]))); 841 | }); 842 | 843 | test('with a version union that covers the whole range, returns empty', () { 844 | expect( 845 | VersionRange(min: v114, max: v140).difference( 846 | VersionConstraint.unionOf([v003, VersionRange(min: v010)])), 847 | equals(VersionConstraint.empty)); 848 | }); 849 | 850 | test('with a range with a pre-release min, returns the original', () { 851 | expect( 852 | VersionRange(max: v200) 853 | .difference(VersionConstraint.parse('>=2.0.0-dev')), 854 | equals(VersionRange(max: v200))); 855 | }); 856 | 857 | test('with a range with a pre-release max, returns null', () { 858 | expect( 859 | VersionRange(max: v200) 860 | .difference(VersionConstraint.parse('<2.0.0-dev')), 861 | equals(VersionConstraint.empty)); 862 | }); 863 | 864 | group('with includeMaxPreRelease', () { 865 | group('for the minuend', () { 866 | test('preserves includeMaxPreRelease if the max version is included', 867 | () { 868 | expect( 869 | includeMaxPreReleaseRange 870 | .difference(VersionConstraint.parse('<1.0.0')), 871 | equals(VersionRange( 872 | min: Version.parse('1.0.0-0'), 873 | max: v200, 874 | includeMin: true, 875 | alwaysIncludeMaxPreRelease: true))); 876 | expect( 877 | includeMaxPreReleaseRange 878 | .difference(VersionConstraint.parse('<2.0.0')), 879 | equals(VersionRange( 880 | min: v200.firstPreRelease, 881 | max: v200, 882 | includeMin: true, 883 | alwaysIncludeMaxPreRelease: true))); 884 | expect( 885 | includeMaxPreReleaseRange.difference(includeMaxPreReleaseRange), 886 | equals(VersionConstraint.empty)); 887 | expect( 888 | includeMaxPreReleaseRange 889 | .difference(VersionConstraint.parse('<3.0.0')), 890 | equals(VersionConstraint.empty)); 891 | }); 892 | 893 | test('with a range with a pre-release min, adjusts the max', () { 894 | expect( 895 | includeMaxPreReleaseRange 896 | .difference(VersionConstraint.parse('>=2.0.0-dev')), 897 | equals(VersionConstraint.parse('<2.0.0-dev'))); 898 | }); 899 | 900 | test('with a range with a pre-release max, adjusts the min', () { 901 | expect( 902 | includeMaxPreReleaseRange 903 | .difference(VersionConstraint.parse('<2.0.0-dev')), 904 | equals(VersionConstraint.parse('>=2.0.0-dev <2.0.0'))); 905 | }); 906 | }); 907 | 908 | group('for the subtrahend', () { 909 | group("doesn't create a pre-release minimum", () { 910 | test('when cutting off the bottom', () { 911 | expect( 912 | VersionConstraint.parse('<3.0.0') 913 | .difference(includeMaxPreReleaseRange), 914 | equals(VersionRange(min: v200, max: v300, includeMin: true))); 915 | }); 916 | 917 | test('with splitting down the middle', () { 918 | expect( 919 | VersionConstraint.parse('<4.0.0').difference(VersionRange( 920 | min: v200, 921 | max: v300, 922 | includeMin: true, 923 | alwaysIncludeMaxPreRelease: true)), 924 | equals(VersionConstraint.unionOf([ 925 | VersionRange(max: v200, alwaysIncludeMaxPreRelease: true), 926 | VersionConstraint.parse('>=3.0.0 <4.0.0') 927 | ]))); 928 | }); 929 | 930 | test('can leave a single version', () { 931 | expect( 932 | VersionConstraint.parse('<=2.0.0') 933 | .difference(includeMaxPreReleaseRange), 934 | equals(v200)); 935 | }); 936 | }); 937 | }); 938 | }); 939 | }); 940 | 941 | test('isEmpty', () { 942 | expect(VersionRange().isEmpty, isFalse); 943 | expect(VersionRange(min: v123, max: v124).isEmpty, isFalse); 944 | }); 945 | 946 | group('compareTo()', () { 947 | test('orders by minimum first', () { 948 | _expectComparesSmaller(VersionRange(min: v003, max: v080), 949 | VersionRange(min: v010, max: v072)); 950 | _expectComparesSmaller(VersionRange(min: v003, max: v080), 951 | VersionRange(min: v010, max: v080)); 952 | _expectComparesSmaller(VersionRange(min: v003, max: v080), 953 | VersionRange(min: v010, max: v114)); 954 | }); 955 | 956 | test('orders by maximum second', () { 957 | _expectComparesSmaller(VersionRange(min: v003, max: v010), 958 | VersionRange(min: v003, max: v072)); 959 | }); 960 | 961 | test('includeMin comes before !includeMin', () { 962 | _expectComparesSmaller( 963 | VersionRange(min: v003, max: v080, includeMin: true), 964 | VersionRange(min: v003, max: v080)); 965 | }); 966 | 967 | test('includeMax comes after !includeMax', () { 968 | _expectComparesSmaller(VersionRange(min: v003, max: v080), 969 | VersionRange(min: v003, max: v080, includeMax: true)); 970 | }); 971 | 972 | test('includeMaxPreRelease comes after !includeMaxPreRelease', () { 973 | _expectComparesSmaller( 974 | VersionRange(max: v200), includeMaxPreReleaseRange); 975 | }); 976 | 977 | test('no minimum comes before small minimum', () { 978 | _expectComparesSmaller( 979 | VersionRange(max: v010), VersionRange(min: v003, max: v010)); 980 | _expectComparesSmaller(VersionRange(max: v010, includeMin: true), 981 | VersionRange(min: v003, max: v010)); 982 | }); 983 | 984 | test('no maximium comes after large maximum', () { 985 | _expectComparesSmaller( 986 | VersionRange(min: v003, max: v300), VersionRange(min: v003)); 987 | _expectComparesSmaller(VersionRange(min: v003, max: v300), 988 | VersionRange(min: v003, includeMax: true)); 989 | }); 990 | }); 991 | } 992 | 993 | void _expectComparesSmaller(VersionRange smaller, VersionRange larger) { 994 | expect(smaller.compareTo(larger), lessThan(0), 995 | reason: 'expected $smaller to sort below $larger'); 996 | expect(larger.compareTo(smaller), greaterThan(0), 997 | reason: 'expected $larger to sort above $smaller'); 998 | } 999 | -------------------------------------------------------------------------------- /test/version_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:pub_semver/pub_semver.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | void main() { 11 | test('none', () { 12 | expect(Version.none.toString(), equals('0.0.0')); 13 | }); 14 | 15 | test('prioritize()', () { 16 | // A correctly sorted list of versions in order of increasing priority. 17 | var versions = [ 18 | '1.0.0-alpha', 19 | '2.0.0-alpha', 20 | '1.0.0', 21 | '1.0.0+build', 22 | '1.0.1', 23 | '1.1.0', 24 | '2.0.0' 25 | ]; 26 | 27 | // Ensure that every pair of versions is prioritized in the order that it 28 | // appears in the list. 29 | for (var i = 0; i < versions.length; i++) { 30 | for (var j = 0; j < versions.length; j++) { 31 | var a = Version.parse(versions[i]); 32 | var b = Version.parse(versions[j]); 33 | expect(Version.prioritize(a, b), equals(i.compareTo(j))); 34 | } 35 | } 36 | }); 37 | 38 | test('antiprioritize()', () { 39 | // A correctly sorted list of versions in order of increasing antipriority. 40 | var versions = [ 41 | '2.0.0-alpha', 42 | '1.0.0-alpha', 43 | '2.0.0', 44 | '1.1.0', 45 | '1.0.1', 46 | '1.0.0+build', 47 | '1.0.0' 48 | ]; 49 | 50 | // Ensure that every pair of versions is prioritized in the order that it 51 | // appears in the list. 52 | for (var i = 0; i < versions.length; i++) { 53 | for (var j = 0; j < versions.length; j++) { 54 | var a = Version.parse(versions[i]); 55 | var b = Version.parse(versions[j]); 56 | expect(Version.antiprioritize(a, b), equals(i.compareTo(j))); 57 | } 58 | } 59 | }); 60 | 61 | group('constructor', () { 62 | test('throws on negative numbers', () { 63 | expect(() => Version(-1, 1, 1), throwsArgumentError); 64 | expect(() => Version(1, -1, 1), throwsArgumentError); 65 | expect(() => Version(1, 1, -1), throwsArgumentError); 66 | }); 67 | }); 68 | 69 | group('comparison', () { 70 | // A correctly sorted list of versions. 71 | var versions = [ 72 | '1.0.0-alpha', 73 | '1.0.0-alpha.1', 74 | '1.0.0-beta.2', 75 | '1.0.0-beta.11', 76 | '1.0.0-rc.1', 77 | '1.0.0-rc.1+build.1', 78 | '1.0.0', 79 | '1.0.0+0.3.7', 80 | '1.3.7+build', 81 | '1.3.7+build.2.b8f12d7', 82 | '1.3.7+build.11.e0f985a', 83 | '2.0.0', 84 | '2.1.0', 85 | '2.2.0', 86 | '2.11.0', 87 | '2.11.1' 88 | ]; 89 | 90 | test('compareTo()', () { 91 | // Ensure that every pair of versions compares in the order that it 92 | // appears in the list. 93 | for (var i = 0; i < versions.length; i++) { 94 | for (var j = 0; j < versions.length; j++) { 95 | var a = Version.parse(versions[i]); 96 | var b = Version.parse(versions[j]); 97 | expect(a.compareTo(b), equals(i.compareTo(j))); 98 | } 99 | } 100 | }); 101 | 102 | test('operators', () { 103 | for (var i = 0; i < versions.length; i++) { 104 | for (var j = 0; j < versions.length; j++) { 105 | var a = Version.parse(versions[i]); 106 | var b = Version.parse(versions[j]); 107 | expect(a < b, equals(i < j)); 108 | expect(a > b, equals(i > j)); 109 | expect(a <= b, equals(i <= j)); 110 | expect(a >= b, equals(i >= j)); 111 | expect(a == b, equals(i == j)); 112 | expect(a != b, equals(i != j)); 113 | } 114 | } 115 | }); 116 | 117 | test('equality', () { 118 | expect(Version.parse('01.2.3'), equals(Version.parse('1.2.3'))); 119 | expect(Version.parse('1.02.3'), equals(Version.parse('1.2.3'))); 120 | expect(Version.parse('1.2.03'), equals(Version.parse('1.2.3'))); 121 | expect(Version.parse('1.2.3-01'), equals(Version.parse('1.2.3-1'))); 122 | expect(Version.parse('1.2.3+01'), equals(Version.parse('1.2.3+1'))); 123 | }); 124 | }); 125 | 126 | test('allows()', () { 127 | expect(v123, allows(v123)); 128 | expect( 129 | v123, 130 | doesNotAllow( 131 | Version.parse('2.2.3'), 132 | Version.parse('1.3.3'), 133 | Version.parse('1.2.4'), 134 | Version.parse('1.2.3-dev'), 135 | Version.parse('1.2.3+build'))); 136 | }); 137 | 138 | test('allowsAll()', () { 139 | expect(v123.allowsAll(v123), isTrue); 140 | expect(v123.allowsAll(v003), isFalse); 141 | expect(v123.allowsAll(VersionRange(min: v114, max: v124)), isFalse); 142 | expect(v123.allowsAll(VersionConstraint.any), isFalse); 143 | expect(v123.allowsAll(VersionConstraint.empty), isTrue); 144 | }); 145 | 146 | test('allowsAny()', () { 147 | expect(v123.allowsAny(v123), isTrue); 148 | expect(v123.allowsAny(v003), isFalse); 149 | expect(v123.allowsAny(VersionRange(min: v114, max: v124)), isTrue); 150 | expect(v123.allowsAny(VersionConstraint.any), isTrue); 151 | expect(v123.allowsAny(VersionConstraint.empty), isFalse); 152 | }); 153 | 154 | test('intersect()', () { 155 | // Intersecting the same version returns the version. 156 | expect(v123.intersect(v123), equals(v123)); 157 | 158 | // Intersecting a different version allows no versions. 159 | expect(v123.intersect(v114).isEmpty, isTrue); 160 | 161 | // Intersecting a range returns the version if the range allows it. 162 | expect(v123.intersect(VersionRange(min: v114, max: v124)), equals(v123)); 163 | 164 | // Intersecting a range allows no versions if the range doesn't allow it. 165 | expect(v114.intersect(VersionRange(min: v123, max: v124)).isEmpty, isTrue); 166 | }); 167 | 168 | group('union()', () { 169 | test('with the same version returns the version', () { 170 | expect(v123.union(v123), equals(v123)); 171 | }); 172 | 173 | test('with a different version returns a version that matches both', () { 174 | var result = v123.union(v080); 175 | expect(result, allows(v123)); 176 | expect(result, allows(v080)); 177 | 178 | // Nothing in between should match. 179 | expect(result, doesNotAllow(v114)); 180 | }); 181 | 182 | test('with a range returns the range if it contains the version', () { 183 | var range = VersionRange(min: v114, max: v124); 184 | expect(v123.union(range), equals(range)); 185 | }); 186 | 187 | test('with a range with the version on the edge, expands the range', () { 188 | expect( 189 | v124.union(VersionRange( 190 | min: v114, max: v124, alwaysIncludeMaxPreRelease: true)), 191 | equals(VersionRange(min: v114, max: v124, includeMax: true))); 192 | expect( 193 | v124.firstPreRelease.union(VersionRange(min: v114, max: v124)), 194 | equals(VersionRange( 195 | min: v114, max: v124.firstPreRelease, includeMax: true))); 196 | expect(v114.union(VersionRange(min: v114, max: v124)), 197 | equals(VersionRange(min: v114, max: v124, includeMin: true))); 198 | }); 199 | 200 | test( 201 | 'with a range allows both the range and the version if the range ' 202 | "doesn't contain the version", () { 203 | var result = v123.union(VersionRange(min: v003, max: v114)); 204 | expect(result, allows(v123)); 205 | expect(result, allows(v010)); 206 | }); 207 | }); 208 | 209 | group('difference()', () { 210 | test('with the same version returns an empty constraint', () { 211 | expect(v123.difference(v123), isEmpty); 212 | }); 213 | 214 | test('with a different version returns the original version', () { 215 | expect(v123.difference(v080), equals(v123)); 216 | }); 217 | 218 | test('returns an empty constraint with a range that contains the version', 219 | () { 220 | expect(v123.difference(VersionRange(min: v114, max: v124)), isEmpty); 221 | }); 222 | 223 | test("returns the version constraint with a range that doesn't contain it", 224 | () { 225 | expect(v123.difference(VersionRange(min: v140, max: v300)), equals(v123)); 226 | }); 227 | }); 228 | 229 | test('isEmpty', () { 230 | expect(v123.isEmpty, isFalse); 231 | }); 232 | 233 | test('nextMajor', () { 234 | expect(v123.nextMajor, equals(v200)); 235 | expect(v114.nextMajor, equals(v200)); 236 | expect(v200.nextMajor, equals(v300)); 237 | 238 | // Ignores pre-release if not on a major version. 239 | expect(Version.parse('1.2.3-dev').nextMajor, equals(v200)); 240 | 241 | // Just removes it if on a major version. 242 | expect(Version.parse('2.0.0-dev').nextMajor, equals(v200)); 243 | 244 | // Strips build suffix. 245 | expect(Version.parse('1.2.3+patch').nextMajor, equals(v200)); 246 | }); 247 | 248 | test('nextMinor', () { 249 | expect(v123.nextMinor, equals(v130)); 250 | expect(v130.nextMinor, equals(v140)); 251 | 252 | // Ignores pre-release if not on a minor version. 253 | expect(Version.parse('1.2.3-dev').nextMinor, equals(v130)); 254 | 255 | // Just removes it if on a minor version. 256 | expect(Version.parse('1.3.0-dev').nextMinor, equals(v130)); 257 | 258 | // Strips build suffix. 259 | expect(Version.parse('1.2.3+patch').nextMinor, equals(v130)); 260 | }); 261 | 262 | test('nextPatch', () { 263 | expect(v123.nextPatch, equals(v124)); 264 | expect(v200.nextPatch, equals(v201)); 265 | 266 | // Just removes pre-release version if present. 267 | expect(Version.parse('1.2.4-dev').nextPatch, equals(v124)); 268 | 269 | // Strips build suffix. 270 | expect(Version.parse('1.2.3+patch').nextPatch, equals(v124)); 271 | }); 272 | 273 | test('nextBreaking', () { 274 | expect(v123.nextBreaking, equals(v200)); 275 | expect(v072.nextBreaking, equals(v080)); 276 | expect(v003.nextBreaking, equals(v010)); 277 | 278 | // Removes pre-release version if present. 279 | expect(Version.parse('1.2.3-dev').nextBreaking, equals(v200)); 280 | 281 | // Strips build suffix. 282 | expect(Version.parse('1.2.3+patch').nextBreaking, equals(v200)); 283 | }); 284 | 285 | test('parse()', () { 286 | expect(Version.parse('0.0.0'), equals(Version(0, 0, 0))); 287 | expect(Version.parse('12.34.56'), equals(Version(12, 34, 56))); 288 | 289 | expect(Version.parse('1.2.3-alpha.1'), 290 | equals(Version(1, 2, 3, pre: 'alpha.1'))); 291 | expect(Version.parse('1.2.3-x.7.z-92'), 292 | equals(Version(1, 2, 3, pre: 'x.7.z-92'))); 293 | 294 | expect(Version.parse('1.2.3+build.1'), 295 | equals(Version(1, 2, 3, build: 'build.1'))); 296 | expect(Version.parse('1.2.3+x.7.z-92'), 297 | equals(Version(1, 2, 3, build: 'x.7.z-92'))); 298 | 299 | expect(Version.parse('1.0.0-rc-1+build-1'), 300 | equals(Version(1, 0, 0, pre: 'rc-1', build: 'build-1'))); 301 | 302 | expect(() => Version.parse('1.0'), throwsFormatException); 303 | expect(() => Version.parse('1a2b3'), throwsFormatException); 304 | expect(() => Version.parse('1.2.3.4'), throwsFormatException); 305 | expect(() => Version.parse('1234'), throwsFormatException); 306 | expect(() => Version.parse('-2.3.4'), throwsFormatException); 307 | expect(() => Version.parse('1.3-pre'), throwsFormatException); 308 | expect(() => Version.parse('1.3+build'), throwsFormatException); 309 | expect(() => Version.parse('1.3+bu?!3ild'), throwsFormatException); 310 | }); 311 | 312 | group('toString()', () { 313 | test('returns the version string', () { 314 | expect(Version(0, 0, 0).toString(), equals('0.0.0')); 315 | expect(Version(12, 34, 56).toString(), equals('12.34.56')); 316 | 317 | expect( 318 | Version(1, 2, 3, pre: 'alpha.1').toString(), equals('1.2.3-alpha.1')); 319 | expect(Version(1, 2, 3, pre: 'x.7.z-92').toString(), 320 | equals('1.2.3-x.7.z-92')); 321 | 322 | expect(Version(1, 2, 3, build: 'build.1').toString(), 323 | equals('1.2.3+build.1')); 324 | expect(Version(1, 2, 3, pre: 'pre', build: 'bui').toString(), 325 | equals('1.2.3-pre+bui')); 326 | }); 327 | 328 | test('preserves leading zeroes', () { 329 | expect(Version.parse('001.02.0003-01.dev+pre.002').toString(), 330 | equals('001.02.0003-01.dev+pre.002')); 331 | }); 332 | }); 333 | 334 | group('canonicalizedVersion', () { 335 | test('returns version string', () { 336 | expect(Version(0, 0, 0).canonicalizedVersion, equals('0.0.0')); 337 | expect(Version(12, 34, 56).canonicalizedVersion, equals('12.34.56')); 338 | 339 | expect(Version(1, 2, 3, pre: 'alpha.1').canonicalizedVersion, 340 | equals('1.2.3-alpha.1')); 341 | expect(Version(1, 2, 3, pre: 'x.7.z-92').canonicalizedVersion, 342 | equals('1.2.3-x.7.z-92')); 343 | 344 | expect(Version(1, 2, 3, build: 'build.1').canonicalizedVersion, 345 | equals('1.2.3+build.1')); 346 | expect(Version(1, 2, 3, pre: 'pre', build: 'bui').canonicalizedVersion, 347 | equals('1.2.3-pre+bui')); 348 | }); 349 | 350 | test('discards leading zeroes', () { 351 | expect(Version.parse('001.02.0003-01.dev+pre.002').canonicalizedVersion, 352 | equals('1.2.3-1.dev+pre.2')); 353 | }); 354 | 355 | test('example from documentation', () { 356 | final v = Version.parse('01.02.03-01.dev+pre.02'); 357 | 358 | assert(v.toString() == '01.02.03-01.dev+pre.02'); 359 | assert(v.canonicalizedVersion == '1.2.3-1.dev+pre.2'); 360 | assert(Version.parse(v.canonicalizedVersion) == v); 361 | }); 362 | }); 363 | 364 | group('primary', () { 365 | test('single', () { 366 | expect( 367 | _primary([ 368 | '1.2.3', 369 | ]).toString(), 370 | '1.2.3', 371 | ); 372 | }); 373 | 374 | test('normal', () { 375 | expect( 376 | _primary([ 377 | '1.2.3', 378 | '1.2.2', 379 | ]).toString(), 380 | '1.2.3', 381 | ); 382 | }); 383 | 384 | test('all prerelease', () { 385 | expect( 386 | _primary([ 387 | '1.2.2-dev.1', 388 | '1.2.2-dev.2', 389 | ]).toString(), 390 | '1.2.2-dev.2', 391 | ); 392 | }); 393 | 394 | test('later prerelease', () { 395 | expect( 396 | _primary([ 397 | '1.2.3', 398 | '1.2.3-dev', 399 | ]).toString(), 400 | '1.2.3', 401 | ); 402 | }); 403 | 404 | test('empty', () { 405 | expect(() => Version.primary([]), throwsStateError); 406 | }); 407 | }); 408 | } 409 | 410 | Version _primary(List input) => 411 | Version.primary(input.map(Version.parse).toList()); 412 | -------------------------------------------------------------------------------- /test/version_union_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:pub_semver/pub_semver.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | void main() { 11 | group('factory', () { 12 | test('ignores empty constraints', () { 13 | expect( 14 | VersionConstraint.unionOf([ 15 | VersionConstraint.empty, 16 | VersionConstraint.empty, 17 | v123, 18 | VersionConstraint.empty 19 | ]), 20 | equals(v123)); 21 | 22 | expect( 23 | VersionConstraint.unionOf( 24 | [VersionConstraint.empty, VersionConstraint.empty]), 25 | isEmpty); 26 | }); 27 | 28 | test('returns an empty constraint for an empty list', () { 29 | expect(VersionConstraint.unionOf([]), isEmpty); 30 | }); 31 | 32 | test('any constraints override everything', () { 33 | expect( 34 | VersionConstraint.unionOf([ 35 | v123, 36 | VersionConstraint.any, 37 | v200, 38 | VersionRange(min: v234, max: v250) 39 | ]), 40 | equals(VersionConstraint.any)); 41 | }); 42 | 43 | test('flattens other unions', () { 44 | expect( 45 | VersionConstraint.unionOf([ 46 | v072, 47 | VersionConstraint.unionOf([v123, v124]), 48 | v250 49 | ]), 50 | equals(VersionConstraint.unionOf([v072, v123, v124, v250]))); 51 | }); 52 | 53 | test('returns a single merged range as-is', () { 54 | expect( 55 | VersionConstraint.unionOf([ 56 | VersionRange(min: v080, max: v140), 57 | VersionRange(min: v123, max: v200) 58 | ]), 59 | equals(VersionRange(min: v080, max: v200))); 60 | }); 61 | }); 62 | 63 | group('equality', () { 64 | test("doesn't depend on original order", () { 65 | expect( 66 | VersionConstraint.unionOf([ 67 | v250, 68 | VersionRange(min: v201, max: v234), 69 | v124, 70 | v072, 71 | VersionRange(min: v080, max: v114), 72 | v123 73 | ]), 74 | equals(VersionConstraint.unionOf([ 75 | v072, 76 | VersionRange(min: v080, max: v114), 77 | v123, 78 | v124, 79 | VersionRange(min: v201, max: v234), 80 | v250 81 | ]))); 82 | }); 83 | 84 | test('merges overlapping ranges', () { 85 | expect( 86 | VersionConstraint.unionOf([ 87 | VersionRange(min: v003, max: v072), 88 | VersionRange(min: v010, max: v080), 89 | VersionRange(min: v114, max: v124), 90 | VersionRange(min: v123, max: v130) 91 | ]), 92 | equals(VersionConstraint.unionOf([ 93 | VersionRange(min: v003, max: v080), 94 | VersionRange(min: v114, max: v130) 95 | ]))); 96 | }); 97 | 98 | test('merges adjacent ranges', () { 99 | expect( 100 | VersionConstraint.unionOf([ 101 | VersionRange(min: v003, max: v072, includeMax: true), 102 | VersionRange(min: v072, max: v080), 103 | VersionRange( 104 | min: v114, max: v124, alwaysIncludeMaxPreRelease: true), 105 | VersionRange(min: v124, max: v130, includeMin: true), 106 | VersionRange(min: v130.firstPreRelease, max: v200, includeMin: true) 107 | ]), 108 | equals(VersionConstraint.unionOf([ 109 | VersionRange(min: v003, max: v080), 110 | VersionRange(min: v114, max: v200) 111 | ]))); 112 | }); 113 | 114 | test("doesn't merge not-quite-adjacent ranges", () { 115 | expect( 116 | VersionConstraint.unionOf([ 117 | VersionRange(min: v114, max: v124), 118 | VersionRange(min: v124, max: v130, includeMin: true) 119 | ]), 120 | isNot(equals(VersionRange(min: v114, max: v130)))); 121 | 122 | expect( 123 | VersionConstraint.unionOf([ 124 | VersionRange(min: v003, max: v072), 125 | VersionRange(min: v072, max: v080) 126 | ]), 127 | isNot(equals(VersionRange(min: v003, max: v080)))); 128 | }); 129 | 130 | test('merges version numbers into ranges', () { 131 | expect( 132 | VersionConstraint.unionOf([ 133 | VersionRange(min: v003, max: v072), 134 | v010, 135 | VersionRange(min: v114, max: v124), 136 | v123 137 | ]), 138 | equals(VersionConstraint.unionOf([ 139 | VersionRange(min: v003, max: v072), 140 | VersionRange(min: v114, max: v124) 141 | ]))); 142 | }); 143 | 144 | test('merges adjacent version numbers into ranges', () { 145 | expect( 146 | VersionConstraint.unionOf([ 147 | VersionRange( 148 | min: v003, max: v072, alwaysIncludeMaxPreRelease: true), 149 | v072, 150 | v114, 151 | VersionRange(min: v114, max: v124), 152 | v124.firstPreRelease 153 | ]), 154 | equals(VersionConstraint.unionOf([ 155 | VersionRange(min: v003, max: v072, includeMax: true), 156 | VersionRange( 157 | min: v114, 158 | max: v124.firstPreRelease, 159 | includeMin: true, 160 | includeMax: true) 161 | ]))); 162 | }); 163 | 164 | test("doesn't merge not-quite-adjacent version numbers into ranges", () { 165 | expect( 166 | VersionConstraint.unionOf([VersionRange(min: v003, max: v072), v072]), 167 | isNot(equals(VersionRange(min: v003, max: v072, includeMax: true)))); 168 | }); 169 | }); 170 | 171 | test('isEmpty returns false', () { 172 | expect( 173 | VersionConstraint.unionOf([ 174 | VersionRange(min: v003, max: v080), 175 | VersionRange(min: v123, max: v130), 176 | ]), 177 | isNot(isEmpty)); 178 | }); 179 | 180 | test('isAny returns false', () { 181 | expect( 182 | VersionConstraint.unionOf([ 183 | VersionRange(min: v003, max: v080), 184 | VersionRange(min: v123, max: v130), 185 | ]).isAny, 186 | isFalse); 187 | }); 188 | 189 | test('allows() allows anything the components allow', () { 190 | var union = VersionConstraint.unionOf([ 191 | VersionRange(min: v003, max: v080), 192 | VersionRange(min: v123, max: v130), 193 | v200 194 | ]); 195 | 196 | expect(union, allows(v010)); 197 | expect(union, doesNotAllow(v080)); 198 | expect(union, allows(v124)); 199 | expect(union, doesNotAllow(v140)); 200 | expect(union, allows(v200)); 201 | }); 202 | 203 | group('allowsAll()', () { 204 | test('for a version, returns true if any component allows the version', () { 205 | var union = VersionConstraint.unionOf([ 206 | VersionRange(min: v003, max: v080), 207 | VersionRange(min: v123, max: v130), 208 | v200 209 | ]); 210 | 211 | expect(union.allowsAll(v010), isTrue); 212 | expect(union.allowsAll(v080), isFalse); 213 | expect(union.allowsAll(v124), isTrue); 214 | expect(union.allowsAll(v140), isFalse); 215 | expect(union.allowsAll(v200), isTrue); 216 | }); 217 | 218 | test( 219 | 'for a version range, returns true if any component allows the whole ' 220 | 'range', () { 221 | var union = VersionConstraint.unionOf([ 222 | VersionRange(min: v003, max: v080), 223 | VersionRange(min: v123, max: v130) 224 | ]); 225 | 226 | expect(union.allowsAll(VersionRange(min: v003, max: v080)), isTrue); 227 | expect(union.allowsAll(VersionRange(min: v010, max: v072)), isTrue); 228 | expect(union.allowsAll(VersionRange(min: v010, max: v124)), isFalse); 229 | }); 230 | 231 | group('for a union,', () { 232 | var union = VersionConstraint.unionOf([ 233 | VersionRange(min: v003, max: v080), 234 | VersionRange(min: v123, max: v130) 235 | ]); 236 | 237 | test('returns true if every constraint matches a different constraint', 238 | () { 239 | expect( 240 | union.allowsAll(VersionConstraint.unionOf([ 241 | VersionRange(min: v010, max: v072), 242 | VersionRange(min: v124, max: v130) 243 | ])), 244 | isTrue); 245 | }); 246 | 247 | test('returns true if every constraint matches the same constraint', () { 248 | expect( 249 | union.allowsAll(VersionConstraint.unionOf([ 250 | VersionRange(min: v003, max: v010), 251 | VersionRange(min: v072, max: v080) 252 | ])), 253 | isTrue); 254 | }); 255 | 256 | test("returns false if there's an unmatched constraint", () { 257 | expect( 258 | union.allowsAll(VersionConstraint.unionOf([ 259 | VersionRange(min: v010, max: v072), 260 | VersionRange(min: v124, max: v130), 261 | VersionRange(min: v140, max: v200) 262 | ])), 263 | isFalse); 264 | }); 265 | 266 | test("returns false if a constraint isn't fully matched", () { 267 | expect( 268 | union.allowsAll(VersionConstraint.unionOf([ 269 | VersionRange(min: v010, max: v114), 270 | VersionRange(min: v124, max: v130) 271 | ])), 272 | isFalse); 273 | }); 274 | }); 275 | }); 276 | 277 | group('allowsAny()', () { 278 | test('for a version, returns true if any component allows the version', () { 279 | var union = VersionConstraint.unionOf([ 280 | VersionRange(min: v003, max: v080), 281 | VersionRange(min: v123, max: v130), 282 | v200 283 | ]); 284 | 285 | expect(union.allowsAny(v010), isTrue); 286 | expect(union.allowsAny(v080), isFalse); 287 | expect(union.allowsAny(v124), isTrue); 288 | expect(union.allowsAny(v140), isFalse); 289 | expect(union.allowsAny(v200), isTrue); 290 | }); 291 | 292 | test( 293 | 'for a version range, returns true if any component allows part of ' 294 | 'the range', () { 295 | var union = 296 | VersionConstraint.unionOf([VersionRange(min: v003, max: v080), v123]); 297 | 298 | expect(union.allowsAny(VersionRange(min: v010, max: v114)), isTrue); 299 | expect(union.allowsAny(VersionRange(min: v114, max: v124)), isTrue); 300 | expect(union.allowsAny(VersionRange(min: v124, max: v130)), isFalse); 301 | }); 302 | 303 | group('for a union,', () { 304 | var union = VersionConstraint.unionOf([ 305 | VersionRange(min: v010, max: v080), 306 | VersionRange(min: v123, max: v130) 307 | ]); 308 | 309 | test('returns true if any constraint matches', () { 310 | expect( 311 | union.allowsAny(VersionConstraint.unionOf( 312 | [v072, VersionRange(min: v200, max: v300)])), 313 | isTrue); 314 | 315 | expect( 316 | union.allowsAny(VersionConstraint.unionOf( 317 | [v003, VersionRange(min: v124, max: v300)])), 318 | isTrue); 319 | }); 320 | 321 | test('returns false if no constraint matches', () { 322 | expect( 323 | union.allowsAny(VersionConstraint.unionOf([ 324 | v003, 325 | VersionRange(min: v130, max: v140), 326 | VersionRange(min: v140, max: v200) 327 | ])), 328 | isFalse); 329 | }); 330 | }); 331 | }); 332 | 333 | group('intersect()', () { 334 | test('with an overlapping version, returns that version', () { 335 | expect( 336 | VersionConstraint.unionOf([ 337 | VersionRange(min: v010, max: v080), 338 | VersionRange(min: v123, max: v140) 339 | ]).intersect(v072), 340 | equals(v072)); 341 | }); 342 | 343 | test('with a non-overlapping version, returns an empty constraint', () { 344 | expect( 345 | VersionConstraint.unionOf([ 346 | VersionRange(min: v010, max: v080), 347 | VersionRange(min: v123, max: v140) 348 | ]).intersect(v300), 349 | isEmpty); 350 | }); 351 | 352 | test('with an overlapping range, returns that range', () { 353 | var range = VersionRange(min: v072, max: v080); 354 | expect( 355 | VersionConstraint.unionOf([ 356 | VersionRange(min: v010, max: v080), 357 | VersionRange(min: v123, max: v140) 358 | ]).intersect(range), 359 | equals(range)); 360 | }); 361 | 362 | test('with a non-overlapping range, returns an empty constraint', () { 363 | expect( 364 | VersionConstraint.unionOf([ 365 | VersionRange(min: v010, max: v080), 366 | VersionRange(min: v123, max: v140) 367 | ]).intersect(VersionRange(min: v080, max: v123)), 368 | isEmpty); 369 | }); 370 | 371 | test('with a parially-overlapping range, returns the overlapping parts', 372 | () { 373 | expect( 374 | VersionConstraint.unionOf([ 375 | VersionRange(min: v010, max: v080), 376 | VersionRange(min: v123, max: v140) 377 | ]).intersect(VersionRange(min: v072, max: v130)), 378 | equals(VersionConstraint.unionOf([ 379 | VersionRange(min: v072, max: v080), 380 | VersionRange(min: v123, max: v130) 381 | ]))); 382 | }); 383 | 384 | group('for a union,', () { 385 | var union = VersionConstraint.unionOf([ 386 | VersionRange(min: v003, max: v080), 387 | VersionRange(min: v123, max: v130) 388 | ]); 389 | 390 | test('returns the overlapping parts', () { 391 | expect( 392 | union.intersect(VersionConstraint.unionOf([ 393 | v010, 394 | VersionRange(min: v072, max: v124), 395 | VersionRange(min: v124, max: v130) 396 | ])), 397 | equals(VersionConstraint.unionOf([ 398 | v010, 399 | VersionRange(min: v072, max: v080), 400 | VersionRange(min: v123, max: v124), 401 | VersionRange(min: v124, max: v130) 402 | ]))); 403 | }); 404 | 405 | test("drops parts that don't match", () { 406 | expect( 407 | union.intersect(VersionConstraint.unionOf([ 408 | v003, 409 | VersionRange(min: v072, max: v080), 410 | VersionRange(min: v080, max: v123) 411 | ])), 412 | equals(VersionRange(min: v072, max: v080))); 413 | }); 414 | }); 415 | }); 416 | 417 | group('difference()', () { 418 | test("ignores ranges that don't intersect", () { 419 | expect( 420 | VersionConstraint.unionOf([ 421 | VersionRange(min: v072, max: v080), 422 | VersionRange(min: v123, max: v130) 423 | ]).difference(VersionConstraint.unionOf([ 424 | VersionRange(min: v003, max: v010), 425 | VersionRange(min: v080, max: v123), 426 | VersionRange(min: v140) 427 | ])), 428 | equals(VersionConstraint.unionOf([ 429 | VersionRange(min: v072, max: v080), 430 | VersionRange(min: v123, max: v130) 431 | ]))); 432 | }); 433 | 434 | test('removes overlapping portions', () { 435 | expect( 436 | VersionConstraint.unionOf([ 437 | VersionRange(min: v010, max: v080), 438 | VersionRange(min: v123, max: v130) 439 | ]).difference(VersionConstraint.unionOf( 440 | [VersionRange(min: v003, max: v072), VersionRange(min: v124)])), 441 | equals(VersionConstraint.unionOf([ 442 | VersionRange( 443 | min: v072.firstPreRelease, max: v080, includeMin: true), 444 | VersionRange(min: v123, max: v124, includeMax: true) 445 | ]))); 446 | }); 447 | 448 | test('removes multiple portions from the same range', () { 449 | expect( 450 | VersionConstraint.unionOf([ 451 | VersionRange(min: v010, max: v114), 452 | VersionRange(min: v130, max: v200) 453 | ]).difference(VersionConstraint.unionOf([v072, v080])), 454 | equals(VersionConstraint.unionOf([ 455 | VersionRange( 456 | min: v010, max: v072, alwaysIncludeMaxPreRelease: true), 457 | VersionRange( 458 | min: v072, max: v080, alwaysIncludeMaxPreRelease: true), 459 | VersionRange(min: v080, max: v114), 460 | VersionRange(min: v130, max: v200) 461 | ]))); 462 | }); 463 | 464 | test('removes the same range from multiple ranges', () { 465 | expect( 466 | VersionConstraint.unionOf([ 467 | VersionRange(min: v010, max: v072), 468 | VersionRange(min: v080, max: v123), 469 | VersionRange(min: v124, max: v130), 470 | VersionRange(min: v200, max: v234), 471 | VersionRange(min: v250, max: v300) 472 | ]).difference(VersionRange(min: v114, max: v201)), 473 | equals(VersionConstraint.unionOf([ 474 | VersionRange(min: v010, max: v072), 475 | VersionRange(min: v080, max: v114, includeMax: true), 476 | VersionRange( 477 | min: v201.firstPreRelease, max: v234, includeMin: true), 478 | VersionRange(min: v250, max: v300) 479 | ]))); 480 | }); 481 | }); 482 | } 483 | --------------------------------------------------------------------------------