1use semver::{Comparator, Op, Prerelease, Version, VersionReq};
8
9pub(crate) fn matches_prerelease(req: &VersionReq, ver: &Version) -> bool {
10 let lower_bound_prerelease = &req.comparators.iter().any(|cmp| {
12 if matches!(cmp.op, Op::Greater | Op::GreaterEq) && !cmp.pre.is_empty() {
13 true
14 } else {
15 false
16 }
17 });
18 for cmp in &req.comparators {
19 if !matches_prerelease_impl(cmp, ver, lower_bound_prerelease) {
20 return false;
21 }
22 }
23
24 true
25}
26
27fn matches_prerelease_impl(cmp: &Comparator, ver: &Version, lower_bound_prerelease: &bool) -> bool {
28 match cmp.op {
29 Op::Exact | Op::Wildcard => matches_exact_prerelease(cmp, ver),
30 Op::Greater => matches_greater(cmp, ver),
31 Op::GreaterEq => {
32 if matches_exact_prerelease(cmp, ver) {
33 return true;
34 }
35 matches_greater(cmp, ver)
36 }
37 Op::Less => {
38 if *lower_bound_prerelease {
39 matches_less(&fill_partial_req(cmp), ver)
40 } else {
41 matches_less(&fill_partial_req_include_pre(cmp), ver)
42 }
43 }
44 Op::LessEq => {
45 if matches_exact_prerelease(cmp, ver) {
46 return true;
47 }
48 matches_less(&fill_partial_req(cmp), ver)
49 }
50 Op::Tilde => matches_tilde_prerelease(cmp, ver),
51 Op::Caret => matches_caret_prerelease(cmp, ver),
52 _ => unreachable!(),
53 }
54}
55
56fn matches_exact(cmp: &Comparator, ver: &Version) -> bool {
58 if ver.major != cmp.major {
59 return false;
60 }
61
62 if let Some(minor) = cmp.minor {
63 if ver.minor != minor {
64 return false;
65 }
66 }
67
68 if let Some(patch) = cmp.patch {
69 if ver.patch != patch {
70 return false;
71 }
72 }
73
74 ver.pre == cmp.pre
75}
76
77fn matches_greater(cmp: &Comparator, ver: &Version) -> bool {
79 if ver.major != cmp.major {
80 return ver.major > cmp.major;
81 }
82
83 match cmp.minor {
84 None => return false,
85 Some(minor) => {
86 if ver.minor != minor {
87 return ver.minor > minor;
88 }
89 }
90 }
91
92 match cmp.patch {
93 None => return false,
94 Some(patch) => {
95 if ver.patch != patch {
96 return ver.patch > patch;
97 }
98 }
99 }
100
101 ver.pre > cmp.pre
102}
103
104fn matches_less(cmp: &Comparator, ver: &Version) -> bool {
106 if ver.major != cmp.major {
107 return ver.major < cmp.major;
108 }
109
110 match cmp.minor {
111 None => return false,
112 Some(minor) => {
113 if ver.minor != minor {
114 return ver.minor < minor;
115 }
116 }
117 }
118
119 match cmp.patch {
120 None => return false,
121 Some(patch) => {
122 if ver.patch != patch {
123 return ver.patch < patch;
124 }
125 }
126 }
127
128 ver.pre < cmp.pre
129}
130
131fn fill_partial_req(cmp: &Comparator) -> Comparator {
132 let mut cmp = cmp.clone();
133 if cmp.minor.is_none() {
134 cmp.minor = Some(0);
135 cmp.patch = Some(0);
136 } else if cmp.patch.is_none() {
137 cmp.patch = Some(0);
138 }
139 cmp
140}
141
142fn fill_partial_req_include_pre(cmp: &Comparator) -> Comparator {
143 let mut cmp = cmp.clone();
144 if cmp.minor.is_none() {
145 cmp.minor = Some(0);
146 cmp.patch = Some(0);
147 cmp.pre = Prerelease::new("0").unwrap();
148 } else if cmp.patch.is_none() {
149 cmp.patch = Some(0);
150 }
151 if cmp.pre.is_empty() {
152 cmp.pre = Prerelease::new("0").unwrap();
153 }
154 cmp
155}
156
157fn matches_exact_prerelease(cmp: &Comparator, ver: &Version) -> bool {
158 if matches_exact(cmp, ver) {
159 return true;
160 }
161
162 if !cmp.pre.is_empty() {
165 return false;
166 }
167
168 if !matches_greater(&fill_partial_req(cmp), ver) {
169 return false;
170 }
171
172 let mut upper = Comparator {
173 op: Op::Less,
174 pre: Prerelease::new("0").unwrap(),
175 ..cmp.clone()
176 };
177
178 match (upper.minor.is_some(), upper.patch.is_some()) {
179 (true, true) => {
180 upper.patch = Some(upper.patch.unwrap() + 1);
181 }
182 (true, false) => {
183 upper.minor = Some(upper.minor.unwrap() + 1);
185 upper.patch = Some(0);
186 }
187 (false, false) => {
188 upper.major += 1;
190 upper.minor = Some(0);
191 upper.patch = Some(0);
192 }
193 _ => {}
194 }
195
196 matches_less(&upper, ver)
197}
198
199fn matches_tilde_prerelease(cmp: &Comparator, ver: &Version) -> bool {
200 if matches_exact(cmp, ver) {
201 return true;
202 }
203
204 if !matches_greater(&fill_partial_req(cmp), ver) {
205 return false;
206 }
207
208 let mut upper = Comparator {
209 op: Op::Less,
210 pre: Prerelease::new("0").unwrap(),
211 ..cmp.clone()
212 };
213
214 match (upper.minor.is_some(), upper.patch.is_some()) {
215 (true, _) => {
216 upper.minor = Some(upper.minor.unwrap() + 1);
217 upper.patch = Some(0);
218 }
219 (false, false) => {
220 upper.major += 1;
221 upper.minor = Some(0);
222 upper.patch = Some(0);
223 }
224 _ => {}
225 }
226
227 matches_less(&upper, ver)
228}
229
230fn matches_caret_prerelease(cmp: &Comparator, ver: &Version) -> bool {
231 if matches_exact(cmp, ver) {
232 return true;
233 }
234
235 if !matches_greater(&fill_partial_req(cmp), ver) {
236 return false;
237 }
238
239 let mut upper = Comparator {
240 op: Op::Less,
241 pre: Prerelease::new("0").unwrap(),
242 ..cmp.clone()
243 };
244
245 match (
246 upper.major > 0,
247 upper.minor.is_some(),
248 upper.patch.is_some(),
249 ) {
250 (true, _, _) | (_, false, false) => {
251 upper.major += 1;
252 upper.minor = Some(0);
253 upper.patch = Some(0);
254 }
255 (_, true, false) => {
256 upper.minor = Some(upper.minor.unwrap() + 1);
257 upper.patch = Some(0);
258 }
259 (_, true, _) if upper.minor.unwrap() > 0 => {
260 upper.minor = Some(upper.minor.unwrap() + 1);
261 upper.patch = Some(0);
262 }
263 (_, true, _) if upper.minor.unwrap() == 0 => {
264 if upper.patch.is_none() {
265 upper.patch = Some(1);
266 } else {
267 upper.patch = Some(upper.patch.unwrap() + 1);
268 }
269 }
270 _ => {}
271 }
272
273 matches_less(&upper, ver)
274}
275
276#[cfg(test)]
277mod matches_prerelease_semantic {
278 use crate::util::semver_ext::VersionReqExt;
279 use semver::{Version, VersionReq};
280
281 fn assert_match_all(req: &VersionReq, versions: &[&str]) {
282 for string in versions {
283 let parsed = Version::parse(string).unwrap();
284 assert!(
285 req.matches_prerelease(&parsed),
286 "{} did not match {}",
287 req,
288 string,
289 );
290 }
291 }
292
293 fn assert_match_none(req: &VersionReq, versions: &[&str]) {
294 for string in versions {
295 let parsed = Version::parse(string).unwrap();
296 assert!(
297 !req.matches_prerelease(&parsed),
298 "{} matched {}",
299 req,
300 string
301 );
302 }
303 }
304
305 pub(super) fn req(text: &str) -> VersionReq {
306 VersionReq::parse(text).unwrap()
307 }
308
309 #[test]
310 fn test_exact() {
311 let ref r = req("=4.2.1-0");
313 assert_match_all(r, &["4.2.1-0"]);
315 assert_match_none(r, &["1.2.3", "4.2.0", "4.2.1-1", "4.2.2"]);
317
318 for r in &[req("=4.2.1"), req(">=4.2.1, <4.2.2-0")] {
320 assert_match_all(r, &["4.2.1"]);
321 assert_match_none(r, &["1.2.3", "4.2.1-0", "4.2.2-0", "4.2.2"]);
322 }
323
324 for r in &[req("=4.2"), req(">=4.2.0, <4.3.0-0")] {
326 assert_match_all(r, &["4.2.0", "4.2.1", "4.2.9"]);
327 assert_match_none(r, &["0.0.1", "2.1.2-0", "4.2.0-0"]);
328 assert_match_none(r, &["4.3.0-0", "4.3.0", "5.0.0-0", "5.0.0"]);
329 }
330
331 for r in &[req("=4"), req(">=4.0.0, <5.0.0-0")] {
333 assert_match_all(r, &["4.0.0", "4.2.1", "4.2.4-0", "4.9.9"]);
334 assert_match_none(r, &["0.0.1", "2.1.2-0", "4.0.0-0"]);
335 assert_match_none(r, &["5.0.0-0", "5.0.0", "5.0.1"]);
336 }
337 }
338
339 #[test]
340 fn test_greater_eq() {
341 let ref r = req(">=4.2.1-0");
343 assert_match_all(r, &["4.2.1-0", "4.2.1", "5.0.0"]);
344 assert_match_none(r, &["0.0.0", "1.2.3"]);
345
346 let ref r = req(">=4.2.1");
348 assert_match_all(r, &["4.2.1", "5.0.0"]);
349 assert_match_none(r, &["0.0.0", "4.2.1-0"]);
350
351 for r in &[req(">=4.2"), req(">=4.2.0")] {
353 assert_match_all(r, &["4.2.1-0", "4.2.0", "4.3.0"]);
354 assert_match_none(r, &["0.0.0", "4.1.1", "4.2.0-0"]);
355 }
356
357 for r in &[req(">=4"), req(">=4.0.0")] {
359 assert_match_all(r, &["4.0.0", "4.1.0-1", "5.0.0"]);
360 assert_match_none(r, &["0.0.0", "1.2.3", "4.0.0-0"]);
361 }
362 }
363
364 #[test]
365 fn test_less() {
366 for r in &[req("<4.2.1"), req("<4.2.1-0")] {
368 assert_match_all(r, &["0.0.0", "4.0.0"]);
369 assert_match_none(r, &["4.2.1-0", "4.2.2", "5.0.0-0", "5.0.0"]);
370 }
371
372 for r in &[req("<4.2"), req("<4.2.0-0")] {
374 assert_match_all(r, &["0.0.0", "4.1.0"]);
375 assert_match_none(r, &["4.2.0-0", "4.2.0", "4.3.0-0", "4.3.0"]);
376 }
377
378 for r in &[req("<4"), req("<4.0.0-0")] {
380 assert_match_all(r, &["0.0.0", "3.9.0"]);
381 assert_match_none(r, &["4.0.0-0", "4.0.0", "5.0.0-1", "5.0.0"]);
382 }
383 }
384
385 #[test]
386 fn test_less_upper_bound() {
387 for r in &[
389 req(">1.2.3, <2"),
390 req(">1.2.3, <2.0"),
391 req(">1.2.3, <2.0.0"),
392 req(">=1.2.3, <2.0.0"),
393 req(">1.2.3, <2.0.0-0"),
394 ] {
395 assert_match_all(r, &["1.2.4", "1.9.9"]);
396 assert_match_none(r, &["2.0.0-0", "2.0.0", "2.1.2"]);
397 }
398
399 for r in &[
401 req(">1.2.3-0, <2"),
402 req(">1.2.3-0, <2.0"),
403 req(">1.2.3-0, <2.0.0"),
404 req(">=1.2.3-0, <2.0.0"),
405 ] {
406 assert_match_all(r, &["1.2.4", "1.9.9", "2.0.0-0"]);
407 assert_match_none(r, &["2.0.0", "2.1.2"]);
408 }
409
410 for r in &[
411 req(">=2.0.0-0, <2"),
412 req(">=2.0.0-0, <2.0"),
413 req(">=2.0.0-0, <2.0.0"),
414 ] {
415 assert_match_all(r, &["2.0.0-0", "2.0.0-11"]);
416 assert_match_none(r, &["0.0.9", "2.0.0"]);
417 }
418
419 let ref r = req(">5.0.0, <2.0.0");
421 assert_match_none(r, &["1.2.3", "3.0.0", "6.0.0"]);
422 let ref r = req(">5.0.0-0, <2.0.0");
423 assert_match_none(r, &["1.2.3", "3.0.0", "6.0.0"]);
424 }
425
426 #[test]
427 fn test_caret() {
428 for r in &[req("^1.2.3-0"), req(">=1.2.3-0, <2.0.0-0")] {
430 assert_match_all(r, &["1.2.3-0", "1.2.3-1", "1.2.3", "1.9.9"]);
431 assert_match_none(r, &["0.0.9", "1.1.1-0", "2.0.0-0", "2.1.1"]);
432 }
433
434 for r in &[req("^1.2.3"), req(">=1.2.3, <2.0.0-0")] {
436 assert_match_all(r, &["1.2.3", "1.9.9"]);
437 assert_match_none(
438 r,
439 &["0.0.9", "1.1.1-0", "1.2.3-0", "1.2.3-1", "2.0.0-0", "2.1.1"],
440 );
441 }
442
443 for r in &[req("^0.2.3-0"), req(">=0.2.3-0, <0.3.0-0")] {
445 assert_match_all(r, &["0.2.3-0", "0.2.3", "0.2.9-0", "0.2.9"]);
446 assert_match_none(r, &["0.0.9", "0.3.0-0", "0.3.11", "1.1.1"]);
447 }
448
449 for r in &[req("^0.2.3"), req(">=0.2.3, <0.3.0-0")] {
451 assert_match_all(r, &["0.2.3", "0.2.9-0", "0.2.9"]);
452 assert_match_none(r, &["0.0.9", "0.2.3-0", "0.3.0-0", "0.3.11", "1.1.1"]);
453 }
454
455 for r in &[req("^0.0.3-0"), req(">=0.0.3-0, <0.1.0-0")] {
457 assert_match_all(r, &["0.0.3-0", "0.0.3-1", "0.0.3"]);
458 assert_match_none(r, &["0.0.1", "0.3.0-0", "0.4.0-0", "1.1.1"]);
459 }
460
461 for r in &[req("^0.0.3"), req(">=0.0.3, <0.1.0-0")] {
463 assert_match_all(r, &["0.0.3"]);
464 assert_match_none(
465 r,
466 &["0.0.1", "0.0.3-0", "0.3.0-0", "0.0.3-1", "0.4.0-0", "1.1.1"],
467 );
468 }
469
470 for r in &[req("^1.2"), req(">=1.2.0, <2.0.0-0")] {
472 assert_match_all(r, &["1.2.0", "1.9.0-0", "1.9.9"]);
473 assert_match_none(r, &["0.0.1", "0.0.4-0", "1.2.0-0", "2.0.0-0", "4.0.1"]);
474 }
475
476 for r in &[req("^0.0"), req(">=0.0.0, <0.1.0-0")] {
478 assert_match_all(r, &["0.0.0", "0.0.1", "0.0.4-0"]);
479 assert_match_none(r, &["0.0.0-0", "0.1.0-0", "0.1.0", "1.1.1"]);
480 }
481
482 for r in &[req("^1"), req(">=1.0.0, <2.0.0-0")] {
484 assert_match_all(r, &["1.0.0", "1.0.1"]);
485 assert_match_none(r, &["0.1.0-0", "0.1.0", "1.0.0-0", "2.0.0-0", "3.1.2"]);
486 }
487 }
488
489 #[test]
490 fn test_wildcard() {
491 for r in &[req("4.2.*"), req("=4.2")] {
495 assert_match_all(r, &["4.2.0", "4.2.1", "4.2.9"]);
497 assert_match_none(r, &["0.0.1", "2.1.2-0", "4.2.0-0"]);
499 assert_match_none(r, &["4.3.0-0", "4.3.0", "5.0.0", "5.0.1"]);
501 }
502
503 for r in &[req("4.*"), req("4.*.*"), req("=4")] {
507 assert_match_all(r, &["4.0.0", "4.2.1", "4.9.9"]);
509 assert_match_none(r, &["0.0.1", "2.1.2-0", "4.0.0-0"]);
511 assert_match_none(r, &["5.0.0-0", "5.0.0", "5.0.1"]);
513 }
514 }
515
516 #[test]
517 fn test_greater() {
518 let ref r = req(">4.2.1-0");
520 assert_match_all(r, &["4.2.1", "4.2.2", "5.0.0"]);
521 assert_match_none(r, &["0.0.0", "4.2.1-0"]);
522
523 let ref r = req(">4.2.1");
525 assert_match_all(r, &["4.2.2", "5.0.0-0", "5.0.0"]);
526 assert_match_none(r, &["0.0.0", "4.2.1-0", "4.2.1"]);
527
528 for r in &[req(">4.2"), req(">=4.3.0-0")] {
530 assert_match_all(r, &["4.3.0-0", "4.3.0", "5.0.0"]);
531 assert_match_none(r, &["0.0.0", "4.2.1"]);
532 }
533
534 for r in &[req(">4"), req(">=5.0.0-0")] {
536 assert_match_all(r, &["5.0.0-0", "5.0.0"]);
537 assert_match_none(r, &["0.0.0", "4.2.1"]);
538 }
539 }
540
541 #[test]
542 fn test_less_eq() {
543 let ref r = req("<=4.2.1");
545 assert_match_all(r, &["0.0.0", "4.2.1-0", "4.2.1"]);
546 assert_match_none(r, &["4.2.2", "5.0.0-0", "5.0.0"]);
547 let ref r = req("<=4.2.1-0");
549 assert_match_all(r, &["0.0.0", "4.2.1-0"]);
550 assert_match_none(r, &["4.2.1", "4.2.2", "5.0.0-0", "5.0.0"]);
551
552 for r in &[req("<=4.2"), req("<4.3.0-0")] {
554 assert_match_all(r, &["0.0.0", "4.2.0-0"]);
555 assert_match_none(r, &["4.3.0-0", "4.3.0", "4.4.0"]);
556 }
557
558 for r in &[req("<=4"), req("<5.0.0-0")] {
560 assert_match_all(r, &["0.0.0", "4.0.0-0", "4.0.0"]);
561 assert_match_none(r, &["5.0.0-1", "5.0.0"]);
562 }
563 }
564
565 #[test]
566 fn test_tilde() {
567 for r in &[req("~1.2.3-0"), req(">= 1.2.3-0, < 1.3.0-0")] {
569 assert_match_all(r, &["1.2.3-0", "1.2.3", "1.2.4-0", "1.2.4"]);
570 assert_match_none(r, &["0.0.1", "1.1.0-0"]);
571 assert_match_none(r, &["1.3.0-0", "1.3.0", "1.3.1", "2.0.0"]);
572 }
573
574 for r in &[req("~1.2.3"), req(">= 1.2.3, < 1.3.0-0")] {
576 assert_match_all(r, &["1.2.3", "1.2.4-0", "1.2.4"]);
577 assert_match_none(r, &["0.0.1", "1.1.0-0", "1.2.3-0"]);
578 assert_match_none(r, &["1.3.0-0", "1.3.0", "1.3.1", "2.0.0"]);
579 }
580
581 for r in &[req("~0.24"), req(">=0.24.0, <0.25.0-0")] {
583 assert_match_all(r, &["0.24.0", "0.24.1-0", "0.24.1", "0.24.9"]);
584 assert_match_none(r, &["0.0.1", "0.9.9", "0.24.0-0"]);
585 assert_match_none(r, &["0.25.0-0", "1.1.0", "1.2.3", "2.0.0"]);
586 }
587
588 for r in &[req("~1"), req(">=1.0.0, <2.0.0-0")] {
590 assert_match_all(r, &["1.0.0", "1.1.0-0", "1.1.0"]);
591 assert_match_none(r, &["0.0.1", "0.9.9", "1.0.0-0"]);
592 assert_match_none(r, &["2.0.0-0", "2.0.0", "2.0.1"]);
593 }
594 }
595}