Skip to content

Commit f007527

Browse files
committed
fix(lints): Mark the im_a_teapot lint as unstable
1 parent 2df02f0 commit f007527

File tree

4 files changed

+260
-4
lines changed

4 files changed

+260
-4
lines changed

src/cargo/core/features.rs

+4
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,10 @@ macro_rules! features {
406406
fn is_enabled(&self, features: &Features) -> bool {
407407
(self.get)(features)
408408
}
409+
410+
pub(crate) fn name(&self) -> &str {
411+
self.name
412+
}
409413
}
410414

411415
impl Features {

src/cargo/core/workspace.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ 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_im_a_teapot, check_implicit_features, unused_dependencies};
27+
use crate::util::lints::{
28+
analyze_cargo_lints_table, check_im_a_teapot, check_implicit_features, unused_dependencies,
29+
};
2830
use crate::util::toml::{read_manifest, InheritableFields};
2931
use crate::util::{
3032
context::CargoResolverConfig, context::CargoResolverPrecedence, context::ConfigRelativePath,
@@ -1227,6 +1229,26 @@ impl<'gctx> Workspace<'gctx> {
12271229
.is_some_and(|l| l.workspace)
12281230
.then(|| ws_cargo_lints);
12291231

1232+
let ws_contents = match self.root_maybe() {
1233+
MaybePackage::Package(pkg) => pkg.manifest().contents(),
1234+
MaybePackage::Virtual(v) => v.contents(),
1235+
};
1236+
1237+
let ws_document = match self.root_maybe() {
1238+
MaybePackage::Package(pkg) => pkg.manifest().document(),
1239+
MaybePackage::Virtual(v) => v.document(),
1240+
};
1241+
1242+
analyze_cargo_lints_table(
1243+
pkg,
1244+
&path,
1245+
&normalized_lints,
1246+
ws_cargo_lints,
1247+
ws_contents,
1248+
ws_document,
1249+
self.root_manifest(),
1250+
self.gctx,
1251+
)?;
12301252
check_im_a_teapot(
12311253
pkg,
12321254
&path,

src/cargo/util/lints.rs

+170-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::core::dependency::DepKind;
22
use crate::core::FeatureValue::Dep;
3-
use crate::core::{Edition, Feature, FeatureValue, Features, Package};
3+
use crate::core::{Edition, Feature, FeatureValue, Features, Manifest, Package};
44
use crate::util::interning::InternedString;
55
use crate::{CargoResult, GlobalContext};
66
use annotate_snippets::{Level, Snippet};
@@ -12,6 +12,164 @@ use std::ops::Range;
1212
use std::path::Path;
1313
use toml_edit::ImDocument;
1414

15+
const LINTS: &[Lint] = &[IM_A_TEAPOT, IMPLICIT_FEATURES, UNUSED_OPTIONAL_DEPENDENCY];
16+
17+
pub fn analyze_cargo_lints_table(
18+
pkg: &Package,
19+
path: &Path,
20+
pkg_lints: &TomlToolLints,
21+
ws_lints: Option<&TomlToolLints>,
22+
ws_contents: &str,
23+
ws_document: &ImDocument<String>,
24+
ws_path: &Path,
25+
gctx: &GlobalContext,
26+
) -> CargoResult<()> {
27+
let mut error_count = 0;
28+
let manifest = pkg.manifest();
29+
let manifest_path = rel_cwd_manifest_path(path, gctx);
30+
let ws_path = rel_cwd_manifest_path(ws_path, gctx);
31+
32+
for lint_name in pkg_lints
33+
.keys()
34+
.chain(ws_lints.map(|l| l.keys()).unwrap_or_default())
35+
{
36+
if let Some(lint) = LINTS.iter().find(|l| l.name == lint_name) {
37+
let (_, reason, _) = level_priority(
38+
lint.name,
39+
lint.default_level,
40+
lint.edition_lint_opts,
41+
pkg_lints,
42+
ws_lints,
43+
manifest.edition(),
44+
);
45+
46+
// Only run analysis on user-specified lints
47+
if !reason.is_user_specified() {
48+
continue;
49+
}
50+
51+
// Only run this on lints that are gated by a feature
52+
if let Some(feature_gate) = lint.feature_gate {
53+
verify_feature_enabled(
54+
lint.name,
55+
feature_gate,
56+
reason,
57+
manifest,
58+
&manifest_path,
59+
ws_contents,
60+
ws_document,
61+
&ws_path,
62+
&mut error_count,
63+
gctx,
64+
)?;
65+
}
66+
}
67+
}
68+
if error_count > 0 {
69+
Err(anyhow::anyhow!(
70+
"encountered {error_count} errors(s) while verifying lints",
71+
))
72+
} else {
73+
Ok(())
74+
}
75+
}
76+
77+
fn verify_feature_enabled(
78+
lint_name: &str,
79+
feature_gate: &Feature,
80+
reason: LintLevelReason,
81+
manifest: &Manifest,
82+
manifest_path: &str,
83+
ws_contents: &str,
84+
ws_document: &ImDocument<String>,
85+
ws_path: &str,
86+
error_count: &mut usize,
87+
gctx: &GlobalContext,
88+
) -> CargoResult<()> {
89+
if !manifest.unstable_features().is_enabled(feature_gate) {
90+
let dash_name = lint_name.replace("_", "-");
91+
let dash_feature_name = feature_gate.name().replace("_", "-");
92+
let title = format!("use of unstable lint `{}`", dash_name);
93+
let label = format!(
94+
"this is behind `{}`, which is not enabled",
95+
dash_feature_name
96+
);
97+
let second_title = format!("`cargo::{}` was inherited", dash_name);
98+
let help = format!(
99+
"consider adding `cargo-features = [\"{}\"]` to the top of the manifest",
100+
dash_feature_name
101+
);
102+
let message = match reason {
103+
LintLevelReason::Package => {
104+
let span = get_span(
105+
manifest.document(),
106+
&["lints", "cargo", dash_name.as_str()],
107+
false,
108+
)
109+
.or(get_span(
110+
manifest.document(),
111+
&["lints", "cargo", lint_name],
112+
false,
113+
))
114+
.unwrap();
115+
116+
Level::Error
117+
.title(&title)
118+
.snippet(
119+
Snippet::source(manifest.contents())
120+
.origin(&manifest_path)
121+
.annotation(Level::Error.span(span).label(&label))
122+
.fold(true),
123+
)
124+
.footer(Level::Help.title(&help))
125+
}
126+
LintLevelReason::Workspace => {
127+
let lint_span = get_span(
128+
ws_document,
129+
&["workspace", "lints", "cargo", dash_name.as_str()],
130+
false,
131+
)
132+
.or(get_span(
133+
ws_document,
134+
&["workspace", "lints", "cargo", lint_name],
135+
false,
136+
))
137+
.unwrap();
138+
let inherit_span_key =
139+
get_span(manifest.document(), &["lints", "workspace"], false).unwrap();
140+
let inherit_span_value =
141+
get_span(manifest.document(), &["lints", "workspace"], true).unwrap();
142+
143+
Level::Error
144+
.title(&title)
145+
.snippet(
146+
Snippet::source(ws_contents)
147+
.origin(&ws_path)
148+
.annotation(Level::Error.span(lint_span).label(&label))
149+
.fold(true),
150+
)
151+
.footer(
152+
Level::Note.title(&second_title).snippet(
153+
Snippet::source(manifest.contents())
154+
.origin(&manifest_path)
155+
.annotation(
156+
Level::Note
157+
.span(inherit_span_key.start..inherit_span_value.end),
158+
)
159+
.fold(true),
160+
),
161+
)
162+
.footer(Level::Help.title(&help))
163+
}
164+
_ => unreachable!("LintLevelReason should be one that is user specified"),
165+
};
166+
167+
*error_count += 1;
168+
gctx.shell().print_message(message)?;
169+
}
170+
Ok(())
171+
}
172+
15173
fn get_span(document: &ImDocument<String>, path: &[&str], get_value: bool) -> Option<Range<usize>> {
16174
let mut table = document.as_item().as_table_like()?;
17175
let mut iter = path.into_iter().peekable();
@@ -194,6 +352,17 @@ impl Display for LintLevelReason {
194352
}
195353
}
196354

355+
impl LintLevelReason {
356+
fn is_user_specified(&self) -> bool {
357+
match self {
358+
LintLevelReason::Default => false,
359+
LintLevelReason::Edition(_) => false,
360+
LintLevelReason::Package => true,
361+
LintLevelReason::Workspace => true,
362+
}
363+
}
364+
}
365+
197366
fn level_priority(
198367
name: &str,
199368
default_level: LintLevel,

tests/testsuite/lints_table.rs

+63-2
Original file line numberDiff line numberDiff line change
@@ -1100,10 +1100,71 @@ im-a-teapot = "warn"
11001100

11011101
p.cargo("check -Zcargo-lints")
11021102
.masquerade_as_nightly_cargo(&["cargo-lints"])
1103+
.with_status(101)
11031104
.with_stderr(
11041105
"\
1105-
[CHECKING] foo v0.0.1 ([CWD])
1106-
[FINISHED] [..]
1106+
error: use of unstable lint `im-a-teapot`
1107+
--> Cargo.toml:9:1
1108+
|
1109+
9 | im-a-teapot = \"warn\"
1110+
| ^^^^^^^^^^^ this is behind `test-dummy-unstable`, which is not enabled
1111+
|
1112+
= help: consider adding `cargo-features = [\"test-dummy-unstable\"]` to the top of the manifest
1113+
error: encountered 1 errors(s) while verifying lints
1114+
",
1115+
)
1116+
.run();
1117+
}
1118+
1119+
#[cargo_test]
1120+
fn check_feature_gated_workspace() {
1121+
let p = project()
1122+
.file(
1123+
"Cargo.toml",
1124+
r#"
1125+
[workspace]
1126+
members = ["foo"]
1127+
1128+
[workspace.lints.cargo]
1129+
im-a-teapot = { level = "warn", priority = 10 }
1130+
test-dummy-unstable = { level = "forbid", priority = -1 }
1131+
"#,
1132+
)
1133+
.file(
1134+
"foo/Cargo.toml",
1135+
r#"
1136+
[package]
1137+
name = "foo"
1138+
version = "0.0.1"
1139+
edition = "2015"
1140+
authors = []
1141+
1142+
[lints]
1143+
workspace = true
1144+
"#,
1145+
)
1146+
.file("foo/src/lib.rs", "")
1147+
.build();
1148+
1149+
p.cargo("check -Zcargo-lints")
1150+
.masquerade_as_nightly_cargo(&["cargo-lints"])
1151+
.with_status(101)
1152+
.with_stderr(
1153+
"\
1154+
error: use of unstable lint `im-a-teapot`
1155+
--> Cargo.toml:6:1
1156+
|
1157+
6 | im-a-teapot = { level = \"warn\", priority = 10 }
1158+
| ^^^^^^^^^^^ this is behind `test-dummy-unstable`, which is not enabled
1159+
|
1160+
note: `cargo::im-a-teapot` was inherited
1161+
--> foo/Cargo.toml:9:1
1162+
|
1163+
9 | workspace = true
1164+
| ----------------
1165+
|
1166+
= help: consider adding `cargo-features = [\"test-dummy-unstable\"]` to the top of the manifest
1167+
error: encountered 1 errors(s) while verifying lints
11071168
",
11081169
)
11091170
.run();

0 commit comments

Comments
 (0)