Skip to content

Commit 6bcfd66

Browse files
committed
fix(lints): Mark the im_a_teapot lint as unstable
1 parent 50c7e0f commit 6bcfd66

File tree

4 files changed

+252
-13
lines changed

4 files changed

+252
-13
lines changed

src/cargo/core/features.rs

Lines changed: 4 additions & 0 deletions
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

Lines changed: 23 additions & 1 deletion
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

Lines changed: 162 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,157 @@ 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) = lint.level(pkg_lints, ws_lints, manifest.edition());
38+
39+
// Only run analysis on user-specified lints
40+
if !reason.is_user_specified() {
41+
continue;
42+
}
43+
44+
// Only run this on lints that are gated by a feature
45+
if let Some(feature_gate) = lint.feature_gate {
46+
verify_feature_enabled(
47+
lint.name,
48+
feature_gate,
49+
reason,
50+
manifest,
51+
&manifest_path,
52+
ws_contents,
53+
ws_document,
54+
&ws_path,
55+
&mut error_count,
56+
gctx,
57+
)?;
58+
}
59+
}
60+
}
61+
if error_count > 0 {
62+
Err(anyhow::anyhow!(
63+
"encountered {error_count} errors(s) while verifying lints",
64+
))
65+
} else {
66+
Ok(())
67+
}
68+
}
69+
70+
fn verify_feature_enabled(
71+
lint_name: &str,
72+
feature_gate: &Feature,
73+
reason: LintLevelReason,
74+
manifest: &Manifest,
75+
manifest_path: &str,
76+
ws_contents: &str,
77+
ws_document: &ImDocument<String>,
78+
ws_path: &str,
79+
error_count: &mut usize,
80+
gctx: &GlobalContext,
81+
) -> CargoResult<()> {
82+
if !manifest.unstable_features().is_enabled(feature_gate) {
83+
let dash_name = lint_name.replace("_", "-");
84+
let dash_feature_name = feature_gate.name().replace("_", "-");
85+
let title = format!("use of unstable lint `{}`", dash_name);
86+
let label = format!(
87+
"this is behind `{}`, which is not enabled",
88+
dash_feature_name
89+
);
90+
let second_title = format!("`cargo::{}` was inherited", dash_name);
91+
let help = format!(
92+
"consider adding `cargo-features = [\"{}\"]` to the top of the manifest",
93+
dash_feature_name
94+
);
95+
let message = match reason {
96+
LintLevelReason::Package => {
97+
let span = get_span(
98+
manifest.document(),
99+
&["lints", "cargo", dash_name.as_str()],
100+
false,
101+
)
102+
.or(get_span(
103+
manifest.document(),
104+
&["lints", "cargo", lint_name],
105+
false,
106+
))
107+
.unwrap();
108+
109+
Level::Error
110+
.title(&title)
111+
.snippet(
112+
Snippet::source(manifest.contents())
113+
.origin(&manifest_path)
114+
.annotation(Level::Error.span(span).label(&label))
115+
.fold(true),
116+
)
117+
.footer(Level::Help.title(&help))
118+
}
119+
LintLevelReason::Workspace => {
120+
let lint_span = get_span(
121+
ws_document,
122+
&["workspace", "lints", "cargo", dash_name.as_str()],
123+
false,
124+
)
125+
.or(get_span(
126+
ws_document,
127+
&["workspace", "lints", "cargo", lint_name],
128+
false,
129+
))
130+
.unwrap();
131+
let inherit_span_key =
132+
get_span(manifest.document(), &["lints", "workspace"], false).unwrap();
133+
let inherit_span_value =
134+
get_span(manifest.document(), &["lints", "workspace"], true).unwrap();
135+
136+
Level::Error
137+
.title(&title)
138+
.snippet(
139+
Snippet::source(ws_contents)
140+
.origin(&ws_path)
141+
.annotation(Level::Error.span(lint_span).label(&label))
142+
.fold(true),
143+
)
144+
.footer(
145+
Level::Note.title(&second_title).snippet(
146+
Snippet::source(manifest.contents())
147+
.origin(&manifest_path)
148+
.annotation(
149+
Level::Note
150+
.span(inherit_span_key.start..inherit_span_value.end),
151+
)
152+
.fold(true),
153+
),
154+
)
155+
.footer(Level::Help.title(&help))
156+
}
157+
_ => unreachable!("LintLevelReason should be one that is user specified"),
158+
};
159+
160+
*error_count += 1;
161+
gctx.shell().print_message(message)?;
162+
}
163+
Ok(())
164+
}
165+
15166
fn get_span(document: &ImDocument<String>, path: &[&str], get_value: bool) -> Option<Range<usize>> {
16167
let mut table = document.as_item().as_table_like()?;
17168
let mut iter = path.into_iter().peekable();
@@ -122,12 +273,6 @@ impl Lint {
122273
.map(|(_, (l, r, _))| (l, r))
123274
.unwrap()
124275
}
125-
126-
/// Returns true if the lint is feature gated and the feature is not enabled
127-
fn is_feature_gated(&self, manifest: &Manifest) -> bool {
128-
self.feature_gate
129-
.map_or(false, |f| !manifest.unstable_features().is_enabled(f))
130-
}
131276
}
132277

133278
#[derive(Copy, Clone, Debug, PartialEq)]
@@ -190,6 +335,17 @@ impl Display for LintLevelReason {
190335
}
191336
}
192337

338+
impl LintLevelReason {
339+
fn is_user_specified(&self) -> bool {
340+
match self {
341+
LintLevelReason::Default => false,
342+
LintLevelReason::Edition(_) => false,
343+
LintLevelReason::Package => true,
344+
LintLevelReason::Workspace => true,
345+
}
346+
}
347+
}
348+
193349
fn level_priority(
194350
name: &str,
195351
default_level: LintLevel,
@@ -249,10 +405,6 @@ pub fn check_im_a_teapot(
249405
let manifest = pkg.manifest();
250406
let (lint_level, reason) = IM_A_TEAPOT.level(pkg_lints, ws_lints, manifest.edition());
251407

252-
if IM_A_TEAPOT.is_feature_gated(manifest) {
253-
return Ok(());
254-
}
255-
256408
if lint_level == LintLevel::Allow {
257409
return Ok(());
258410
}

tests/testsuite/lints_table.rs

Lines changed: 63 additions & 2 deletions
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)