From 6490fe43676919bc1dcc8659ec4e52da225f92e6 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 22 Feb 2026 16:17:03 +0900 Subject: fix(constraint): handle single pipe OR separator in version constraints Composer uses `|` (single pipe) as the standard OR separator, but split_or() only recognized `||` (double pipe). This caused disjunctive constraints like `^6.0|^7.0|^8.0` to be parsed as only `^6.0`, breaking dependency resolution for packages like laravel/tinker. Co-Authored-By: Claude Opus 4.6 --- crates/mozart-constraint/src/lib.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'crates/mozart-constraint') diff --git a/crates/mozart-constraint/src/lib.rs b/crates/mozart-constraint/src/lib.rs index e41818c..7c417ec 100644 --- a/crates/mozart-constraint/src/lib.rs +++ b/crates/mozart-constraint/src/lib.rs @@ -365,23 +365,28 @@ impl VersionConstraint { } } -/// Split on `||` (pipe-OR), but not inside version strings. +/// Split on `|` or `||` (pipe-OR). Composer accepts both forms. fn split_or(s: &str) -> Vec<&str> { let mut parts = Vec::new(); let mut start = 0; let bytes = s.as_bytes(); let mut i = 0; while i < bytes.len() { - if i + 1 < bytes.len() && bytes[i] == b'|' && bytes[i + 1] == b'|' { + if bytes[i] == b'|' { parts.push(s[start..i].trim()); - i += 2; + i += 1; + // Skip second pipe if `||` + if i < bytes.len() && bytes[i] == b'|' { + i += 1; + } start = i; } else { i += 1; } } parts.push(s[start..].trim()); - parts + // Filter out empty parts (e.g. from leading/trailing pipes) + parts.into_iter().filter(|p| !p.is_empty()).collect() } /// Parse an AND group (space or comma separated constraints). @@ -1604,6 +1609,16 @@ mod tests { assert!(!satisfies("^8.0||^9.0||^10.0||^11.0", "12.0.0")); } + #[test] + fn test_single_pipe_or() { + // Single pipe `|` is the standard Composer OR separator + assert!(satisfies("^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "6.0.0")); + assert!(satisfies("^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "9.0.0")); + assert!(satisfies("^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "11.5.0")); + assert!(!satisfies("^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "5.9.9")); + assert!(!satisfies("^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "12.0.0")); + } + #[test] fn test_combined_real_world_symfony_pattern() { // ">=5.4 <7.0" — typical Symfony range -- cgit v1.3.1