diff --git a/src/eval.rs b/src/eval.rs index 18bb5547..2994bc90 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -2,7 +2,11 @@ use crate::{Comparator, Op, Prerelease, Version, VersionReq}; pub(crate) fn matches_req(req: &VersionReq, ver: &Version, prerelease_matches: bool) -> bool { for cmp in &req.comparators { - if !matches_impl(cmp, ver, prerelease_matches) { + if prerelease_matches { + if !matches_prerelease_impl(cmp, ver) { + return false; + } + } else if !matches_impl(cmp, ver) { return false; } } @@ -24,10 +28,40 @@ pub(crate) fn matches_req(req: &VersionReq, ver: &Version, prerelease_matches: b } pub(crate) fn matches_comparator(cmp: &Comparator, ver: &Version) -> bool { - matches_impl(cmp, ver, false) && (ver.pre.is_empty() || pre_is_compatible(cmp, ver)) + matches_impl(cmp, ver) && (ver.pre.is_empty() || pre_is_compatible(cmp, ver)) +} +// If VersionReq missing Minor, Patch, then filling them with 0 +fn fill_partial_req(cmp: &mut Comparator) { + if cmp.minor.is_none() { + cmp.minor = Some(0); + cmp.patch = Some(0); + } else if cmp.patch.is_none() { + cmp.patch = Some(0); + } +} + +fn matches_prerelease_impl(cmp: &Comparator, ver: &Version) -> bool { + let cmp = &{ + let mut cmp = cmp.clone(); + if matches!(cmp.op, Op::Greater | Op::GreaterEq | Op::Less | Op::LessEq) { + fill_partial_req(&mut cmp) + } + cmp + }; + match cmp.op { + Op::Exact | Op::Wildcard => matches_exact_prerelease(cmp, ver), + Op::Greater => matches_greater(cmp, ver), + Op::GreaterEq => matches_exact(cmp, ver) || matches_greater(cmp, ver), + Op::Less => matches_less(cmp, ver), + Op::LessEq => matches_exact(cmp, ver) || matches_less(cmp, ver), + Op::Tilde => matches_tilde_prerelease(cmp, ver), + Op::Caret => matches_caret_prerelease(cmp, ver), + #[cfg(no_non_exhaustive)] + Op::__NonExhaustive => unreachable!(), + } } -fn matches_impl(cmp: &Comparator, ver: &Version, prerelease_matches: bool) -> bool { +fn matches_impl(cmp: &Comparator, ver: &Version) -> bool { match cmp.op { Op::Exact | Op::Wildcard => matches_exact(cmp, ver), Op::Greater => matches_greater(cmp, ver), @@ -35,17 +69,53 @@ fn matches_impl(cmp: &Comparator, ver: &Version, prerelease_matches: bool) -> bo Op::Less => matches_less(cmp, ver), Op::LessEq => matches_exact(cmp, ver) || matches_less(cmp, ver), Op::Tilde => matches_tilde(cmp, ver), - Op::Caret => { - if prerelease_matches { - return matches_caret_prerelease(cmp, ver); - } - matches_caret(cmp, ver) - } + Op::Caret => matches_caret(cmp, ver), #[cfg(no_non_exhaustive)] Op::__NonExhaustive => unreachable!(), } } +fn matches_exact_prerelease(cmp: &Comparator, ver: &Version) -> bool { + if matches_exact(cmp, ver) { + return true; + } + + let mut lower = Comparator { + op: Op::Less, + ..cmp.clone() + }; + fill_partial_req(&mut lower); + if !matches_greater(&lower, ver) { + return false; + } + + let mut upper = Comparator { + op: Op::Less, + pre: Prerelease::new("0").unwrap(), + ..cmp.clone() + }; + + match (upper.minor.is_some(), upper.patch.is_some()) { + (true, true) => { + upper.patch = Some(upper.patch.unwrap() + 1); + } + (true, false) => { + // Partial Exact VersionReq eg. =0.24 + upper.minor = Some(upper.minor.unwrap() + 1); + upper.patch = Some(0); + } + (false, false) => { + // Partial Exact VersionReq eg. =0 + upper.major += 1; + upper.minor = Some(0); + upper.patch = Some(0); + } + _ => {} + } + + matches_less(&upper, ver) +} + fn matches_exact(cmp: &Comparator, ver: &Version) -> bool { if ver.major != cmp.major { return false; @@ -118,6 +188,39 @@ fn matches_less(cmp: &Comparator, ver: &Version) -> bool { ver.pre < cmp.pre } +fn matches_tilde_prerelease(cmp: &Comparator, ver: &Version) -> bool { + if matches_exact(cmp, ver) { + return true; + } + + let mut lower = cmp.clone(); + fill_partial_req(&mut lower); + if !matches_greater(&lower, ver) { + return false; + } + + let mut upper = Comparator { + op: Op::Less, + pre: Prerelease::new("0").unwrap(), + ..cmp.clone() + }; + + match (upper.minor.is_some(), upper.patch.is_some()) { + (true, _) => { + upper.minor = Some(upper.minor.unwrap() + 1); + upper.patch = Some(0); + } + (false, false) => { + upper.major += 1; + upper.minor = Some(0); + upper.patch = Some(0); + } + _ => {} + } + + matches_less(&upper, ver) +} + fn matches_tilde(cmp: &Comparator, ver: &Version) -> bool { if ver.major != cmp.major { return false; @@ -142,33 +245,47 @@ fn matches_caret_prerelease(cmp: &Comparator, ver: &Version) -> bool { if matches_exact(cmp, ver) { return true; } - if !matches_greater(cmp, ver) { + let mut lower = cmp.clone(); + fill_partial_req(&mut lower); + if !matches_greater(&lower, ver) { return false; } - let mut pre_cmp = Comparator { - op: Op::LessEq, + let mut upper = Comparator { + op: Op::Less, pre: Prerelease::new("0").unwrap(), ..cmp.clone() }; - let Comparator { - major, - minor, - patch, - .. - } = *cmp; - - if major > 0 { - pre_cmp.major += 1; - pre_cmp.minor = Some(0); - pre_cmp.patch = Some(0); - } else if minor.is_some() && minor.unwrap() > 0 { - pre_cmp.minor = Some(minor.unwrap() + 1); - pre_cmp.patch = Some(0); - } else { - pre_cmp.patch = Some(patch.unwrap() + 1); - } - matches_less(&pre_cmp, ver) + + match ( + upper.major > 0, + upper.minor.is_some(), + upper.patch.is_some(), + ) { + (true, _, _) | (_, false, false) => { + upper.major += 1; + upper.minor = Some(0); + upper.patch = Some(0); + } + (_, true, false) => { + upper.minor = Some(upper.minor.unwrap() + 1); + upper.patch = Some(0); + } + (_, true, _) if upper.minor.unwrap() > 0 => { + upper.minor = Some(upper.minor.unwrap() + 1); + upper.patch = Some(0); + } + (_, true, _) if upper.minor.unwrap() == 0 => { + if upper.patch.is_none() { + upper.patch = Some(1); + } else { + upper.patch = Some(upper.patch.unwrap() + 1); + } + } + _ => {} + } + + matches_less(&upper, ver) } fn matches_caret(cmp: &Comparator, ver: &Version) -> bool { diff --git a/src/lib.rs b/src/lib.rs index 871be645..42f1b09a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -526,27 +526,7 @@ impl VersionReq { /// Simliar to [`Self::matches`], it allows to match any "Semver-compatible" pre-release version. pub fn matches_prerelease(&self, version: &Version) -> bool { - let req = { - let mut req = self.clone(); - req.comparators = req - .comparators - .into_iter() - .map(|mut cmp| { - // Paritial VersionReq (eg. `0.24`) needs to be filled with minor and patch, - // so that we can make sure `0.24.2-pre` matches `~0.24` or `0.24` - if cmp.minor.is_none() { - cmp.minor = Some(0); - cmp.patch = Some(0); - } else if cmp.patch.is_none() { - cmp.patch = Some(0); - } - cmp - }) - .collect(); - req - }; - - eval::matches_req(&req, version, true) + eval::matches_req(self, version, true) } } diff --git a/tests/test_matches_prerelease.rs b/tests/test_matches_prerelease.rs index 24e96a2f..646b953a 100644 --- a/tests/test_matches_prerelease.rs +++ b/tests/test_matches_prerelease.rs @@ -75,6 +75,29 @@ fn minor_caret() { #[test] fn major_caret() { + let ref r = req("=0.0.0-r"); + assert_prerelease_match_all(r, &["0.0.0"]); + + let ref r = req("0"); + // Match >= 0.0.0, < 1.0.0-0 + assert_prerelease_match_all(r, &["0.0.0", "0.0.1-0", "0.0.1-pre", "0.1.1"]); + // Not Match < 0.0.0 + assert_prerelease_match_none(r, &["0.0.0-pre"]); + // Not Match >= 1.0.0-0 + assert_prerelease_match_none(r, &["1.0.0-0", "1.0.0", "1.0.1-pre", "2.0.0-0"]); + + let ref r = req("0.0"); + // Match >= 0.0.0, < 0.1.0-0 + assert_prerelease_match_all(r, &["0.0.1-z0", "0.0.9"]); + // Not Match >= 0.1.0-0 + assert_prerelease_match_none(r, &["0.1.0-0", "0.1.0", "0.1.1-pre", "0.1.1-z0", "1.1.0"]); + + let ref r = req("0.0.0"); + // Match >= 0.0.0, < 0.0.1-0 + assert_prerelease_match_all(r, &["0.0.0"]); + // Not Match >= 0.0.1-0 + assert_prerelease_match_none(r, &["0.0.1-0", "0.0.1", "1.0.1-pre"]); + let ref r = req("1"); // Match >= 1.0.0, < 2.0.0-0 assert_prerelease_match_all(r, &["1.2.3", "1.2.4-0", "1.2.4", "1.8.8"]); @@ -125,12 +148,12 @@ fn test_tilde() { assert_prerelease_match_none(r, &["0.25.0-0", "1.1.0", "1.2.3", "2.0.0"]); let ref r = req("~1"); - // Match >= 1.0.0, < 1.1.0-0 - assert_prerelease_match_all(r, &["1.0.0", "1.0.1-pre", "1.0.1", "1.0.9"]); + // Match >= 1.0.0, < 2.0.0-0 + assert_prerelease_match_all(r, &["1.0.0", "1.1.0-0", "1.1.0", "1.2.3"]); // Not Match < 1.0.0 assert_prerelease_match_none(r, &["0.0.1", "0.9.9", "1.0.0-pre"]); - // Not Match >= 1.1.0-0 - assert_prerelease_match_none(r, &["1.1.0-0", "1.1.0", "1.2.3", "2.0.0"]); + // Not Match >= 2.0.0-0 + assert_prerelease_match_none(r, &["2.0.0-0", "2.0.0", "2.0.1"]); let ref r = req("~1.2"); // Match >= 1.2.0, < 1.3.0-0 @@ -216,4 +239,49 @@ fn test_range_partial() { let ref r = req("*"); assert_prerelease_match_all(r, &["0.0.1", "1.0.0", "1.2.9", "2.0.0-pre"]); + + let ref r = req("^1, <=1.9"); + assert_prerelease_match_all(r, &["1.1.1-pre", "1.1.1"]); + let ref r = req("^0, <=0.0.1-z0"); + assert_prerelease_match_all(r, &["0.0.1-z0"]); +} + +#[test] +fn test_exact() { + let ref r = req("=4"); + // Match >= 4.0.0, < 5.0.0-0 + assert_prerelease_match_all(r, &["4.0.0", "4.2.1", "4.2.4-pre", "4.9.9"]); + // Not Match < 4.0.0 + assert_prerelease_match_none(r, &["0.0.1", "2.1.2-pre", "4.0.0-pre"]); + // Not Match >= 5.0.0-0 + assert_prerelease_match_none(r, &["5.0.0-0", "5.0.0", "5.0.1"]); + + let ref r = req("=4.2"); + // Match >= 4.2.0, < 4.3.0-0 + assert_prerelease_match_all(r, &["4.2.0", "4.2.1", "4.2.4-pre", "4.2.9"]); + // Not Match < 4.2.0 + assert_prerelease_match_none(r, &["0.0.1", "2.1.2-pre", "4.0.0-pre"]); + // Not Match >= 4.3.0-0 + assert_prerelease_match_none(r, &["4.3.0-0", "4.3.0", "5.0.0-0", "5.0.0", "5.0.1"]); + + let ref r = req("=4.2.1"); + assert_prerelease_match_all(r, &["4.2.1"]); + assert_prerelease_match_none(r, &["1.2.3", "4.2.1-pre", "4.2.2", "5.0.0"]); + + let ref r = req("=4.2.1-0"); + // Match >= 4.2.1-0 < 4.2.2-0 + assert_prerelease_match_all(r, &["4.2.1-0", "4.2.1-1", "4.2.1-pre"]); + // Not Match < 4.2.1-0 + assert_prerelease_match_none(r, &["1.2.3", "4.2.0"]); + // Not Match >= 4.2.2-0 + assert_prerelease_match_none(r, &["4.2.2-0", "4.2.2", "4.3.5", "6.8.9"]); + + // Speicial Case + let ref r = req("=0"); + // Match >= 0.0.0, < 1.0.0-0 + assert_prerelease_match_all(r, &["0.0.0", "0.1.1", "0.9.9"]); + // Not Match < 0.0.0 + assert_prerelease_match_none(r, &["0.0.0-0", "0.0.0-pre"]); + // Not Match >= 1.0.0-0 + assert_prerelease_match_none(r, &["1.0.0-0", "1.0.0", "2.0.1"]); }