Skip to main content

plan_updates

Function plan_updates 

Source
pub fn plan_updates(
    analysis: &ManifestAnalysis,
    content: &str,
    formatter: &dyn EcosystemFormatter,
    reparse: &dyn ManifestReparse,
    package_filter: &[String],
    ignore_rules: &IgnoreRules,
    freshness: FreshnessSettings,
    now: PublishTime,
) -> UpdatePlan
Expand description

Default-mode planner: every dependency deps_core::edit::collect_update_candidates considers, narrowed by --package and [update].ignore (FR-003, FR-005, FR-006, FR-007).

An outdated dependency collect_update_candidates could not safely rewrite (a deps_core::edit::UpdateCandidate::Unplannable — an unsafe registry value, a non-literal span, or a formatter with no unambiguous rewrite) is still reported here as Outcome::Skipped with reason SkipReason::NotSafelyEditable rather than silently vanishing from the plan (spec 068 S4) — a --package <NAME> run naming exactly that dependency must not exit 0 with no signal.

[update].ignore rules are honored only when ignore_rules was actually built from an explicit --config <path> (FR-007) — callers pass IgnoreRules::empty otherwise, never an auto-discovered config’s rules.

§Examples

use deps_cli::analyze::ManifestAnalysis;
use deps_cli::update::ignore::IgnoreRules;
use deps_cli::update::{Outcome, plan_updates};
use deps_core::lsp_helpers::{
    DiagnosticMessages, DiagnosticPolicy, DependencyOutcomes, OsvNaming, PackageNaming,
    PackageRendering, PackageVersions, RequirementResolution, SourcePolicy,
};
use deps_core::parser::DependencySource;
use deps_core::position::{Position, Range};
use deps_core::{ConcreteVersion, Dependency, EcosystemId, ParseResult, PackageName, VersionReq};
use std::any::Any;
use std::collections::{HashMap, HashSet};

struct MockFormatter;
impl PackageNaming for MockFormatter {}
impl PackageRendering for MockFormatter {
    fn format_version_for_text_edit(&self, v: &ConcreteVersion) -> String { v.to_string() }
    fn package_url(&self, name: &PackageName) -> String { name.as_str().to_string() }
}
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 }
impl Dependency for MockDep {
    fn name(&self) -> &PackageName { &self.name }
    fn name_range(&self) -> Range { Range::default() }
    fn version_requirement(&self) -> Option<&VersionReq> { Some(&self.version_req) }
    fn version_range(&self) -> Option<Range> { Some(self.version_range) }
    fn source(&self) -> DependencySource { DependencySource::Registry }
    fn as_any(&self) -> &dyn Any { self }
}

struct MockParseResult { deps: Vec<MockDep>, uri: url::Url }
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) -> &url::Url { &self.uri }
    fn as_any(&self) -> &dyn Any { self }
}

let content = r#"serde = "1.0.0""#;
let mut cached_versions = HashMap::new();
cached_versions.insert(PackageName::new("serde"), PackageVersions::latest_only("1.2.0"));

let analysis = ManifestAnalysis {
    parse_result: Box::new(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)),
        }],
        uri: deps_core::test_util::test_uri("/test/Cargo.toml"),
    }),
    uri: deps_core::test_util::test_uri("/test/Cargo.toml"),
    now: deps_core::PublishTime::now(),
    ecosystem_id: EcosystemId::Cargo,
    cached_versions,
    resolved_versions: HashMap::new(),
    resolved_version_candidates: HashMap::new(),
    outcomes: DependencyOutcomes::new(),
    vulnerabilities: None,
    latest_status: None,
    fallback_status: None,
    cooldown_fallback_view: None,
    gossip_findings: HashMap::new(),
    licenses: HashMap::new(),
    license_policy: deps_core::licenses::LicensePolicy::default(),
    license_source: deps_core::LicenseSource::default(),
    network: deps_core::NetworkMode::Online,
    fetch_failed: HashSet::new(),
    registry_unreachable: false,
    license_fetch_incomplete: false,
};

// Freshness is disabled by default, so this fixture never reaches the fallback-edit guard
// — the closure is never actually called.
let reparse = |_content: &str| -> Option<Box<dyn ParseResult>> { None };

let plan = plan_updates(
    &analysis,
    content,
    &MockFormatter,
    &reparse,
    &[],
    &IgnoreRules::empty(),
    deps_core::FreshnessSettings::default(),
    deps_core::PublishTime::now(),
);

assert_eq!(plan.items.len(), 1);
assert!(matches!(plan.items[0].outcome, Outcome::Applied { .. }));
assert_eq!(plan.items[0].target(), Some(&ConcreteVersion::from("1.2.0")));