use std::collections::HashMap;
use crate::lsp_helpers::EcosystemFormatter;
use crate::{ConcreteVersion, Dependency, EcosystemId, PackageName};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BareRequirementPolicy {
AlwaysRange,
ConcreteIfFullVersion,
Concrete,
}
const fn bare_requirement_policy(ecosystem: EcosystemId) -> BareRequirementPolicy {
match ecosystem {
EcosystemId::Cargo => BareRequirementPolicy::AlwaysRange,
EcosystemId::GithubActions
| EcosystemId::GitlabCi
| EcosystemId::Npm
| EcosystemId::Composer
| EcosystemId::Deno => BareRequirementPolicy::ConcreteIfFullVersion,
EcosystemId::Pypi
| EcosystemId::Go
| EcosystemId::Bundler
| EcosystemId::Dart
| EcosystemId::Maven
| EcosystemId::Gradle
| EcosystemId::Swift
| EcosystemId::NuGet => BareRequirementPolicy::Concrete,
}
}
#[allow(clippy::string_slice)]
#[must_use]
pub fn is_full_semver_shape(s: &str) -> bool {
let s = crate::github::normalize_tag(s);
let core = match s.find(['-', '+']) {
Some(idx) => &s[..idx],
None => s,
};
let mut parts = core.split('.');
let (Some(major), Some(minor), Some(patch), None) =
(parts.next(), parts.next(), parts.next(), parts.next())
else {
return false;
};
[major, minor, patch]
.iter()
.all(|p| !p.is_empty() && p.bytes().all(|b| b.is_ascii_digit()))
}
fn looks_like_a_single_version(s: &str) -> bool {
if s.is_empty() {
return false;
}
if s.contains([
'^', '~', '*', '<', '>', ',', '|', '(', ')', '[', ']', ' ', '\t', ':', '+', 'x', 'X',
]) {
return false;
}
let core = crate::github::normalize_tag(s);
core.chars().next().is_some_and(|c| c.is_ascii_digit())
}
pub fn concrete_pin_version(requirement: &str, ecosystem: EcosystemId) -> Option<&str> {
let trimmed = requirement.trim();
if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("latest") {
return None;
}
let pinned = trimmed
.strip_prefix("==")
.or_else(|| trimmed.strip_prefix('='));
let bracket_pinned = trimmed
.strip_circumfix('[', ']')
.filter(|inner| !inner.contains(','));
match pinned.or(bracket_pinned) {
Some(body) => looks_like_a_single_version(body).then_some(body),
None => match bare_requirement_policy(ecosystem) {
BareRequirementPolicy::AlwaysRange => None,
BareRequirementPolicy::Concrete => {
looks_like_a_single_version(trimmed).then_some(trimmed)
}
BareRequirementPolicy::ConcreteIfFullVersion => {
is_full_semver_shape(trimmed).then_some(trimmed)
}
},
}
}
#[cfg(test)]
fn is_concrete_version(requirement: &str, ecosystem: EcosystemId) -> bool {
concrete_pin_version(requirement, ecosystem).is_some()
}
fn version_matches_requirement(
formatter: &dyn EcosystemFormatter,
version: &ConcreteVersion,
requirement: &crate::VersionReq,
) -> bool {
if let Some(matcher) = formatter.compile_requirement(requirement) {
matcher.matches(version) == Some(true)
} else {
formatter.version_satisfies_requirement(version, requirement.as_str())
}
}
fn best_candidate_for_requirement<'a>(
candidates: &'a [ConcreteVersion],
requirement: &crate::VersionReq,
formatter: &dyn EcosystemFormatter,
) -> Option<&'a ConcreteVersion> {
candidates
.iter()
.filter(|v| version_matches_requirement(formatter, v, requirement))
.max_by(|a, b| crate::lockfile::compare_lockfile_versions(a.as_str(), b.as_str()))
}
pub(crate) fn resolve_occurrence_version<'a>(
dep: &dyn Dependency,
normalized_name: &str,
resolved_versions: &'a HashMap<PackageName, ConcreteVersion>,
resolved_version_candidates: Option<&'a HashMap<PackageName, Vec<ConcreteVersion>>>,
formatter: &dyn EcosystemFormatter,
) -> Option<&'a ConcreteVersion> {
let candidates = resolved_version_candidates.and_then(|candidates| {
candidates
.get(normalized_name)
.or_else(|| candidates.get(dep.name()))
});
match (candidates, dep.version_requirement()) {
(Some(candidates), Some(req)) if candidates.len() > 1 => {
best_candidate_for_requirement(candidates, req, formatter)
}
_ => resolved_versions
.get(normalized_name)
.or_else(|| resolved_versions.get(dep.name())),
}
}
pub fn resolve_in_use_version(
dep: &dyn Dependency,
normalized_name: &str,
resolved_versions: &HashMap<PackageName, ConcreteVersion>,
resolved_version_candidates: Option<&HashMap<PackageName, Vec<ConcreteVersion>>>,
formatter: &dyn EcosystemFormatter,
ecosystem: EcosystemId,
) -> Option<String> {
if formatter.manifest_requirement_is_resolved_version(dep) {
return dep
.version_requirement()
.and_then(|req| concrete_pin_version(req.as_str(), ecosystem))
.map(str::to_string);
}
resolve_occurrence_version(
dep,
normalized_name,
resolved_versions,
resolved_version_candidates,
formatter,
)
.map(ConcreteVersion::to_string)
.or_else(|| {
dep.version_requirement()
.and_then(|req| concrete_pin_version(req.as_str(), ecosystem))
.map(str::to_string)
})
}
#[cfg(test)]
mod tests {
use super::*;
struct CaretFormatter;
struct SemverMatcher(semver::VersionReq);
impl crate::lsp_helpers::RequirementMatcher for SemverMatcher {
fn matches(&self, version: &ConcreteVersion) -> Option<bool> {
version
.as_str()
.parse::<semver::Version>()
.ok()
.map(|v| self.0.matches(&v))
}
}
impl crate::lsp_helpers::PackageNaming for CaretFormatter {}
impl crate::lsp_helpers::PackageRendering for CaretFormatter {
fn format_version_for_text_edit(&self, version: &ConcreteVersion) -> String {
version.to_string()
}
fn package_url(&self, name: &PackageName) -> String {
name.to_string()
}
}
impl crate::lsp_helpers::RequirementResolution for CaretFormatter {
fn compile_requirement(
&self,
requirement: &crate::VersionReq,
) -> Option<Box<dyn crate::lsp_helpers::RequirementMatcher>> {
requirement
.as_str()
.parse::<semver::VersionReq>()
.ok()
.map(|req| {
Box::new(SemverMatcher(req)) as Box<dyn crate::lsp_helpers::RequirementMatcher>
})
}
}
impl crate::lsp_helpers::DiagnosticMessages for CaretFormatter {}
impl crate::lsp_helpers::DiagnosticPolicy for CaretFormatter {}
impl crate::lsp_helpers::SourcePolicy for CaretFormatter {}
impl crate::lsp_helpers::OsvNaming for CaretFormatter {}
#[test]
fn is_concrete_version_accepts_explicit_pins_in_any_ecosystem() {
for eco in [EcosystemId::Cargo, EcosystemId::Npm, EcosystemId::Go] {
assert!(is_concrete_version("=1.2.3", eco), "{eco:?}");
}
assert!(is_concrete_version("v1.9.1", EcosystemId::Go));
}
#[test]
fn is_concrete_version_pep440_double_equals_is_a_pin() {
assert!(is_concrete_version("==2.28.0", EcosystemId::Pypi));
}
#[test]
fn is_concrete_version_bare_digit_accepted_for_non_range_default_ecosystems() {
for eco in [
EcosystemId::Maven,
EcosystemId::Go,
EcosystemId::Bundler,
EcosystemId::Dart,
EcosystemId::Gradle,
EcosystemId::NuGet,
] {
assert!(is_concrete_version("2.14.1", eco), "{eco:?}");
}
}
#[test]
fn is_concrete_version_nuget_bare_version_is_a_deliberate_pin_approximation() {
assert!(is_concrete_version("1.0.0", EcosystemId::NuGet));
assert!(is_concrete_version("[1.0.0]", EcosystemId::NuGet));
}
#[test]
fn is_concrete_version_bare_digit_rejected_for_range_default_ecosystems() {
assert!(!is_concrete_version("1.2.3", EcosystemId::Cargo));
assert!(is_concrete_version("=1.2.3", EcosystemId::Cargo));
}
#[test]
fn is_concrete_version_rejects_partials_and_wildcards() {
for eco in [EcosystemId::Npm, EcosystemId::Composer] {
assert!(!is_concrete_version("1.x", eco), "{eco:?}");
assert!(!is_concrete_version("1.2.x", eco), "{eco:?}");
assert!(!is_concrete_version("1.2", eco), "{eco:?}");
}
assert!(!is_concrete_version("1.+", EcosystemId::Gradle));
}
#[test]
fn is_concrete_version_rejects_ranges_and_wildcards() {
for eco in [EcosystemId::Maven, EcosystemId::Go] {
assert!(!is_concrete_version("^1.0", eco));
assert!(!is_concrete_version("~1.2", eco));
assert!(!is_concrete_version("*", eco));
assert!(!is_concrete_version(">=1.0", eco));
assert!(!is_concrete_version(">=1.0 <2.0", eco));
assert!(!is_concrete_version("1.0.*", eco));
assert!(!is_concrete_version("", eco));
}
}
#[test]
fn is_concrete_version_rejects_non_version_schemes() {
let eco = EcosystemId::Go;
assert!(!is_concrete_version("latest", eco));
assert!(!is_concrete_version("github:user/repo", eco));
assert!(!is_concrete_version("file:../x", eco));
assert!(!is_concrete_version("main", eco));
}
#[test]
fn concrete_pin_version_strips_pep440_double_equals_comparator() {
assert_eq!(
concrete_pin_version("==4.9.0", EcosystemId::Pypi),
Some("4.9.0")
);
}
#[test]
fn concrete_pin_version_strips_single_equals_and_bracket_pins() {
assert_eq!(
concrete_pin_version("=1.2.3", EcosystemId::Cargo),
Some("1.2.3")
);
assert_eq!(
concrete_pin_version("[1.0.0]", EcosystemId::NuGet),
Some("1.0.0")
);
}
#[test]
fn concrete_pin_version_bare_version_returned_verbatim() {
assert_eq!(
concrete_pin_version("2.14.1", EcosystemId::Maven),
Some("2.14.1")
);
}
#[test]
fn concrete_pin_version_rejects_ranges_and_partials() {
assert_eq!(concrete_pin_version("^1.0", EcosystemId::Cargo), None);
assert_eq!(concrete_pin_version("1.2.3", EcosystemId::Cargo), None);
assert_eq!(concrete_pin_version(">=1.0,<2.0", EcosystemId::Pypi), None);
}
#[test]
fn is_full_semver_shape_accepts_full_versions_with_and_without_v_prefix() {
assert!(is_full_semver_shape("4.2.0"));
assert!(is_full_semver_shape("v4.2.0"));
assert!(is_full_semver_shape("V4.2.0"));
}
#[test]
fn is_full_semver_shape_accepts_prerelease_and_build_suffixes() {
assert!(is_full_semver_shape("v4.2.0-beta.1"));
assert!(is_full_semver_shape("4.2.0+build.5"));
}
#[test]
fn is_full_semver_shape_rejects_partial_versions() {
assert!(!is_full_semver_shape("v4"));
assert!(!is_full_semver_shape("v4.2"));
}
#[test]
fn is_full_semver_shape_rejects_non_version_and_sha_shapes() {
assert!(!is_full_semver_shape("main"));
assert!(!is_full_semver_shape(
"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
));
assert!(!is_full_semver_shape(""));
assert!(!is_full_semver_shape("4.2.0.1"));
assert!(!is_full_semver_shape("4..0"));
}
#[test]
fn concrete_pin_version_github_actions_full_bare_tag_is_concrete() {
assert_eq!(
concrete_pin_version("v4.2.0", EcosystemId::GithubActions),
Some("v4.2.0")
);
}
#[test]
fn concrete_pin_version_github_actions_moving_major_tag_is_a_range() {
assert_eq!(concrete_pin_version("v4", EcosystemId::GithubActions), None);
assert_eq!(
concrete_pin_version("v4.2", EcosystemId::GithubActions),
None
);
}
#[test]
fn concrete_pin_version_github_actions_bare_sha_is_not_concrete() {
assert_eq!(
concrete_pin_version(
"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
EcosystemId::GithubActions
),
None
);
}
#[test]
fn concrete_pin_version_gitlab_ci_full_tag_is_concrete() {
assert_eq!(
concrete_pin_version("1.2.3", EcosystemId::GitlabCi),
Some("1.2.3")
);
assert_eq!(
concrete_pin_version("v1.2.3", EcosystemId::GitlabCi),
Some("v1.2.3")
);
}
#[test]
fn concrete_pin_version_gitlab_ci_partial_pin_is_a_range() {
assert_eq!(concrete_pin_version("1.2", EcosystemId::GitlabCi), None);
assert_eq!(concrete_pin_version("1", EcosystemId::GitlabCi), None);
}
#[test]
fn concrete_pin_version_gitlab_ci_digit_leading_sha_is_not_concrete() {
assert_eq!(
concrete_pin_version(
"1234567890abcdef1234567890abcdef12345678",
EcosystemId::GitlabCi
),
None
);
}
#[test]
fn concrete_pin_version_npm_full_bare_version_is_concrete() {
assert_eq!(
concrete_pin_version("4.17.0", EcosystemId::Npm),
Some("4.17.0")
);
}
#[test]
fn concrete_pin_version_npm_partial_bare_version_is_a_range() {
assert_eq!(concrete_pin_version("4.17", EcosystemId::Npm), None);
assert_eq!(concrete_pin_version("4", EcosystemId::Npm), None);
}
#[test]
fn concrete_pin_version_npm_explicit_pin_still_concrete() {
assert_eq!(
concrete_pin_version("=4.17.0", EcosystemId::Npm),
Some("4.17.0")
);
}
#[test]
fn concrete_pin_version_composer_full_bare_version_is_concrete() {
assert_eq!(
concrete_pin_version("2.0.0", EcosystemId::Composer),
Some("2.0.0")
);
}
#[test]
fn concrete_pin_version_composer_partial_bare_version_is_a_range() {
assert_eq!(concrete_pin_version("2.0", EcosystemId::Composer), None);
assert_eq!(concrete_pin_version("2", EcosystemId::Composer), None);
}
#[test]
fn concrete_pin_version_composer_explicit_pin_still_concrete() {
assert_eq!(
concrete_pin_version("=2.0.0", EcosystemId::Composer),
Some("2.0.0")
);
}
#[test]
fn concrete_pin_version_deno_full_bare_version_is_concrete() {
assert_eq!(
concrete_pin_version("4.17.0", EcosystemId::Deno),
Some("4.17.0")
);
}
#[test]
fn concrete_pin_version_deno_partial_bare_version_is_a_range() {
assert_eq!(concrete_pin_version("4.17", EcosystemId::Deno), None);
assert_eq!(concrete_pin_version("4", EcosystemId::Deno), None);
}
#[test]
fn concrete_pin_version_deno_explicit_pin_still_concrete() {
assert_eq!(
concrete_pin_version("=1.0.0", EcosystemId::Deno),
Some("1.0.0")
);
}
#[test]
fn in_use_version_renamed_occurrence_resolves_its_own_major() {
use crate::VersionReq;
use crate::lsp_helpers::test_support::MockDep;
let renamed_old_major = MockDep {
name: PackageName::new("serde"),
version_req: VersionReq::new("0.9"),
version_range: tower_lsp_server::ls_types::Range::default(),
name_range: tower_lsp_server::ls_types::Range::default(),
};
let plain_current_major = MockDep {
name: PackageName::new("serde"),
version_req: VersionReq::new("1.0"),
version_range: tower_lsp_server::ls_types::Range::default(),
name_range: tower_lsp_server::ls_types::Range::default(),
};
let mut resolved_versions = HashMap::new();
resolved_versions.insert(PackageName::new("serde"), ConcreteVersion::from("1.0.219"));
let mut candidates = HashMap::new();
candidates.insert(
PackageName::new("serde"),
vec![
ConcreteVersion::from("0.9.15"),
ConcreteVersion::from("1.0.219"),
],
);
let renamed_result = resolve_in_use_version(
&renamed_old_major,
"serde",
&resolved_versions,
Some(&candidates),
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::Cargo,
);
let plain_result = resolve_in_use_version(
&plain_current_major,
"serde",
&resolved_versions,
Some(&candidates),
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::Cargo,
);
assert_eq!(renamed_result, Some("0.9.15".to_string()));
assert_eq!(plain_result, Some("1.0.219".to_string()));
}
#[test]
fn in_use_version_no_candidate_satisfies_requirement_returns_none() {
use crate::VersionReq;
use crate::lsp_helpers::test_support::MockDep;
let dep = MockDep {
name: PackageName::new("serde"),
version_req: VersionReq::new("0.9"),
version_range: tower_lsp_server::ls_types::Range::default(),
name_range: tower_lsp_server::ls_types::Range::default(),
};
let resolved_versions = HashMap::new();
let mut candidates = HashMap::new();
candidates.insert(
PackageName::new("serde"),
vec![
ConcreteVersion::from("1.0.219"),
ConcreteVersion::from("1.1.0"),
],
);
let result = resolve_in_use_version(
&dep,
"serde",
&resolved_versions,
Some(&candidates),
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::Cargo,
);
assert_eq!(result, None);
}
#[test]
fn in_use_version_no_requirement_falls_back_to_collapsed_value() {
use crate::lsp_helpers::test_support::MockMarkedDep;
let dep = MockMarkedDep {
name: PackageName::new("serde"),
name_range: tower_lsp_server::ls_types::Range::default(),
markers: None,
};
let mut resolved_versions = HashMap::new();
resolved_versions.insert(PackageName::new("serde"), ConcreteVersion::from("1.0.219"));
let mut candidates = HashMap::new();
candidates.insert(
PackageName::new("serde"),
vec![
ConcreteVersion::from("0.9.15"),
ConcreteVersion::from("1.0.219"),
],
);
let result = resolve_in_use_version(
&dep,
"serde",
&resolved_versions,
Some(&candidates),
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::Cargo,
);
assert_eq!(result, Some("1.0.219".to_string()));
}
#[test]
fn in_use_version_single_candidate_matches_collapsed_fast_path() {
use crate::VersionReq;
use crate::lsp_helpers::test_support::MockDep;
let dep = MockDep {
name: PackageName::new("serde"),
version_req: VersionReq::new("1.0"),
version_range: tower_lsp_server::ls_types::Range::default(),
name_range: tower_lsp_server::ls_types::Range::default(),
};
let mut resolved_versions = HashMap::new();
resolved_versions.insert(PackageName::new("serde"), ConcreteVersion::from("1.0.219"));
let mut candidates = HashMap::new();
candidates.insert(
PackageName::new("serde"),
vec![ConcreteVersion::from("1.0.219")],
);
let with_candidates = resolve_in_use_version(
&dep,
"serde",
&resolved_versions,
Some(&candidates),
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::Cargo,
);
let without_candidates = resolve_in_use_version(
&dep,
"serde",
&resolved_versions,
None,
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::Cargo,
);
assert_eq!(with_candidates, Some("1.0.219".to_string()));
assert_eq!(with_candidates, without_candidates);
}
#[test]
fn best_candidate_for_requirement_non_semver_uses_lexicographic_tiebreak() {
let candidates = vec![
ConcreteVersion::from("1-rc1"),
ConcreteVersion::from("1-rc2"),
];
let result = best_candidate_for_requirement(
&candidates,
&crate::VersionReq::new("1"),
&crate::lsp_helpers::test_support::MockFormatter,
);
assert_eq!(result, Some(&ConcreteVersion::from("1-rc2")));
}
#[test]
fn best_candidate_for_requirement_uses_compile_requirement_when_available() {
use crate::VersionReq;
let candidates = vec![
ConcreteVersion::from("1.3.2"),
ConcreteVersion::from("2.9.4"),
];
let result =
best_candidate_for_requirement(&candidates, &VersionReq::new("2.4"), &CaretFormatter);
assert_eq!(result, Some(&ConcreteVersion::from("2.9.4")));
}
#[test]
fn best_candidate_for_requirement_picks_highest_of_satisfying_subset() {
use crate::VersionReq;
let candidates = vec![
ConcreteVersion::from("1.0.1"),
ConcreteVersion::from("1.0.5"),
ConcreteVersion::from("2.0.0"),
];
let result =
best_candidate_for_requirement(&candidates, &VersionReq::new("^1.0"), &CaretFormatter);
assert_eq!(result, Some(&ConcreteVersion::from("1.0.5")));
}
#[test]
fn in_use_version_tiebreaks_among_multiple_satisfying_candidates() {
use crate::VersionReq;
use crate::lsp_helpers::test_support::MockDep;
let dep = MockDep {
name: PackageName::new("pkg"),
version_req: VersionReq::new("^1.0"),
version_range: tower_lsp_server::ls_types::Range::default(),
name_range: tower_lsp_server::ls_types::Range::default(),
};
let resolved_versions = HashMap::new();
let mut candidates = HashMap::new();
candidates.insert(
PackageName::new("pkg"),
vec![
ConcreteVersion::from("1.0.1"),
ConcreteVersion::from("1.0.5"),
ConcreteVersion::from("2.0.0"),
],
);
let result = resolve_in_use_version(
&dep,
"pkg",
&resolved_versions,
Some(&candidates),
&CaretFormatter,
EcosystemId::Cargo,
);
assert_eq!(result, Some("1.0.5".to_string()));
}
#[test]
fn in_use_version_npm_bare_full_version_no_lockfile_resolves_664_repro() {
use crate::VersionReq;
use crate::lsp_helpers::test_support::MockDep;
let dep = MockDep {
name: PackageName::new("express"),
version_req: VersionReq::new("4.17.0"),
version_range: tower_lsp_server::ls_types::Range::default(),
name_range: tower_lsp_server::ls_types::Range::default(),
};
let result = resolve_in_use_version(
&dep,
"express",
&HashMap::new(),
None,
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::Npm,
);
assert_eq!(result, Some("4.17.0".to_string()));
}
#[test]
fn in_use_version_composer_bare_full_version_no_lockfile_resolves_664_repro() {
use crate::VersionReq;
use crate::lsp_helpers::test_support::MockDep;
let dep = MockDep {
name: PackageName::new("monolog/monolog"),
version_req: VersionReq::new("2.0.0"),
version_range: tower_lsp_server::ls_types::Range::default(),
name_range: tower_lsp_server::ls_types::Range::default(),
};
let result = resolve_in_use_version(
&dep,
"monolog/monolog",
&HashMap::new(),
None,
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::Composer,
);
assert_eq!(result, Some("2.0.0".to_string()));
}
#[test]
fn in_use_version_cargo_bare_full_version_no_lockfile_still_none() {
use crate::VersionReq;
use crate::lsp_helpers::test_support::MockDep;
let dep = MockDep {
name: PackageName::new("serde"),
version_req: VersionReq::new("1.0.219"),
version_range: tower_lsp_server::ls_types::Range::default(),
name_range: tower_lsp_server::ls_types::Range::default(),
};
let result = resolve_in_use_version(
&dep,
"serde",
&HashMap::new(),
None,
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::Cargo,
);
assert_eq!(result, None);
}
#[test]
fn in_use_version_deno_bare_full_version_no_lockfile_resolves_667_repro() {
use crate::VersionReq;
use crate::lsp_helpers::test_support::MockDep;
let dep = MockDep {
name: PackageName::new("express"),
version_req: VersionReq::new("4.17.0"),
version_range: tower_lsp_server::ls_types::Range::default(),
name_range: tower_lsp_server::ls_types::Range::default(),
};
let result = resolve_in_use_version(
&dep,
"express",
&HashMap::new(),
None,
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::Deno,
);
assert_eq!(result, Some("4.17.0".to_string()));
}
#[test]
fn in_use_version_nuget_bare_floor_resolves_as_pin_approximation() {
use crate::VersionReq;
use crate::lsp_helpers::test_support::MockDep;
let dep = MockDep {
name: PackageName::new("Newtonsoft.Json"),
version_req: VersionReq::new("1.0.0"),
version_range: tower_lsp_server::ls_types::Range::default(),
name_range: tower_lsp_server::ls_types::Range::default(),
};
let result = resolve_in_use_version(
&dep,
"newtonsoft.json",
&HashMap::new(),
None,
&crate::lsp_helpers::test_support::MockFormatter,
EcosystemId::NuGet,
);
assert_eq!(result, Some("1.0.0".to_string()));
}
}