Skip to content

Commit 70fb498

Browse files
committed
Auto merge of #13797 - Muscraft:cleanup-linting-system, r=epage
Cleanup linting system There are a number of problems with the current linting system, most notably that lints could run without `-Zcargo-lints` being set. This PR fixes that issue and a few others that are low-hanging fruit.
2 parents c939267 + 11d6013 commit 70fb498

File tree

6 files changed

+189
-58
lines changed

6 files changed

+189
-58
lines changed

crates/cargo-util-schemas/src/manifest/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1506,7 +1506,7 @@ pub struct TomlLintConfig {
15061506
pub priority: i8,
15071507
}
15081508

1509-
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
1509+
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq)]
15101510
#[serde(rename_all = "kebab-case")]
15111511
pub enum TomlLintLevel {
15121512
Forbid,

src/cargo/core/workspace.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
2424
use crate::util::edit_distance;
2525
use crate::util::errors::{CargoResult, ManifestError};
2626
use crate::util::interning::InternedString;
27-
use crate::util::lints::{check_implicit_features, unused_dependencies};
27+
use crate::util::lints::{check_im_a_teapot, check_implicit_features, unused_dependencies};
2828
use crate::util::toml::{read_manifest, InheritableFields};
2929
use crate::util::{
3030
context::CargoResolverConfig, context::CargoResolverPrecedence, context::ConfigRelativePath,
@@ -1150,7 +1150,9 @@ impl<'gctx> Workspace<'gctx> {
11501150
for (path, maybe_pkg) in &self.packages.packages {
11511151
let path = path.join("Cargo.toml");
11521152
if let MaybePackage::Package(pkg) = maybe_pkg {
1153-
self.emit_lints(pkg, &path)?
1153+
if self.gctx.cli_unstable().cargo_lints {
1154+
self.emit_lints(pkg, &path)?
1155+
}
11541156
}
11551157
let warnings = match maybe_pkg {
11561158
MaybePackage::Package(pkg) => pkg.manifest().warnings().warnings(),
@@ -1195,6 +1197,7 @@ impl<'gctx> Workspace<'gctx> {
11951197
.map(|(name, lint)| (name.replace('-', "_"), lint))
11961198
.collect();
11971199

1200+
check_im_a_teapot(pkg, &path, &normalized_lints, &mut error_count, self.gctx)?;
11981201
check_implicit_features(pkg, &path, &normalized_lints, &mut error_count, self.gctx)?;
11991202
unused_dependencies(pkg, &path, &normalized_lints, &mut error_count, self.gctx)?;
12001203
if error_count > 0 {

src/cargo/util/lints.rs

+83-7
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ pub struct LintGroup {
6868
pub edition_lint_opts: Option<(Edition, LintLevel)>,
6969
}
7070

71+
const TEST_DUMMY_UNSTABLE: LintGroup = LintGroup {
72+
name: "test_dummy_unstable",
73+
desc: "test_dummy_unstable is meant to only be used in tests",
74+
default_level: LintLevel::Allow,
75+
edition_lint_opts: None,
76+
};
77+
7178
#[derive(Copy, Clone, Debug)]
7279
pub struct Lint {
7380
pub name: &'static str,
@@ -79,23 +86,37 @@ pub struct Lint {
7986

8087
impl Lint {
8188
pub fn level(&self, lints: &TomlToolLints, edition: Edition) -> LintLevel {
89+
let edition_level = self
90+
.edition_lint_opts
91+
.filter(|(e, _)| edition >= *e)
92+
.map(|(_, l)| l);
93+
94+
if self.default_level == LintLevel::Forbid || edition_level == Some(LintLevel::Forbid) {
95+
return LintLevel::Forbid;
96+
}
97+
8298
let level = self
8399
.groups
84100
.iter()
85101
.map(|g| g.name)
86102
.chain(std::iter::once(self.name))
87103
.filter_map(|n| lints.get(n).map(|l| (n, l)))
88-
.max_by_key(|(n, l)| (l.priority(), std::cmp::Reverse(*n)));
104+
.max_by_key(|(n, l)| {
105+
(
106+
l.level() == TomlLintLevel::Forbid,
107+
l.priority(),
108+
std::cmp::Reverse(*n),
109+
)
110+
});
89111

90112
match level {
91113
Some((_, toml_lint)) => toml_lint.level().into(),
92114
None => {
93-
if let Some((lint_edition, lint_level)) = self.edition_lint_opts {
94-
if edition >= lint_edition {
95-
return lint_level;
96-
}
115+
if let Some(level) = edition_level {
116+
level
117+
} else {
118+
self.default_level
97119
}
98-
self.default_level
99120
}
100121
}
101122
}
@@ -123,7 +144,7 @@ impl Display for LintLevel {
123144
impl LintLevel {
124145
pub fn to_diagnostic_level(self) -> Level {
125146
match self {
126-
LintLevel::Allow => Level::Note,
147+
LintLevel::Allow => unreachable!("allow does not map to a diagnostic level"),
127148
LintLevel::Warn => Level::Warning,
128149
LintLevel::Deny => Level::Error,
129150
LintLevel::Forbid => Level::Error,
@@ -142,6 +163,61 @@ impl From<TomlLintLevel> for LintLevel {
142163
}
143164
}
144165

166+
const IM_A_TEAPOT: Lint = Lint {
167+
name: "im_a_teapot",
168+
desc: "`im_a_teapot` is specified",
169+
groups: &[TEST_DUMMY_UNSTABLE],
170+
default_level: LintLevel::Allow,
171+
edition_lint_opts: None,
172+
};
173+
174+
pub fn check_im_a_teapot(
175+
pkg: &Package,
176+
path: &Path,
177+
lints: &TomlToolLints,
178+
error_count: &mut usize,
179+
gctx: &GlobalContext,
180+
) -> CargoResult<()> {
181+
let manifest = pkg.manifest();
182+
let lint_level = IM_A_TEAPOT.level(lints, manifest.edition());
183+
if lint_level == LintLevel::Allow {
184+
return Ok(());
185+
}
186+
187+
if manifest
188+
.resolved_toml()
189+
.package()
190+
.is_some_and(|p| p.im_a_teapot.is_some())
191+
{
192+
if lint_level == LintLevel::Forbid || lint_level == LintLevel::Deny {
193+
*error_count += 1;
194+
}
195+
let level = lint_level.to_diagnostic_level();
196+
let manifest_path = rel_cwd_manifest_path(path, gctx);
197+
let emitted_reason = format!("`cargo::{}` is set to `{lint_level}`", IM_A_TEAPOT.name);
198+
199+
let key_span = get_span(manifest.document(), &["package", "im-a-teapot"], false).unwrap();
200+
let value_span = get_span(manifest.document(), &["package", "im-a-teapot"], true).unwrap();
201+
let message = level
202+
.title(IM_A_TEAPOT.desc)
203+
.snippet(
204+
Snippet::source(manifest.contents())
205+
.origin(&manifest_path)
206+
.annotation(level.span(key_span.start..value_span.end))
207+
.fold(true),
208+
)
209+
.footer(Level::Note.title(&emitted_reason));
210+
let renderer = Renderer::styled().term_width(
211+
gctx.shell()
212+
.err_width()
213+
.diagnostic_terminal_width()
214+
.unwrap_or(annotate_snippets::renderer::DEFAULT_TERM_WIDTH),
215+
);
216+
writeln!(gctx.shell().err(), "{}", renderer.render(message))?;
217+
}
218+
Ok(())
219+
}
220+
145221
/// By default, cargo will treat any optional dependency as a [feature]. As of
146222
/// cargo 1.60, these can be disabled by declaring a feature that activates the
147223
/// optional dependency as `dep:<name>` (see [RFC #3143]).

tests/testsuite/lints/unused_optional_dependencies/edition_2024/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ target-dep = { version = "0.1.0", optional = true }
3333
.build();
3434

3535
snapbox::cmd::Command::cargo_ui()
36-
.masquerade_as_nightly_cargo(&["edition2024"])
36+
.masquerade_as_nightly_cargo(&["cargo-lints", "edition2024"])
3737
.current_dir(p.root())
3838
.arg("check")
39+
.arg("-Zcargo-lints")
3940
.assert()
4041
.success()
4142
.stdout_matches(str![""])

tests/testsuite/lints/unused_optional_dependencies/renamed_deps/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ target-dep = { version = "0.1.0", optional = true }
3232
.build();
3333

3434
snapbox::cmd::Command::cargo_ui()
35-
.masquerade_as_nightly_cargo(&["edition2024"])
35+
.masquerade_as_nightly_cargo(&["cargo-lints", "edition2024"])
3636
.current_dir(p.root())
3737
.arg("check")
38+
.arg("-Zcargo-lints")
3839
.assert()
3940
.success()
4041
.stdout_matches(str![""])

tests/testsuite/lints_table.rs

+96-46
Original file line numberDiff line numberDiff line change
@@ -756,14 +756,14 @@ fn cargo_lints_nightly_required() {
756756
.file(
757757
"Cargo.toml",
758758
r#"
759-
[package]
760-
name = "foo"
761-
version = "0.0.1"
762-
edition = "2015"
763-
authors = []
764-
765-
[lints.cargo]
766-
"unused-features" = "deny"
759+
[package]
760+
name = "foo"
761+
version = "0.0.1"
762+
edition = "2015"
763+
authors = []
764+
765+
[lints.cargo]
766+
im-a-teapot = "warn"
767767
"#,
768768
)
769769
.file("src/lib.rs", "")
@@ -790,21 +790,24 @@ fn cargo_lints_no_z_flag() {
790790
.file(
791791
"Cargo.toml",
792792
r#"
793-
[package]
794-
name = "foo"
795-
version = "0.0.1"
796-
edition = "2015"
797-
authors = []
793+
cargo-features = ["test-dummy-unstable"]
794+
795+
[package]
796+
name = "foo"
797+
version = "0.0.1"
798+
edition = "2015"
799+
authors = []
800+
im-a-teapot = true
798801
799-
[lints.cargo]
800-
"unused-features" = "deny"
802+
[lints.cargo]
803+
im-a-teapot = "warn"
801804
"#,
802805
)
803806
.file("src/lib.rs", "")
804807
.build();
805808

806809
foo.cargo("check")
807-
.masquerade_as_nightly_cargo(&["-Zcargo-lints"])
810+
.masquerade_as_nightly_cargo(&["cargo-lints", "test-dummy-unstable"])
808811
.with_stderr(
809812
"\
810813
[WARNING] unused manifest key `lints.cargo` (may be supported in a future version)
@@ -819,27 +822,37 @@ consider passing `-Zcargo-lints` to enable this feature.
819822

820823
#[cargo_test]
821824
fn cargo_lints_success() {
822-
let foo = project()
825+
let p = project()
823826
.file(
824827
"Cargo.toml",
825828
r#"
826-
[package]
827-
name = "foo"
828-
version = "0.0.1"
829-
edition = "2015"
830-
authors = []
829+
cargo-features = ["test-dummy-unstable"]
831830
832-
[lints.cargo]
833-
"unused-features" = "deny"
831+
[package]
832+
name = "foo"
833+
version = "0.0.1"
834+
edition = "2015"
835+
authors = []
836+
im-a-teapot = true
837+
838+
[lints.cargo]
839+
im-a-teapot = "warn"
834840
"#,
835841
)
836842
.file("src/lib.rs", "")
837843
.build();
838844

839-
foo.cargo("check -Zcargo-lints")
840-
.masquerade_as_nightly_cargo(&["-Zcargo-lints"])
845+
p.cargo("check -Zcargo-lints")
846+
.masquerade_as_nightly_cargo(&["cargo-lints", "test-dummy-unstable"])
841847
.with_stderr(
842848
"\
849+
warning: `im_a_teapot` is specified
850+
--> Cargo.toml:9:1
851+
|
852+
9 | im-a-teapot = true
853+
| ------------------
854+
|
855+
= note: `cargo::im_a_teapot` is set to `warn`
843856
[CHECKING] foo v0.0.1 ([CWD])
844857
[FINISHED] [..]
845858
",
@@ -849,43 +862,80 @@ fn cargo_lints_success() {
849862

850863
#[cargo_test]
851864
fn cargo_lints_underscore_supported() {
852-
Package::new("bar", "0.1.0").publish();
853865
let foo = project()
854866
.file(
855867
"Cargo.toml",
856868
r#"
857-
[package]
858-
name = "foo"
859-
version = "0.0.1"
860-
edition = "2021"
861-
authors = []
869+
cargo-features = ["test-dummy-unstable"]
862870
863-
[lints.cargo]
864-
"implicit_features" = "warn"
871+
[package]
872+
name = "foo"
873+
version = "0.0.1"
874+
edition = "2015"
875+
authors = []
876+
im-a-teapot = true
865877
866-
[dependencies]
867-
bar = { version = "0.1.0", optional = true }
878+
[lints.cargo]
879+
im_a_teapot = "warn"
868880
"#,
869881
)
870882
.file("src/lib.rs", "")
871883
.build();
872884

873885
foo.cargo("check -Zcargo-lints")
874-
.masquerade_as_nightly_cargo(&["-Zcargo-lints"])
886+
.masquerade_as_nightly_cargo(&["cargo-lints", "test-dummy-unstable"])
875887
.with_stderr(
876888
"\
877-
warning: implicit features for optional dependencies is deprecated and will be unavailable in the 2024 edition
878-
--> Cargo.toml:12:17
879-
|
880-
12 | bar = { version = \"0.1.0\", optional = true }
881-
| ---
882-
|
883-
= note: `cargo::implicit_features` is set to `warn`
884-
[UPDATING] `dummy-registry` index
885-
[LOCKING] [..]
889+
warning: `im_a_teapot` is specified
890+
--> Cargo.toml:9:1
891+
|
892+
9 | im-a-teapot = true
893+
| ------------------
894+
|
895+
= note: `cargo::im_a_teapot` is set to `warn`
886896
[CHECKING] foo v0.0.1 ([CWD])
887897
[FINISHED] [..]
888898
",
889899
)
890900
.run();
891901
}
902+
903+
#[cargo_test]
904+
fn forbid_not_overridden() {
905+
let p = project()
906+
.file(
907+
"Cargo.toml",
908+
r#"
909+
cargo-features = ["test-dummy-unstable"]
910+
911+
[package]
912+
name = "foo"
913+
version = "0.0.1"
914+
edition = "2015"
915+
authors = []
916+
im-a-teapot = true
917+
918+
[lints.cargo]
919+
im-a-teapot = { level = "warn", priority = 10 }
920+
test-dummy-unstable = { level = "forbid", priority = -1 }
921+
"#,
922+
)
923+
.file("src/lib.rs", "")
924+
.build();
925+
926+
p.cargo("check -Zcargo-lints")
927+
.masquerade_as_nightly_cargo(&["cargo-lints", "test-dummy-unstable"])
928+
.with_status(101)
929+
.with_stderr(
930+
"\
931+
error: `im_a_teapot` is specified
932+
--> Cargo.toml:9:1
933+
|
934+
9 | im-a-teapot = true
935+
| ^^^^^^^^^^^^^^^^^^
936+
|
937+
= note: `cargo::im_a_teapot` is set to `forbid`
938+
",
939+
)
940+
.run();
941+
}

0 commit comments

Comments
 (0)