| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, 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 /// Handles version numbers, following the [Semantic Versioning][semver] spec. | |
| 6 /// | |
| 7 /// [semver]: http://semver.org/ | |
| 8 library pub.version; | |
| 9 | |
| 10 import 'dart:math'; | |
| 11 | |
| 12 import 'package:collection/equality.dart'; | |
| 13 | |
| 14 /// Regex that matches a version number at the beginning of a string. | |
| 15 final _START_VERSION = new RegExp( | |
| 16 r'^' // Start at beginning. | |
| 17 r'(\d+).(\d+).(\d+)' // Version number. | |
| 18 r'(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Pre-release. | |
| 19 r'(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?'); // Build. | |
| 20 | |
| 21 /// Like [_START_VERSION] but matches the entire string. | |
| 22 final _COMPLETE_VERSION = new RegExp("${_START_VERSION.pattern}\$"); | |
| 23 | |
| 24 /// Parses a comparison operator ("<", ">", "<=", or ">=") at the beginning of | |
| 25 /// a string. | |
| 26 final _START_COMPARISON = new RegExp(r"^[<>]=?"); | |
| 27 | |
| 28 /// The equality operator to use for comparing version components. | |
| 29 final _equality = const IterableEquality(); | |
| 30 | |
| 31 /// A parsed semantic version number. | |
| 32 class Version implements Comparable<Version>, VersionConstraint { | |
| 33 /// No released version: i.e. "0.0.0". | |
| 34 static Version get none => new Version(0, 0, 0); | |
| 35 | |
| 36 /// Compares [a] and [b] to see which takes priority over the other. | |
| 37 /// | |
| 38 /// Returns `1` if [a] takes priority over [b] and `-1` if vice versa. If | |
| 39 /// [a] and [b] are equivalent, returns `0`. | |
| 40 /// | |
| 41 /// Unlike [compareTo], which *orders* versions, this determines which | |
| 42 /// version a user is likely to prefer. In particular, it prioritizes | |
| 43 /// pre-release versions lower than stable versions, regardless of their | |
| 44 /// version numbers. | |
| 45 /// | |
| 46 /// When used to sort a list, orders in ascending priority so that the | |
| 47 /// highest priority version is *last* in the result. | |
| 48 static int prioritize(Version a, Version b) { | |
| 49 // Sort all prerelease versions after all normal versions. This way | |
| 50 // the solver will prefer stable packages over unstable ones. | |
| 51 if (a.isPreRelease && !b.isPreRelease) return -1; | |
| 52 if (!a.isPreRelease && b.isPreRelease) return 1; | |
| 53 | |
| 54 return a.compareTo(b); | |
| 55 } | |
| 56 | |
| 57 /// Like [proiritize], but lower version numbers are considered greater than | |
| 58 /// higher version numbers. | |
| 59 /// | |
| 60 /// This still considers prerelease versions to be lower than non-prerelease | |
| 61 /// versions. | |
| 62 static int antiPrioritize(Version a, Version b) { | |
| 63 if (a.isPreRelease && !b.isPreRelease) return -1; | |
| 64 if (!a.isPreRelease && b.isPreRelease) return 1; | |
| 65 | |
| 66 return b.compareTo(a); | |
| 67 } | |
| 68 | |
| 69 /// The major version number: "1" in "1.2.3". | |
| 70 final int major; | |
| 71 | |
| 72 /// The minor version number: "2" in "1.2.3". | |
| 73 final int minor; | |
| 74 | |
| 75 /// The patch version number: "3" in "1.2.3". | |
| 76 final int patch; | |
| 77 | |
| 78 /// The pre-release identifier: "foo" in "1.2.3-foo". | |
| 79 /// | |
| 80 /// This is split into a list of components, each of which may be either a | |
| 81 /// string or a non-negative integer. It may also be empty, indicating that | |
| 82 /// this version has no pre-release identifier. | |
| 83 final List preRelease; | |
| 84 | |
| 85 /// The build identifier: "foo" in "1.2.3+foo". | |
| 86 /// | |
| 87 /// This is split into a list of components, each of which may be either a | |
| 88 /// string or a non-negative integer. It may also be empty, indicating that | |
| 89 /// this version has no build identifier. | |
| 90 final List build; | |
| 91 | |
| 92 /// The original string representation of the version number. | |
| 93 /// | |
| 94 /// This preserves textual artifacts like leading zeros that may be left out | |
| 95 /// of the parsed version. | |
| 96 final String _text; | |
| 97 | |
| 98 Version._(this.major, this.minor, this.patch, String preRelease, String build, | |
| 99 this._text) | |
| 100 : preRelease = preRelease == null ? [] : _splitParts(preRelease), | |
| 101 build = build == null ? [] : _splitParts(build) { | |
| 102 if (major < 0) throw new ArgumentError( | |
| 103 'Major version must be non-negative.'); | |
| 104 if (minor < 0) throw new ArgumentError( | |
| 105 'Minor version must be non-negative.'); | |
| 106 if (patch < 0) throw new ArgumentError( | |
| 107 'Patch version must be non-negative.'); | |
| 108 } | |
| 109 | |
| 110 /// Creates a new [Version] object. | |
| 111 factory Version(int major, int minor, int patch, {String pre, String build}) { | |
| 112 var text = "$major.$minor.$patch"; | |
| 113 if (pre != null) text += "-$pre"; | |
| 114 if (build != null) text += "+$build"; | |
| 115 | |
| 116 return new Version._(major, minor, patch, pre, build, text); | |
| 117 } | |
| 118 | |
| 119 /// Creates a new [Version] by parsing [text]. | |
| 120 factory Version.parse(String text) { | |
| 121 final match = _COMPLETE_VERSION.firstMatch(text); | |
| 122 if (match == null) { | |
| 123 throw new FormatException('Could not parse "$text".'); | |
| 124 } | |
| 125 | |
| 126 try { | |
| 127 int major = int.parse(match[1]); | |
| 128 int minor = int.parse(match[2]); | |
| 129 int patch = int.parse(match[3]); | |
| 130 | |
| 131 String preRelease = match[5]; | |
| 132 String build = match[8]; | |
| 133 | |
| 134 return new Version._(major, minor, patch, preRelease, build, text); | |
| 135 } on FormatException catch (ex) { | |
| 136 throw new FormatException('Could not parse "$text".'); | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 /// Returns the primary version out of a list of candidates. | |
| 141 /// | |
| 142 /// This is the highest-numbered stable (non-prerelease) version. If there | |
| 143 /// are no stable versions, it's just the highest-numbered version. | |
| 144 static Version primary(List<Version> versions) { | |
| 145 var primary; | |
| 146 for (var version in versions) { | |
| 147 if (primary == null || (!version.isPreRelease && primary.isPreRelease) || | |
| 148 (version.isPreRelease == primary.isPreRelease && version > primary)) { | |
| 149 primary = version; | |
| 150 } | |
| 151 } | |
| 152 return primary; | |
| 153 } | |
| 154 | |
| 155 /// Splits a string of dot-delimited identifiers into their component parts. | |
| 156 /// | |
| 157 /// Identifiers that are numeric are converted to numbers. | |
| 158 static List _splitParts(String text) { | |
| 159 return text.split('.').map((part) { | |
| 160 try { | |
| 161 return int.parse(part); | |
| 162 } on FormatException catch (ex) { | |
| 163 // Not a number. | |
| 164 return part; | |
| 165 } | |
| 166 }).toList(); | |
| 167 } | |
| 168 | |
| 169 bool operator ==(other) { | |
| 170 if (other is! Version) return false; | |
| 171 return major == other.major && minor == other.minor && | |
| 172 patch == other.patch && | |
| 173 _equality.equals(preRelease, other.preRelease) && | |
| 174 _equality.equals(build, other.build); | |
| 175 } | |
| 176 | |
| 177 int get hashCode => major ^ minor ^ patch ^ _equality.hash(preRelease) ^ | |
| 178 _equality.hash(build); | |
| 179 | |
| 180 bool operator <(Version other) => compareTo(other) < 0; | |
| 181 bool operator >(Version other) => compareTo(other) > 0; | |
| 182 bool operator <=(Version other) => compareTo(other) <= 0; | |
| 183 bool operator >=(Version other) => compareTo(other) >= 0; | |
| 184 | |
| 185 bool get isAny => false; | |
| 186 bool get isEmpty => false; | |
| 187 | |
| 188 /// Whether or not this is a pre-release version. | |
| 189 bool get isPreRelease => preRelease.isNotEmpty; | |
| 190 | |
| 191 /// Gets the next major version number that follows this one. | |
| 192 /// | |
| 193 /// If this version is a pre-release of a major version release (i.e. the | |
| 194 /// minor and patch versions are zero), then it just strips the pre-release | |
| 195 /// suffix. Otherwise, it increments the major version and resets the minor | |
| 196 /// and patch. | |
| 197 Version get nextMajor { | |
| 198 if (isPreRelease && minor == 0 && patch == 0) { | |
| 199 return new Version(major, minor, patch); | |
| 200 } | |
| 201 | |
| 202 return new Version(major + 1, 0, 0); | |
| 203 } | |
| 204 | |
| 205 /// Gets the next minor version number that follows this one. | |
| 206 /// | |
| 207 /// If this version is a pre-release of a minor version release (i.e. the | |
| 208 /// patch version is zero), then it just strips the pre-release suffix. | |
| 209 /// Otherwise, it increments the minor version and resets the patch. | |
| 210 Version get nextMinor { | |
| 211 if (isPreRelease && patch == 0) { | |
| 212 return new Version(major, minor, patch); | |
| 213 } | |
| 214 | |
| 215 return new Version(major, minor + 1, 0); | |
| 216 } | |
| 217 | |
| 218 /// Gets the next patch version number that follows this one. | |
| 219 /// | |
| 220 /// If this version is a pre-release, then it just strips the pre-release | |
| 221 /// suffix. Otherwise, it increments the patch version. | |
| 222 Version get nextPatch { | |
| 223 if (isPreRelease) { | |
| 224 return new Version(major, minor, patch); | |
| 225 } | |
| 226 | |
| 227 return new Version(major, minor, patch + 1); | |
| 228 } | |
| 229 | |
| 230 /// Tests if [other] matches this version exactly. | |
| 231 bool allows(Version other) => this == other; | |
| 232 | |
| 233 VersionConstraint intersect(VersionConstraint other) { | |
| 234 if (other.isEmpty) return other; | |
| 235 | |
| 236 // Intersect a version and a range. | |
| 237 if (other is VersionRange) return other.intersect(this); | |
| 238 | |
| 239 // Intersecting two versions only works if they are the same. | |
| 240 if (other is Version) { | |
| 241 return this == other ? this : VersionConstraint.empty; | |
| 242 } | |
| 243 | |
| 244 throw new ArgumentError( | |
| 245 'Unknown VersionConstraint type $other.'); | |
| 246 } | |
| 247 | |
| 248 int compareTo(Version other) { | |
| 249 if (major != other.major) return major.compareTo(other.major); | |
| 250 if (minor != other.minor) return minor.compareTo(other.minor); | |
| 251 if (patch != other.patch) return patch.compareTo(other.patch); | |
| 252 | |
| 253 // Pre-releases always come before no pre-release string. | |
| 254 if (!isPreRelease && other.isPreRelease) return 1; | |
| 255 if (!other.isPreRelease && isPreRelease) return -1; | |
| 256 | |
| 257 var comparison = _compareLists(preRelease, other.preRelease); | |
| 258 if (comparison != 0) return comparison; | |
| 259 | |
| 260 // Builds always come after no build string. | |
| 261 if (build.isEmpty && other.build.isNotEmpty) return -1; | |
| 262 if (other.build.isEmpty && build.isNotEmpty) return 1; | |
| 263 return _compareLists(build, other.build); | |
| 264 } | |
| 265 | |
| 266 String toString() => _text; | |
| 267 | |
| 268 /// Compares a dot-separated component of two versions. | |
| 269 /// | |
| 270 /// This is used for the pre-release and build version parts. This follows | |
| 271 /// Rule 12 of the Semantic Versioning spec (v2.0.0-rc.1). | |
| 272 int _compareLists(List a, List b) { | |
| 273 for (var i = 0; i < max(a.length, b.length); i++) { | |
| 274 var aPart = (i < a.length) ? a[i] : null; | |
| 275 var bPart = (i < b.length) ? b[i] : null; | |
| 276 | |
| 277 if (aPart == bPart) continue; | |
| 278 | |
| 279 // Missing parts come before present ones. | |
| 280 if (aPart == null) return -1; | |
| 281 if (bPart == null) return 1; | |
| 282 | |
| 283 if (aPart is num) { | |
| 284 if (bPart is num) { | |
| 285 // Compare two numbers. | |
| 286 return aPart.compareTo(bPart); | |
| 287 } else { | |
| 288 // Numbers come before strings. | |
| 289 return -1; | |
| 290 } | |
| 291 } else { | |
| 292 if (bPart is num) { | |
| 293 // Strings come after numbers. | |
| 294 return 1; | |
| 295 } else { | |
| 296 // Compare two strings. | |
| 297 return aPart.compareTo(bPart); | |
| 298 } | |
| 299 } | |
| 300 } | |
| 301 | |
| 302 // The lists are entirely equal. | |
| 303 return 0; | |
| 304 } | |
| 305 } | |
| 306 | |
| 307 /// A [VersionConstraint] is a predicate that can determine whether a given | |
| 308 /// version is valid or not. | |
| 309 /// | |
| 310 /// For example, a ">= 2.0.0" constraint allows any version that is "2.0.0" or | |
| 311 /// greater. Version objects themselves implement this to match a specific | |
| 312 /// version. | |
| 313 abstract class VersionConstraint { | |
| 314 /// A [VersionConstraint] that allows all versions. | |
| 315 static VersionConstraint any = new VersionRange(); | |
| 316 | |
| 317 /// A [VersionConstraint] that allows no versions: i.e. the empty set. | |
| 318 static VersionConstraint empty = const _EmptyVersion(); | |
| 319 | |
| 320 /// Parses a version constraint. | |
| 321 /// | |
| 322 /// This string is either "any" or a series of version parts. Each part can | |
| 323 /// be one of: | |
| 324 /// | |
| 325 /// * A version string like `1.2.3`. In other words, anything that can be | |
| 326 /// parsed by [Version.parse()]. | |
| 327 /// * A comparison operator (`<`, `>`, `<=`, or `>=`) followed by a version | |
| 328 /// string. | |
| 329 /// | |
| 330 /// Whitespace is ignored. | |
| 331 /// | |
| 332 /// Examples: | |
| 333 /// | |
| 334 /// any | |
| 335 /// 1.2.3-alpha | |
| 336 /// <=5.1.4 | |
| 337 /// >2.0.4 <= 2.4.6 | |
| 338 factory VersionConstraint.parse(String text) { | |
| 339 // Handle the "any" constraint. | |
| 340 if (text.trim() == "any") return new VersionRange(); | |
| 341 | |
| 342 var originalText = text; | |
| 343 var constraints = <VersionConstraint>[]; | |
| 344 | |
| 345 void skipWhitespace() { | |
| 346 text = text.trim(); | |
| 347 } | |
| 348 | |
| 349 // Try to parse and consume a version number. | |
| 350 Version matchVersion() { | |
| 351 var version = _START_VERSION.firstMatch(text); | |
| 352 if (version == null) return null; | |
| 353 | |
| 354 text = text.substring(version.end); | |
| 355 return new Version.parse(version[0]); | |
| 356 } | |
| 357 | |
| 358 // Try to parse and consume a comparison operator followed by a version. | |
| 359 VersionConstraint matchComparison() { | |
| 360 var comparison = _START_COMPARISON.firstMatch(text); | |
| 361 if (comparison == null) return null; | |
| 362 | |
| 363 var op = comparison[0]; | |
| 364 text = text.substring(comparison.end); | |
| 365 skipWhitespace(); | |
| 366 | |
| 367 var version = matchVersion(); | |
| 368 if (version == null) { | |
| 369 throw new FormatException('Expected version number after "$op" in ' | |
| 370 '"$originalText", got "$text".'); | |
| 371 } | |
| 372 | |
| 373 switch (op) { | |
| 374 case '<=': | |
| 375 return new VersionRange(max: version, includeMax: true); | |
| 376 case '<': | |
| 377 return new VersionRange(max: version, includeMax: false); | |
| 378 case '>=': | |
| 379 return new VersionRange(min: version, includeMin: true); | |
| 380 case '>': | |
| 381 return new VersionRange(min: version, includeMin: false); | |
| 382 } | |
| 383 throw "Unreachable."; | |
| 384 } | |
| 385 | |
| 386 while (true) { | |
| 387 skipWhitespace(); | |
| 388 if (text.isEmpty) break; | |
| 389 | |
| 390 var version = matchVersion(); | |
| 391 if (version != null) { | |
| 392 constraints.add(version); | |
| 393 continue; | |
| 394 } | |
| 395 | |
| 396 var comparison = matchComparison(); | |
| 397 if (comparison != null) { | |
| 398 constraints.add(comparison); | |
| 399 continue; | |
| 400 } | |
| 401 | |
| 402 // If we got here, we couldn't parse the remaining string. | |
| 403 throw new FormatException('Could not parse version "$originalText". ' | |
| 404 'Unknown text at "$text".'); | |
| 405 } | |
| 406 | |
| 407 if (constraints.isEmpty) { | |
| 408 throw new FormatException('Cannot parse an empty string.'); | |
| 409 } | |
| 410 | |
| 411 return new VersionConstraint.intersection(constraints); | |
| 412 } | |
| 413 | |
| 414 /// Creates a new version constraint that is the intersection of | |
| 415 /// [constraints]. | |
| 416 /// | |
| 417 /// It only allows versions that all of those constraints allow. If | |
| 418 /// constraints is empty, then it returns a VersionConstraint that allows | |
| 419 /// all versions. | |
| 420 factory VersionConstraint.intersection( | |
| 421 Iterable<VersionConstraint> constraints) { | |
| 422 var constraint = new VersionRange(); | |
| 423 for (var other in constraints) { | |
| 424 constraint = constraint.intersect(other); | |
| 425 } | |
| 426 return constraint; | |
| 427 } | |
| 428 | |
| 429 /// Returns `true` if this constraint allows no versions. | |
| 430 bool get isEmpty; | |
| 431 | |
| 432 /// Returns `true` if this constraint allows all versions. | |
| 433 bool get isAny; | |
| 434 | |
| 435 /// Returns `true` if this constraint allows [version]. | |
| 436 bool allows(Version version); | |
| 437 | |
| 438 /// Creates a new [VersionConstraint] that only allows [Version]s allowed by | |
| 439 /// both this and [other]. | |
| 440 VersionConstraint intersect(VersionConstraint other); | |
| 441 } | |
| 442 | |
| 443 /// Constrains versions to a fall within a given range. | |
| 444 /// | |
| 445 /// If there is a minimum, then this only allows versions that are at that | |
| 446 /// minimum or greater. If there is a maximum, then only versions less than | |
| 447 /// that are allowed. In other words, this allows `>= min, < max`. | |
| 448 class VersionRange implements VersionConstraint { | |
| 449 final Version min; | |
| 450 final Version max; | |
| 451 final bool includeMin; | |
| 452 final bool includeMax; | |
| 453 | |
| 454 VersionRange({this.min, this.max, | |
| 455 this.includeMin: false, this.includeMax: false}) { | |
| 456 if (min != null && max != null && min > max) { | |
| 457 throw new ArgumentError( | |
| 458 'Minimum version ("$min") must be less than maximum ("$max").'); | |
| 459 } | |
| 460 } | |
| 461 | |
| 462 bool operator ==(other) { | |
| 463 if (other is! VersionRange) return false; | |
| 464 | |
| 465 return min == other.min && | |
| 466 max == other.max && | |
| 467 includeMin == other.includeMin && | |
| 468 includeMax == other.includeMax; | |
| 469 } | |
| 470 | |
| 471 bool get isEmpty => false; | |
| 472 | |
| 473 bool get isAny => min == null && max == null; | |
| 474 | |
| 475 /// Tests if [other] matches falls within this version range. | |
| 476 bool allows(Version other) { | |
| 477 if (min != null) { | |
| 478 if (other < min) return false; | |
| 479 if (!includeMin && other == min) return false; | |
| 480 } | |
| 481 | |
| 482 if (max != null) { | |
| 483 if (other > max) return false; | |
| 484 if (!includeMax && other == max) return false; | |
| 485 | |
| 486 // If the max isn't itself a pre-release, don't allow any pre-release | |
| 487 // versions of the max. | |
| 488 // | |
| 489 // See: https://www.npmjs.org/doc/misc/semver.html | |
| 490 if (!includeMax && | |
| 491 !max.isPreRelease && other.isPreRelease && | |
| 492 other.major == max.major && other.minor == max.minor && | |
| 493 other.patch == max.patch) { | |
| 494 return false; | |
| 495 } | |
| 496 } | |
| 497 | |
| 498 return true; | |
| 499 } | |
| 500 | |
| 501 VersionConstraint intersect(VersionConstraint other) { | |
| 502 if (other.isEmpty) return other; | |
| 503 | |
| 504 // A range and a Version just yields the version if it's in the range. | |
| 505 if (other is Version) { | |
| 506 return allows(other) ? other : VersionConstraint.empty; | |
| 507 } | |
| 508 | |
| 509 if (other is VersionRange) { | |
| 510 // Intersect the two ranges. | |
| 511 var intersectMin = min; | |
| 512 var intersectIncludeMin = includeMin; | |
| 513 var intersectMax = max; | |
| 514 var intersectIncludeMax = includeMax; | |
| 515 | |
| 516 if (other.min == null) { | |
| 517 // Do nothing. | |
| 518 } else if (intersectMin == null || intersectMin < other.min) { | |
| 519 intersectMin = other.min; | |
| 520 intersectIncludeMin = other.includeMin; | |
| 521 } else if (intersectMin == other.min && !other.includeMin) { | |
| 522 // The edges are the same, but one is exclusive, make it exclusive. | |
| 523 intersectIncludeMin = false; | |
| 524 } | |
| 525 | |
| 526 if (other.max == null) { | |
| 527 // Do nothing. | |
| 528 } else if (intersectMax == null || intersectMax > other.max) { | |
| 529 intersectMax = other.max; | |
| 530 intersectIncludeMax = other.includeMax; | |
| 531 } else if (intersectMax == other.max && !other.includeMax) { | |
| 532 // The edges are the same, but one is exclusive, make it exclusive. | |
| 533 intersectIncludeMax = false; | |
| 534 } | |
| 535 | |
| 536 if (intersectMin == null && intersectMax == null) { | |
| 537 // Open range. | |
| 538 return new VersionRange(); | |
| 539 } | |
| 540 | |
| 541 // If the range is just a single version. | |
| 542 if (intersectMin == intersectMax) { | |
| 543 // If both ends are inclusive, allow that version. | |
| 544 if (intersectIncludeMin && intersectIncludeMax) return intersectMin; | |
| 545 | |
| 546 // Otherwise, no versions. | |
| 547 return VersionConstraint.empty; | |
| 548 } | |
| 549 | |
| 550 if (intersectMin != null && intersectMax != null && | |
| 551 intersectMin > intersectMax) { | |
| 552 // Non-overlapping ranges, so empty. | |
| 553 return VersionConstraint.empty; | |
| 554 } | |
| 555 | |
| 556 // If we got here, there is an actual range. | |
| 557 return new VersionRange(min: intersectMin, max: intersectMax, | |
| 558 includeMin: intersectIncludeMin, includeMax: intersectIncludeMax); | |
| 559 } | |
| 560 | |
| 561 throw new ArgumentError( | |
| 562 'Unknown VersionConstraint type $other.'); | |
| 563 } | |
| 564 | |
| 565 String toString() { | |
| 566 var buffer = new StringBuffer(); | |
| 567 | |
| 568 if (min != null) { | |
| 569 buffer.write(includeMin ? '>=' : '>'); | |
| 570 buffer.write(min); | |
| 571 } | |
| 572 | |
| 573 if (max != null) { | |
| 574 if (min != null) buffer.write(' '); | |
| 575 buffer.write(includeMax ? '<=' : '<'); | |
| 576 buffer.write(max); | |
| 577 } | |
| 578 | |
| 579 if (min == null && max == null) buffer.write('any'); | |
| 580 return buffer.toString(); | |
| 581 } | |
| 582 } | |
| 583 | |
| 584 class _EmptyVersion implements VersionConstraint { | |
| 585 const _EmptyVersion(); | |
| 586 | |
| 587 bool get isEmpty => true; | |
| 588 bool get isAny => false; | |
| 589 bool allows(Version other) => false; | |
| 590 VersionConstraint intersect(VersionConstraint other) => this; | |
| 591 String toString() => '<empty>'; | |
| 592 } | |
| OLD | NEW |