Skip to content

Commit

Permalink
Add matches_prerelease to allow to match Semver Compatible prerel…
Browse files Browse the repository at this point in the history
…ease
  • Loading branch information
linyihai committed Jul 9, 2024
1 parent 4ea60ae commit 5b8de4e
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 8 deletions.
52 changes: 45 additions & 7 deletions src/eval.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use crate::{Comparator, Op, Version, VersionReq};
use crate::{Comparator, Op, Prerelease, Version, VersionReq};

pub(crate) fn matches_req(req: &VersionReq, ver: &Version) -> bool {
pub(crate) fn matches_req(req: &VersionReq, ver: &Version, prerelease_matches: bool) -> bool {
for cmp in &req.comparators {
if !matches_impl(cmp, ver) {
if !matches_impl(cmp, ver, prerelease_matches) {
return false;
}
}

if ver.pre.is_empty() {
if ver.pre.is_empty() || prerelease_matches {
return true;
}

Expand All @@ -24,18 +24,23 @@ pub(crate) fn matches_req(req: &VersionReq, ver: &Version) -> bool {
}

pub(crate) fn matches_comparator(cmp: &Comparator, ver: &Version) -> bool {
matches_impl(cmp, ver) && (ver.pre.is_empty() || pre_is_compatible(cmp, ver))
matches_impl(cmp, ver, false) && (ver.pre.is_empty() || pre_is_compatible(cmp, ver))
}

fn matches_impl(cmp: &Comparator, ver: &Version) -> bool {
fn matches_impl(cmp: &Comparator, ver: &Version, prerelease_matches: bool) -> bool {
match cmp.op {
Op::Exact | Op::Wildcard => matches_exact(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(cmp, ver),
Op::Caret => matches_caret(cmp, ver),
Op::Caret => {
if prerelease_matches {
return matches_caret_prerelease(cmp, ver);
}
matches_caret(cmp, ver)
}
#[cfg(no_non_exhaustive)]
Op::__NonExhaustive => unreachable!(),
}
Expand Down Expand Up @@ -133,6 +138,39 @@ fn matches_tilde(cmp: &Comparator, ver: &Version) -> bool {
ver.pre >= cmp.pre
}

fn matches_caret_prerelease(cmp: &Comparator, ver: &Version) -> bool {
if matches_exact(cmp, ver) {
return true;
}
if !matches_greater(cmp, ver) {
return false;
}

let mut pre_cmp = Comparator {
op: Op::LessEq,
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)
}

fn matches_caret(cmp: &Comparator, ver: &Version) -> bool {
if ver.major != cmp.major {
return false;
Expand Down
7 changes: 6 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,12 @@ impl VersionReq {
/// Evaluate whether the given `Version` satisfies the version requirement
/// described by `self`.
pub fn matches(&self, version: &Version) -> bool {
eval::matches_req(self, version)
eval::matches_req(self, version, false)
}

/// Simliar to [`Self::matches`], it allows to match any "Semver-compatible" pre-release version.
pub fn matches_prerelease(&self, version: &Version) -> bool {
eval::matches_req(self, version, true)
}
}

Expand Down
112 changes: 112 additions & 0 deletions tests/test_matches_prerelease.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#![allow(
clippy::missing_panics_doc,
clippy::shadow_unrelated,
clippy::toplevel_ref_arg,
clippy::wildcard_imports
)]

mod node;
mod util;

use crate::util::*;

#[cfg(test_node_semver)]
use node::{req, VersionReq};
#[cfg(not(test_node_semver))]
use semver::VersionReq;
#[cfg_attr(not(no_track_caller), track_caller)]
fn assert_prerelease_match_all(req: &VersionReq, versions: &[&str]) {
for string in versions {
let parsed = version(string);
assert!(req.matches_prerelease(&parsed), "did not match {}", string);
}
}

#[cfg_attr(not(no_track_caller), track_caller)]
fn assert_prerelease_match_none(req: &VersionReq, versions: &[&str]) {
for string in versions {
let parsed = version(string);
assert!(!req.matches_prerelease(&parsed), "matched {}", string);
}
}

#[test]
fn patch_caret() {
let ref r = req("0.0.7");
// Match >=0.0.7, <0.0.8-0, that's only match 0.0.7
assert_prerelease_match_all(r, &["0.0.7"]);
// Not Match <0.7.0
assert_prerelease_match_none(r, &["0.0.6", "0.0.7-0", "0.0.7-pre"]);
// Not Match >=0.8.0-0
assert_prerelease_match_none(r, &["0.0.8-0", "0.0.8-pre.1", "0.0.8"]);

let ref r = req("0.0.7-0");
// Match >=0.0.7-0, <0.0.8-0
assert_prerelease_match_all(r, &["0.0.7-0", "0.0.7-pre", "0.0.7"]);
// Not Match <0.7.0-0
assert_prerelease_match_none(r, &["0.0.6-0", "0.0.6-pre", "0.0.6"]);
// Not Match >=0.0.8-0
assert_prerelease_match_none(r, &["0.0.8-0", "0.0.8-pre.1", "0.0.8"]);
}

#[test]
fn minor_caret() {
let ref r = req("0.8.0");
// Match >=0.8.0, <0.9.0-0
assert_prerelease_match_all(r, &["0.8.0", "0.8.1-0", "0.8.1-pre", "0.8.1"]);
// Not Match <0.8.0
assert_prerelease_match_none(r, &["0.1.0", "0.8.0-pre"]);
// Not Match >=0.9.0

let ref r = req("0.8.0-0");
// Match >=0.8.0-0, <0.9.0-0
assert_prerelease_match_all(r, &["0.8.0-0", "0.8.0", "0.8.1-pre", "0.8.1"]);
// Not Match <0.8.0-0
assert_prerelease_match_none(r, &["0.1.0", "0.7.7-pre", "0.7.7"]);
// Not Match >=0.9.0
assert_prerelease_match_none(r, &["0.9.0-0", "0.9.0", "1.0.0-pre", "2.0.0-0"]);
}

#[test]
fn major_caret() {
let ref r = req("1.2.3");
// Match >=1.2.3, <2.0.0-0
assert_prerelease_match_all(r, &["1.2.3", "1.2.4-0", "1.2.4", "1.8.8"]);
// Not Match < 1.2.3
assert_prerelease_match_none(r, &["0.8.8", "1.2.0", "1.2.3-pre"]);
// 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.3-0");
// Match >=1.2.3-0, <2.0.0-0
assert_prerelease_match_all(r, &["1.2.3-pre", "1.2.3", "1.2.4-0", "1.2.4"]);
// Not Match < 1.2.3-0, >= 2.0.0-0
assert_prerelease_match_none(r, &["1.2.0", "2.0.0-0", "2.0.0"]);
}

#[test]
fn test_non_caret() {
let ref r: VersionReq = req(">=1.0.0");
assert_prerelease_match_all(r, &["1.0.0", "1.0.1-pre", "2.0.0-0", "2.0.0"]);
assert_prerelease_match_none(r, &["0.9.9", "0.10.0", "1.0.0-pre.0"]);

let ref r: VersionReq = req(">=1.0.0-pre.1");
assert_prerelease_match_all(r, &["1.0.0-pre.1", "1.0.0", "1.0.1-pre", "2.0.0-0"]);
assert_prerelease_match_none(r, &["0.9.9", "0.10.0", "1.0.0-pre.0"]);

let ref r: VersionReq = req("<=1.0.0");
assert_prerelease_match_all(r, &["0.9.9", "0.10.0", "1.0.0-pre", "1.0.0"]);
assert_prerelease_match_none(r, &["1.0.1", "1.0.1-pre", "1.1.0", "2.0.0-0"]);

let ref r: VersionReq = req("<=1.0.0-0");
assert_prerelease_match_all(r, &["0.9.9", "0.1.0", "0.10.0", "1.0.0-0"]);
assert_prerelease_match_none(r, &["1.0.0-pre", "1.0.1-pre", "1.1.0", "2.0.0-0"]);

let ref r: VersionReq = req(">1.0.0-0,<=1.1.0-0");
assert_prerelease_match_all(r, &["1.0.0-pre", "1.0.0", "1.0.9", "1.1.0-0"]);
assert_prerelease_match_none(r, &["0.0.1", "1.0.0-0", "1.2.3-pre", "2.0.0"]);

let ref r: VersionReq = req(">=1.0.0,<1.1.0-0");
assert_prerelease_match_all(r, &["1.0.0", "1.0.0", "1.0.9"]);
assert_prerelease_match_none(r, &["0.0.1", "1.0.0-pre", "1.1.0-0", "1.1.0", "2.0.0"]);
}

0 comments on commit 5b8de4e

Please sign in to comment.