Skip to main content

collect_update_all_edits

Function collect_update_all_edits 

Source
pub fn collect_update_all_edits(
    parse_result: &dyn ParseResult,
    content: &str,
    versions: VersionData<'_>,
    formatter: &dyn EcosystemFormatter,
) -> Vec<TextEdit>
Expand description

Manifest edits bringing every safely-editable outdated dependency to latest.

A dependency is included when all of the following hold:

  • it declares a version_range (a span to rewrite exists);
  • a latest version is known in versions.cached (normalized name first, then raw — mirroring crate::lsp_helpers::generate_diagnostics_from_cache);
  • formatter.is_requirement_up_to_date reports the declared requirement as not satisfying latest — the same predicate diagnostics use, so on a fixture where the guard below is a no-op, collect_update_all_edits(..).len() equals the number of generate_diagnostics_from_cache “Newer version available” diagnostics;
  • the literal-span guard (literal_span_matches): content sliced over version_range must still be (up to whitespace and NuGet’s bracket wrap) the literal text — Dependency::version_literal when the ecosystem provides one (e.g. deps-swift, whose synthesized comparator requirement string diverges from the bare literal version_range spans), falling back to the declared requirement text otherwise. Some ecosystems point version_range at something that is not a version literal at all — a Maven ${property} reference or a Gradle DSL variable/version-catalog alias — and rewriting those spans would corrupt the manifest instead of fixing it. A dependency that fails the guard is skipped entirely: neither counted nor edited.

Accepted edits are sorted by start position; a later edit whose start falls before the previous edit’s end (an overlap — a WorkspaceEdit protocol violation) is dropped with a tracing::warn!. No current parser produces overlapping version_ranges, so this is a guard against future parser changes, not an expected code path.

content is the manifest source, needed for the literal-span guard above — the same parameter Ecosystem::generate_completions already threads through for a similar reason.

§Examples

use deps_core::lsp_helpers::{
    collect_update_all_edits, DiagnosticMessages, DiagnosticPolicy, OsvNaming, PackageNaming,
    PackageRendering, PackageVersions, RequirementResolution, SourcePolicy, VersionData,
};
use deps_core::{ConcreteVersion, Dependency, ParseResult, PackageName, VersionReq};
use std::any::Any;
use std::collections::HashMap;
use tower_lsp_server::ls_types::{Position, Range, Uri};

struct MockFormatter;
impl PackageNaming for MockFormatter {}
impl PackageRendering for MockFormatter {
    fn format_version_for_text_edit(&self, version: &ConcreteVersion) -> String {
        version.to_string()
    }
    fn package_url(&self, name: &PackageName) -> String {
        format!("https://example.com/{name}")
    }
}
impl RequirementResolution for MockFormatter {}
impl DiagnosticMessages for MockFormatter {}
impl DiagnosticPolicy for MockFormatter {}
impl SourcePolicy for MockFormatter {}
impl OsvNaming for MockFormatter {}

struct MockDep {
    name: PackageName,
    version_req: VersionReq,
    version_range: Range,
    name_range: Range,
}
impl Dependency for MockDep {
    fn name(&self) -> &PackageName { &self.name }
    fn name_range(&self) -> Range { self.name_range }
    fn version_requirement(&self) -> Option<&VersionReq> { Some(&self.version_req) }
    fn version_range(&self) -> Option<Range> { Some(self.version_range) }
    fn source(&self) -> deps_core::parser::DependencySource {
        deps_core::parser::DependencySource::Registry
    }
    fn as_any(&self) -> &dyn Any { self }
}

struct MockParseResult { deps: Vec<MockDep>, uri: Uri }
impl ParseResult for MockParseResult {
    fn dependencies(&self) -> Vec<&dyn Dependency> {
        self.deps.iter().map(|d| d as &dyn Dependency).collect()
    }
    fn workspace_root(&self) -> Option<&std::path::Path> { None }
    fn uri(&self) -> &Uri { &self.uri }
    fn as_any(&self) -> &dyn Any { self }
}

let content = r#"serde = "1.0.0""#;
let parse_result = MockParseResult {
    deps: vec![MockDep {
        name: PackageName::new("serde"),
        version_req: VersionReq::new("1.0.0"),
        version_range: Range::new(Position::new(0, 9), Position::new(0, 14)),
        name_range: Range::new(Position::new(0, 0), Position::new(0, 5)),
    }],
    uri: deps_core::test_util::test_uri("/test/Cargo.toml"),
};

let mut cached = HashMap::new();
cached.insert("serde".into(), PackageVersions::latest_only("1.2.0"));
let resolved = HashMap::new();

let edits = collect_update_all_edits(
    &parse_result,
    content,
    VersionData::new(&cached, &resolved),
    &MockFormatter,
);

assert_eq!(edits.len(), 1);
assert_eq!(edits[0].new_text, "1.2.0");