use tower_lsp_server::ls_types::{InlayHint, InlayHintKind, InlayHintLabel, InlayHintTooltip};
use crate::{ConcreteVersion, EcosystemConfig, ParseResult};
use super::diagnostics::MAX_VERSION_DIAGNOSTIC_CHARS;
use super::{
EcosystemFormatter, LatestVerdict, RequirementGate, RequirementStatus, VersionData,
in_use_version, latest_verdict, sanitize_and_truncate_for_diagnostic,
};
fn sanitize_hint_version(version: &str) -> String {
sanitize_and_truncate_for_diagnostic(version, MAX_VERSION_DIAGNOSTIC_CHARS)
}
pub fn generate_inlay_hints(
parse_result: &dyn ParseResult,
versions: VersionData<'_>,
loading_state: crate::LoadingState,
config: &EcosystemConfig,
formatter: &dyn EcosystemFormatter,
) -> Vec<InlayHint> {
let deps = parse_result.dependencies();
let mut hints = Vec::with_capacity(deps.len());
let vuln_keys = versions.ecosystem.map(|ecosystem| {
crate::osv::vulnerability_keys(
parse_result,
versions.resolved,
versions.resolved_version_candidates,
formatter,
ecosystem,
)
});
for dep in deps {
let Some(version_range) = dep.version_range() else {
continue;
};
let version_range: tower_lsp_server::ls_types::Range = version_range.into();
let normalized_name = formatter.normalize_package_name(dep.name());
let latest_version = versions
.cached
.get(normalized_name.as_str())
.or_else(|| versions.cached.get(dep.name()))
.map(|v| &v.latest);
let resolved_version: Option<ConcreteVersion> =
if formatter.manifest_requirement_is_resolved_version(dep) {
dep.version_requirement()
.map(|r| ConcreteVersion::new(r.as_str()))
} else {
in_use_version::resolve_occurrence_version(
dep,
normalized_name.as_str(),
versions.resolved,
versions.resolved_version_candidates,
formatter,
)
.cloned()
};
if super::version_range_is_synthetic_empty(dep) && resolved_version.is_none() {
continue;
}
if loading_state == crate::LoadingState::Loading
&& config.show_loading_hints
&& latest_version.is_none()
{
hints.push(InlayHint {
position: version_range.end,
label: InlayHintLabel::String(config.loading_text.clone()),
kind: Some(InlayHintKind::TYPE),
tooltip: Some(InlayHintTooltip::String(
"Fetching latest version...".to_string(),
)),
padding_left: Some(true),
padding_right: None,
text_edits: None,
data: None,
});
continue;
}
let Some(latest) = latest_version else {
if config.network.is_offline() {
let label = resolved_version.as_ref().map_or_else(
|| "📴".to_string(),
|resolved| format!("📴 {}", sanitize_hint_version(resolved.as_str())),
);
hints.push(InlayHint {
position: version_range.end,
label: InlayHintLabel::String(label),
kind: Some(InlayHintKind::TYPE),
tooltip: Some(InlayHintTooltip::String(
"Offline: registry not checked".to_string(),
)),
padding_left: Some(true),
padding_right: None,
text_edits: None,
data: None,
});
continue;
}
if let Some(resolved) = &resolved_version
&& config.show_up_to_date_hints
{
hints.push(InlayHint {
position: version_range.end,
label: InlayHintLabel::String(format!(
"{} {}",
config.up_to_date_text,
sanitize_hint_version(resolved.as_str())
)),
kind: Some(InlayHintKind::TYPE),
padding_left: Some(true),
padding_right: None,
text_edits: None,
tooltip: None,
data: None,
});
}
continue;
};
let status = if let Some(resolved) = &resolved_version {
if formatter
.build_metadata_policy()
.versions_equal(resolved.as_str(), latest.as_str())
{
RequirementStatus::UpToDate
} else {
RequirementStatus::Outdated
}
} else {
match dep.version_requirement() {
Some(version_req) => formatter.requirement_status_for(dep, version_req, latest),
None => RequirementStatus::Unresolved,
}
};
let mut flagged_tooltip: Option<String> = None;
let label_text = match status {
RequirementStatus::UpToDate => {
if !config.show_up_to_date_hints {
continue;
}
let verdict = latest_verdict(
versions.latest_status,
dep,
vuln_keys.as_ref(),
normalized_name.as_str(),
latest.as_str(),
formatter,
);
if let LatestVerdict::Flagged {
advisory_ids,
malicious,
} = verdict
{
let icon = if malicious { "🚫" } else { "⚠️" };
flagged_tooltip = Some(if advisory_ids.is_empty() {
"This requirement already admits a version OSV.dev flags — do not \
rely on it being safe"
.to_string()
} else {
format!(
"This requirement already admits a version OSV.dev flags ({}) — \
do not rely on it being safe",
advisory_ids.join(", ")
)
});
format!("{icon} {}", sanitize_hint_version(latest.as_str()))
} else if let Some(resolved) = &resolved_version {
format!(
"{} {}",
config.up_to_date_text,
sanitize_hint_version(resolved.as_str())
)
} else {
config.up_to_date_text.clone()
}
}
RequirementStatus::Outdated => {
let verdict = latest_verdict(
versions.latest_status,
dep,
vuln_keys.as_ref(),
normalized_name.as_str(),
latest.as_str(),
formatter,
);
if let LatestVerdict::Flagged {
advisory_ids,
malicious,
} = verdict
{
let icon = if malicious { "🚫" } else { "⚠️" };
flagged_tooltip = Some(if advisory_ids.is_empty() {
"Flagged by OSV.dev — do not upgrade to this version".to_string()
} else {
format!(
"Flagged by OSV.dev ({}) — do not upgrade to this version",
advisory_ids.join(", ")
)
});
format!("{icon} {} flagged", sanitize_hint_version(latest.as_str()))
} else {
config
.needs_update_text
.replace("{}", &sanitize_hint_version(latest.as_str()))
}
}
RequirementStatus::Unresolved => continue,
};
let label_text = if config.network.is_offline() {
format!("{label_text} 📴")
} else {
label_text
};
hints.push(InlayHint {
position: version_range.end,
label: InlayHintLabel::String(label_text),
kind: Some(InlayHintKind::TYPE),
padding_left: Some(true),
padding_right: None,
text_edits: None,
tooltip: flagged_tooltip.map(InlayHintTooltip::String),
data: None,
});
}
hints
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lsp_helpers::test_support::*;
use crate::lsp_helpers::test_support_lsp::*;
use crate::lsp_helpers::*;
#[test]
fn test_generate_inlay_hints_skips_dependency_with_no_requirement_even_while_loading() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockMixedParseResult {
deps: vec![Box::new(MockNoRequirementDep {
name: "com.example:foo".into(),
name_range: Range::new(Position::new(4, 18), Position::new(4, 21)).into(),
version_range: Range::new(Position::new(5, 15), Position::new(5, 15)).into(),
})],
uri: crate::test_util::test_uri("/test/pom.xml"),
};
let hints_while_loading = generate_inlay_hints(
&parse_result,
VersionData::new(&HashMap::new(), &HashMap::new()),
crate::LoadingState::Loading,
&config,
&formatter,
);
assert!(
hints_while_loading.is_empty(),
"must not flash a Loading… hint for a dependency with no version_requirement"
);
let hints_loaded = generate_inlay_hints(
&parse_result,
VersionData::new(&HashMap::new(), &HashMap::new()),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert!(hints_loaded.is_empty());
}
#[test]
fn test_generate_inlay_hints_shows_resolved_version_hint_despite_no_requirement() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Offline,
};
let parse_result = MockMixedParseResult {
deps: vec![Box::new(MockNoRequirementDep {
name: "com.example:foo".into(),
name_range: Range::new(Position::new(4, 18), Position::new(4, 21)).into(),
version_range: Range::new(Position::new(5, 15), Position::new(5, 15)).into(),
})],
uri: crate::test_util::test_uri("/test/pom.xml"),
};
let mut resolved_versions = HashMap::new();
resolved_versions.insert("com.example:foo".into(), "1.2.3".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&HashMap::new(), &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(
hints.len(),
1,
"resolved_version alone must still produce a hint"
);
match &hints[0].label {
InlayHintLabel::String(text) => assert_eq!(text, "📴 1.2.3"),
_ => panic!("expected string label"),
}
}
#[test]
fn test_generate_inlay_hints_shows_loading_hint_for_non_empty_range_with_no_requirement() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockMixedParseResult {
deps: vec![Box::new(MockNoRequirementDep {
name: "com.example:guava".into(),
name_range: Range::new(Position::new(1, 0), Position::new(1, 5)).into(),
version_range: Range::new(Position::new(4, 40), Position::new(4, 45)).into(),
})],
uri: crate::test_util::test_uri("/test/libs.versions.toml"),
};
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&HashMap::new(), &HashMap::new()),
crate::LoadingState::Loading,
&config,
&formatter,
);
assert_eq!(
hints.len(),
1,
"must still show the Loading… hint for a real, non-empty version_range"
);
match &hints[0].label {
InlayHintLabel::String(text) => assert_eq!(text, "⏳"),
_ => panic!("expected string label"),
}
}
#[test]
fn test_inlay_hint_exact_version_shows_update_needed() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "=2.0.12".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert("serde".into(), PackageVersions::latest_only("2.1.1"));
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), "2.0.12".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert_eq!(text, "❌ 2.1.1");
}
_ => panic!("Expected string label"),
}
}
fn resolved_vs_latest_label(
formatter: &dyn EcosystemFormatter,
resolved: &str,
latest: &str,
) -> String {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "pkg".into(),
version_req: "^0.8.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/pubspec.yaml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert("pkg".into(), PackageVersions::latest_only(latest));
let mut resolved_versions = HashMap::new();
resolved_versions.insert("pkg".into(), resolved.into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => text.clone(),
_ => panic!("expected string label"),
}
}
#[test]
fn test_inlay_hint_significant_build_metadata_resolved_older_build_is_outdated() {
let label = resolved_vs_latest_label(&MockBuildAwareFormatter, "0.8.13+1", "0.8.13+23");
assert_eq!(label, "❌ 0.8.13+23");
let label = resolved_vs_latest_label(&MockBuildAwareFormatter, "0.8.13+23", "0.8.13+23");
assert!(label.starts_with("✅"), "got: {label}");
}
#[test]
fn test_inlay_hint_ignored_build_metadata_resolved_build_difference_is_up_to_date() {
let label = resolved_vs_latest_label(&MOCK_FORMATTER, "1.2.3+1", "1.2.3+23");
assert!(label.starts_with("✅"), "got: {label}");
}
#[test]
fn test_inlay_hint_outdated_flagged_by_osv_uses_distinct_label_and_tooltip() {
use crate::osv::{Capped, LatestStatusMap, UpgradeStatus, VulnSeverity};
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "feed-widget-helper".into(),
version_req: "1.0.4".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/package.json"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert(
"feed-widget-helper".into(),
PackageVersions::latest_only("1.0.8"),
);
let resolved_versions = HashMap::new();
let mut latest_status = LatestStatusMap::new();
latest_status.insert(
crate::test_util::vuln_key("feed-widget-helper"),
UpgradeStatus::CandidateVulnerable {
version: ConcreteVersion::new("1.0.8"),
advisory_ids: Capped::new(vec!["MAL-2026-16332".to_string()], 1),
worst_severity: Some(VulnSeverity::Malicious),
},
);
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions)
.with_latest_status(&latest_status),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
!text.starts_with('❌'),
"must not use the plain badge: {text}"
);
assert!(text.contains("1.0.8"), "got: {text}");
assert!(text.contains("flagged"), "got: {text}");
}
InlayHintLabel::LabelParts(_) => panic!("Expected string label"),
}
match &hints[0].tooltip {
Some(InlayHintTooltip::String(tooltip)) => {
assert!(tooltip.contains("MAL-2026-16332"), "got: {tooltip}");
}
other => panic!("expected a tooltip naming the advisory, got: {other:?}"),
}
}
#[test]
fn test_inlay_hint_up_to_date_admitted_latest_flagged_by_osv_uses_distinct_label_and_tooltip() {
use crate::osv::{Capped, LatestStatusMap, UpgradeStatus, VulnSeverity};
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "feed-widget-helper".into(),
version_req: "^1.0.4".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/package.json"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert(
"feed-widget-helper".into(),
PackageVersions::latest_only("1.0.8"),
);
let resolved_versions = HashMap::new();
let mut latest_status = LatestStatusMap::new();
latest_status.insert(
crate::test_util::vuln_key("feed-widget-helper"),
UpgradeStatus::CandidateVulnerable {
version: ConcreteVersion::new("1.0.8"),
advisory_ids: Capped::new(vec!["MAL-2026-16332".to_string()], 1),
worst_severity: Some(VulnSeverity::Malicious),
},
);
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions)
.with_latest_status(&latest_status),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
!text.starts_with('✅'),
"must not use the plain up-to-date badge: {text}"
);
assert!(text.contains("1.0.8"), "got: {text}");
}
InlayHintLabel::LabelParts(_) => panic!("Expected string label"),
}
match &hints[0].tooltip {
Some(InlayHintTooltip::String(tooltip)) => {
assert!(tooltip.contains("MAL-2026-16332"), "got: {tooltip}");
}
other => panic!("expected a tooltip naming the advisory, got: {other:?}"),
}
}
#[test]
fn test_inlay_hint_caret_version_up_to_date() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "^2.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert("serde".into(), PackageVersions::latest_only("2.1.1"));
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), "2.1.1".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
text.starts_with("✅"),
"Expected up-to-date hint, got: {}",
text
);
}
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_go_prefers_manifest_requirement_over_stale_resolved_version() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_GO_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "example.com/mod".into(),
version_req: "v0.8.1".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/go.mod"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert(
"example.com/mod".into(),
PackageVersions::latest_only("v0.9.1"),
);
let mut resolved_versions = HashMap::new();
resolved_versions.insert("example.com/mod".into(), "v0.9.1".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
text.starts_with("❌"),
"expected outdated hint driven by go.mod pin, got: {text}"
);
}
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_non_go_formatter_uses_resolved_lockfile_version() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "1.0.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert("serde".into(), PackageVersions::latest_only("1.2.0"));
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), "1.2.0".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
text.starts_with("✅"),
"expected up-to-date hint from resolved lockfile version, got: {text}"
);
}
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_resolved_differing_only_by_build_metadata_is_up_to_date() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "1.0.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert(
"serde".into(),
PackageVersions::latest_only("1.2.0+build.7"),
);
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), "1.2.0+build.3".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&MOCK_FORMATTER,
);
assert_eq!(hints.len(), 1);
std::assert_matches!(&hints[0].label, InlayHintLabel::String(text) if text.starts_with("✅"));
}
#[test]
fn test_inlay_hint_resolved_vs_latest_build_metadata_matrix() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
for (resolved, latest, up_to_date) in [
("1.2.0", "1.2.0", true),
("1.2.0+build.3", "1.2.0", true),
("1.2.0", "1.2.0+build.7", true),
("1.2.0+a", "1.2.0+b", true),
("1.2.0+build.3", "1.3.0+build.3", false),
("1.2.0-beta.1", "1.2.0+build.7", false),
] {
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "1.0.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert("serde".into(), PackageVersions::latest_only(latest));
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), resolved.into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&MOCK_FORMATTER,
);
assert_eq!(hints.len(), 1, "{resolved} vs {latest}");
let InlayHintLabel::String(text) = &hints[0].label else {
panic!("expected string label");
};
if up_to_date {
assert!(text.starts_with("✅"), "{resolved} vs {latest}: {text}");
} else {
assert_eq!(text, &format!("❌ {latest}"), "{resolved} vs {latest}");
}
}
}
#[test]
fn test_loading_hint_shows_when_no_cached_version() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "tokio".into(),
version_req: "1.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let cached_versions = HashMap::new();
let resolved_versions = HashMap::new();
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loading,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert_eq!(text, "⏳", "Expected loading hint");
}
_ => panic!("Expected string label"),
}
if let Some(InlayHintTooltip::String(tooltip)) = &hints[0].tooltip {
assert_eq!(tooltip, "Fetching latest version...");
} else {
panic!("Expected tooltip");
}
}
#[test]
fn test_loading_hint_disabled_when_config_false() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: false,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "tokio".into(),
version_req: "1.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let cached_versions = HashMap::new();
let resolved_versions = HashMap::new();
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loading,
&config,
&formatter,
);
assert_eq!(
hints.len(),
0,
"Expected no hints when loading hints disabled"
);
}
#[test]
fn test_caret_version_0x_edge_cases() {
let formatter = MOCK_FORMATTER;
assert!(formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.2.0"),
&VersionReq::new("^0.2")
));
assert!(formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.2.5"),
&VersionReq::new("^0.2")
));
assert!(formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.2.99"),
&VersionReq::new("^0.2")
));
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.3.0"),
&VersionReq::new("^0.2")
));
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.1.0"),
&VersionReq::new("^0.2")
));
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("1.0.0"),
&VersionReq::new("^0.2")
));
assert!(formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.0.3"),
&VersionReq::new("^0.0.3")
));
assert!(formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.0.3"),
&VersionReq::new("^0.0")
));
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.0.2"),
&VersionReq::new("^0.0.3")
));
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.0.4"),
&VersionReq::new("^0.0.3")
));
assert!(
formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.0.0"),
&VersionReq::new("^0")
)
);
assert!(
formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.5.0"),
&VersionReq::new("^0")
)
);
assert!(
!formatter.version_satisfies_requirement(
&ConcreteVersion::new("1.0.0"),
&VersionReq::new("^0")
)
);
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.1.0"),
&VersionReq::new("^0.0")
));
}
#[test]
fn test_caret_version_non_zero_major() {
let formatter = MOCK_FORMATTER;
assert!(formatter.version_satisfies_requirement(
&ConcreteVersion::new("1.2.0"),
&VersionReq::new("^1.2")
));
assert!(formatter.version_satisfies_requirement(
&ConcreteVersion::new("1.9.9"),
&VersionReq::new("^1.2")
));
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("2.0.0"),
&VersionReq::new("^1.2")
));
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("0.9.0"),
&VersionReq::new("^1.2")
));
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("1.0.0"),
&VersionReq::new("^1.2")
));
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("1.1.9"),
&VersionReq::new("^1.2")
));
}
#[test]
fn test_caret_requirement_enforces_minor_lower_bound() {
let formatter = MOCK_FORMATTER;
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("1.1.0"),
&VersionReq::new("^1.5")
));
assert!(formatter.version_satisfies_requirement(
&ConcreteVersion::new("1.5.0"),
&VersionReq::new("^1.5")
));
assert!(formatter.version_satisfies_requirement(
&ConcreteVersion::new("1.9.0"),
&VersionReq::new("^1.5")
));
assert!(!formatter.version_satisfies_requirement(
&ConcreteVersion::new("2.0.0"),
&VersionReq::new("^1.5")
));
}
#[test]
fn test_loading_hint_not_shown_when_cached_version_exists() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "1.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert("serde".into(), PackageVersions::latest_only("1.0.214"));
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), "1.0.214".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loading,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert_eq!(
text, "✅ 1.0.214",
"Expected up-to-date hint, not loading hint, got: {}",
text
);
}
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_not_in_lockfile_but_satisfies_requirement() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "criterion".into(),
version_req: "0.5".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 9)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert("criterion".into(), PackageVersions::latest_only("0.5.1"));
let resolved_versions = HashMap::new();
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
text.starts_with("✅"),
"Expected up-to-date hint for satisfied requirement, got: {}",
text
);
}
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_not_in_lockfile_and_outdated() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "criterion".into(),
version_req: "0.4".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 9)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert("criterion".into(), PackageVersions::latest_only("0.5.1"));
let resolved_versions = HashMap::new();
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
text.starts_with("❌"),
"Expected needs-update hint for unsatisfied requirement, got: {}",
text
);
assert!(text.contains("0.5.1"), "Expected latest version in hint");
}
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_unresolved_requirement_emits_no_hint() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "spring-boot-starter".into(),
version_req: "$missing".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/libs.versions.toml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert(
"spring-boot-starter".into(),
PackageVersions::latest_only("3.2.0"),
);
let resolved_versions = HashMap::new();
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&MockUnresolvedFormatter,
);
assert!(
hints.is_empty(),
"Expected no inlay hint at all for an unresolved requirement (not even 'up to date'), got: {hints:?}"
);
}
#[test]
fn test_inlay_hint_offline_cold_no_resolved_shows_bare_marker() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Offline,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "^2.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let cached_versions = HashMap::new();
let resolved_versions = HashMap::new();
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => assert_eq!(text, "📴"),
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_offline_cold_with_resolved_shows_marker_and_version() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Offline,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "^2.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let cached_versions = HashMap::new();
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), "2.0.12".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => assert_eq!(text, "📴 2.0.12"),
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_offline_warm_cache_appends_marker_to_outdated_badge() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Offline,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "=2.0.12".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let mut cached_versions = HashMap::new();
cached_versions.insert("serde".into(), PackageVersions::latest_only("2.1.1"));
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), "2.0.12".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => assert_eq!(text, "❌ 2.1.1 📴"),
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_offline_marker_strips_bidi_override_from_resolved_version() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Offline,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "^2.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let malicious_resolved = "2.0.12\u{202E}deifidom ton";
let cached_versions = HashMap::new();
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), malicious_resolved.into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
!text.contains('\u{202E}'),
"bidi override must not survive into the offline-marker label; got: {text}"
);
}
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_no_cached_latest_up_to_date_strips_bidi_override_from_resolved_version() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "criterion".into(),
version_req: "0.5".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 9)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let malicious_resolved = "0.5.1\u{202E}deifidom ton";
let cached_versions = HashMap::new();
let mut resolved_versions = HashMap::new();
resolved_versions.insert("criterion".into(), malicious_resolved.into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
!text.contains('\u{202E}'),
"bidi override must not survive into the up-to-date label; got: {text}"
);
}
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_up_to_date_label_strips_bidi_override_from_resolved_version() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "^2.0".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let malicious_resolved = "2.1.1\u{202E}deifidom ton";
let mut cached_versions = HashMap::new();
cached_versions.insert(
"serde".into(),
PackageVersions::latest_only(malicious_resolved),
);
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), malicious_resolved.into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
!text.contains('\u{202E}'),
"bidi override must not survive into the up-to-date label; got: {text}"
);
}
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_outdated_label_strips_bidi_override_from_latest_version() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "=2.0.12".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let malicious_latest = "2.1.1\u{202E}live.tsr";
let mut cached_versions = HashMap::new();
cached_versions.insert(
"serde".into(),
PackageVersions::latest_only(malicious_latest),
);
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), "2.0.12".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert!(
!text.contains('\u{202E}'),
"bidi override must not survive into the inlay hint label; got: {text}"
);
}
_ => panic!("Expected string label"),
}
}
#[test]
fn test_inlay_hint_outdated_label_caps_oversized_latest_version() {
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range};
let formatter = MOCK_FORMATTER;
let config = EcosystemConfig {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
network: crate::NetworkMode::Online,
};
let parse_result = MockParseResult {
deps: vec![MockDep {
name: "serde".into(),
version_req: "=2.0.12".into(),
version_range: Range::new(Position::new(0, 10), Position::new(0, 20)).into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 5)).into(),
}],
uri: crate::test_util::test_uri("/test/Cargo.toml"),
};
let oversized_latest = format!("2.0.0-{}", "X".repeat(MAX_VERSION_DIAGNOSTIC_CHARS + 50));
let mut cached_versions = HashMap::new();
cached_versions.insert(
"serde".into(),
PackageVersions::latest_only(oversized_latest),
);
let mut resolved_versions = HashMap::new();
resolved_versions.insert("serde".into(), "2.0.12".into());
let hints = generate_inlay_hints(
&parse_result,
VersionData::new(&cached_versions, &resolved_versions),
crate::LoadingState::Loaded,
&config,
&formatter,
);
assert_eq!(hints.len(), 1);
match &hints[0].label {
InlayHintLabel::String(text) => {
assert_eq!(
text.chars().count(),
2 + MAX_VERSION_DIAGNOSTIC_CHARS + 1,
"expected exact capped length; got: {text}"
);
assert!(
text.ends_with('…'),
"expected truncation marker; got: {text}"
);
}
_ => panic!("Expected string label"),
}
}
}