use semver::{Comparator, Op, Prerelease, Version, VersionReq};
pub(crate) fn matches_prerelease(req: &VersionReq, ver: &Version) -> bool {
let lower_bound_prerelease = &req.comparators.iter().any(|cmp| {
if matches!(cmp.op, Op::Greater | Op::GreaterEq) && !cmp.pre.is_empty() {
true
} else {
false
}
});
for cmp in &req.comparators {
if !matches_prerelease_impl(cmp, ver, lower_bound_prerelease) {
return false;
}
}
true
}
fn matches_prerelease_impl(cmp: &Comparator, ver: &Version, lower_bound_prerelease: &bool) -> bool {
match cmp.op {
Op::Exact | Op::Wildcard => matches_exact_prerelease(cmp, ver),
Op::Greater => matches_greater(cmp, ver),
Op::GreaterEq => {
if matches_exact_prerelease(cmp, ver) {
return true;
}
matches_greater(cmp, ver)
}
Op::Less => {
if *lower_bound_prerelease {
matches_less(&fill_partial_req(cmp), ver)
} else {
matches_less(&fill_partial_req_include_pre(cmp), ver)
}
}
Op::LessEq => {
if matches_exact_prerelease(cmp, ver) {
return true;
}
matches_less(&fill_partial_req(cmp), ver)
}
Op::Tilde => matches_tilde_prerelease(cmp, ver),
Op::Caret => matches_caret_prerelease(cmp, ver),
_ => unreachable!(),
}
}
fn matches_exact(cmp: &Comparator, ver: &Version) -> bool {
if ver.major != cmp.major {
return false;
}
if let Some(minor) = cmp.minor {
if ver.minor != minor {
return false;
}
}
if let Some(patch) = cmp.patch {
if ver.patch != patch {
return false;
}
}
ver.pre == cmp.pre
}
fn matches_greater(cmp: &Comparator, ver: &Version) -> bool {
if ver.major != cmp.major {
return ver.major > cmp.major;
}
match cmp.minor {
None => return false,
Some(minor) => {
if ver.minor != minor {
return ver.minor > minor;
}
}
}
match cmp.patch {
None => return false,
Some(patch) => {
if ver.patch != patch {
return ver.patch > patch;
}
}
}
ver.pre > cmp.pre
}
fn matches_less(cmp: &Comparator, ver: &Version) -> bool {
if ver.major != cmp.major {
return ver.major < cmp.major;
}
match cmp.minor {
None => return false,
Some(minor) => {
if ver.minor != minor {
return ver.minor < minor;
}
}
}
match cmp.patch {
None => return false,
Some(patch) => {
if ver.patch != patch {
return ver.patch < patch;
}
}
}
ver.pre < cmp.pre
}
fn fill_partial_req(cmp: &Comparator) -> Comparator {
let mut cmp = cmp.clone();
if cmp.minor.is_none() {
cmp.minor = Some(0);
cmp.patch = Some(0);
} else if cmp.patch.is_none() {
cmp.patch = Some(0);
}
cmp
}
fn fill_partial_req_include_pre(cmp: &Comparator) -> Comparator {
let mut cmp = cmp.clone();
if cmp.minor.is_none() {
cmp.minor = Some(0);
cmp.patch = Some(0);
cmp.pre = Prerelease::new("0").unwrap();
} else if cmp.patch.is_none() {
cmp.patch = Some(0);
}
if cmp.pre.is_empty() {
cmp.pre = Prerelease::new("0").unwrap();
}
cmp
}
fn matches_exact_prerelease(cmp: &Comparator, ver: &Version) -> bool {
if matches_exact(cmp, ver) {
return true;
}
if !cmp.pre.is_empty() {
return false;
}
if !matches_greater(&fill_partial_req(cmp), 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) => {
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_prerelease(cmp: &Comparator, ver: &Version) -> bool {
if matches_exact(cmp, ver) {
return true;
}
if !matches_greater(&fill_partial_req(cmp), 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_caret_prerelease(cmp: &Comparator, ver: &Version) -> bool {
if matches_exact(cmp, ver) {
return true;
}
if !matches_greater(&fill_partial_req(cmp), ver) {
return false;
}
let mut upper = Comparator {
op: Op::Less,
pre: Prerelease::new("0").unwrap(),
..cmp.clone()
};
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)
}
#[cfg(test)]
mod matches_prerelease_semantic {
use crate::util::semver_ext::VersionReqExt;
use semver::{Version, VersionReq};
fn assert_match_all(req: &VersionReq, versions: &[&str]) {
for string in versions {
let parsed = Version::parse(string).unwrap();
assert!(
req.matches_prerelease(&parsed),
"{} did not match {}",
req,
string,
);
}
}
fn assert_match_none(req: &VersionReq, versions: &[&str]) {
for string in versions {
let parsed = Version::parse(string).unwrap();
assert!(
!req.matches_prerelease(&parsed),
"{} matched {}",
req,
string
);
}
}
pub(super) fn req(text: &str) -> VersionReq {
VersionReq::parse(text).unwrap()
}
#[test]
fn test_exact() {
let ref r = req("=4.2.1-0");
assert_match_all(r, &["4.2.1-0"]);
assert_match_none(r, &["1.2.3", "4.2.0", "4.2.1-1", "4.2.2"]);
for r in &[req("=4.2.1"), req(">=4.2.1, <4.2.2-0")] {
assert_match_all(r, &["4.2.1"]);
assert_match_none(r, &["1.2.3", "4.2.1-0", "4.2.2-0", "4.2.2"]);
}
for r in &[req("=4.2"), req(">=4.2.0, <4.3.0-0")] {
assert_match_all(r, &["4.2.0", "4.2.1", "4.2.9"]);
assert_match_none(r, &["0.0.1", "2.1.2-0", "4.2.0-0"]);
assert_match_none(r, &["4.3.0-0", "4.3.0", "5.0.0-0", "5.0.0"]);
}
for r in &[req("=4"), req(">=4.0.0, <5.0.0-0")] {
assert_match_all(r, &["4.0.0", "4.2.1", "4.2.4-0", "4.9.9"]);
assert_match_none(r, &["0.0.1", "2.1.2-0", "4.0.0-0"]);
assert_match_none(r, &["5.0.0-0", "5.0.0", "5.0.1"]);
}
}
#[test]
fn test_greater_eq() {
let ref r = req(">=4.2.1-0");
assert_match_all(r, &["4.2.1-0", "4.2.1", "5.0.0"]);
assert_match_none(r, &["0.0.0", "1.2.3"]);
let ref r = req(">=4.2.1");
assert_match_all(r, &["4.2.1", "5.0.0"]);
assert_match_none(r, &["0.0.0", "4.2.1-0"]);
for r in &[req(">=4.2"), req(">=4.2.0")] {
assert_match_all(r, &["4.2.1-0", "4.2.0", "4.3.0"]);
assert_match_none(r, &["0.0.0", "4.1.1", "4.2.0-0"]);
}
for r in &[req(">=4"), req(">=4.0.0")] {
assert_match_all(r, &["4.0.0", "4.1.0-1", "5.0.0"]);
assert_match_none(r, &["0.0.0", "1.2.3", "4.0.0-0"]);
}
}
#[test]
fn test_less() {
for r in &[req("<4.2.1"), req("<4.2.1-0")] {
assert_match_all(r, &["0.0.0", "4.0.0"]);
assert_match_none(r, &["4.2.1-0", "4.2.2", "5.0.0-0", "5.0.0"]);
}
for r in &[req("<4.2"), req("<4.2.0-0")] {
assert_match_all(r, &["0.0.0", "4.1.0"]);
assert_match_none(r, &["4.2.0-0", "4.2.0", "4.3.0-0", "4.3.0"]);
}
for r in &[req("<4"), req("<4.0.0-0")] {
assert_match_all(r, &["0.0.0", "3.9.0"]);
assert_match_none(r, &["4.0.0-0", "4.0.0", "5.0.0-1", "5.0.0"]);
}
}
#[test]
fn test_less_upper_bound() {
for r in &[
req(">1.2.3, <2"),
req(">1.2.3, <2.0"),
req(">1.2.3, <2.0.0"),
req(">=1.2.3, <2.0.0"),
req(">1.2.3, <2.0.0-0"),
] {
assert_match_all(r, &["1.2.4", "1.9.9"]);
assert_match_none(r, &["2.0.0-0", "2.0.0", "2.1.2"]);
}
for r in &[
req(">1.2.3-0, <2"),
req(">1.2.3-0, <2.0"),
req(">1.2.3-0, <2.0.0"),
req(">=1.2.3-0, <2.0.0"),
] {
assert_match_all(r, &["1.2.4", "1.9.9", "2.0.0-0"]);
assert_match_none(r, &["2.0.0", "2.1.2"]);
}
for r in &[
req(">=2.0.0-0, <2"),
req(">=2.0.0-0, <2.0"),
req(">=2.0.0-0, <2.0.0"),
] {
assert_match_all(r, &["2.0.0-0", "2.0.0-11"]);
assert_match_none(r, &["0.0.9", "2.0.0"]);
}
let ref r = req(">5.0.0, <2.0.0");
assert_match_none(r, &["1.2.3", "3.0.0", "6.0.0"]);
let ref r = req(">5.0.0-0, <2.0.0");
assert_match_none(r, &["1.2.3", "3.0.0", "6.0.0"]);
}
#[test]
fn test_caret() {
for r in &[req("^1.2.3-0"), req(">=1.2.3-0, <2.0.0-0")] {
assert_match_all(r, &["1.2.3-0", "1.2.3-1", "1.2.3", "1.9.9"]);
assert_match_none(r, &["0.0.9", "1.1.1-0", "2.0.0-0", "2.1.1"]);
}
for r in &[req("^1.2.3"), req(">=1.2.3, <2.0.0-0")] {
assert_match_all(r, &["1.2.3", "1.9.9"]);
assert_match_none(
r,
&["0.0.9", "1.1.1-0", "1.2.3-0", "1.2.3-1", "2.0.0-0", "2.1.1"],
);
}
for r in &[req("^0.2.3-0"), req(">=0.2.3-0, <0.3.0-0")] {
assert_match_all(r, &["0.2.3-0", "0.2.3", "0.2.9-0", "0.2.9"]);
assert_match_none(r, &["0.0.9", "0.3.0-0", "0.3.11", "1.1.1"]);
}
for r in &[req("^0.2.3"), req(">=0.2.3, <0.3.0-0")] {
assert_match_all(r, &["0.2.3", "0.2.9-0", "0.2.9"]);
assert_match_none(r, &["0.0.9", "0.2.3-0", "0.3.0-0", "0.3.11", "1.1.1"]);
}
for r in &[req("^0.0.3-0"), req(">=0.0.3-0, <0.1.0-0")] {
assert_match_all(r, &["0.0.3-0", "0.0.3-1", "0.0.3"]);
assert_match_none(r, &["0.0.1", "0.3.0-0", "0.4.0-0", "1.1.1"]);
}
for r in &[req("^0.0.3"), req(">=0.0.3, <0.1.0-0")] {
assert_match_all(r, &["0.0.3"]);
assert_match_none(
r,
&["0.0.1", "0.0.3-0", "0.3.0-0", "0.0.3-1", "0.4.0-0", "1.1.1"],
);
}
for r in &[req("^1.2"), req(">=1.2.0, <2.0.0-0")] {
assert_match_all(r, &["1.2.0", "1.9.0-0", "1.9.9"]);
assert_match_none(r, &["0.0.1", "0.0.4-0", "1.2.0-0", "2.0.0-0", "4.0.1"]);
}
for r in &[req("^0.0"), req(">=0.0.0, <0.1.0-0")] {
assert_match_all(r, &["0.0.0", "0.0.1", "0.0.4-0"]);
assert_match_none(r, &["0.0.0-0", "0.1.0-0", "0.1.0", "1.1.1"]);
}
for r in &[req("^1"), req(">=1.0.0, <2.0.0-0")] {
assert_match_all(r, &["1.0.0", "1.0.1"]);
assert_match_none(r, &["0.1.0-0", "0.1.0", "1.0.0-0", "2.0.0-0", "3.1.2"]);
}
}
#[test]
fn test_wildcard() {
for r in &[req("4.2.*"), req("=4.2")] {
assert_match_all(r, &["4.2.0", "4.2.1", "4.2.9"]);
assert_match_none(r, &["0.0.1", "2.1.2-0", "4.2.0-0"]);
assert_match_none(r, &["4.3.0-0", "4.3.0", "5.0.0", "5.0.1"]);
}
for r in &[req("4.*"), req("4.*.*"), req("=4")] {
assert_match_all(r, &["4.0.0", "4.2.1", "4.9.9"]);
assert_match_none(r, &["0.0.1", "2.1.2-0", "4.0.0-0"]);
assert_match_none(r, &["5.0.0-0", "5.0.0", "5.0.1"]);
}
}
#[test]
fn test_greater() {
let ref r = req(">4.2.1-0");
assert_match_all(r, &["4.2.1", "4.2.2", "5.0.0"]);
assert_match_none(r, &["0.0.0", "4.2.1-0"]);
let ref r = req(">4.2.1");
assert_match_all(r, &["4.2.2", "5.0.0-0", "5.0.0"]);
assert_match_none(r, &["0.0.0", "4.2.1-0", "4.2.1"]);
for r in &[req(">4.2"), req(">=4.3.0-0")] {
assert_match_all(r, &["4.3.0-0", "4.3.0", "5.0.0"]);
assert_match_none(r, &["0.0.0", "4.2.1"]);
}
for r in &[req(">4"), req(">=5.0.0-0")] {
assert_match_all(r, &["5.0.0-0", "5.0.0"]);
assert_match_none(r, &["0.0.0", "4.2.1"]);
}
}
#[test]
fn test_less_eq() {
let ref r = req("<=4.2.1");
assert_match_all(r, &["0.0.0", "4.2.1-0", "4.2.1"]);
assert_match_none(r, &["4.2.2", "5.0.0-0", "5.0.0"]);
let ref r = req("<=4.2.1-0");
assert_match_all(r, &["0.0.0", "4.2.1-0"]);
assert_match_none(r, &["4.2.1", "4.2.2", "5.0.0-0", "5.0.0"]);
for r in &[req("<=4.2"), req("<4.3.0-0")] {
assert_match_all(r, &["0.0.0", "4.2.0-0"]);
assert_match_none(r, &["4.3.0-0", "4.3.0", "4.4.0"]);
}
for r in &[req("<=4"), req("<5.0.0-0")] {
assert_match_all(r, &["0.0.0", "4.0.0-0", "4.0.0"]);
assert_match_none(r, &["5.0.0-1", "5.0.0"]);
}
}
#[test]
fn test_tilde() {
for r in &[req("~1.2.3-0"), req(">= 1.2.3-0, < 1.3.0-0")] {
assert_match_all(r, &["1.2.3-0", "1.2.3", "1.2.4-0", "1.2.4"]);
assert_match_none(r, &["0.0.1", "1.1.0-0"]);
assert_match_none(r, &["1.3.0-0", "1.3.0", "1.3.1", "2.0.0"]);
}
for r in &[req("~1.2.3"), req(">= 1.2.3, < 1.3.0-0")] {
assert_match_all(r, &["1.2.3", "1.2.4-0", "1.2.4"]);
assert_match_none(r, &["0.0.1", "1.1.0-0", "1.2.3-0"]);
assert_match_none(r, &["1.3.0-0", "1.3.0", "1.3.1", "2.0.0"]);
}
for r in &[req("~0.24"), req(">=0.24.0, <0.25.0-0")] {
assert_match_all(r, &["0.24.0", "0.24.1-0", "0.24.1", "0.24.9"]);
assert_match_none(r, &["0.0.1", "0.9.9", "0.24.0-0"]);
assert_match_none(r, &["0.25.0-0", "1.1.0", "1.2.3", "2.0.0"]);
}
for r in &[req("~1"), req(">=1.0.0, <2.0.0-0")] {
assert_match_all(r, &["1.0.0", "1.1.0-0", "1.1.0"]);
assert_match_none(r, &["0.0.1", "0.9.9", "1.0.0-0"]);
assert_match_none(r, &["2.0.0-0", "2.0.0", "2.0.1"]);
}
}
}