diff --git a/crates/xtask-lint-docs/src/main.rs b/crates/xtask-lint-docs/src/main.rs index 5dca5190dc5..7107b5a5a50 100644 --- a/crates/xtask-lint-docs/src/main.rs +++ b/crates/xtask-lint-docs/src/main.rs @@ -117,6 +117,10 @@ fn add_lint(lint: &Lint, buf: &mut String) -> std::fmt::Result { writeln!(buf, "Group: `{}`", lint.primary_group.name)?; writeln!(buf)?; writeln!(buf, "Level: `{}`", lint.primary_group.default_level)?; + if let Some(msrv) = &lint.msrv { + writeln!(buf)?; + writeln!(buf, "MSRV: `{msrv}`")?; + } writeln!(buf, "{}\n", lint.docs.as_ref().unwrap()) } diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index bdf06d60757..e4b640ab44f 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -1431,7 +1431,7 @@ impl<'gctx> Workspace<'gctx> { let mut verify_error_count = 0; analyze_cargo_lints_table( - self.root_maybe().into(), + (self, self.root_maybe()).into(), self.root_manifest(), &cargo_lints, &mut verify_error_count, @@ -1460,6 +1460,7 @@ impl<'gctx> Workspace<'gctx> { self.gctx, )?; implicit_minimum_version_req_ws( + self, self.root_maybe(), self.root_manifest(), &cargo_lints, @@ -1473,6 +1474,7 @@ impl<'gctx> Workspace<'gctx> { // improve the testing experience while we are collecting feedback if self.gctx.cli_unstable().profile_hint_mostly_unused { blanket_hint_mostly_unused( + self, self.root_maybe(), self.root_manifest(), &cargo_lints, diff --git a/src/cargo/lints/mod.rs b/src/cargo/lints/mod.rs index 88db968e2bc..a70d36982f1 100644 --- a/src/cargo/lints/mod.rs +++ b/src/cargo/lints/mod.rs @@ -8,10 +8,12 @@ use annotate_snippets::AnnotationKind; use annotate_snippets::Group; use annotate_snippets::Level; use annotate_snippets::Snippet; +use cargo_util_schemas::manifest::RustVersion; use cargo_util_schemas::manifest::TomlLintLevel; use cargo_util_schemas::manifest::TomlToolLints; use pathdiff::diff_paths; +use crate::core::Workspace; use crate::core::{Edition, Feature, Features, MaybePackage, Package}; use crate::{CargoResult, GlobalContext}; @@ -35,39 +37,54 @@ pub enum ManifestFor<'a> { /// Lint runs for a specific package. Package(&'a Package), /// Lint runs for workspace-level config. - Workspace(&'a MaybePackage), + Workspace { + ws: &'a Workspace<'a>, + maybe_pkg: &'a MaybePackage, + }, } impl ManifestFor<'_> { fn lint_level(&self, pkg_lints: &TomlToolLints, lint: &Lint) -> (LintLevel, LintLevelReason) { - lint.level(pkg_lints, self.edition(), self.unstable_features()) + lint.level( + pkg_lints, + self.rust_version(), + self.edition(), + self.unstable_features(), + ) + } + + pub fn rust_version(&self) -> Option<&RustVersion> { + match self { + ManifestFor::Package(p) => p.rust_version(), + ManifestFor::Workspace { ws, maybe_pkg: _ } => ws.lowest_rust_version(), + } } pub fn contents(&self) -> Option<&str> { match self { ManifestFor::Package(p) => p.manifest().contents(), - ManifestFor::Workspace(p) => p.contents(), + ManifestFor::Workspace { ws: _, maybe_pkg } => maybe_pkg.contents(), } } pub fn document(&self) -> Option<&toml::Spanned>> { match self { ManifestFor::Package(p) => p.manifest().document(), - ManifestFor::Workspace(p) => p.document(), + ManifestFor::Workspace { ws: _, maybe_pkg } => maybe_pkg.document(), } } pub fn edition(&self) -> Edition { match self { ManifestFor::Package(p) => p.manifest().edition(), - ManifestFor::Workspace(p) => p.edition(), + ManifestFor::Workspace { ws: _, maybe_pkg } => maybe_pkg.edition(), } } pub fn unstable_features(&self) -> &Features { match self { ManifestFor::Package(p) => p.manifest().unstable_features(), - ManifestFor::Workspace(p) => p.unstable_features(), + ManifestFor::Workspace { ws: _, maybe_pkg } => maybe_pkg.unstable_features(), } } } @@ -78,9 +95,9 @@ 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) +impl<'a> From<(&'a Workspace<'a>, &'a MaybePackage)> for ManifestFor<'a> { + fn from((ws, maybe_pkg): (&'a Workspace<'a>, &'a MaybePackage)) -> ManifestFor<'a> { + ManifestFor::Workspace { ws, maybe_pkg } } } @@ -184,7 +201,7 @@ fn report_feature_not_enabled( let key_path = match manifest { ManifestFor::Package(_) => &["lints", "cargo", lint_name][..], - ManifestFor::Workspace(_) => &["workspace", "lints", "cargo", lint_name][..], + ManifestFor::Workspace { .. } => &["workspace", "lints", "cargo", lint_name][..], }; let mut error = Group::with_title(Level::ERROR.primary_title(title)); @@ -400,6 +417,12 @@ pub struct Lint { pub name: &'static str, pub desc: &'static str, pub primary_group: &'static LintGroup, + /// The minimum supported Rust version for applying this lint + /// + /// Note: If the lint is on by default and did not qualify as a hard-warning before the + /// linting system, then at earliest an MSRV of 1.78 is required as `[lints.cargo]` was a hard + /// error before then. + pub msrv: Option, pub edition_lint_opts: Option<(Edition, LintLevel)>, pub feature_gate: Option<&'static Feature>, /// This is a markdown formatted string that will be used when generating @@ -412,6 +435,7 @@ impl Lint { pub fn level( &self, pkg_lints: &TomlToolLints, + pkg_rust_version: Option<&RustVersion>, edition: Edition, unstable_features: &Features, ) -> (LintLevel, LintLevelReason) { @@ -424,6 +448,13 @@ impl Lint { return (LintLevel::Allow, LintLevelReason::Default); } + if let (Some(msrv), Some(pkg_rust_version)) = (&self.msrv, pkg_rust_version) { + let pkg_rust_version = pkg_rust_version.to_partial(); + if !msrv.is_compatible_with(&pkg_rust_version) { + return (LintLevel::Allow, LintLevelReason::Default); + } + } + let lint_level_priority = level_priority( self.name, self.primary_group.default_level, diff --git a/src/cargo/lints/rules/blanket_hint_mostly_unused.rs b/src/cargo/lints/rules/blanket_hint_mostly_unused.rs index 8527600cbd3..d315fcf01ca 100644 --- a/src/cargo/lints/rules/blanket_hint_mostly_unused.rs +++ b/src/cargo/lints/rules/blanket_hint_mostly_unused.rs @@ -12,6 +12,7 @@ use cargo_util_schemas::manifest::TomlToolLints; use crate::CargoResult; use crate::GlobalContext; use crate::core::MaybePackage; +use crate::core::Workspace; use crate::lints::Lint; use crate::lints::LintLevel; use crate::lints::SUSPICIOUS; @@ -22,6 +23,7 @@ pub static LINT: &Lint = &Lint { name: "blanket_hint_mostly_unused", desc: "blanket_hint_mostly_unused lint", primary_group: &SUSPICIOUS, + msrv: Some(super::CARGO_LINTS_MSRV), edition_lint_opts: None, feature_gate: None, docs: Some( @@ -54,6 +56,7 @@ hint-mostly-unused = true }; pub fn blanket_hint_mostly_unused( + ws: &Workspace<'_>, maybe_pkg: &MaybePackage, path: &Path, pkg_lints: &TomlToolLints, @@ -62,6 +65,7 @@ pub fn blanket_hint_mostly_unused( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( pkg_lints, + ws.lowest_rust_version(), maybe_pkg.edition(), maybe_pkg.unstable_features(), ); diff --git a/src/cargo/lints/rules/im_a_teapot.rs b/src/cargo/lints/rules/im_a_teapot.rs index 2b66771662b..15bbae4afd6 100644 --- a/src/cargo/lints/rules/im_a_teapot.rs +++ b/src/cargo/lints/rules/im_a_teapot.rs @@ -22,6 +22,7 @@ pub static LINT: &Lint = &Lint { name: "im_a_teapot", desc: "`im_a_teapot` is specified", primary_group: &TEST_DUMMY_UNSTABLE, + msrv: None, edition_lint_opts: None, feature_gate: Some(Feature::test_dummy_unstable()), docs: None, @@ -35,8 +36,12 @@ pub fn check_im_a_teapot( gctx: &GlobalContext, ) -> CargoResult<()> { let manifest = pkg.manifest(); - let (lint_level, reason) = - LINT.level(pkg_lints, manifest.edition(), manifest.unstable_features()); + let (lint_level, reason) = LINT.level( + pkg_lints, + pkg.rust_version(), + manifest.edition(), + manifest.unstable_features(), + ); if lint_level == LintLevel::Allow { return Ok(()); diff --git a/src/cargo/lints/rules/implicit_minimum_version_req.rs b/src/cargo/lints/rules/implicit_minimum_version_req.rs index 2e5ceda29f2..f7d8050c280 100644 --- a/src/cargo/lints/rules/implicit_minimum_version_req.rs +++ b/src/cargo/lints/rules/implicit_minimum_version_req.rs @@ -17,6 +17,7 @@ use crate::GlobalContext; use crate::core::Manifest; use crate::core::MaybePackage; use crate::core::Package; +use crate::core::Workspace; use crate::lints::Lint; use crate::lints::LintLevel; use crate::lints::LintLevelReason; @@ -29,6 +30,7 @@ pub static LINT: &Lint = &Lint { name: "implicit_minimum_version_req", desc: "dependency version requirement without an explicit minimum version", primary_group: &PEDANTIC, + msrv: None, edition_lint_opts: None, feature_gate: None, docs: Some( @@ -90,6 +92,7 @@ pub fn implicit_minimum_version_req_pkg( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + pkg.rust_version(), pkg.manifest().edition(), pkg.manifest().unstable_features(), ); @@ -148,6 +151,7 @@ pub fn implicit_minimum_version_req_pkg( } pub fn implicit_minimum_version_req_ws( + ws: &Workspace<'_>, maybe_pkg: &MaybePackage, manifest_path: &Path, cargo_lints: &TomlToolLints, @@ -156,6 +160,7 @@ pub fn implicit_minimum_version_req_ws( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + ws.lowest_rust_version(), maybe_pkg.edition(), maybe_pkg.unstable_features(), ); diff --git a/src/cargo/lints/rules/missing_lints_inheritance.rs b/src/cargo/lints/rules/missing_lints_inheritance.rs index 9762d226359..eb988925af8 100644 --- a/src/cargo/lints/rules/missing_lints_inheritance.rs +++ b/src/cargo/lints/rules/missing_lints_inheritance.rs @@ -20,6 +20,7 @@ pub static LINT: &Lint = &Lint { name: "missing_lints_inheritance", desc: "missing `[lints]` to inherit `[workspace.lints]`", primary_group: &SUSPICIOUS, + msrv: Some(super::CARGO_LINTS_MSRV), edition_lint_opts: None, feature_gate: None, docs: Some( @@ -62,6 +63,7 @@ pub fn missing_lints_inheritance( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + pkg.rust_version(), pkg.manifest().edition(), pkg.manifest().unstable_features(), ); diff --git a/src/cargo/lints/rules/mod.rs b/src/cargo/lints/rules/mod.rs index f0f7c684dd4..edaa648505d 100644 --- a/src/cargo/lints/rules/mod.rs +++ b/src/cargo/lints/rules/mod.rs @@ -45,3 +45,10 @@ pub static LINTS: &[&crate::lints::Lint] = &[ unused_workspace_dependencies::LINT, unused_workspace_package_fields::LINT, ]; + +/// Version required for specifying `[lints.cargo]` +/// +/// Before this, it was an error. No on-by-default lint should fire before this time without +/// another way of disabling it. +static CARGO_LINTS_MSRV: cargo_util_schemas::manifest::RustVersion = + cargo_util_schemas::manifest::RustVersion::new(1, 79, 0); diff --git a/src/cargo/lints/rules/non_kebab_case_bins.rs b/src/cargo/lints/rules/non_kebab_case_bins.rs index 9e435f7685a..ba7bc1389fb 100644 --- a/src/cargo/lints/rules/non_kebab_case_bins.rs +++ b/src/cargo/lints/rules/non_kebab_case_bins.rs @@ -24,6 +24,7 @@ pub static LINT: &Lint = &Lint { name: "non_kebab_case_bins", desc: "binaries should have a kebab-case name", primary_group: &STYLE, + msrv: Some(super::CARGO_LINTS_MSRV), edition_lint_opts: None, feature_gate: None, docs: Some( @@ -71,6 +72,7 @@ pub fn non_kebab_case_bins( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + pkg.rust_version(), pkg.manifest().edition(), pkg.manifest().unstable_features(), ); diff --git a/src/cargo/lints/rules/non_kebab_case_features.rs b/src/cargo/lints/rules/non_kebab_case_features.rs index d9a20086dab..43cee2d2617 100644 --- a/src/cargo/lints/rules/non_kebab_case_features.rs +++ b/src/cargo/lints/rules/non_kebab_case_features.rs @@ -22,6 +22,7 @@ pub static LINT: &Lint = &Lint { name: "non_kebab_case_features", desc: "features should have a kebab-case name", primary_group: &RESTRICTION, + msrv: None, edition_lint_opts: None, feature_gate: None, docs: Some( @@ -64,6 +65,7 @@ pub fn non_kebab_case_features( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + pkg.rust_version(), pkg.manifest().edition(), pkg.manifest().unstable_features(), ); diff --git a/src/cargo/lints/rules/non_kebab_case_packages.rs b/src/cargo/lints/rules/non_kebab_case_packages.rs index 9be4ab076e5..9a640620162 100644 --- a/src/cargo/lints/rules/non_kebab_case_packages.rs +++ b/src/cargo/lints/rules/non_kebab_case_packages.rs @@ -22,6 +22,7 @@ pub static LINT: &Lint = &Lint { name: "non_kebab_case_packages", desc: "packages should have a kebab-case name", primary_group: &RESTRICTION, + msrv: None, edition_lint_opts: None, feature_gate: None, docs: Some( @@ -64,6 +65,7 @@ pub fn non_kebab_case_packages( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + pkg.rust_version(), pkg.manifest().edition(), pkg.manifest().unstable_features(), ); diff --git a/src/cargo/lints/rules/non_snake_case_features.rs b/src/cargo/lints/rules/non_snake_case_features.rs index aeda20871a0..7d80f9cdff4 100644 --- a/src/cargo/lints/rules/non_snake_case_features.rs +++ b/src/cargo/lints/rules/non_snake_case_features.rs @@ -22,6 +22,7 @@ pub static LINT: &Lint = &Lint { name: "non_snake_case_features", desc: "features should have a snake-case name", primary_group: &RESTRICTION, + msrv: None, edition_lint_opts: None, feature_gate: None, docs: Some( @@ -64,6 +65,7 @@ pub fn non_snake_case_features( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + pkg.rust_version(), pkg.manifest().edition(), pkg.manifest().unstable_features(), ); diff --git a/src/cargo/lints/rules/non_snake_case_packages.rs b/src/cargo/lints/rules/non_snake_case_packages.rs index 4c0bccb6307..3d3559385a9 100644 --- a/src/cargo/lints/rules/non_snake_case_packages.rs +++ b/src/cargo/lints/rules/non_snake_case_packages.rs @@ -22,6 +22,7 @@ pub static LINT: &Lint = &Lint { name: "non_snake_case_packages", desc: "packages should have a snake-case name", primary_group: &RESTRICTION, + msrv: None, edition_lint_opts: None, feature_gate: None, docs: Some( @@ -64,6 +65,7 @@ pub fn non_snake_case_packages( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + pkg.rust_version(), pkg.manifest().edition(), pkg.manifest().unstable_features(), ); diff --git a/src/cargo/lints/rules/redundant_homepage.rs b/src/cargo/lints/rules/redundant_homepage.rs index d9896ad3b9a..8707795bedf 100644 --- a/src/cargo/lints/rules/redundant_homepage.rs +++ b/src/cargo/lints/rules/redundant_homepage.rs @@ -23,6 +23,7 @@ pub static LINT: &Lint = &Lint { name: "redundant_homepage", desc: "`package.homepage` is redundant with another manifest field", primary_group: &STYLE, + msrv: Some(super::CARGO_LINTS_MSRV), edition_lint_opts: None, feature_gate: None, docs: Some( @@ -68,6 +69,7 @@ pub fn redundant_homepage( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + pkg.rust_version(), pkg.manifest().edition(), pkg.manifest().unstable_features(), ); diff --git a/src/cargo/lints/rules/redundant_readme.rs b/src/cargo/lints/rules/redundant_readme.rs index 5cd0e42634d..5d4dcc983ac 100644 --- a/src/cargo/lints/rules/redundant_readme.rs +++ b/src/cargo/lints/rules/redundant_readme.rs @@ -25,6 +25,7 @@ pub static LINT: &Lint = &Lint { name: "redundant_readme", desc: "explicit `package.readme` can be inferred", primary_group: &STYLE, + msrv: Some(super::CARGO_LINTS_MSRV), edition_lint_opts: None, feature_gate: None, docs: Some( @@ -70,6 +71,7 @@ pub fn redundant_readme( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + pkg.rust_version(), pkg.manifest().edition(), pkg.manifest().unstable_features(), ); diff --git a/src/cargo/lints/rules/unknown_lints.rs b/src/cargo/lints/rules/unknown_lints.rs index 4f027e4ec2d..f5bc9e115ab 100644 --- a/src/cargo/lints/rules/unknown_lints.rs +++ b/src/cargo/lints/rules/unknown_lints.rs @@ -19,6 +19,7 @@ pub static LINT: &Lint = &Lint { name: "unknown_lints", desc: "unknown lint", primary_group: &SUSPICIOUS, + msrv: Some(super::CARGO_LINTS_MSRV), edition_lint_opts: None, feature_gate: None, docs: Some( @@ -74,7 +75,7 @@ pub fn output_unknown_lints( let key_path = match manifest { ManifestFor::Package(_) => &["lints", "cargo", lint_name][..], - ManifestFor::Workspace(_) => &["workspace", "lints", "cargo", lint_name][..], + ManifestFor::Workspace { .. } => &["workspace", "lints", "cargo", lint_name][..], }; let mut report = Vec::new(); diff --git a/src/cargo/lints/rules/unused_workspace_dependencies.rs b/src/cargo/lints/rules/unused_workspace_dependencies.rs index ddc3ae2b506..54fe099e208 100644 --- a/src/cargo/lints/rules/unused_workspace_dependencies.rs +++ b/src/cargo/lints/rules/unused_workspace_dependencies.rs @@ -24,6 +24,7 @@ pub static LINT: &Lint = &Lint { name: "unused_workspace_dependencies", desc: "unused workspace dependency", primary_group: &SUSPICIOUS, + msrv: Some(super::CARGO_LINTS_MSRV), edition_lint_opts: None, feature_gate: None, docs: Some( @@ -55,6 +56,7 @@ pub fn unused_workspace_dependencies( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + ws.lowest_rust_version(), maybe_pkg.edition(), maybe_pkg.unstable_features(), ); diff --git a/src/cargo/lints/rules/unused_workspace_package_fields.rs b/src/cargo/lints/rules/unused_workspace_package_fields.rs index f96583c784e..f35df2e6ef9 100644 --- a/src/cargo/lints/rules/unused_workspace_package_fields.rs +++ b/src/cargo/lints/rules/unused_workspace_package_fields.rs @@ -23,6 +23,7 @@ pub static LINT: &Lint = &Lint { name: "unused_workspace_package_fields", desc: "unused field in `workspace.package`", primary_group: &SUSPICIOUS, + msrv: Some(super::CARGO_LINTS_MSRV), edition_lint_opts: None, feature_gate: None, docs: Some( @@ -55,6 +56,7 @@ pub fn unused_workspace_package_fields( ) -> CargoResult<()> { let (lint_level, reason) = LINT.level( cargo_lints, + ws.lowest_rust_version(), maybe_pkg.edition(), maybe_pkg.unstable_features(), ); diff --git a/src/doc/src/reference/lints.md b/src/doc/src/reference/lints.md index 7d52c7c529c..b568a75e38c 100644 --- a/src/doc/src/reference/lints.md +++ b/src/doc/src/reference/lints.md @@ -42,6 +42,8 @@ Group: `suspicious` Level: `warn` +MSRV: `1.79.0` + ### What it does Checks if `hint-mostly-unused` being applied to all dependencies. @@ -123,6 +125,8 @@ Group: `suspicious` Level: `warn` +MSRV: `1.79.0` + ### What it does Checks for packages without a `lints` table while `workspace.lints` is present. @@ -154,6 +158,8 @@ Group: `style` Level: `warn` +MSRV: `1.79.0` + ### What it does Detect binary names, explicit and implicit, that are not kebab-case @@ -318,6 +324,8 @@ Group: `style` Level: `warn` +MSRV: `1.79.0` + ### What it does Checks if the value of `package.homepage` is already covered by another field. @@ -353,6 +361,8 @@ Group: `style` Level: `warn` +MSRV: `1.79.0` + ### What it does Checks for `package.readme` fields that can be inferred. @@ -388,6 +398,8 @@ Group: `suspicious` Level: `warn` +MSRV: `1.79.0` + ### What it does Checks for unknown lints in the `[lints.cargo]` table @@ -409,6 +421,8 @@ Group: `suspicious` Level: `warn` +MSRV: `1.79.0` + ### What it does Checks for any entry in `[workspace.dependencies]` that has not been inherited @@ -429,6 +443,8 @@ Group: `suspicious` Level: `warn` +MSRV: `1.79.0` + ### What it does Checks for any fields in `[workspace.package]` that has not been inherited