pub fn plan_updates(
analysis: &ManifestAnalysis,
content: &str,
formatter: &dyn EcosystemFormatter,
reparse: &dyn ManifestReparse,
package_filter: &[String],
ignore_rules: &IgnoreRules,
freshness: FreshnessSettings,
now: PublishTime,
) -> UpdatePlanExpand 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")));