mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
fix(npm): better node version and version requirement compatibility (#15714)
This commit is contained in:
parent
20c835407c
commit
e1d7d7b0e3
13 changed files with 1785 additions and 241 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -829,6 +829,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log 0.4.17",
|
"log 0.4.17",
|
||||||
"mitata",
|
"mitata",
|
||||||
|
"monch",
|
||||||
"nix",
|
"nix",
|
||||||
"node_resolver",
|
"node_resolver",
|
||||||
"notify",
|
"notify",
|
||||||
|
@ -1211,12 +1212,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_task_shell"
|
name = "deno_task_shell"
|
||||||
version = "0.5.0"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8348a58271a9672a735850dd2293770c83344759f8d18e4636e53de9e4605d2"
|
checksum = "91059ae4dccefd55b84e0582683fe41e78b4287a2fe283962ea0a1698ea43d4d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures",
|
"futures",
|
||||||
|
"monch",
|
||||||
"os_pipe",
|
"os_pipe",
|
||||||
"path-dedot",
|
"path-dedot",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -2724,6 +2726,12 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "monch"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "350537f27a69018269e534582e2f1ec532ea7078b06485fdd4db0509bd70feb8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
|
@ -53,7 +53,7 @@ deno_emit = "0.8.0"
|
||||||
deno_graph = "0.33.0"
|
deno_graph = "0.33.0"
|
||||||
deno_lint = { version = "0.32.0", features = ["docs"] }
|
deno_lint = { version = "0.32.0", features = ["docs"] }
|
||||||
deno_runtime = { version = "0.74.0", path = "../runtime" }
|
deno_runtime = { version = "0.74.0", path = "../runtime" }
|
||||||
deno_task_shell = "0.5.0"
|
deno_task_shell = "0.5.2"
|
||||||
|
|
||||||
atty = "=0.2.14"
|
atty = "=0.2.14"
|
||||||
base64 = "=0.13.0"
|
base64 = "=0.13.0"
|
||||||
|
@ -78,7 +78,8 @@ indexmap = "1.8.1"
|
||||||
jsonc-parser = { version = "=0.21.0", features = ["serde"] }
|
jsonc-parser = { version = "=0.21.0", features = ["serde"] }
|
||||||
libc = "=0.2.126"
|
libc = "=0.2.126"
|
||||||
log = { version = "=0.4.17", features = ["serde"] }
|
log = { version = "=0.4.17", features = ["serde"] }
|
||||||
mitata = '=0.0.7'
|
mitata = "=0.0.7"
|
||||||
|
monch = "=0.2.0"
|
||||||
node_resolver = "=0.1.1"
|
node_resolver = "=0.1.1"
|
||||||
notify = "=5.0.0-pre.15"
|
notify = "=5.0.0-pre.15"
|
||||||
once_cell = "=1.12.0"
|
once_cell = "=1.12.0"
|
||||||
|
|
|
@ -17,6 +17,7 @@ use crate::deno_dir::DenoDir;
|
||||||
use crate::file_fetcher::CacheSetting;
|
use crate::file_fetcher::CacheSetting;
|
||||||
use crate::fs_util;
|
use crate::fs_util;
|
||||||
|
|
||||||
|
use super::semver::NpmVersion;
|
||||||
use super::tarball::verify_and_extract_tarball;
|
use super::tarball::verify_and_extract_tarball;
|
||||||
use super::NpmPackageId;
|
use super::NpmPackageId;
|
||||||
use super::NpmPackageVersionDistInfo;
|
use super::NpmPackageVersionDistInfo;
|
||||||
|
@ -147,7 +148,7 @@ impl ReadonlyNpmCache {
|
||||||
|
|
||||||
Some(NpmPackageId {
|
Some(NpmPackageId {
|
||||||
name,
|
name,
|
||||||
version: semver::Version::parse(version).unwrap(),
|
version: NpmVersion::parse(version).unwrap(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,6 +282,7 @@ mod test {
|
||||||
use deno_core::url::Url;
|
use deno_core::url::Url;
|
||||||
|
|
||||||
use super::ReadonlyNpmCache;
|
use super::ReadonlyNpmCache;
|
||||||
|
use crate::npm::semver::NpmVersion;
|
||||||
use crate::npm::NpmPackageId;
|
use crate::npm::NpmPackageId;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -294,7 +296,7 @@ mod test {
|
||||||
cache.package_folder(
|
cache.package_folder(
|
||||||
&NpmPackageId {
|
&NpmPackageId {
|
||||||
name: "json".to_string(),
|
name: "json".to_string(),
|
||||||
version: semver::Version::parse("1.2.5").unwrap(),
|
version: NpmVersion::parse("1.2.5").unwrap(),
|
||||||
},
|
},
|
||||||
®istry_url,
|
®istry_url,
|
||||||
),
|
),
|
||||||
|
@ -317,7 +319,7 @@ mod test {
|
||||||
cache.package_folder(
|
cache.package_folder(
|
||||||
&NpmPackageId {
|
&NpmPackageId {
|
||||||
name: "JSON".to_string(),
|
name: "JSON".to_string(),
|
||||||
version: semver::Version::parse("1.2.5").unwrap(),
|
version: NpmVersion::parse("1.2.5").unwrap(),
|
||||||
},
|
},
|
||||||
®istry_url,
|
®istry_url,
|
||||||
),
|
),
|
||||||
|
@ -331,7 +333,7 @@ mod test {
|
||||||
cache.package_folder(
|
cache.package_folder(
|
||||||
&NpmPackageId {
|
&NpmPackageId {
|
||||||
name: "@types/JSON".to_string(),
|
name: "@types/JSON".to_string(),
|
||||||
version: semver::Version::parse("1.2.5").unwrap(),
|
version: NpmVersion::parse("1.2.5").unwrap(),
|
||||||
},
|
},
|
||||||
®istry_url,
|
®istry_url,
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
mod cache;
|
mod cache;
|
||||||
mod registry;
|
mod registry;
|
||||||
mod resolution;
|
mod resolution;
|
||||||
|
mod semver;
|
||||||
mod tarball;
|
mod tarball;
|
||||||
mod version_req;
|
|
||||||
|
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
|
@ -23,7 +23,7 @@ use crate::fs_util;
|
||||||
use crate::http_cache::CACHE_PERM;
|
use crate::http_cache::CACHE_PERM;
|
||||||
|
|
||||||
use super::cache::NpmCache;
|
use super::cache::NpmCache;
|
||||||
use super::version_req::NpmVersionReq;
|
use super::semver::NpmVersionReq;
|
||||||
|
|
||||||
// npm registry docs: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md
|
// npm registry docs: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,13 @@ use super::registry::NpmPackageInfo;
|
||||||
use super::registry::NpmPackageVersionDistInfo;
|
use super::registry::NpmPackageVersionDistInfo;
|
||||||
use super::registry::NpmPackageVersionInfo;
|
use super::registry::NpmPackageVersionInfo;
|
||||||
use super::registry::NpmRegistryApi;
|
use super::registry::NpmRegistryApi;
|
||||||
use super::version_req::SpecifierVersionReq;
|
use super::semver::NpmVersion;
|
||||||
|
use super::semver::SpecifierVersionReq;
|
||||||
|
|
||||||
/// The version matcher used for npm schemed urls is more strict than
|
/// The version matcher used for npm schemed urls is more strict than
|
||||||
/// the one used by npm packages.
|
/// the one used by npm packages.
|
||||||
pub trait NpmVersionMatcher {
|
pub trait NpmVersionMatcher {
|
||||||
fn matches(&self, version: &semver::Version) -> bool;
|
fn matches(&self, version: &NpmVersion) -> bool;
|
||||||
fn version_text(&self) -> String;
|
fn version_text(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +105,7 @@ impl std::fmt::Display for NpmPackageReq {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NpmVersionMatcher for NpmPackageReq {
|
impl NpmVersionMatcher for NpmPackageReq {
|
||||||
fn matches(&self, version: &semver::Version) -> bool {
|
fn matches(&self, version: &NpmVersion) -> bool {
|
||||||
match &self.version_req {
|
match &self.version_req {
|
||||||
Some(req) => req.matches(version),
|
Some(req) => req.matches(version),
|
||||||
None => version.pre.is_empty(),
|
None => version.pre.is_empty(),
|
||||||
|
@ -123,7 +124,7 @@ impl NpmVersionMatcher for NpmPackageReq {
|
||||||
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||||
pub struct NpmPackageId {
|
pub struct NpmPackageId {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub version: semver::Version,
|
pub version: NpmVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NpmPackageId {
|
impl NpmPackageId {
|
||||||
|
@ -154,8 +155,8 @@ pub struct NpmResolutionPackage {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct NpmResolutionSnapshot {
|
pub struct NpmResolutionSnapshot {
|
||||||
package_reqs: HashMap<NpmPackageReq, semver::Version>,
|
package_reqs: HashMap<NpmPackageReq, NpmVersion>,
|
||||||
packages_by_name: HashMap<String, Vec<semver::Version>>,
|
packages_by_name: HashMap<String, Vec<NpmVersion>>,
|
||||||
packages: HashMap<NpmPackageId, NpmResolutionPackage>,
|
packages: HashMap<NpmPackageId, NpmResolutionPackage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,8 +231,8 @@ impl NpmResolutionSnapshot {
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
version_matcher: &impl NpmVersionMatcher,
|
version_matcher: &impl NpmVersionMatcher,
|
||||||
) -> Option<semver::Version> {
|
) -> Option<NpmVersion> {
|
||||||
let mut maybe_best_version: Option<&semver::Version> = None;
|
let mut maybe_best_version: Option<&NpmVersion> = None;
|
||||||
if let Some(versions) = self.packages_by_name.get(name) {
|
if let Some(versions) = self.packages_by_name.get(name) {
|
||||||
for version in versions {
|
for version in versions {
|
||||||
if version_matcher.matches(version) {
|
if version_matcher.matches(version) {
|
||||||
|
@ -472,7 +473,7 @@ impl NpmResolution {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct VersionAndInfo {
|
struct VersionAndInfo {
|
||||||
version: semver::Version,
|
version: NpmVersion,
|
||||||
info: NpmPackageVersionInfo,
|
info: NpmPackageVersionInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,7 +485,7 @@ fn get_resolved_package_version_and_info(
|
||||||
) -> Result<VersionAndInfo, AnyError> {
|
) -> Result<VersionAndInfo, AnyError> {
|
||||||
let mut maybe_best_version: Option<VersionAndInfo> = None;
|
let mut maybe_best_version: Option<VersionAndInfo> = None;
|
||||||
for (_, version_info) in info.versions.into_iter() {
|
for (_, version_info) in info.versions.into_iter() {
|
||||||
let version = semver::Version::parse(&version_info.version)?;
|
let version = NpmVersion::parse(&version_info.version)?;
|
||||||
if version_matcher.matches(&version) {
|
if version_matcher.matches(&version) {
|
||||||
let is_best_version = maybe_best_version
|
let is_best_version = maybe_best_version
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
38
cli/npm/semver/errors.rs
Normal file
38
cli/npm/semver/errors.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use deno_core::anyhow::bail;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use monch::ParseError;
|
||||||
|
use monch::ParseErrorFailure;
|
||||||
|
use monch::ParseResult;
|
||||||
|
|
||||||
|
pub fn with_failure_handling<'a, T>(
|
||||||
|
combinator: impl Fn(&'a str) -> ParseResult<T>,
|
||||||
|
) -> impl Fn(&'a str) -> Result<T, AnyError> {
|
||||||
|
move |input| match combinator(input) {
|
||||||
|
Ok((input, result)) => {
|
||||||
|
if !input.is_empty() {
|
||||||
|
error_for_failure(fail_for_trailing_input(input))
|
||||||
|
} else {
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ParseError::Backtrace) => {
|
||||||
|
error_for_failure(fail_for_trailing_input(input))
|
||||||
|
}
|
||||||
|
Err(ParseError::Failure(e)) => error_for_failure(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_for_failure<T>(e: ParseErrorFailure) -> Result<T, AnyError> {
|
||||||
|
bail!(
|
||||||
|
"{}\n {}\n ~",
|
||||||
|
e.message,
|
||||||
|
// truncate the output to prevent wrapping in the console
|
||||||
|
e.input.chars().take(60).collect::<String>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fail_for_trailing_input(input: &str) -> ParseErrorFailure {
|
||||||
|
ParseErrorFailure::new(input, "Unexpected character.")
|
||||||
|
}
|
956
cli/npm/semver/mod.rs
Normal file
956
cli/npm/semver/mod.rs
Normal file
|
@ -0,0 +1,956 @@
|
||||||
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use deno_core::anyhow::Context;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use monch::*;
|
||||||
|
|
||||||
|
use crate::npm::resolution::NpmVersionMatcher;
|
||||||
|
|
||||||
|
use self::errors::with_failure_handling;
|
||||||
|
use self::range::Partial;
|
||||||
|
use self::range::VersionBoundKind;
|
||||||
|
use self::range::VersionRange;
|
||||||
|
use self::range::VersionRangeSet;
|
||||||
|
|
||||||
|
use self::range::XRange;
|
||||||
|
pub use self::specifier::SpecifierVersionReq;
|
||||||
|
|
||||||
|
mod errors;
|
||||||
|
mod range;
|
||||||
|
mod specifier;
|
||||||
|
|
||||||
|
// A lot of the below is a re-implementation of parts of https://github.com/npm/node-semver
|
||||||
|
// which is Copyright (c) Isaac Z. Schlueter and Contributors (ISC License)
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)]
|
||||||
|
pub struct NpmVersion {
|
||||||
|
pub major: u64,
|
||||||
|
pub minor: u64,
|
||||||
|
pub patch: u64,
|
||||||
|
pub pre: Vec<String>,
|
||||||
|
pub build: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NpmVersion {
|
||||||
|
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 NpmVersion {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::cmp::Ord for NpmVersion {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NpmVersion {
|
||||||
|
pub fn parse(text: &str) -> Result<Self, AnyError> {
|
||||||
|
let text = text.trim();
|
||||||
|
with_failure_handling(parse_npm_version)(text)
|
||||||
|
.with_context(|| format!("Invalid npm version '{}'.", text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_npm_version(input: &str) -> ParseResult<NpmVersion> {
|
||||||
|
let (input, _) = maybe(ch('='))(input)?; // skip leading =
|
||||||
|
let (input, _) = skip_whitespace(input)?;
|
||||||
|
let (input, _) = maybe(ch('v'))(input)?; // skip leading v
|
||||||
|
let (input, _) = skip_whitespace(input)?;
|
||||||
|
let (input, major) = nr(input)?;
|
||||||
|
let (input, _) = ch('.')(input)?;
|
||||||
|
let (input, minor) = nr(input)?;
|
||||||
|
let (input, _) = ch('.')(input)?;
|
||||||
|
let (input, patch) = nr(input)?;
|
||||||
|
let (input, q) = maybe(qualifier)(input)?;
|
||||||
|
let q = q.unwrap_or_default();
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
NpmVersion {
|
||||||
|
major,
|
||||||
|
minor,
|
||||||
|
patch,
|
||||||
|
pre: q.pre,
|
||||||
|
build: q.build,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A version requirement found in an npm package's dependencies.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct NpmVersionReq {
|
||||||
|
raw_text: String,
|
||||||
|
range_set: VersionRangeSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NpmVersionMatcher for NpmVersionReq {
|
||||||
|
fn matches(&self, version: &NpmVersion) -> bool {
|
||||||
|
self.satisfies(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version_text(&self) -> String {
|
||||||
|
self.raw_text.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NpmVersionReq {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", &self.raw_text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NpmVersionReq {
|
||||||
|
pub fn parse(text: &str) -> Result<Self, AnyError> {
|
||||||
|
let text = text.trim();
|
||||||
|
with_failure_handling(parse_npm_version_req)(text)
|
||||||
|
.with_context(|| format!("Invalid npm version requirement '{}'.", text))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn satisfies(&self, version: &NpmVersion) -> bool {
|
||||||
|
self.range_set.satisfies(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_npm_version_req(input: &str) -> ParseResult<NpmVersionReq> {
|
||||||
|
map(range_set, |set| NpmVersionReq {
|
||||||
|
raw_text: input.to_string(),
|
||||||
|
range_set: set,
|
||||||
|
})(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/npm/node-semver/tree/4907647d169948a53156502867ed679268063a9f#range-grammar
|
||||||
|
// range-set ::= range ( logical-or range ) *
|
||||||
|
// logical-or ::= ( ' ' ) * '||' ( ' ' ) *
|
||||||
|
// range ::= hyphen | simple ( ' ' simple ) * | ''
|
||||||
|
// hyphen ::= partial ' - ' partial
|
||||||
|
// simple ::= primitive | partial | tilde | caret
|
||||||
|
// primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial
|
||||||
|
// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
|
||||||
|
// xr ::= 'x' | 'X' | '*' | nr
|
||||||
|
// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
|
||||||
|
// tilde ::= '~' partial
|
||||||
|
// caret ::= '^' partial
|
||||||
|
// qualifier ::= ( '-' pre )? ( '+' build )?
|
||||||
|
// pre ::= parts
|
||||||
|
// build ::= parts
|
||||||
|
// parts ::= part ( '.' part ) *
|
||||||
|
// part ::= nr | [-0-9A-Za-z]+
|
||||||
|
|
||||||
|
// range-set ::= range ( logical-or range ) *
|
||||||
|
fn range_set(input: &str) -> ParseResult<VersionRangeSet> {
|
||||||
|
if input.is_empty() {
|
||||||
|
return Ok((input, VersionRangeSet(vec![VersionRange::all()])));
|
||||||
|
}
|
||||||
|
map(if_not_empty(separated_list(range, logical_or)), |ranges| {
|
||||||
|
// filter out the ranges that won't match anything for the tests
|
||||||
|
VersionRangeSet(ranges.into_iter().filter(|r| !r.is_none()).collect())
|
||||||
|
})(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// range ::= hyphen | simple ( ' ' simple ) * | ''
|
||||||
|
fn range(input: &str) -> ParseResult<VersionRange> {
|
||||||
|
or(
|
||||||
|
map(hyphen, |hyphen| VersionRange {
|
||||||
|
start: hyphen.start.as_lower_bound(),
|
||||||
|
end: hyphen.end.as_upper_bound(),
|
||||||
|
}),
|
||||||
|
map(separated_list(simple, whitespace), |ranges| {
|
||||||
|
let mut final_range = VersionRange::all();
|
||||||
|
for range in ranges {
|
||||||
|
final_range = final_range.clamp(&range);
|
||||||
|
}
|
||||||
|
final_range
|
||||||
|
}),
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Hyphen {
|
||||||
|
start: Partial,
|
||||||
|
end: Partial,
|
||||||
|
}
|
||||||
|
|
||||||
|
// hyphen ::= partial ' - ' partial
|
||||||
|
fn hyphen(input: &str) -> ParseResult<Hyphen> {
|
||||||
|
let (input, first) = partial(input)?;
|
||||||
|
let (input, _) = whitespace(input)?;
|
||||||
|
let (input, _) = tag("-")(input)?;
|
||||||
|
let (input, _) = whitespace(input)?;
|
||||||
|
let (input, second) = partial(input)?;
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
Hyphen {
|
||||||
|
start: first,
|
||||||
|
end: second,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// logical-or ::= ( ' ' ) * '||' ( ' ' ) *
|
||||||
|
fn logical_or(input: &str) -> ParseResult<&str> {
|
||||||
|
delimited(skip_whitespace, tag("||"), skip_whitespace)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_whitespace_or_v(input: &str) -> ParseResult<()> {
|
||||||
|
map(
|
||||||
|
pair(skip_whitespace, pair(maybe(ch('v')), skip_whitespace)),
|
||||||
|
|_| (),
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// simple ::= primitive | partial | tilde | caret
|
||||||
|
fn simple(input: &str) -> ParseResult<VersionRange> {
|
||||||
|
let tilde = pair(or(tag("~>"), tag("~")), skip_whitespace_or_v);
|
||||||
|
or4(
|
||||||
|
map(preceded(tilde, partial), |partial| {
|
||||||
|
partial.as_tilde_version_range()
|
||||||
|
}),
|
||||||
|
map(
|
||||||
|
preceded(pair(ch('^'), skip_whitespace_or_v), partial),
|
||||||
|
|partial| partial.as_caret_version_range(),
|
||||||
|
),
|
||||||
|
map(primitive, |primitive| {
|
||||||
|
let partial = primitive.partial;
|
||||||
|
match primitive.kind {
|
||||||
|
PrimitiveKind::Equal => partial.as_equal_range(),
|
||||||
|
PrimitiveKind::GreaterThan => {
|
||||||
|
partial.as_greater_than(VersionBoundKind::Exclusive)
|
||||||
|
}
|
||||||
|
PrimitiveKind::GreaterThanOrEqual => {
|
||||||
|
partial.as_greater_than(VersionBoundKind::Inclusive)
|
||||||
|
}
|
||||||
|
PrimitiveKind::LessThan => {
|
||||||
|
partial.as_less_than(VersionBoundKind::Exclusive)
|
||||||
|
}
|
||||||
|
PrimitiveKind::LessThanOrEqual => {
|
||||||
|
partial.as_less_than(VersionBoundKind::Inclusive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
map(partial, |partial| {
|
||||||
|
partial.as_greater_range(VersionBoundKind::Inclusive)
|
||||||
|
}),
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum PrimitiveKind {
|
||||||
|
GreaterThan,
|
||||||
|
LessThan,
|
||||||
|
GreaterThanOrEqual,
|
||||||
|
LessThanOrEqual,
|
||||||
|
Equal,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Primitive {
|
||||||
|
kind: PrimitiveKind,
|
||||||
|
partial: Partial,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn primitive(input: &str) -> ParseResult<Primitive> {
|
||||||
|
let (input, kind) = primitive_kind(input)?;
|
||||||
|
let (input, _) = skip_whitespace(input)?;
|
||||||
|
let (input, partial) = partial(input)?;
|
||||||
|
Ok((input, Primitive { kind, partial }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn primitive_kind(input: &str) -> ParseResult<PrimitiveKind> {
|
||||||
|
or5(
|
||||||
|
map(tag(">="), |_| PrimitiveKind::GreaterThanOrEqual),
|
||||||
|
map(tag("<="), |_| PrimitiveKind::LessThanOrEqual),
|
||||||
|
map(ch('<'), |_| PrimitiveKind::LessThan),
|
||||||
|
map(ch('>'), |_| PrimitiveKind::GreaterThan),
|
||||||
|
map(ch('='), |_| PrimitiveKind::Equal),
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
|
||||||
|
fn partial(input: &str) -> ParseResult<Partial> {
|
||||||
|
let (input, _) = maybe(ch('v'))(input)?; // skip leading v
|
||||||
|
let (input, major) = xr()(input)?;
|
||||||
|
let (input, maybe_minor) = maybe(preceded(ch('.'), xr()))(input)?;
|
||||||
|
let (input, maybe_patch) = if maybe_minor.is_some() {
|
||||||
|
maybe(preceded(ch('.'), xr()))(input)?
|
||||||
|
} else {
|
||||||
|
(input, None)
|
||||||
|
};
|
||||||
|
let (input, qual) = if maybe_patch.is_some() {
|
||||||
|
maybe(qualifier)(input)?
|
||||||
|
} else {
|
||||||
|
(input, None)
|
||||||
|
};
|
||||||
|
let qual = qual.unwrap_or_default();
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
Partial {
|
||||||
|
major,
|
||||||
|
minor: maybe_minor.unwrap_or(XRange::Wildcard),
|
||||||
|
patch: maybe_patch.unwrap_or(XRange::Wildcard),
|
||||||
|
pre: qual.pre,
|
||||||
|
build: qual.build,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// xr ::= 'x' | 'X' | '*' | nr
|
||||||
|
fn xr<'a>() -> impl Fn(&'a str) -> ParseResult<'a, XRange> {
|
||||||
|
or(
|
||||||
|
map(or3(tag("x"), tag("X"), tag("*")), |_| XRange::Wildcard),
|
||||||
|
map(nr, XRange::Val),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
|
||||||
|
fn nr(input: &str) -> ParseResult<u64> {
|
||||||
|
// we do loose parsing to support people doing stuff like 01.02.03
|
||||||
|
let (input, result) =
|
||||||
|
if_not_empty(substring(skip_while(|c| c.is_ascii_digit())))(input)?;
|
||||||
|
let val = match result.parse::<u64>() {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(err) => {
|
||||||
|
return ParseError::fail(
|
||||||
|
input,
|
||||||
|
format!("Error parsing '{}' to u64.\n\n{:#}", result, err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((input, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct Qualifier {
|
||||||
|
pre: Vec<String>,
|
||||||
|
build: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// qualifier ::= ( '-' pre )? ( '+' build )?
|
||||||
|
fn qualifier(input: &str) -> ParseResult<Qualifier> {
|
||||||
|
let (input, pre_parts) = maybe(pre)(input)?;
|
||||||
|
let (input, build_parts) = maybe(build)(input)?;
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
Qualifier {
|
||||||
|
pre: pre_parts.unwrap_or_default(),
|
||||||
|
build: build_parts.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// pre ::= parts
|
||||||
|
fn pre(input: &str) -> ParseResult<Vec<String>> {
|
||||||
|
preceded(maybe(ch('-')), parts)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// build ::= parts
|
||||||
|
fn build(input: &str) -> ParseResult<Vec<String>> {
|
||||||
|
preceded(ch('+'), parts)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parts ::= part ( '.' part ) *
|
||||||
|
fn parts(input: &str) -> ParseResult<Vec<String>> {
|
||||||
|
if_not_empty(map(separated_list(part, ch('.')), |text| {
|
||||||
|
text.into_iter().map(ToOwned::to_owned).collect()
|
||||||
|
}))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// part ::= nr | [-0-9A-Za-z]+
|
||||||
|
fn part(input: &str) -> ParseResult<&str> {
|
||||||
|
// nr is in the other set, so don't bother checking for it
|
||||||
|
if_true(
|
||||||
|
take_while(|c| c.is_ascii_alphanumeric() || c == '-'),
|
||||||
|
|result| !result.is_empty(),
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct NpmVersionReqTester(NpmVersionReq);
|
||||||
|
|
||||||
|
impl NpmVersionReqTester {
|
||||||
|
fn new(text: &str) -> Self {
|
||||||
|
Self(NpmVersionReq::parse(text).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches(&self, version: &str) -> bool {
|
||||||
|
self.0.matches(&NpmVersion::parse(version).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn npm_version_req_with_v() {
|
||||||
|
assert!(NpmVersionReq::parse("v1.0.0").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn npm_version_req_exact() {
|
||||||
|
let tester = NpmVersionReqTester::new("2.1.2");
|
||||||
|
assert!(!tester.matches("2.1.1"));
|
||||||
|
assert!(tester.matches("2.1.2"));
|
||||||
|
assert!(!tester.matches("2.1.3"));
|
||||||
|
|
||||||
|
let tester = NpmVersionReqTester::new("2.1.2 || 2.1.5");
|
||||||
|
assert!(!tester.matches("2.1.1"));
|
||||||
|
assert!(tester.matches("2.1.2"));
|
||||||
|
assert!(!tester.matches("2.1.3"));
|
||||||
|
assert!(!tester.matches("2.1.4"));
|
||||||
|
assert!(tester.matches("2.1.5"));
|
||||||
|
assert!(!tester.matches("2.1.6"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn npm_version_req_minor() {
|
||||||
|
let tester = NpmVersionReqTester::new("1.1");
|
||||||
|
assert!(!tester.matches("1.0.0"));
|
||||||
|
assert!(tester.matches("1.1.0"));
|
||||||
|
assert!(tester.matches("1.1.1"));
|
||||||
|
assert!(!tester.matches("1.2.0"));
|
||||||
|
assert!(!tester.matches("1.2.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn npm_version_req_ranges() {
|
||||||
|
let tester = NpmVersionReqTester::new(">= 2.1.2 < 3.0.0 || 5.x");
|
||||||
|
assert!(!tester.matches("2.1.1"));
|
||||||
|
assert!(tester.matches("2.1.2"));
|
||||||
|
assert!(tester.matches("2.9.9"));
|
||||||
|
assert!(!tester.matches("3.0.0"));
|
||||||
|
assert!(tester.matches("5.0.0"));
|
||||||
|
assert!(tester.matches("5.1.0"));
|
||||||
|
assert!(!tester.matches("6.1.0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_cmp {
|
||||||
|
($a:expr, $b:expr, $expected:expr) => {
|
||||||
|
assert_eq!(
|
||||||
|
$a.cmp(&$b),
|
||||||
|
$expected,
|
||||||
|
"expected {} to be {:?} {}",
|
||||||
|
$a,
|
||||||
|
$expected,
|
||||||
|
$b
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! test_compare {
|
||||||
|
($a:expr, $b:expr, $expected:expr) => {
|
||||||
|
let a = NpmVersion::parse($a).unwrap();
|
||||||
|
let b = NpmVersion::parse($b).unwrap();
|
||||||
|
assert_cmp!(a, b, $expected);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_compare() {
|
||||||
|
test_compare!("1.2.3", "2.3.4", Ordering::Less);
|
||||||
|
test_compare!("1.2.3", "1.2.4", Ordering::Less);
|
||||||
|
test_compare!("1.2.3", "1.2.3", Ordering::Equal);
|
||||||
|
test_compare!("1.2.3", "1.2.2", Ordering::Greater);
|
||||||
|
test_compare!("1.2.3", "1.1.5", Ordering::Greater);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_compare_equal() {
|
||||||
|
// https://github.com/npm/node-semver/blob/bce42589d33e1a99454530a8fd52c7178e2b11c1/test/fixtures/equality.js
|
||||||
|
let fixtures = &[
|
||||||
|
("1.2.3", "v1.2.3"),
|
||||||
|
("1.2.3", "=1.2.3"),
|
||||||
|
("1.2.3", "v 1.2.3"),
|
||||||
|
("1.2.3", "= 1.2.3"),
|
||||||
|
("1.2.3", " v1.2.3"),
|
||||||
|
("1.2.3", " =1.2.3"),
|
||||||
|
("1.2.3", " v 1.2.3"),
|
||||||
|
("1.2.3", " = 1.2.3"),
|
||||||
|
("1.2.3-0", "v1.2.3-0"),
|
||||||
|
("1.2.3-0", "=1.2.3-0"),
|
||||||
|
("1.2.3-0", "v 1.2.3-0"),
|
||||||
|
("1.2.3-0", "= 1.2.3-0"),
|
||||||
|
("1.2.3-0", " v1.2.3-0"),
|
||||||
|
("1.2.3-0", " =1.2.3-0"),
|
||||||
|
("1.2.3-0", " v 1.2.3-0"),
|
||||||
|
("1.2.3-0", " = 1.2.3-0"),
|
||||||
|
("1.2.3-1", "v1.2.3-1"),
|
||||||
|
("1.2.3-1", "=1.2.3-1"),
|
||||||
|
("1.2.3-1", "v 1.2.3-1"),
|
||||||
|
("1.2.3-1", "= 1.2.3-1"),
|
||||||
|
("1.2.3-1", " v1.2.3-1"),
|
||||||
|
("1.2.3-1", " =1.2.3-1"),
|
||||||
|
("1.2.3-1", " v 1.2.3-1"),
|
||||||
|
("1.2.3-1", " = 1.2.3-1"),
|
||||||
|
("1.2.3-beta", "v1.2.3-beta"),
|
||||||
|
("1.2.3-beta", "=1.2.3-beta"),
|
||||||
|
("1.2.3-beta", "v 1.2.3-beta"),
|
||||||
|
("1.2.3-beta", "= 1.2.3-beta"),
|
||||||
|
("1.2.3-beta", " v1.2.3-beta"),
|
||||||
|
("1.2.3-beta", " =1.2.3-beta"),
|
||||||
|
("1.2.3-beta", " v 1.2.3-beta"),
|
||||||
|
("1.2.3-beta", " = 1.2.3-beta"),
|
||||||
|
("1.2.3-beta+build", " = 1.2.3-beta+otherbuild"),
|
||||||
|
("1.2.3+build", " = 1.2.3+otherbuild"),
|
||||||
|
("1.2.3-beta+build", "1.2.3-beta+otherbuild"),
|
||||||
|
("1.2.3+build", "1.2.3+otherbuild"),
|
||||||
|
(" v1.2.3+build", "1.2.3+otherbuild"),
|
||||||
|
];
|
||||||
|
for (a, b) in fixtures {
|
||||||
|
test_compare!(a, b, Ordering::Equal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_comparisons_test() {
|
||||||
|
// https://github.com/npm/node-semver/blob/bce42589d33e1a99454530a8fd52c7178e2b11c1/test/fixtures/comparisons.js
|
||||||
|
let fixtures = &[
|
||||||
|
("0.0.0", "0.0.0-foo"),
|
||||||
|
("0.0.1", "0.0.0"),
|
||||||
|
("1.0.0", "0.9.9"),
|
||||||
|
("0.10.0", "0.9.0"),
|
||||||
|
("0.99.0", "0.10.0"),
|
||||||
|
("2.0.0", "1.2.3"),
|
||||||
|
("v0.0.0", "0.0.0-foo"),
|
||||||
|
("v0.0.1", "0.0.0"),
|
||||||
|
("v1.0.0", "0.9.9"),
|
||||||
|
("v0.10.0", "0.9.0"),
|
||||||
|
("v0.99.0", "0.10.0"),
|
||||||
|
("v2.0.0", "1.2.3"),
|
||||||
|
("0.0.0", "v0.0.0-foo"),
|
||||||
|
("0.0.1", "v0.0.0"),
|
||||||
|
("1.0.0", "v0.9.9"),
|
||||||
|
("0.10.0", "v0.9.0"),
|
||||||
|
("0.99.0", "v0.10.0"),
|
||||||
|
("2.0.0", "v1.2.3"),
|
||||||
|
("1.2.3", "1.2.3-asdf"),
|
||||||
|
("1.2.3", "1.2.3-4"),
|
||||||
|
("1.2.3", "1.2.3-4-foo"),
|
||||||
|
("1.2.3-5-foo", "1.2.3-5"),
|
||||||
|
("1.2.3-5", "1.2.3-4"),
|
||||||
|
("1.2.3-5-foo", "1.2.3-5-Foo"),
|
||||||
|
("3.0.0", "2.7.2+asdf"),
|
||||||
|
("1.2.3-a.10", "1.2.3-a.5"),
|
||||||
|
("1.2.3-a.b", "1.2.3-a.5"),
|
||||||
|
("1.2.3-a.b", "1.2.3-a"),
|
||||||
|
("1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"),
|
||||||
|
("1.2.3-r2", "1.2.3-r100"),
|
||||||
|
("1.2.3-r100", "1.2.3-R2"),
|
||||||
|
];
|
||||||
|
for (a, b) in fixtures {
|
||||||
|
let a = NpmVersion::parse(a).unwrap();
|
||||||
|
let b = NpmVersion::parse(b).unwrap();
|
||||||
|
assert_cmp!(a, b, Ordering::Greater);
|
||||||
|
assert_cmp!(b, a, Ordering::Less);
|
||||||
|
assert_cmp!(a, a, Ordering::Equal);
|
||||||
|
assert_cmp!(b, b, Ordering::Equal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn range_parse() {
|
||||||
|
// https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/test/fixtures/range-parse.js
|
||||||
|
let fixtures = &[
|
||||||
|
("1.0.0 - 2.0.0", ">=1.0.0 <=2.0.0"),
|
||||||
|
("1 - 2", ">=1.0.0 <3.0.0-0"),
|
||||||
|
("1.0 - 2.0", ">=1.0.0 <2.1.0-0"),
|
||||||
|
("1.0.0", "1.0.0"),
|
||||||
|
(">=*", "*"),
|
||||||
|
("", "*"),
|
||||||
|
("*", "*"),
|
||||||
|
("*", "*"),
|
||||||
|
(">=1.0.0", ">=1.0.0"),
|
||||||
|
(">1.0.0", ">1.0.0"),
|
||||||
|
("<=2.0.0", "<=2.0.0"),
|
||||||
|
("1", ">=1.0.0 <2.0.0-0"),
|
||||||
|
("<=2.0.0", "<=2.0.0"),
|
||||||
|
("<=2.0.0", "<=2.0.0"),
|
||||||
|
("<2.0.0", "<2.0.0"),
|
||||||
|
("<2.0.0", "<2.0.0"),
|
||||||
|
(">= 1.0.0", ">=1.0.0"),
|
||||||
|
(">= 1.0.0", ">=1.0.0"),
|
||||||
|
(">= 1.0.0", ">=1.0.0"),
|
||||||
|
("> 1.0.0", ">1.0.0"),
|
||||||
|
("> 1.0.0", ">1.0.0"),
|
||||||
|
("<= 2.0.0", "<=2.0.0"),
|
||||||
|
("<= 2.0.0", "<=2.0.0"),
|
||||||
|
("<= 2.0.0", "<=2.0.0"),
|
||||||
|
("< 2.0.0", "<2.0.0"),
|
||||||
|
("<\t2.0.0", "<2.0.0"),
|
||||||
|
(">=0.1.97", ">=0.1.97"),
|
||||||
|
(">=0.1.97", ">=0.1.97"),
|
||||||
|
("0.1.20 || 1.2.4", "0.1.20||1.2.4"),
|
||||||
|
(">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"),
|
||||||
|
(">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"),
|
||||||
|
(">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"),
|
||||||
|
("||", "*"),
|
||||||
|
("2.x.x", ">=2.0.0 <3.0.0-0"),
|
||||||
|
("1.2.x", ">=1.2.0 <1.3.0-0"),
|
||||||
|
("1.2.x || 2.x", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"),
|
||||||
|
("1.2.x || 2.x", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"),
|
||||||
|
("x", "*"),
|
||||||
|
("2.*.*", ">=2.0.0 <3.0.0-0"),
|
||||||
|
("1.2.*", ">=1.2.0 <1.3.0-0"),
|
||||||
|
("1.2.* || 2.*", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"),
|
||||||
|
("*", "*"),
|
||||||
|
("2", ">=2.0.0 <3.0.0-0"),
|
||||||
|
("2.3", ">=2.3.0 <2.4.0-0"),
|
||||||
|
("~2.4", ">=2.4.0 <2.5.0-0"),
|
||||||
|
("~2.4", ">=2.4.0 <2.5.0-0"),
|
||||||
|
("~>3.2.1", ">=3.2.1 <3.3.0-0"),
|
||||||
|
("~1", ">=1.0.0 <2.0.0-0"),
|
||||||
|
("~>1", ">=1.0.0 <2.0.0-0"),
|
||||||
|
("~> 1", ">=1.0.0 <2.0.0-0"),
|
||||||
|
("~1.0", ">=1.0.0 <1.1.0-0"),
|
||||||
|
("~ 1.0", ">=1.0.0 <1.1.0-0"),
|
||||||
|
("^0", "<1.0.0-0"),
|
||||||
|
("^ 1", ">=1.0.0 <2.0.0-0"),
|
||||||
|
("^0.1", ">=0.1.0 <0.2.0-0"),
|
||||||
|
("^1.0", ">=1.0.0 <2.0.0-0"),
|
||||||
|
("^1.2", ">=1.2.0 <2.0.0-0"),
|
||||||
|
("^0.0.1", ">=0.0.1 <0.0.2-0"),
|
||||||
|
("^0.0.1-beta", ">=0.0.1-beta <0.0.2-0"),
|
||||||
|
("^0.1.2", ">=0.1.2 <0.2.0-0"),
|
||||||
|
("^1.2.3", ">=1.2.3 <2.0.0-0"),
|
||||||
|
("^1.2.3-beta.4", ">=1.2.3-beta.4 <2.0.0-0"),
|
||||||
|
("<1", "<1.0.0-0"),
|
||||||
|
("< 1", "<1.0.0-0"),
|
||||||
|
(">=1", ">=1.0.0"),
|
||||||
|
(">= 1", ">=1.0.0"),
|
||||||
|
("<1.2", "<1.2.0-0"),
|
||||||
|
("< 1.2", "<1.2.0-0"),
|
||||||
|
("1", ">=1.0.0 <2.0.0-0"),
|
||||||
|
("^ 1.2 ^ 1", ">=1.2.0 <2.0.0-0 >=1.0.0"),
|
||||||
|
("1.2 - 3.4.5", ">=1.2.0 <=3.4.5"),
|
||||||
|
("1.2.3 - 3.4", ">=1.2.3 <3.5.0-0"),
|
||||||
|
("1.2 - 3.4", ">=1.2.0 <3.5.0-0"),
|
||||||
|
(">1", ">=2.0.0"),
|
||||||
|
(">1.2", ">=1.3.0"),
|
||||||
|
(">X", "<0.0.0-0"),
|
||||||
|
("<X", "<0.0.0-0"),
|
||||||
|
("<x <* || >* 2.x", "<0.0.0-0"),
|
||||||
|
(">x 2.x || * || <x", "*"),
|
||||||
|
(">01.02.03", ">1.2.3"),
|
||||||
|
("~1.2.3beta", ">=1.2.3-beta <1.3.0-0"),
|
||||||
|
(">=09090", ">=9090.0.0"),
|
||||||
|
];
|
||||||
|
for (range_text, expected) in fixtures {
|
||||||
|
let range = NpmVersionReq::parse(range_text).unwrap();
|
||||||
|
let expected_range = NpmVersionReq::parse(expected).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
range.range_set, expected_range.range_set,
|
||||||
|
"failed for {} and {}",
|
||||||
|
range_text, expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn range_satisfies() {
|
||||||
|
// https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/test/fixtures/range-include.js
|
||||||
|
let fixtures = &[
|
||||||
|
("1.0.0 - 2.0.0", "1.2.3"),
|
||||||
|
("^1.2.3+build", "1.2.3"),
|
||||||
|
("^1.2.3+build", "1.3.0"),
|
||||||
|
("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"),
|
||||||
|
("1.2.3pre+asdf - 2.4.3-pre+asdf", "1.2.3"),
|
||||||
|
("1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"),
|
||||||
|
("1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"),
|
||||||
|
("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"),
|
||||||
|
("1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"),
|
||||||
|
("1.2.3+asdf - 2.4.3+asdf", "1.2.3"),
|
||||||
|
("1.0.0", "1.0.0"),
|
||||||
|
(">=*", "0.2.4"),
|
||||||
|
("", "1.0.0"),
|
||||||
|
("*", "1.2.3"),
|
||||||
|
("*", "v1.2.3"),
|
||||||
|
(">=1.0.0", "1.0.0"),
|
||||||
|
(">=1.0.0", "1.0.1"),
|
||||||
|
(">=1.0.0", "1.1.0"),
|
||||||
|
(">1.0.0", "1.0.1"),
|
||||||
|
(">1.0.0", "1.1.0"),
|
||||||
|
("<=2.0.0", "2.0.0"),
|
||||||
|
("<=2.0.0", "1.9999.9999"),
|
||||||
|
("<=2.0.0", "0.2.9"),
|
||||||
|
("<2.0.0", "1.9999.9999"),
|
||||||
|
("<2.0.0", "0.2.9"),
|
||||||
|
(">= 1.0.0", "1.0.0"),
|
||||||
|
(">= 1.0.0", "1.0.1"),
|
||||||
|
(">= 1.0.0", "1.1.0"),
|
||||||
|
("> 1.0.0", "1.0.1"),
|
||||||
|
("> 1.0.0", "1.1.0"),
|
||||||
|
("<= 2.0.0", "2.0.0"),
|
||||||
|
("<= 2.0.0", "1.9999.9999"),
|
||||||
|
("<= 2.0.0", "0.2.9"),
|
||||||
|
("< 2.0.0", "1.9999.9999"),
|
||||||
|
("<\t2.0.0", "0.2.9"),
|
||||||
|
(">=0.1.97", "v0.1.97"),
|
||||||
|
(">=0.1.97", "0.1.97"),
|
||||||
|
("0.1.20 || 1.2.4", "1.2.4"),
|
||||||
|
(">=0.2.3 || <0.0.1", "0.0.0"),
|
||||||
|
(">=0.2.3 || <0.0.1", "0.2.3"),
|
||||||
|
(">=0.2.3 || <0.0.1", "0.2.4"),
|
||||||
|
("||", "1.3.4"),
|
||||||
|
("2.x.x", "2.1.3"),
|
||||||
|
("1.2.x", "1.2.3"),
|
||||||
|
("1.2.x || 2.x", "2.1.3"),
|
||||||
|
("1.2.x || 2.x", "1.2.3"),
|
||||||
|
("x", "1.2.3"),
|
||||||
|
("2.*.*", "2.1.3"),
|
||||||
|
("1.2.*", "1.2.3"),
|
||||||
|
("1.2.* || 2.*", "2.1.3"),
|
||||||
|
("1.2.* || 2.*", "1.2.3"),
|
||||||
|
("*", "1.2.3"),
|
||||||
|
("2", "2.1.2"),
|
||||||
|
("2.3", "2.3.1"),
|
||||||
|
("~0.0.1", "0.0.1"),
|
||||||
|
("~0.0.1", "0.0.2"),
|
||||||
|
("~x", "0.0.9"), // >=2.4.0 <2.5.0
|
||||||
|
("~2", "2.0.9"), // >=2.4.0 <2.5.0
|
||||||
|
("~2.4", "2.4.0"), // >=2.4.0 <2.5.0
|
||||||
|
("~2.4", "2.4.5"),
|
||||||
|
("~>3.2.1", "3.2.2"), // >=3.2.1 <3.3.0,
|
||||||
|
("~1", "1.2.3"), // >=1.0.0 <2.0.0
|
||||||
|
("~>1", "1.2.3"),
|
||||||
|
("~> 1", "1.2.3"),
|
||||||
|
("~1.0", "1.0.2"), // >=1.0.0 <1.1.0,
|
||||||
|
("~ 1.0", "1.0.2"),
|
||||||
|
("~ 1.0.3", "1.0.12"),
|
||||||
|
("~ 1.0.3alpha", "1.0.12"),
|
||||||
|
(">=1", "1.0.0"),
|
||||||
|
(">= 1", "1.0.0"),
|
||||||
|
("<1.2", "1.1.1"),
|
||||||
|
("< 1.2", "1.1.1"),
|
||||||
|
("~v0.5.4-pre", "0.5.5"),
|
||||||
|
("~v0.5.4-pre", "0.5.4"),
|
||||||
|
("=0.7.x", "0.7.2"),
|
||||||
|
("<=0.7.x", "0.7.2"),
|
||||||
|
(">=0.7.x", "0.7.2"),
|
||||||
|
("<=0.7.x", "0.6.2"),
|
||||||
|
("~1.2.1 >=1.2.3", "1.2.3"),
|
||||||
|
("~1.2.1 =1.2.3", "1.2.3"),
|
||||||
|
("~1.2.1 1.2.3", "1.2.3"),
|
||||||
|
("~1.2.1 >=1.2.3 1.2.3", "1.2.3"),
|
||||||
|
("~1.2.1 1.2.3 >=1.2.3", "1.2.3"),
|
||||||
|
("~1.2.1 1.2.3", "1.2.3"),
|
||||||
|
(">=1.2.1 1.2.3", "1.2.3"),
|
||||||
|
("1.2.3 >=1.2.1", "1.2.3"),
|
||||||
|
(">=1.2.3 >=1.2.1", "1.2.3"),
|
||||||
|
(">=1.2.1 >=1.2.3", "1.2.3"),
|
||||||
|
(">=1.2", "1.2.8"),
|
||||||
|
("^1.2.3", "1.8.1"),
|
||||||
|
("^0.1.2", "0.1.2"),
|
||||||
|
("^0.1", "0.1.2"),
|
||||||
|
("^0.0.1", "0.0.1"),
|
||||||
|
("^1.2", "1.4.2"),
|
||||||
|
("^1.2 ^1", "1.4.2"),
|
||||||
|
("^1.2.3-alpha", "1.2.3-pre"),
|
||||||
|
("^1.2.0-alpha", "1.2.0-pre"),
|
||||||
|
("^0.0.1-alpha", "0.0.1-beta"),
|
||||||
|
("^0.0.1-alpha", "0.0.1"),
|
||||||
|
("^0.1.1-alpha", "0.1.1-beta"),
|
||||||
|
("^x", "1.2.3"),
|
||||||
|
("x - 1.0.0", "0.9.7"),
|
||||||
|
("x - 1.x", "0.9.7"),
|
||||||
|
("1.0.0 - x", "1.9.7"),
|
||||||
|
("1.x - x", "1.9.7"),
|
||||||
|
("<=7.x", "7.9.9"),
|
||||||
|
];
|
||||||
|
for (req_text, version_text) in fixtures {
|
||||||
|
let req = NpmVersionReq::parse(req_text).unwrap();
|
||||||
|
let version = NpmVersion::parse(version_text).unwrap();
|
||||||
|
assert!(
|
||||||
|
req.satisfies(&version),
|
||||||
|
"Checking {} satisfies {}",
|
||||||
|
req_text,
|
||||||
|
version_text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn range_not_satisfies() {
|
||||||
|
let fixtures = &[
|
||||||
|
("1.0.0 - 2.0.0", "2.2.3"),
|
||||||
|
("1.2.3+asdf - 2.4.3+asdf", "1.2.3-pre.2"),
|
||||||
|
("1.2.3+asdf - 2.4.3+asdf", "2.4.3-alpha"),
|
||||||
|
("^1.2.3+build", "2.0.0"),
|
||||||
|
("^1.2.3+build", "1.2.0"),
|
||||||
|
("^1.2.3", "1.2.3-pre"),
|
||||||
|
("^1.2", "1.2.0-pre"),
|
||||||
|
(">1.2", "1.3.0-beta"),
|
||||||
|
("<=1.2.3", "1.2.3-beta"),
|
||||||
|
("^1.2.3", "1.2.3-beta"),
|
||||||
|
("=0.7.x", "0.7.0-asdf"),
|
||||||
|
(">=0.7.x", "0.7.0-asdf"),
|
||||||
|
("<=0.7.x", "0.7.0-asdf"),
|
||||||
|
("1", "1.0.0beta"),
|
||||||
|
("<1", "1.0.0beta"),
|
||||||
|
("< 1", "1.0.0beta"),
|
||||||
|
("1.0.0", "1.0.1"),
|
||||||
|
(">=1.0.0", "0.0.0"),
|
||||||
|
(">=1.0.0", "0.0.1"),
|
||||||
|
(">=1.0.0", "0.1.0"),
|
||||||
|
(">1.0.0", "0.0.1"),
|
||||||
|
(">1.0.0", "0.1.0"),
|
||||||
|
("<=2.0.0", "3.0.0"),
|
||||||
|
("<=2.0.0", "2.9999.9999"),
|
||||||
|
("<=2.0.0", "2.2.9"),
|
||||||
|
("<2.0.0", "2.9999.9999"),
|
||||||
|
("<2.0.0", "2.2.9"),
|
||||||
|
(">=0.1.97", "v0.1.93"),
|
||||||
|
(">=0.1.97", "0.1.93"),
|
||||||
|
("0.1.20 || 1.2.4", "1.2.3"),
|
||||||
|
(">=0.2.3 || <0.0.1", "0.0.3"),
|
||||||
|
(">=0.2.3 || <0.0.1", "0.2.2"),
|
||||||
|
("2.x.x", "1.1.3"),
|
||||||
|
("2.x.x", "3.1.3"),
|
||||||
|
("1.2.x", "1.3.3"),
|
||||||
|
("1.2.x || 2.x", "3.1.3"),
|
||||||
|
("1.2.x || 2.x", "1.1.3"),
|
||||||
|
("2.*.*", "1.1.3"),
|
||||||
|
("2.*.*", "3.1.3"),
|
||||||
|
("1.2.*", "1.3.3"),
|
||||||
|
("1.2.* || 2.*", "3.1.3"),
|
||||||
|
("1.2.* || 2.*", "1.1.3"),
|
||||||
|
("2", "1.1.2"),
|
||||||
|
("2.3", "2.4.1"),
|
||||||
|
("~0.0.1", "0.1.0-alpha"),
|
||||||
|
("~0.0.1", "0.1.0"),
|
||||||
|
("~2.4", "2.5.0"), // >=2.4.0 <2.5.0
|
||||||
|
("~2.4", "2.3.9"),
|
||||||
|
("~>3.2.1", "3.3.2"), // >=3.2.1 <3.3.0
|
||||||
|
("~>3.2.1", "3.2.0"), // >=3.2.1 <3.3.0
|
||||||
|
("~1", "0.2.3"), // >=1.0.0 <2.0.0
|
||||||
|
("~>1", "2.2.3"),
|
||||||
|
("~1.0", "1.1.0"), // >=1.0.0 <1.1.0
|
||||||
|
("<1", "1.0.0"),
|
||||||
|
(">=1.2", "1.1.1"),
|
||||||
|
("1", "2.0.0beta"),
|
||||||
|
("~v0.5.4-beta", "0.5.4-alpha"),
|
||||||
|
("=0.7.x", "0.8.2"),
|
||||||
|
(">=0.7.x", "0.6.2"),
|
||||||
|
("<0.7.x", "0.7.2"),
|
||||||
|
("<1.2.3", "1.2.3-beta"),
|
||||||
|
("=1.2.3", "1.2.3-beta"),
|
||||||
|
(">1.2", "1.2.8"),
|
||||||
|
("^0.0.1", "0.0.2-alpha"),
|
||||||
|
("^0.0.1", "0.0.2"),
|
||||||
|
("^1.2.3", "2.0.0-alpha"),
|
||||||
|
("^1.2.3", "1.2.2"),
|
||||||
|
("^1.2", "1.1.9"),
|
||||||
|
("*", "v1.2.3-foo"),
|
||||||
|
("^1.0.0", "2.0.0-rc1"),
|
||||||
|
("1 - 2", "2.0.0-pre"),
|
||||||
|
("1 - 2", "1.0.0-pre"),
|
||||||
|
("1.0 - 2", "1.0.0-pre"),
|
||||||
|
("1.1.x", "1.0.0-a"),
|
||||||
|
("1.1.x", "1.1.0-a"),
|
||||||
|
("1.1.x", "1.2.0-a"),
|
||||||
|
("1.x", "1.0.0-a"),
|
||||||
|
("1.x", "1.1.0-a"),
|
||||||
|
("1.x", "1.2.0-a"),
|
||||||
|
(">=1.0.0 <1.1.0", "1.1.0"),
|
||||||
|
(">=1.0.0 <1.1.0", "1.1.0-pre"),
|
||||||
|
(">=1.0.0 <1.1.0-pre", "1.1.0-pre"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (req_text, version_text) in fixtures {
|
||||||
|
let req = NpmVersionReq::parse(req_text).unwrap();
|
||||||
|
let version = NpmVersion::parse(version_text).unwrap();
|
||||||
|
assert!(
|
||||||
|
!req.satisfies(&version),
|
||||||
|
"Checking {} not satisfies {}",
|
||||||
|
req_text,
|
||||||
|
version_text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
506
cli/npm/semver/range.rs
Normal file
506
cli/npm/semver/range.rs
Normal file
|
@ -0,0 +1,506 @@
|
||||||
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use super::NpmVersion;
|
||||||
|
|
||||||
|
/// Collection of ranges.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct VersionRangeSet(pub Vec<VersionRange>);
|
||||||
|
|
||||||
|
impl VersionRangeSet {
|
||||||
|
pub fn satisfies(&self, version: &NpmVersion) -> bool {
|
||||||
|
self.0.iter().any(|r| r.satisfies(version))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
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)]
|
||||||
|
pub enum VersionBoundKind {
|
||||||
|
Inclusive,
|
||||||
|
Exclusive,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
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)]
|
||||||
|
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<String>,
|
||||||
|
pub build: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
250
cli/npm/semver/specifier.rs
Normal file
250
cli/npm/semver/specifier.rs
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use deno_core::anyhow::Context;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use monch::*;
|
||||||
|
|
||||||
|
use super::errors::with_failure_handling;
|
||||||
|
use super::range::Partial;
|
||||||
|
use super::range::VersionBoundKind;
|
||||||
|
use super::range::VersionRange;
|
||||||
|
use super::range::XRange;
|
||||||
|
use super::NpmVersion;
|
||||||
|
|
||||||
|
/// Version requirement found in npm specifiers.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct SpecifierVersionReq {
|
||||||
|
raw_text: String,
|
||||||
|
range: VersionRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SpecifierVersionReq {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.raw_text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpecifierVersionReq {
|
||||||
|
pub fn parse(text: &str) -> Result<Self, AnyError> {
|
||||||
|
with_failure_handling(parse_npm_specifier)(text).with_context(|| {
|
||||||
|
format!("Invalid npm specifier version requirement '{}'.", text)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matches(&self, version: &NpmVersion) -> bool {
|
||||||
|
self.range.satisfies(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_npm_specifier(input: &str) -> ParseResult<SpecifierVersionReq> {
|
||||||
|
map(version_range, |range| SpecifierVersionReq {
|
||||||
|
raw_text: input.to_string(),
|
||||||
|
range,
|
||||||
|
})(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Although the code below looks very similar to what's used for
|
||||||
|
// parsing npm version requirements, the code here is more strict
|
||||||
|
// in order to not allow for people to get ridiculous when using
|
||||||
|
// npm specifiers.
|
||||||
|
|
||||||
|
// version_range ::= partial | tilde | caret
|
||||||
|
fn version_range(input: &str) -> ParseResult<VersionRange> {
|
||||||
|
or3(
|
||||||
|
map(preceded(ch('~'), partial), |partial| {
|
||||||
|
partial.as_tilde_version_range()
|
||||||
|
}),
|
||||||
|
map(preceded(ch('^'), partial), |partial| {
|
||||||
|
partial.as_caret_version_range()
|
||||||
|
}),
|
||||||
|
map(partial, |partial| {
|
||||||
|
partial.as_greater_range(VersionBoundKind::Inclusive)
|
||||||
|
}),
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
|
||||||
|
fn partial(input: &str) -> ParseResult<Partial> {
|
||||||
|
let (input, major) = xr()(input)?;
|
||||||
|
let (input, maybe_minor) = maybe(preceded(ch('.'), xr()))(input)?;
|
||||||
|
let (input, maybe_patch) = if maybe_minor.is_some() {
|
||||||
|
maybe(preceded(ch('.'), xr()))(input)?
|
||||||
|
} else {
|
||||||
|
(input, None)
|
||||||
|
};
|
||||||
|
let (input, qual) = if maybe_patch.is_some() {
|
||||||
|
maybe(qualifier)(input)?
|
||||||
|
} else {
|
||||||
|
(input, None)
|
||||||
|
};
|
||||||
|
let qual = qual.unwrap_or_default();
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
Partial {
|
||||||
|
major,
|
||||||
|
minor: maybe_minor.unwrap_or(XRange::Wildcard),
|
||||||
|
patch: maybe_patch.unwrap_or(XRange::Wildcard),
|
||||||
|
pre: qual.pre,
|
||||||
|
build: qual.build,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// xr ::= 'x' | 'X' | '*' | nr
|
||||||
|
fn xr<'a>() -> impl Fn(&'a str) -> ParseResult<'a, XRange> {
|
||||||
|
or(
|
||||||
|
map(or3(tag("x"), tag("X"), tag("*")), |_| XRange::Wildcard),
|
||||||
|
map(nr, XRange::Val),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
|
||||||
|
fn nr(input: &str) -> ParseResult<u64> {
|
||||||
|
or(map(tag("0"), |_| 0), move |input| {
|
||||||
|
let (input, result) = if_not_empty(substring(pair(
|
||||||
|
if_true(next_char, |c| c.is_ascii_digit() && *c != '0'),
|
||||||
|
skip_while(|c| c.is_ascii_digit()),
|
||||||
|
)))(input)?;
|
||||||
|
let val = match result.parse::<u64>() {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(err) => {
|
||||||
|
return ParseError::fail(
|
||||||
|
input,
|
||||||
|
format!("Error parsing '{}' to u64.\n\n{:#}", result, err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((input, val))
|
||||||
|
})(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct Qualifier {
|
||||||
|
pre: Vec<String>,
|
||||||
|
build: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// qualifier ::= ( '-' pre )? ( '+' build )?
|
||||||
|
fn qualifier(input: &str) -> ParseResult<Qualifier> {
|
||||||
|
let (input, pre_parts) = maybe(pre)(input)?;
|
||||||
|
let (input, build_parts) = maybe(build)(input)?;
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
Qualifier {
|
||||||
|
pre: pre_parts.unwrap_or_default(),
|
||||||
|
build: build_parts.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// pre ::= parts
|
||||||
|
fn pre(input: &str) -> ParseResult<Vec<String>> {
|
||||||
|
preceded(ch('-'), parts)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// build ::= parts
|
||||||
|
fn build(input: &str) -> ParseResult<Vec<String>> {
|
||||||
|
preceded(ch('+'), parts)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parts ::= part ( '.' part ) *
|
||||||
|
fn parts(input: &str) -> ParseResult<Vec<String>> {
|
||||||
|
if_not_empty(map(separated_list(part, ch('.')), |text| {
|
||||||
|
text.into_iter().map(ToOwned::to_owned).collect()
|
||||||
|
}))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// part ::= nr | [-0-9A-Za-z]+
|
||||||
|
fn part(input: &str) -> ParseResult<&str> {
|
||||||
|
// nr is in the other set, so don't bother checking for it
|
||||||
|
if_true(
|
||||||
|
take_while(|c| c.is_ascii_alphanumeric() || c == '-'),
|
||||||
|
|result| !result.is_empty(),
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct VersionReqTester(SpecifierVersionReq);
|
||||||
|
|
||||||
|
impl VersionReqTester {
|
||||||
|
fn new(text: &str) -> Self {
|
||||||
|
Self(SpecifierVersionReq::parse(text).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches(&self, version: &str) -> bool {
|
||||||
|
self.0.matches(&NpmVersion::parse(version).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_req_exact() {
|
||||||
|
let tester = VersionReqTester::new("1.0.1");
|
||||||
|
assert!(!tester.matches("1.0.0"));
|
||||||
|
assert!(tester.matches("1.0.1"));
|
||||||
|
assert!(!tester.matches("1.0.2"));
|
||||||
|
assert!(!tester.matches("1.1.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_req_minor() {
|
||||||
|
let tester = VersionReqTester::new("1.1");
|
||||||
|
assert!(!tester.matches("1.0.0"));
|
||||||
|
assert!(tester.matches("1.1.0"));
|
||||||
|
assert!(tester.matches("1.1.1"));
|
||||||
|
assert!(!tester.matches("1.2.0"));
|
||||||
|
assert!(!tester.matches("1.2.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_req_caret() {
|
||||||
|
let tester = VersionReqTester::new("^1.1.1");
|
||||||
|
assert!(!tester.matches("1.1.0"));
|
||||||
|
assert!(tester.matches("1.1.1"));
|
||||||
|
assert!(tester.matches("1.1.2"));
|
||||||
|
assert!(tester.matches("1.2.0"));
|
||||||
|
assert!(!tester.matches("2.0.0"));
|
||||||
|
|
||||||
|
let tester = VersionReqTester::new("^0.1.1");
|
||||||
|
assert!(!tester.matches("0.0.0"));
|
||||||
|
assert!(!tester.matches("0.1.0"));
|
||||||
|
assert!(tester.matches("0.1.1"));
|
||||||
|
assert!(tester.matches("0.1.2"));
|
||||||
|
assert!(!tester.matches("0.2.0"));
|
||||||
|
assert!(!tester.matches("1.0.0"));
|
||||||
|
|
||||||
|
let tester = VersionReqTester::new("^0.0.1");
|
||||||
|
assert!(!tester.matches("0.0.0"));
|
||||||
|
assert!(tester.matches("0.0.1"));
|
||||||
|
assert!(!tester.matches("0.0.2"));
|
||||||
|
assert!(!tester.matches("0.1.0"));
|
||||||
|
assert!(!tester.matches("1.0.0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_req_tilde() {
|
||||||
|
let tester = VersionReqTester::new("~1.1.1");
|
||||||
|
assert!(!tester.matches("1.1.0"));
|
||||||
|
assert!(tester.matches("1.1.1"));
|
||||||
|
assert!(tester.matches("1.1.2"));
|
||||||
|
assert!(!tester.matches("1.2.0"));
|
||||||
|
assert!(!tester.matches("2.0.0"));
|
||||||
|
|
||||||
|
let tester = VersionReqTester::new("~0.1.1");
|
||||||
|
assert!(!tester.matches("0.0.0"));
|
||||||
|
assert!(!tester.matches("0.1.0"));
|
||||||
|
assert!(tester.matches("0.1.1"));
|
||||||
|
assert!(tester.matches("0.1.2"));
|
||||||
|
assert!(!tester.matches("0.2.0"));
|
||||||
|
assert!(!tester.matches("1.0.0"));
|
||||||
|
|
||||||
|
let tester = VersionReqTester::new("~0.0.1");
|
||||||
|
assert!(!tester.matches("0.0.0"));
|
||||||
|
assert!(tester.matches("0.0.1"));
|
||||||
|
assert!(tester.matches("0.0.2")); // for some reason this matches, but not with ^
|
||||||
|
assert!(!tester.matches("0.1.0"));
|
||||||
|
assert!(!tester.matches("1.0.0"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,12 +158,13 @@ fn extract_tarball(data: &[u8], output_folder: &Path) -> Result<(), AnyError> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::npm::semver::NpmVersion;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_verify_tarball() {
|
pub fn test_verify_tarball() {
|
||||||
let package_id = NpmPackageId {
|
let package_id = NpmPackageId {
|
||||||
name: "package".to_string(),
|
name: "package".to_string(),
|
||||||
version: semver::Version::parse("1.0.0").unwrap(),
|
version: NpmVersion::parse("1.0.0").unwrap(),
|
||||||
};
|
};
|
||||||
let actual_checksum =
|
let actual_checksum =
|
||||||
"z4phnx7vul3xvchq1m2ab9yg5aulvxxcg/spidns6c5h0ne8xyxysp+dgnkhfuwvy7kxvudbeoglodj6+sfapg==";
|
"z4phnx7vul3xvchq1m2ab9yg5aulvxxcg/spidns6c5h0ne8xyxysp+dgnkhfuwvy7kxvudbeoglodj6+sfapg==";
|
||||||
|
|
|
@ -1,219 +0,0 @@
|
||||||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use deno_core::anyhow::bail;
|
|
||||||
use deno_core::error::AnyError;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
use super::resolution::NpmVersionMatcher;
|
|
||||||
|
|
||||||
static MINOR_SPECIFIER_RE: Lazy<Regex> =
|
|
||||||
Lazy::new(|| Regex::new(r#"^[0-9]+\.[0-9]+$"#).unwrap());
|
|
||||||
|
|
||||||
/// Version requirement found in npm specifiers.
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
|
||||||
pub struct SpecifierVersionReq(semver::VersionReq);
|
|
||||||
|
|
||||||
impl std::fmt::Display for SpecifierVersionReq {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpecifierVersionReq {
|
|
||||||
// in order to keep using semver, we do some pre-processing to change the behavior
|
|
||||||
pub fn parse(text: &str) -> Result<Self, AnyError> {
|
|
||||||
// for now, we don't support these scenarios
|
|
||||||
if text.contains("||") {
|
|
||||||
bail!("not supported '||'");
|
|
||||||
}
|
|
||||||
if text.contains(',') {
|
|
||||||
bail!("not supported ','");
|
|
||||||
}
|
|
||||||
// force exact versions to be matched exactly
|
|
||||||
let text = if semver::Version::parse(text).is_ok() {
|
|
||||||
Cow::Owned(format!("={}", text))
|
|
||||||
} else {
|
|
||||||
Cow::Borrowed(text)
|
|
||||||
};
|
|
||||||
// force requirements like 1.2 to be ~1.2 instead of ^1.2
|
|
||||||
let text = if MINOR_SPECIFIER_RE.is_match(&text) {
|
|
||||||
Cow::Owned(format!("~{}", text))
|
|
||||||
} else {
|
|
||||||
text
|
|
||||||
};
|
|
||||||
Ok(Self(semver::VersionReq::parse(&text)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches(&self, version: &semver::Version) -> bool {
|
|
||||||
self.0.matches(version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A version requirement found in an npm package's dependencies.
|
|
||||||
pub struct NpmVersionReq {
|
|
||||||
raw_text: String,
|
|
||||||
comparators: Vec<semver::VersionReq>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NpmVersionReq {
|
|
||||||
pub fn parse(text: &str) -> Result<NpmVersionReq, AnyError> {
|
|
||||||
// semver::VersionReq doesn't support spaces between comparators
|
|
||||||
// and it doesn't support using || for "OR", so we pre-process
|
|
||||||
// the version requirement in order to make this work.
|
|
||||||
let raw_text = text.to_string();
|
|
||||||
let part_texts = text.split("||").collect::<Vec<_>>();
|
|
||||||
let mut comparators = Vec::with_capacity(part_texts.len());
|
|
||||||
for part in part_texts {
|
|
||||||
comparators.push(npm_version_req_parse_part(part)?);
|
|
||||||
}
|
|
||||||
Ok(NpmVersionReq {
|
|
||||||
raw_text,
|
|
||||||
comparators,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NpmVersionMatcher for NpmVersionReq {
|
|
||||||
fn matches(&self, version: &semver::Version) -> bool {
|
|
||||||
self.comparators.iter().any(|c| c.matches(version))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn version_text(&self) -> String {
|
|
||||||
self.raw_text.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn npm_version_req_parse_part(
|
|
||||||
text: &str,
|
|
||||||
) -> Result<semver::VersionReq, AnyError> {
|
|
||||||
let text = text.trim();
|
|
||||||
let text = text.strip_prefix('v').unwrap_or(text);
|
|
||||||
// force exact versions to be matched exactly
|
|
||||||
let text = if semver::Version::parse(text).is_ok() {
|
|
||||||
Cow::Owned(format!("={}", text))
|
|
||||||
} else {
|
|
||||||
Cow::Borrowed(text)
|
|
||||||
};
|
|
||||||
// force requirements like 1.2 to be ~1.2 instead of ^1.2
|
|
||||||
let text = if MINOR_SPECIFIER_RE.is_match(&text) {
|
|
||||||
Cow::Owned(format!("~{}", text))
|
|
||||||
} else {
|
|
||||||
text
|
|
||||||
};
|
|
||||||
let mut chars = text.chars().enumerate().peekable();
|
|
||||||
let mut final_text = String::new();
|
|
||||||
while chars.peek().is_some() {
|
|
||||||
let (i, c) = chars.next().unwrap();
|
|
||||||
let is_greater_or_less_than = c == '<' || c == '>';
|
|
||||||
if is_greater_or_less_than || c == '=' {
|
|
||||||
if i > 0 {
|
|
||||||
final_text = final_text.trim().to_string();
|
|
||||||
// add a comma to make semver::VersionReq parse this
|
|
||||||
final_text.push(',');
|
|
||||||
}
|
|
||||||
final_text.push(c);
|
|
||||||
let next_char = chars.peek().map(|(_, c)| c);
|
|
||||||
if is_greater_or_less_than && matches!(next_char, Some('=')) {
|
|
||||||
let c = chars.next().unwrap().1; // skip
|
|
||||||
final_text.push(c);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final_text.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(semver::VersionReq::parse(&final_text)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
struct VersionReqTester(SpecifierVersionReq);
|
|
||||||
|
|
||||||
impl VersionReqTester {
|
|
||||||
fn new(text: &str) -> Self {
|
|
||||||
Self(SpecifierVersionReq::parse(text).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&self, version: &str) -> bool {
|
|
||||||
self.0.matches(&semver::Version::parse(version).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn version_req_exact() {
|
|
||||||
let tester = VersionReqTester::new("1.0.1");
|
|
||||||
assert!(!tester.matches("1.0.0"));
|
|
||||||
assert!(tester.matches("1.0.1"));
|
|
||||||
assert!(!tester.matches("1.0.2"));
|
|
||||||
assert!(!tester.matches("1.1.1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn version_req_minor() {
|
|
||||||
let tester = VersionReqTester::new("1.1");
|
|
||||||
assert!(!tester.matches("1.0.0"));
|
|
||||||
assert!(tester.matches("1.1.0"));
|
|
||||||
assert!(tester.matches("1.1.1"));
|
|
||||||
assert!(!tester.matches("1.2.0"));
|
|
||||||
assert!(!tester.matches("1.2.1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NpmVersionReqTester(NpmVersionReq);
|
|
||||||
|
|
||||||
impl NpmVersionReqTester {
|
|
||||||
fn new(text: &str) -> Self {
|
|
||||||
Self(NpmVersionReq::parse(text).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&self, version: &str) -> bool {
|
|
||||||
self.0.matches(&semver::Version::parse(version).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn npm_version_req_with_v() {
|
|
||||||
assert!(NpmVersionReq::parse("v1.0.0").is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn npm_version_req_exact() {
|
|
||||||
let tester = NpmVersionReqTester::new("2.1.2");
|
|
||||||
assert!(!tester.matches("2.1.1"));
|
|
||||||
assert!(tester.matches("2.1.2"));
|
|
||||||
assert!(!tester.matches("2.1.3"));
|
|
||||||
|
|
||||||
let tester = NpmVersionReqTester::new("2.1.2 || 2.1.5");
|
|
||||||
assert!(!tester.matches("2.1.1"));
|
|
||||||
assert!(tester.matches("2.1.2"));
|
|
||||||
assert!(!tester.matches("2.1.3"));
|
|
||||||
assert!(!tester.matches("2.1.4"));
|
|
||||||
assert!(tester.matches("2.1.5"));
|
|
||||||
assert!(!tester.matches("2.1.6"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn npm_version_req_minor() {
|
|
||||||
let tester = NpmVersionReqTester::new("1.1");
|
|
||||||
assert!(!tester.matches("1.0.0"));
|
|
||||||
assert!(tester.matches("1.1.0"));
|
|
||||||
assert!(tester.matches("1.1.1"));
|
|
||||||
assert!(!tester.matches("1.2.0"));
|
|
||||||
assert!(!tester.matches("1.2.1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn npm_version_req_ranges() {
|
|
||||||
let tester = NpmVersionReqTester::new(">= 2.1.2 < 3.0.0 || 5.x");
|
|
||||||
assert!(!tester.matches("2.1.1"));
|
|
||||||
assert!(tester.matches("2.1.2"));
|
|
||||||
assert!(tester.matches("2.9.9"));
|
|
||||||
assert!(!tester.matches("3.0.0"));
|
|
||||||
assert!(tester.matches("5.0.0"));
|
|
||||||
assert!(tester.matches("5.1.0"));
|
|
||||||
assert!(!tester.matches("6.1.0"));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +1,2 @@
|
||||||
Download http://localhost:4545/npm/registry/mkdirp
|
Download http://localhost:4545/npm/registry/mkdirp
|
||||||
error: Could not find npm package 'mkdirp' matching =0.5.125. Try retreiving the latest npm package information by running with --reload
|
error: Could not find npm package 'mkdirp' matching 0.5.125. Try retreiving the latest npm package information by running with --reload
|
||||||
|
|
Loading…
Add table
Reference in a new issue