OLD | NEW |
(Empty) | |
| 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 library pub_semver.src.version_range; |
| 6 |
| 7 import 'version.dart'; |
| 8 import 'version_constraint.dart'; |
| 9 import 'version_union.dart'; |
| 10 |
| 11 /// Constrains versions to a fall within a given range. |
| 12 /// |
| 13 /// If there is a minimum, then this only allows versions that are at that |
| 14 /// minimum or greater. If there is a maximum, then only versions less than |
| 15 /// that are allowed. In other words, this allows `>= min, < max`. |
| 16 class VersionRange implements VersionConstraint { |
| 17 /// The minimum end of the range. |
| 18 /// |
| 19 /// If [includeMin] is `true`, this will be the minimum allowed version. |
| 20 /// Otherwise, it will be the highest version below the range that is not |
| 21 /// allowed. |
| 22 /// |
| 23 /// This may be `null` in which case the range has no minimum end and allows |
| 24 /// any version less than the maximum. |
| 25 final Version min; |
| 26 |
| 27 /// The maximum end of the range. |
| 28 /// |
| 29 /// If [includeMax] is `true`, this will be the maximum allowed version. |
| 30 /// Otherwise, it will be the lowest version above the range that is not |
| 31 /// allowed. |
| 32 /// |
| 33 /// This may be `null` in which case the range has no maximum end and allows |
| 34 /// any version greater than the minimum. |
| 35 final Version max; |
| 36 |
| 37 /// If `true` then [min] is allowed by the range. |
| 38 final bool includeMin; |
| 39 |
| 40 /// If `true`, then [max] is allowed by the range. |
| 41 final bool includeMax; |
| 42 |
| 43 /// Creates a new version range from [min] to [max], either inclusive or |
| 44 /// exclusive. |
| 45 /// |
| 46 /// If it is an error if [min] is greater than [max]. |
| 47 /// |
| 48 /// Either [max] or [min] may be omitted to not clamp the range at that end. |
| 49 /// If both are omitted, the range allows all versions. |
| 50 /// |
| 51 /// If [includeMin] is `true`, then the minimum end of the range is inclusive. |
| 52 /// Likewise, passing [includeMax] as `true` makes the upper end inclusive. |
| 53 VersionRange({this.min, this.max, |
| 54 this.includeMin: false, this.includeMax: false}) { |
| 55 if (min != null && max != null && min > max) { |
| 56 throw new ArgumentError( |
| 57 'Minimum version ("$min") must be less than maximum ("$max").'); |
| 58 } |
| 59 } |
| 60 |
| 61 bool operator ==(other) { |
| 62 if (other is! VersionRange) return false; |
| 63 |
| 64 return min == other.min && |
| 65 max == other.max && |
| 66 includeMin == other.includeMin && |
| 67 includeMax == other.includeMax; |
| 68 } |
| 69 |
| 70 int get hashCode => min.hashCode ^ (max.hashCode * 3) ^ |
| 71 (includeMin.hashCode * 5) ^ (includeMax.hashCode * 7); |
| 72 |
| 73 bool get isEmpty => false; |
| 74 |
| 75 bool get isAny => min == null && max == null; |
| 76 |
| 77 /// Tests if [other] falls within this version range. |
| 78 bool allows(Version other) { |
| 79 if (min != null) { |
| 80 if (other < min) return false; |
| 81 if (!includeMin && other == min) return false; |
| 82 } |
| 83 |
| 84 if (max != null) { |
| 85 if (other > max) return false; |
| 86 if (!includeMax && other == max) return false; |
| 87 |
| 88 |
| 89 // Disallow pre-release versions that have the same major, minor, and |
| 90 // patch version as the max, but only if neither the max nor the min is a |
| 91 // pre-release of that version. This ensures that "^1.2.3" doesn't include |
| 92 // "2.0.0-pre", while also allowing both ">=2.0.0-pre.2 <2.0.0" and |
| 93 // ">=1.2.3 <2.0.0-pre.7" to match "2.0.0-pre.5". |
| 94 // |
| 95 // It's worth noting that this is different than [NPM's semantics][]. NPM |
| 96 // disallows **all** pre-release versions unless their major, minor, and |
| 97 // patch numbers match those of a prerelease min or max. This ensures that |
| 98 // no prerelease versions will ever be selected if the user doesn't |
| 99 // explicitly allow them. |
| 100 // |
| 101 // [NPM's semantics]: https://www.npmjs.org/doc/misc/semver.html#prereleas
e-tags |
| 102 // |
| 103 // Instead, we ensure that release versions will always be preferred over |
| 104 // prerelease versions by ordering the release versions first in |
| 105 // [Version.prioritize]. This means that constraints like "any" or |
| 106 // ">1.2.3" can still match prerelease versions if they're the only things |
| 107 // available. |
| 108 var maxIsReleaseOfOther = !includeMax && |
| 109 !max.isPreRelease && other.isPreRelease && |
| 110 _equalsWithoutPreRelease(other, max); |
| 111 var minIsPreReleaseOfOther = min != null && min.isPreRelease && |
| 112 _equalsWithoutPreRelease(other, min); |
| 113 if (maxIsReleaseOfOther && !minIsPreReleaseOfOther) return false; |
| 114 } |
| 115 |
| 116 return true; |
| 117 } |
| 118 |
| 119 bool _equalsWithoutPreRelease(Version version1, Version version2) => |
| 120 version1.major == version2.major && |
| 121 version1.minor == version2.minor && |
| 122 version1.patch == version2.patch; |
| 123 |
| 124 bool allowsAll(VersionConstraint other) { |
| 125 if (other.isEmpty) return true; |
| 126 if (other is Version) return allows(other); |
| 127 |
| 128 if (other is VersionUnion) { |
| 129 return other.constraints.every((constraint) => allowsAll(constraint)); |
| 130 } |
| 131 |
| 132 if (other is VersionRange) { |
| 133 if (min != null) { |
| 134 if (other.min == null) return false; |
| 135 if (min > other.min) return false; |
| 136 if (min == other.min && !includeMin && other.includeMin) return false; |
| 137 } |
| 138 |
| 139 if (max != null) { |
| 140 if (other.max == null) return false; |
| 141 if (max < other.max) return false; |
| 142 if (max == other.max && !includeMax && other.includeMax) return false; |
| 143 } |
| 144 |
| 145 return true; |
| 146 } |
| 147 |
| 148 throw new ArgumentError('Unknown VersionConstraint type $other.'); |
| 149 } |
| 150 |
| 151 bool allowsAny(VersionConstraint other) { |
| 152 if (other.isEmpty) return false; |
| 153 if (other is Version) return allows(other); |
| 154 |
| 155 if (other is VersionUnion) { |
| 156 return other.constraints.any((constraint) => allowsAny(constraint)); |
| 157 } |
| 158 |
| 159 if (other is VersionRange) { |
| 160 // If neither range has a minimum, they'll overlap at some point. |
| 161 // |
| 162 // ... this ] |
| 163 // ... other ] |
| 164 if (min == null && other.min == null) return true; |
| 165 |
| 166 // If this range has a lower minimum than the other range, it overlaps as |
| 167 // long as its maximum is higher than or the same as the other range's |
| 168 // minimum. |
| 169 // |
| 170 // [ this ] [ this ] |
| 171 // [ other ] [ other ] |
| 172 if (min == null || (other.min != null && min < other.min)) { |
| 173 if (max == null) return true; |
| 174 if (max > other.min) return true; |
| 175 if (max < other.min) return false; |
| 176 assert(max == other.min); |
| 177 return includeMax && other.includeMin; |
| 178 } |
| 179 |
| 180 // If this range has a higher minimum than the other range, it overlaps as |
| 181 // long as its minimum is lower than or the same as the other range's |
| 182 // maximum. |
| 183 // |
| 184 // [ this ] [ this ] |
| 185 // [ other ] [ other ] |
| 186 if (other.max == null) return true; |
| 187 if (min < other.max) return true; |
| 188 if (min > other.max) return false; |
| 189 assert(min == other.max); |
| 190 return includeMin && other.includeMax; |
| 191 } |
| 192 |
| 193 throw new ArgumentError('Unknown VersionConstraint type $other.'); |
| 194 } |
| 195 |
| 196 VersionConstraint intersect(VersionConstraint other) { |
| 197 if (other.isEmpty) return other; |
| 198 if (other is VersionUnion) return other.intersect(this); |
| 199 |
| 200 // A range and a Version just yields the version if it's in the range. |
| 201 if (other is Version) { |
| 202 return allows(other) ? other : VersionConstraint.empty; |
| 203 } |
| 204 |
| 205 if (other is VersionRange) { |
| 206 // Intersect the two ranges. |
| 207 var intersectMin = min; |
| 208 var intersectIncludeMin = includeMin; |
| 209 var intersectMax = max; |
| 210 var intersectIncludeMax = includeMax; |
| 211 |
| 212 if (other.min == null) { |
| 213 // Do nothing. |
| 214 } else if (intersectMin == null || intersectMin < other.min) { |
| 215 intersectMin = other.min; |
| 216 intersectIncludeMin = other.includeMin; |
| 217 } else if (intersectMin == other.min && !other.includeMin) { |
| 218 // The edges are the same, but one is exclusive, make it exclusive. |
| 219 intersectIncludeMin = false; |
| 220 } |
| 221 |
| 222 if (other.max == null) { |
| 223 // Do nothing. |
| 224 } else if (intersectMax == null || intersectMax > other.max) { |
| 225 intersectMax = other.max; |
| 226 intersectIncludeMax = other.includeMax; |
| 227 } else if (intersectMax == other.max && !other.includeMax) { |
| 228 // The edges are the same, but one is exclusive, make it exclusive. |
| 229 intersectIncludeMax = false; |
| 230 } |
| 231 |
| 232 if (intersectMin == null && intersectMax == null) { |
| 233 // Open range. |
| 234 return new VersionRange(); |
| 235 } |
| 236 |
| 237 // If the range is just a single version. |
| 238 if (intersectMin == intersectMax) { |
| 239 // If both ends are inclusive, allow that version. |
| 240 if (intersectIncludeMin && intersectIncludeMax) return intersectMin; |
| 241 |
| 242 // Otherwise, no versions. |
| 243 return VersionConstraint.empty; |
| 244 } |
| 245 |
| 246 if (intersectMin != null && intersectMax != null && |
| 247 intersectMin > intersectMax) { |
| 248 // Non-overlapping ranges, so empty. |
| 249 return VersionConstraint.empty; |
| 250 } |
| 251 |
| 252 // If we got here, there is an actual range. |
| 253 return new VersionRange(min: intersectMin, max: intersectMax, |
| 254 includeMin: intersectIncludeMin, includeMax: intersectIncludeMax); |
| 255 } |
| 256 |
| 257 throw new ArgumentError('Unknown VersionConstraint type $other.'); |
| 258 } |
| 259 |
| 260 VersionConstraint union(VersionConstraint other) { |
| 261 if (other is Version) { |
| 262 if (allows(other)) return this; |
| 263 |
| 264 if (other == min) { |
| 265 return new VersionRange( |
| 266 min: this.min, max: this.max, |
| 267 includeMin: true, includeMax: this.includeMax); |
| 268 } |
| 269 |
| 270 if (other == max) { |
| 271 return new VersionRange( |
| 272 min: this.min, max: this.max, |
| 273 includeMin: this.includeMin, includeMax: true); |
| 274 } |
| 275 |
| 276 return new VersionConstraint.unionOf([this, other]); |
| 277 } |
| 278 |
| 279 if (other is VersionRange) { |
| 280 // If the two ranges don't overlap, we won't be able to create a single |
| 281 // VersionRange for both of them. |
| 282 var edgesTouch = (max == other.min && (includeMax || other.includeMin)) || |
| 283 (min == other.max && (includeMin || other.includeMax)); |
| 284 if (!edgesTouch && !allowsAny(other)) { |
| 285 return new VersionConstraint.unionOf([this, other]); |
| 286 } |
| 287 |
| 288 var unionMin = min; |
| 289 var unionIncludeMin = includeMin; |
| 290 var unionMax = max; |
| 291 var unionIncludeMax = includeMax; |
| 292 |
| 293 if (unionMin == null) { |
| 294 // Do nothing. |
| 295 } else if (other.min == null || other.min < min) { |
| 296 unionMin = other.min; |
| 297 unionIncludeMin = other.includeMin; |
| 298 } else if (min == other.min && other.includeMin) { |
| 299 // If the edges are the same but one is inclusive, make it inclusive. |
| 300 unionIncludeMin = true; |
| 301 } |
| 302 |
| 303 if (unionMax == null) { |
| 304 // Do nothing. |
| 305 } else if (other.max == null || other.max > max) { |
| 306 unionMax = other.max; |
| 307 unionIncludeMax = other.includeMax; |
| 308 } else if (max == other.max && other.includeMax) { |
| 309 // If the edges are the same but one is inclusive, make it inclusive. |
| 310 unionIncludeMax = true; |
| 311 } |
| 312 |
| 313 return new VersionRange(min: unionMin, max: unionMax, |
| 314 includeMin: unionIncludeMin, includeMax: unionIncludeMax); |
| 315 } |
| 316 |
| 317 return new VersionConstraint.unionOf([this, other]); |
| 318 } |
| 319 |
| 320 String toString() { |
| 321 var buffer = new StringBuffer(); |
| 322 |
| 323 if (min != null) { |
| 324 buffer.write(includeMin ? '>=' : '>'); |
| 325 buffer.write(min); |
| 326 } |
| 327 |
| 328 if (max != null) { |
| 329 if (min != null) buffer.write(' '); |
| 330 buffer.write(includeMax ? '<=' : '<'); |
| 331 buffer.write(max); |
| 332 } |
| 333 |
| 334 if (min == null && max == null) buffer.write('any'); |
| 335 return buffer.toString(); |
| 336 } |
| 337 } |
OLD | NEW |