1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-28 10:09:20 -05:00
denoland-deno/cli/semver/mod.rs
David Sherret 600fff79cd
refactor(semver): generalize semver related structs (#17605)
- Generalizes the npm version code (ex. `NpmVersion` -> `Version`,
`NpmVersionReq` -> `VersionReq`). This is a slow refactor towards
extracting out this code for deno specifiers and better usage in
deno_graph.
- Removes `SpecifierVersionReq`. Consolidates `NpmVersionReq` and
`SpecifierVersionReq` to just `VersionReq`
- Removes `NpmVersionMatcher`. This now just looks at `VersionReq`.
- Paves the way to allow us to create `NpmPackageReference`'s from a
package.json's dependencies/dev dependencies
(`VersionReq::parse_from_npm`).
2023-01-31 21:27:40 -05:00

200 lines
4.8 KiB
Rust

// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::cmp::Ordering;
use std::fmt;
use deno_core::error::AnyError;
use serde::Deserialize;
use serde::Serialize;
mod npm;
mod range;
mod specifier;
use self::npm::parse_npm_version_req;
pub use self::range::Partial;
pub use self::range::VersionBoundKind;
pub use self::range::VersionRange;
pub use self::range::VersionRangeSet;
pub use self::range::XRange;
use self::specifier::parse_version_req_from_specifier;
#[derive(
Clone, Debug, PartialEq, Eq, Default, Hash, Serialize, Deserialize,
)]
pub struct Version {
pub major: u64,
pub minor: u64,
pub patch: u64,
pub pre: Vec<String>,
pub build: Vec<String>,
}
impl Version {
pub fn parse_from_npm(text: &str) -> Result<Version, AnyError> {
npm::parse_npm_version(text)
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
if !self.pre.is_empty() {
write!(f, "-")?;
for (i, part) in self.pre.iter().enumerate() {
if i > 0 {
write!(f, ".")?;
}
write!(f, "{part}")?;
}
}
if !self.build.is_empty() {
write!(f, "+")?;
for (i, part) in self.build.iter().enumerate() {
if i > 0 {
write!(f, ".")?;
}
write!(f, "{part}")?;
}
}
Ok(())
}
}
impl std::cmp::PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
let cmp_result = self.major.cmp(&other.major);
if cmp_result != Ordering::Equal {
return cmp_result;
}
let cmp_result = self.minor.cmp(&other.minor);
if cmp_result != Ordering::Equal {
return cmp_result;
}
let cmp_result = self.patch.cmp(&other.patch);
if cmp_result != Ordering::Equal {
return cmp_result;
}
// only compare the pre-release and not the build as node-semver does
if self.pre.is_empty() && other.pre.is_empty() {
Ordering::Equal
} else if !self.pre.is_empty() && other.pre.is_empty() {
Ordering::Less
} else if self.pre.is_empty() && !other.pre.is_empty() {
Ordering::Greater
} else {
let mut i = 0;
loop {
let a = self.pre.get(i);
let b = other.pre.get(i);
if a.is_none() && b.is_none() {
return Ordering::Equal;
}
// https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/internal/identifiers.js
let a = match a {
Some(a) => a,
None => return Ordering::Less,
};
let b = match b {
Some(b) => b,
None => return Ordering::Greater,
};
// prefer numbers
if let Ok(a_num) = a.parse::<u64>() {
if let Ok(b_num) = b.parse::<u64>() {
return a_num.cmp(&b_num);
} else {
return Ordering::Less;
}
} else if b.parse::<u64>().is_ok() {
return Ordering::Greater;
}
let cmp_result = a.cmp(b);
if cmp_result != Ordering::Equal {
return cmp_result;
}
i += 1;
}
}
}
}
pub(super) fn is_valid_tag(value: &str) -> bool {
// we use the same rules as npm tags
npm::is_valid_npm_tag(value)
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RangeSetOrTag {
RangeSet(VersionRangeSet),
Tag(String),
}
/// A version requirement found in an npm package's dependencies.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct VersionReq {
raw_text: String,
inner: RangeSetOrTag,
}
impl VersionReq {
/// Creates a version requirement without examining the raw text.
pub fn from_raw_text_and_inner(
raw_text: String,
inner: RangeSetOrTag,
) -> Self {
Self { raw_text, inner }
}
pub fn parse_from_specifier(specifier: &str) -> Result<Self, AnyError> {
parse_version_req_from_specifier(specifier)
}
pub fn parse_from_npm(text: &str) -> Result<Self, AnyError> {
parse_npm_version_req(text)
}
#[cfg(test)]
pub fn inner(&self) -> &RangeSetOrTag {
&self.inner
}
pub fn tag(&self) -> Option<&str> {
match &self.inner {
RangeSetOrTag::RangeSet(_) => None,
RangeSetOrTag::Tag(tag) => Some(tag.as_str()),
}
}
pub fn matches(&self, version: &Version) -> bool {
match &self.inner {
RangeSetOrTag::RangeSet(range_set) => range_set.satisfies(version),
RangeSetOrTag::Tag(_) => panic!(
"programming error: cannot use matches with a tag: {}",
self.raw_text
),
}
}
pub fn version_text(&self) -> &str {
&self.raw_text
}
}
impl fmt::Display for VersionReq {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.raw_text)
}
}