use tower_lsp_server::ls_types::Position;
use super::{RequirementMatcher, RequirementStatus, is_same_major_minor, position_in_range};
use crate::{ConcreteVersion, Dependency, InvalidPackageName, PackageName, VersionReq};
pub trait PackageNaming: Send + Sync {
fn normalize_package_name(&self, name: &PackageName) -> String {
name.to_string()
}
fn validate_package_name(&self, _name: &str) -> Result<(), InvalidPackageName> {
Ok(())
}
}
pub trait PackageRendering: Send + Sync {
fn format_version_for_text_edit(&self, version: &ConcreteVersion) -> String;
fn format_version_replacing(&self, version: &ConcreteVersion, _current: &str) -> String {
self.format_version_for_text_edit(version)
}
fn format_version_replacing_for(
&self,
_dep: &dyn Dependency,
version: &ConcreteVersion,
current: &str,
) -> String {
self.format_version_replacing(version, current)
}
fn package_url(&self, name: &PackageName) -> String;
fn suppress_package_url(&self, source: &crate::parser::DependencySource) -> bool {
let _ = source;
false
}
fn is_position_on_dependency(&self, dep: &dyn Dependency, position: Position) -> bool {
dep.version_range()
.is_some_and(|r| position_in_range(position, r))
}
}
pub trait RequirementResolution: Send + Sync {
fn version_satisfies_requirement(&self, version: &ConcreteVersion, requirement: &str) -> bool {
let version = version.as_str();
if let Some(req) = requirement.strip_prefix('^') {
let req_parts: Vec<&str> = req.split('.').collect();
let ver_parts: Vec<&str> = version.split('.').collect();
if req_parts.first() != ver_parts.first() {
return false;
}
if req_parts.first().is_some_and(|m| *m != "0") {
return true;
}
if req_parts.len() >= 2 && ver_parts.len() >= 2 {
return req_parts[1] == ver_parts[1];
}
return true;
}
if let Some(req) = requirement.strip_prefix('~') {
return is_same_major_minor(req, version);
}
let req_parts: Vec<&str> = requirement.split('.').collect();
let is_partial_version = req_parts.len() <= 2;
version == requirement
|| (is_partial_version && is_same_major_minor(requirement, version))
|| (is_partial_version && version.starts_with(requirement))
}
fn is_requirement_up_to_date(
&self,
requirement: &VersionReq,
latest: &ConcreteVersion,
) -> bool {
self.version_satisfies_requirement(latest, requirement.as_str())
}
fn requirement_is_unresolved(&self, _requirement: &VersionReq) -> bool {
false
}
fn requirement_status(
&self,
requirement: &VersionReq,
latest: &ConcreteVersion,
) -> RequirementStatus {
if self.requirement_is_unresolved(requirement) {
return RequirementStatus::Unresolved;
}
if self.is_requirement_up_to_date(requirement, latest) {
RequirementStatus::UpToDate
} else {
RequirementStatus::Outdated
}
}
fn compile_requirement(
&self,
_requirement: &VersionReq,
) -> Option<Box<dyn RequirementMatcher>> {
None
}
fn requirement_is_undecidable_given_available(
&self,
_requirement: &VersionReq,
_available: &[ConcreteVersion],
) -> bool {
false
}
fn manifest_requirement_is_resolved_version(&self, dep: &dyn Dependency) -> bool {
let _ = dep;
false
}
}
pub trait DiagnosticMessages: Send + Sync {
fn yanked_message(&self) -> &'static str {
"This version has been yanked"
}
fn yanked_label(&self) -> &'static str {
"*(yanked)*"
}
fn deprecated_message(&self) -> &'static str {
"This package is deprecated"
}
fn deprecated_label(&self) -> &'static str {
"*(deprecated)*"
}
}
pub trait DiagnosticPolicy: Send + Sync {
fn strict_semver_prerelease_exclusion(&self) -> bool {
false
}
fn supports_package_rename(&self) -> bool {
false
}
fn yanked_diagnostic_applies_to(
&self,
_dep: &dyn Dependency,
_requirement: &VersionReq,
) -> bool {
true
}
}
pub trait SourcePolicy: Send + Sync {
fn can_resolve_source(&self, source: &crate::parser::DependencySource) -> bool {
source.is_version_resolvable()
}
fn source_is_public_registry_content(&self, source: &crate::parser::DependencySource) -> bool {
matches!(source, crate::parser::DependencySource::Registry)
}
}
pub trait OsvNaming: Send + Sync {
fn osv_package_name(&self, dep: &dyn Dependency) -> Option<String> {
Some(dep.name().to_string())
}
fn osv_version_to_native(&self, version: &str) -> String {
version.to_string()
}
fn osv_version(&self, version: &str) -> String {
version.to_string()
}
}
pub trait EcosystemFormatter:
PackageNaming
+ PackageRendering
+ RequirementResolution
+ DiagnosticMessages
+ DiagnosticPolicy
+ SourcePolicy
+ OsvNaming
{
}
impl<
T: PackageNaming
+ PackageRendering
+ RequirementResolution
+ DiagnosticMessages
+ DiagnosticPolicy
+ SourcePolicy
+ OsvNaming,
> EcosystemFormatter for T
{
}