1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-01 09:24:20 -04:00
denoland-deno/cli/npm/semver/mod.rs
David Sherret cbb3f85433
feat(unstable/npm): support peer dependencies (#16561)
This adds support for peer dependencies in npm packages.

1. If not found higher in the tree (ancestor and ancestor siblings),
peer dependencies are resolved like a dependency similar to npm 7.
2. Optional peer dependencies are only resolved if found higher in the
tree.
3. This creates "copy packages" or duplicates of a package when a
package has different resolution due to peer dependency resolution—see
https://pnpm.io/how-peers-are-resolved. Unlike pnpm though, duplicates
of packages will have `_1`, `_2`, etc. added to the end of the package
version in the directory in order to minimize the chance of hitting the
max file path limit on Windows. This is done for both the local
"node_modules" directory and also the global npm cache. The files are
hard linked in this case to reduce hard drive space.

This is a first pass and the code is definitely more inefficient than it
could be.

Closes #15823
2022-11-08 14:17:24 -05:00

962 lines
27 KiB
Rust

// 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 serde::Deserialize;
use serde::Serialize;
use crate::npm::resolution::NpmVersionMatcher;
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 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, Serialize, Deserialize,
)]
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 tag(&self) -> Option<&str> {
None
}
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_equal_range()),
)(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"),
// additional tests
("1.0.0-alpha.13", "1.0.0-alpha.13"),
];
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
);
}
}
}