From d094bfd99788c440b48a66612709be0390147845 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Wed, 16 Sep 2026 08:56:42 +0100 Subject: [PATCH 1/2] Fix Cargo native range parsing Accept comma as the AND separator per Cargo's documented requirement syntax, and treat a bare version as a caret requirement (Cargo's default) instead of an exact match. Fixes #37 --- lib/vers/parser.rb | 9 ++++++++- test/test_parser.rb | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/vers/parser.rb b/lib/vers/parser.rb index ceef528..19c1eb2 100644 --- a/lib/vers/parser.rb +++ b/lib/vers/parser.rb @@ -133,7 +133,7 @@ def parse_native(range_string, scheme) when "maven" parse_maven_range(range_string) when "cargo" - parse_npm_range(range_string, scheme: "cargo") + parse_cargo_range(range_string) when "nuget" parse_nuget_range(range_string) when "hex" @@ -817,6 +817,10 @@ def parse_nginx_range(range_string) parse_constraints(constraint, "nginx") end + def parse_cargo_range(range_string) + parse_npm_range(range_string.to_s.tr(",", " "), scheme: "cargo") + end + # NPM range parsing (^, ~, -, ||, etc.) def parse_npm_range(range_string, scheme: "npm") constraint = range_string.to_s.strip @@ -855,6 +859,9 @@ def parse_npm_range(range_string, scheme: "npm") def parse_npm_single_range(range_string, scheme: "npm") constraint = range_string.to_s.strip + if scheme == "cargo" && !constraint.match?(/\A[~^><=!]/) + return parse_caret_range(constraint, scheme: scheme) + end return parse_caret_range(constraint.delete_prefix("^").strip, scheme: scheme) if constraint.start_with?("^") return parse_tilde_range(constraint.delete_prefix("~>").strip, scheme: scheme) if constraint.start_with?("~>") return parse_tilde_range(constraint.delete_prefix("~").strip, scheme: scheme) if constraint.start_with?("~") diff --git a/test/test_parser.rb b/test/test_parser.rb index b6e11e4..6f86694 100644 --- a/test/test_parser.rb +++ b/test/test_parser.rb @@ -711,6 +711,42 @@ def test_parse_native_cargo_space_separated_and refute range.contains?("2.0.0") end + def test_parse_native_cargo_comma_separated_and + range = @parser.parse_native(">= 2.1.0, < 2.41.0", "cargo") + assert range.contains?("2.1.0") + assert range.contains?("2.40.0") + refute range.contains?("2.0.0") + refute range.contains?("2.41.0") + end + + def test_parse_native_cargo_comma_without_spaces + range = @parser.parse_native(">=1.0.0,<2.0.0", "cargo") + assert range.contains?("1.5.0") + refute range.contains?("2.0.0") + end + + def test_parse_native_cargo_bare_version_is_caret + range = @parser.parse_native("1.2.3", "cargo") + assert range.contains?("1.2.3") + assert range.contains?("1.9.0") + refute range.contains?("2.0.0") + refute range.contains?("1.2.2") + end + + def test_parse_native_cargo_bare_partial_version_is_caret + range = @parser.parse_native("1.2", "cargo") + assert range.contains?("1.2.0") + assert range.contains?("1.9.0") + refute range.contains?("2.0.0") + end + + def test_parse_native_cargo_bare_zero_major + range = @parser.parse_native("0.2.3", "cargo") + assert range.contains?("0.2.3") + assert range.contains?("0.2.9") + refute range.contains?("0.3.0") + end + # Go module tests def test_parse_native_go_comma_separated From 4ba03782f8778045812da4ce5f0b44f893da2c84 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Wed, 16 Sep 2026 08:56:42 +0100 Subject: [PATCH 2/2] Add include_prerelease: to satisfies? and contains? Lets callers opt out of the node-semver prerelease gate on npm/cargo ranges so advisory-style matching can include prereleases whose bounds carry no prerelease tag. Fixes #38 --- lib/vers.rb | 4 ++-- lib/vers/version_range.rb | 4 ++-- test/test_vers.rb | 22 ++++++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/vers.rb b/lib/vers.rb index 182dbcf..8b6c973 100644 --- a/lib/vers.rb +++ b/lib/vers.rb @@ -119,7 +119,7 @@ def self.to_vers_string(version_range, scheme) # Vers.satisfies?("1.5.0", "vers:npm/>=1.0.0|<2.0.0") # => true # Vers.satisfies?("1.5.0", "^1.2.3", "npm") # => true # - def self.satisfies?(version, constraint, scheme = nil) + def self.satisfies?(version, constraint, scheme = nil, include_prerelease: false) sub_ranges = constraint.split('||').map(&:strip).reject(&:empty?) sub_ranges.any? do |sub_range| range = if scheme @@ -127,7 +127,7 @@ def self.satisfies?(version, constraint, scheme = nil) else parse(sub_range) end - range&.contains?(version) || false + range&.contains?(version, include_prerelease: include_prerelease) || false end end diff --git a/lib/vers/version_range.rb b/lib/vers/version_range.rb index 798e1c8..8d89a37 100644 --- a/lib/vers/version_range.rb +++ b/lib/vers/version_range.rb @@ -56,7 +56,7 @@ def unbounded? exclusions.empty? && intervals.length == 1 && intervals.first.unbounded? end - def contains?(version) + def contains?(version, include_prerelease: false) if VALIDATED_CONTAINMENT_SCHEMES.include?(scheme) && !Version.valid?(version, scheme) return false end @@ -70,7 +70,7 @@ def contains?(version) else interval.contains?(version) end - contains && prerelease_allowed?(interval, version) + contains && (include_prerelease || prerelease_allowed?(interval, version)) end end diff --git a/test/test_vers.rb b/test/test_vers.rb index 249132c..a36b0d0 100644 --- a/test/test_vers.rb +++ b/test/test_vers.rb @@ -48,6 +48,28 @@ def test_satisfies_with_native_format refute Vers.satisfies?("2.0.0", "^1.2.3", "npm") end + def test_satisfies_excludes_prerelease_by_default + refute Vers.satisfies?("1.7.0-alpha.2", "< 3.0.0", "npm") + refute Vers.satisfies?("1.7.0-alpha.2", "^1.0.0", "cargo") + end + + def test_satisfies_include_prerelease + assert Vers.satisfies?("1.7.0-alpha.2", "< 3.0.0", "npm", include_prerelease: true) + assert Vers.satisfies?("1.7.0-alpha.2", "^1.0.0", "cargo", include_prerelease: true) + refute Vers.satisfies?("3.1.0-alpha.1", "< 3.0.0", "npm", include_prerelease: true) + end + + def test_contains_include_prerelease + range = Vers.parse_native("^1.0.0", "npm") + refute range.contains?("1.7.0-alpha.2") + assert range.contains?("1.7.0-alpha.2", include_prerelease: true) + end + + def test_satisfies_cargo_comma_separated_and + assert Vers.satisfies?("2.1.0", ">= 2.1.0, < 2.41.0", "cargo") + refute Vers.satisfies?("2.41.0", ">= 2.1.0, < 2.41.0", "cargo") + end + def test_compare_versions assert_equal(-1, Vers.compare("1.2.3", "1.2.4")) assert_equal(1, Vers.compare("2.0.0", "1.9.9"))