diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 27baa0611..a0bd9367d 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -1340,6 +1340,13 @@ impl<'gctx> Workspace<'gctx> { if self.gctx.cli_unstable().cargo_lints { // Calls to lint functions go in here + implicit_minimum_version_req( + self.root_maybe().into(), + self.root_manifest(), + &cargo_lints, + &mut error_count, + self.gctx, + )?; } // This is a short term hack to allow `blanket_hint_mostly_unused` diff --git a/src/cargo/util/lints/implicit_minimum_version_req.rs b/src/cargo/util/lints/implicit_minimum_version_req.rs index a56f3eae9..39825e004 100644 --- a/src/cargo/util/lints/implicit_minimum_version_req.rs +++ b/src/cargo/util/lints/implicit_minimum_version_req.rs @@ -7,12 +7,14 @@ use annotate_snippets::Level; use annotate_snippets::Patch; use annotate_snippets::Snippet; use cargo_platform::Platform; +use cargo_util_schemas::manifest::TomlDependency; use cargo_util_schemas::manifest::TomlToolLints; use toml::de::DeValue; use crate::CargoResult; use crate::GlobalContext; use crate::core::Manifest; +use crate::core::MaybePackage; use crate::core::Package; use crate::util::OptVersionReq; use crate::util::lints::Lint; @@ -98,6 +100,14 @@ pub fn implicit_minimum_version_req( ManifestFor::Package(pkg) => { lint_package(pkg, manifest_path, lint_level, reason, error_count, gctx) } + ManifestFor::Workspace(maybe_pkg) => lint_workspace( + maybe_pkg, + manifest_path, + lint_level, + reason, + error_count, + gctx, + ), } } @@ -151,6 +161,72 @@ pub fn lint_package( Ok(()) } +pub fn lint_workspace( + maybe_pkg: &MaybePackage, + manifest_path: String, + lint_level: LintLevel, + reason: LintLevelReason, + error_count: &mut usize, + gctx: &GlobalContext, +) -> CargoResult<()> { + let document = maybe_pkg.document(); + let contents = maybe_pkg.contents(); + let toml = match maybe_pkg { + MaybePackage::Package(p) => p.manifest().normalized_toml(), + MaybePackage::Virtual(vm) => vm.normalized_toml(), + }; + let dep_iter = toml + .workspace + .as_ref() + .and_then(|ws| ws.dependencies.as_ref()) + .into_iter() + .flat_map(|deps| deps.iter()) + .map(|(name, dep)| { + let name = name.as_str(); + let ver = match dep { + TomlDependency::Simple(ver) => ver, + TomlDependency::Detailed(detailed) => { + let Some(ver) = detailed.version.as_ref() else { + return (name, OptVersionReq::Any); + }; + ver + } + }; + let req = semver::VersionReq::parse(ver) + .map(Into::into) + .unwrap_or(OptVersionReq::Any); + (name, req) + }); + + for (name_in_toml, version_req) in dep_iter { + let Some(suggested_req) = get_suggested_version_req(&version_req) else { + continue; + }; + + let key_path = ["workspace", "dependencies", name_in_toml]; + + let Some(span) = span_of_version_req(document, &key_path) else { + continue; + }; + + let report = report( + lint_level, + reason, + span, + contents, + &manifest_path, + &suggested_req, + ); + + if lint_level.is_error() { + *error_count += 1; + } + gctx.shell().print_report(&report, lint_level.force())?; + } + + Ok(()) +} + pub fn span_of_version_req<'doc>( document: &'doc toml::Spanned>, path: &[&str], diff --git a/src/cargo/util/lints/mod.rs b/src/cargo/util/lints/mod.rs index b8bdca09b..39cb38af5 100644 --- a/src/cargo/util/lints/mod.rs +++ b/src/cargo/util/lints/mod.rs @@ -23,6 +23,8 @@ pub const LINTS: &[Lint] = &[ pub enum ManifestFor<'a> { /// Lint runs for a specific package. Package(&'a Package), + /// Lint runs for workspace-level config. + Workspace(&'a MaybePackage), } impl ManifestFor<'_> { @@ -33,6 +35,7 @@ impl ManifestFor<'_> { p.manifest().edition(), p.manifest().unstable_features(), ), + ManifestFor::Workspace(p) => lint.level(pkg_lints, p.edition(), p.unstable_features()), } } } @@ -43,6 +46,12 @@ impl<'a> From<&'a Package> for ManifestFor<'a> { } } +impl<'a> From<&'a MaybePackage> for ManifestFor<'a> { + fn from(value: &'a MaybePackage) -> ManifestFor<'a> { + ManifestFor::Workspace(value) + } +} + pub fn analyze_cargo_lints_table( pkg: &Package, path: &Path, diff --git a/tests/testsuite/lints/implicit_minimum_version_req.rs b/tests/testsuite/lints/implicit_minimum_version_req.rs index 36a3c20d8..80044e20f 100644 --- a/tests/testsuite/lints/implicit_minimum_version_req.rs +++ b/tests/testsuite/lints/implicit_minimum_version_req.rs @@ -1029,6 +1029,17 @@ workspace = true p.cargo("check -Zcargo-lints") .masquerade_as_nightly_cargo(&["cargo-lints"]) .with_stderr_data(str![[r#" +[WARNING] dependency version requirement without an explicit minimum version + --> Cargo.toml:7:7 + | +7 | dep = "1" + | ^^^ missing full version components + | +[HELP] consider specifying full `major.minor.patch` version components + | +7 | dep = "1.0.0" + | ++++ + = [NOTE] `cargo::implicit_minimum_version_req` is set to `warn` in `[lints]` [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... @@ -1075,6 +1086,17 @@ edition = "2021" p.cargo("check -Zcargo-lints") .masquerade_as_nightly_cargo(&["cargo-lints"]) .with_stderr_data(str![[r#" +[WARNING] dependency version requirement without an explicit minimum version + --> Cargo.toml:7:7 + | +7 | dep = "1" + | ^^^ missing full version components + | +[HELP] consider specifying full `major.minor.patch` version components + | +7 | dep = "1.0.0" + | ++++ + = [NOTE] `cargo::implicit_minimum_version_req` is set to `warn` in `[lints]` [CHECKING] member v0.0.0 ([ROOT]/foo/member) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s @@ -1122,6 +1144,17 @@ workspace = true p.cargo("check -Zcargo-lints") .masquerade_as_nightly_cargo(&["cargo-lints"]) .with_stderr_data(str![[r#" +[WARNING] dependency version requirement without an explicit minimum version + --> Cargo.toml:7:7 + | +7 | dep = "1" + | ^^^ missing full version components + | +[HELP] consider specifying full `major.minor.patch` version components + | +7 | dep = "1.0.0" + | ++++ + = [NOTE] `cargo::implicit_minimum_version_req` is set to `warn` in `[lints]` [WARNING] dependency version requirement without an explicit minimum version --> member/Cargo.toml:7:7 |