// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use std::cmp::Ordering; use serde::Deserialize; use serde::Serialize; use super::NpmVersion; /// Collection of ranges. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VersionRangeSet(pub Vec); impl VersionRangeSet { pub fn satisfies(&self, version: &NpmVersion) -> bool { self.0.iter().any(|r| r.satisfies(version)) } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum RangeBound { Version(VersionBound), Unbounded, // matches everything } impl RangeBound { pub fn inclusive(version: NpmVersion) -> Self { Self::version(VersionBoundKind::Inclusive, version) } pub fn exclusive(version: NpmVersion) -> Self { Self::version(VersionBoundKind::Exclusive, version) } pub fn version(kind: VersionBoundKind, version: NpmVersion) -> Self { Self::Version(VersionBound::new(kind, version)) } pub fn clamp_start(&self, other: &RangeBound) -> RangeBound { match &self { RangeBound::Unbounded => other.clone(), RangeBound::Version(self_bound) => RangeBound::Version(match &other { RangeBound::Unbounded => self_bound.clone(), RangeBound::Version(other_bound) => { match self_bound.version.cmp(&other_bound.version) { Ordering::Greater => self_bound.clone(), Ordering::Less => other_bound.clone(), Ordering::Equal => match self_bound.kind { VersionBoundKind::Exclusive => self_bound.clone(), VersionBoundKind::Inclusive => other_bound.clone(), }, } } }), } } pub fn clamp_end(&self, other: &RangeBound) -> RangeBound { match &self { RangeBound::Unbounded => other.clone(), RangeBound::Version(self_bound) => { RangeBound::Version(match other { RangeBound::Unbounded => self_bound.clone(), RangeBound::Version(other_bound) => { match self_bound.version.cmp(&other_bound.version) { // difference with above is the next two lines are switched Ordering::Greater => other_bound.clone(), Ordering::Less => self_bound.clone(), Ordering::Equal => match self_bound.kind { VersionBoundKind::Exclusive => self_bound.clone(), VersionBoundKind::Inclusive => other_bound.clone(), }, } } }) } } } pub fn has_pre_with_exact_major_minor_patch( &self, version: &NpmVersion, ) -> bool { if let RangeBound::Version(self_version) = &self { if !self_version.version.pre.is_empty() && self_version.version.major == version.major && self_version.version.minor == version.minor && self_version.version.patch == version.patch { return true; } } false } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum VersionBoundKind { Inclusive, Exclusive, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct VersionBound { pub kind: VersionBoundKind, pub version: NpmVersion, } impl VersionBound { pub fn new(kind: VersionBoundKind, version: NpmVersion) -> Self { Self { kind, version } } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct VersionRange { pub start: RangeBound, pub end: RangeBound, } impl VersionRange { pub fn all() -> VersionRange { VersionRange { start: RangeBound::Version(VersionBound { kind: VersionBoundKind::Inclusive, version: NpmVersion::default(), }), end: RangeBound::Unbounded, } } pub fn none() -> VersionRange { VersionRange { start: RangeBound::Version(VersionBound { kind: VersionBoundKind::Inclusive, version: NpmVersion::default(), }), end: RangeBound::Version(VersionBound { kind: VersionBoundKind::Exclusive, version: NpmVersion::default(), }), } } /// If this range won't match anything. pub fn is_none(&self) -> bool { if let RangeBound::Version(end) = &self.end { end.kind == VersionBoundKind::Exclusive && end.version.major == 0 && end.version.minor == 0 && end.version.patch == 0 } else { false } } pub fn satisfies(&self, version: &NpmVersion) -> bool { let satisfies = self.min_satisfies(version) && self.max_satisfies(version); if satisfies && !version.pre.is_empty() { // check either side of the range has a pre and same version self.start.has_pre_with_exact_major_minor_patch(version) || self.end.has_pre_with_exact_major_minor_patch(version) } else { satisfies } } fn min_satisfies(&self, version: &NpmVersion) -> bool { match &self.start { RangeBound::Unbounded => true, RangeBound::Version(bound) => match version.cmp(&bound.version) { Ordering::Less => false, Ordering::Equal => bound.kind == VersionBoundKind::Inclusive, Ordering::Greater => true, }, } } fn max_satisfies(&self, version: &NpmVersion) -> bool { match &self.end { RangeBound::Unbounded => true, RangeBound::Version(bound) => match version.cmp(&bound.version) { Ordering::Less => true, Ordering::Equal => bound.kind == VersionBoundKind::Inclusive, Ordering::Greater => false, }, } } pub fn clamp(&self, range: &VersionRange) -> VersionRange { let start = self.start.clamp_start(&range.start); let end = self.end.clamp_end(&range.end); // clamp the start range to the end when greater let start = start.clamp_end(&end); VersionRange { start, end } } } /// A range that could be a wildcard or number value. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum XRange { Wildcard, Val(u64), } /// A partial version. #[derive(Debug, Clone)] pub struct Partial { pub major: XRange, pub minor: XRange, pub patch: XRange, pub pre: Vec, pub build: Vec, } impl Partial { pub fn as_tilde_version_range(&self) -> VersionRange { // tilde ranges allow patch-level changes let end = match self.major { XRange::Wildcard => return VersionRange::all(), XRange::Val(major) => match self.minor { XRange::Wildcard => NpmVersion { major: major + 1, minor: 0, patch: 0, pre: Vec::new(), build: Vec::new(), }, XRange::Val(minor) => NpmVersion { major, minor: minor + 1, patch: 0, pre: Vec::new(), build: Vec::new(), }, }, }; VersionRange { start: self.as_lower_bound(), end: RangeBound::exclusive(end), } } pub fn as_caret_version_range(&self) -> VersionRange { // partial ranges allow patch and minor updates, except when // leading parts are < 1 in which case it will only bump the // first non-zero or patch part let end = match self.major { XRange::Wildcard => return VersionRange::all(), XRange::Val(major) => { let next_major = NpmVersion { major: major + 1, ..Default::default() }; if major > 0 { next_major } else { match self.minor { XRange::Wildcard => next_major, XRange::Val(minor) => { let next_minor = NpmVersion { minor: minor + 1, ..Default::default() }; if minor > 0 { next_minor } else { match self.patch { XRange::Wildcard => next_minor, XRange::Val(patch) => NpmVersion { patch: patch + 1, ..Default::default() }, } } } } } } }; VersionRange { start: self.as_lower_bound(), end: RangeBound::Version(VersionBound { kind: VersionBoundKind::Exclusive, version: end, }), } } pub fn as_lower_bound(&self) -> RangeBound { RangeBound::inclusive(NpmVersion { major: match self.major { XRange::Val(val) => val, XRange::Wildcard => 0, }, minor: match self.minor { XRange::Val(val) => val, XRange::Wildcard => 0, }, patch: match self.patch { XRange::Val(val) => val, XRange::Wildcard => 0, }, pre: self.pre.clone(), build: self.build.clone(), }) } pub fn as_upper_bound(&self) -> RangeBound { let mut end = NpmVersion::default(); let mut kind = VersionBoundKind::Inclusive; match self.patch { XRange::Wildcard => { end.minor += 1; kind = VersionBoundKind::Exclusive; } XRange::Val(val) => { end.patch = val; } } match self.minor { XRange::Wildcard => { end.minor = 0; end.major += 1; kind = VersionBoundKind::Exclusive; } XRange::Val(val) => { end.minor += val; } } match self.major { XRange::Wildcard => { return RangeBound::Unbounded; } XRange::Val(val) => { end.major += val; } } if kind == VersionBoundKind::Inclusive { end.pre = self.pre.clone(); } RangeBound::version(kind, end) } pub fn as_equal_range(&self) -> VersionRange { let major = match self.major { XRange::Wildcard => { return self.as_greater_range(VersionBoundKind::Inclusive) } XRange::Val(val) => val, }; let minor = match self.minor { XRange::Wildcard => { return self.as_greater_range(VersionBoundKind::Inclusive) } XRange::Val(val) => val, }; let patch = match self.patch { XRange::Wildcard => { return self.as_greater_range(VersionBoundKind::Inclusive) } XRange::Val(val) => val, }; let version = NpmVersion { major, minor, patch, pre: self.pre.clone(), build: self.build.clone(), }; VersionRange { start: RangeBound::inclusive(version.clone()), end: RangeBound::inclusive(version), } } pub fn as_greater_than( &self, mut start_kind: VersionBoundKind, ) -> VersionRange { let major = match self.major { XRange::Wildcard => match start_kind { VersionBoundKind::Inclusive => return VersionRange::all(), VersionBoundKind::Exclusive => return VersionRange::none(), }, XRange::Val(major) => major, }; let mut start = NpmVersion::default(); if start_kind == VersionBoundKind::Inclusive { start.pre = self.pre.clone(); } start.major = major; match self.minor { XRange::Wildcard => { if start_kind == VersionBoundKind::Exclusive { start_kind = VersionBoundKind::Inclusive; start.major += 1; } } XRange::Val(minor) => { start.minor = minor; } } match self.patch { XRange::Wildcard => { if start_kind == VersionBoundKind::Exclusive { start_kind = VersionBoundKind::Inclusive; start.minor += 1; } } XRange::Val(patch) => { start.patch = patch; } } VersionRange { start: RangeBound::version(start_kind, start), end: RangeBound::Unbounded, } } pub fn as_less_than(&self, mut end_kind: VersionBoundKind) -> VersionRange { let major = match self.major { XRange::Wildcard => match end_kind { VersionBoundKind::Inclusive => return VersionRange::all(), VersionBoundKind::Exclusive => return VersionRange::none(), }, XRange::Val(major) => major, }; let mut end = NpmVersion { major, ..Default::default() }; match self.minor { XRange::Wildcard => { if end_kind == VersionBoundKind::Inclusive { end.major += 1; } end_kind = VersionBoundKind::Exclusive; } XRange::Val(minor) => { end.minor = minor; } } match self.patch { XRange::Wildcard => { if end_kind == VersionBoundKind::Inclusive { end.minor += 1; } end_kind = VersionBoundKind::Exclusive; } XRange::Val(patch) => { end.patch = patch; } } if end_kind == VersionBoundKind::Inclusive { end.pre = self.pre.clone(); } VersionRange { start: RangeBound::Unbounded, end: RangeBound::version(end_kind, end), } } pub fn as_greater_range(&self, start_kind: VersionBoundKind) -> VersionRange { let major = match self.major { XRange::Wildcard => return VersionRange::all(), XRange::Val(major) => major, }; let mut start = NpmVersion::default(); let mut end = NpmVersion::default(); start.major = major; end.major = major; match self.patch { XRange::Wildcard => { if self.minor != XRange::Wildcard { end.minor += 1; } } XRange::Val(patch) => { start.patch = patch; end.patch = patch; } } match self.minor { XRange::Wildcard => { end.major += 1; } XRange::Val(minor) => { start.minor = minor; end.minor += minor; } } let end_kind = if start_kind == VersionBoundKind::Inclusive && start == end { VersionBoundKind::Inclusive } else { VersionBoundKind::Exclusive }; VersionRange { start: RangeBound::version(start_kind, start), end: RangeBound::version(end_kind, end), } } }