Skip to content

Commit ce71f25

Browse files
committed
feat(cargo-lints): Add a lint verification step
1 parent c0c2dde commit ce71f25

File tree

4 files changed

+235
-42
lines changed

4 files changed

+235
-42
lines changed

src/cargo/core/features.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ features! {
515515
#[derive(Debug)]
516516
pub struct Feature {
517517
/// Feature name. This is valid Rust identifer so no dash only underscore.
518-
name: &'static str,
518+
pub name: &'static str,
519519
stability: Status,
520520
/// Version that this feature was stabilized or removed.
521521
version: &'static str,

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+
check_im_a_teapot, check_implicit_features, unused_dependencies, verify_lints,
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+
verify_lints(
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: 154 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,160 @@ 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 verify_lints(
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+
if let Some(feature_gate) = lint.feature_gate {
39+
feature_gated_lint(
40+
lint.name,
41+
feature_gate,
42+
reason,
43+
manifest,
44+
&manifest_path,
45+
ws_contents,
46+
ws_document,
47+
&ws_path,
48+
&mut error_count,
49+
gctx,
50+
)?;
51+
}
52+
}
53+
}
54+
if error_count > 0 {
55+
Err(anyhow::anyhow!(
56+
"encountered {error_count} errors(s) while verifying lints",
57+
))
58+
} else {
59+
Ok(())
60+
}
61+
}
62+
63+
fn feature_gated_lint(
64+
lint_name: &str,
65+
feature_gate: &Feature,
66+
reason: LintLevelReason,
67+
manifest: &Manifest,
68+
manifest_path: &str,
69+
ws_contents: &str,
70+
ws_document: &ImDocument<String>,
71+
ws_path: &str,
72+
error_count: &mut usize,
73+
gctx: &GlobalContext,
74+
) -> CargoResult<()> {
75+
if !manifest.unstable_features().is_enabled(feature_gate) {
76+
let dash_name = lint_name.replace("_", "-");
77+
let dash_feature_name = feature_gate.name.replace("_", "-");
78+
let title = format!("use of unstable lint `{}`", dash_name);
79+
let label = format!(
80+
"this is behind `{}`, which is not enabled",
81+
dash_feature_name
82+
);
83+
let second_title = format!("`cargo::{}` was inherited", dash_name);
84+
let help = format!(
85+
"consider adding `cargo-features = [\"{}\"]` to the top of the manifest",
86+
dash_feature_name
87+
);
88+
let message = match reason {
89+
LintLevelReason::Package => {
90+
let span = get_span(
91+
manifest.document(),
92+
&["lints", "cargo", dash_name.as_str()],
93+
false,
94+
)
95+
.or(get_span(
96+
manifest.document(),
97+
&["lints", "cargo", lint_name],
98+
false,
99+
))
100+
.unwrap();
101+
Some(
102+
Level::Error
103+
.title(&title)
104+
.snippet(
105+
Snippet::source(manifest.contents())
106+
.origin(&manifest_path)
107+
.annotation(Level::Error.span(span).label(&label))
108+
.fold(true),
109+
)
110+
.footer(Level::Help.title(&help)),
111+
)
112+
}
113+
LintLevelReason::Workspace => {
114+
let lint_span = get_span(
115+
ws_document,
116+
&["workspace", "lints", "cargo", dash_name.as_str()],
117+
false,
118+
)
119+
.or(get_span(
120+
ws_document,
121+
&["workspace", "lints", "cargo", lint_name],
122+
false,
123+
))
124+
.unwrap();
125+
let inherit_span_key =
126+
get_span(manifest.document(), &["lints", "workspace"], false).unwrap();
127+
let inherit_span_value =
128+
get_span(manifest.document(), &["lints", "workspace"], true).unwrap();
129+
Some(
130+
Level::Error
131+
.title(&title)
132+
.snippet(
133+
Snippet::source(ws_contents)
134+
.origin(&ws_path)
135+
.annotation(Level::Error.span(lint_span).label(&label))
136+
.fold(true),
137+
)
138+
.footer(
139+
Level::Note.title(&second_title).snippet(
140+
Snippet::source(manifest.contents())
141+
.origin(&manifest_path)
142+
.annotation(
143+
Level::Note
144+
.span(inherit_span_key.start..inherit_span_value.end),
145+
)
146+
.fold(true),
147+
),
148+
)
149+
.footer(Level::Help.title(&help)),
150+
)
151+
}
152+
_ => None,
153+
};
154+
155+
if let Some(message) = message {
156+
*error_count += 1;
157+
let renderer = Renderer::styled().term_width(
158+
gctx.shell()
159+
.err_width()
160+
.diagnostic_terminal_width()
161+
.unwrap_or(annotate_snippets::renderer::DEFAULT_TERM_WIDTH),
162+
);
163+
writeln!(gctx.shell().err(), "{}", renderer.render(message))?;
164+
}
165+
}
166+
Ok(())
167+
}
168+
15169
fn get_span(document: &ImDocument<String>, path: &[&str], get_value: bool) -> Option<Range<usize>> {
16170
let mut table = document.as_item().as_table_like()?;
17171
let mut iter = path.into_iter().peekable();
@@ -122,12 +276,6 @@ impl Lint {
122276
.map(|(_, (l, r, _))| (l, r))
123277
.unwrap()
124278
}
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-
}
131279
}
132280

133281
#[derive(Copy, Clone, Debug, PartialEq)]
@@ -250,39 +398,6 @@ pub fn check_im_a_teapot(
250398
let manifest_path = rel_cwd_manifest_path(path, gctx);
251399
let (lint_level, reason) = IM_A_TEAPOT.level(pkg_lints, ws_lints, manifest.edition());
252400

253-
if IM_A_TEAPOT.is_feature_gated(manifest) {
254-
if reason == LintLevelReason::Package || reason == LintLevelReason::Workspace {
255-
*error_count += 1;
256-
let span = get_span(
257-
manifest.document(),
258-
&["lints", "cargo", "im-a-teapot"],
259-
false,
260-
)
261-
.or(get_span(
262-
manifest.document(),
263-
&["lints", "cargo", "im_a_teapot"],
264-
false,
265-
))
266-
.unwrap();
267-
let message = Level::Error
268-
.title("use of unstable lint `im-a-teapot`")
269-
.snippet(
270-
Snippet::source(manifest.contents())
271-
.origin(&manifest_path)
272-
.annotation(Level::Error.span(span))
273-
.fold(true),
274-
);
275-
let renderer = Renderer::styled().term_width(
276-
gctx.shell()
277-
.err_width()
278-
.diagnostic_terminal_width()
279-
.unwrap_or(annotate_snippets::renderer::DEFAULT_TERM_WIDTH),
280-
);
281-
writeln!(gctx.shell().err(), "{}", renderer.render(message))?;
282-
}
283-
return Ok(());
284-
}
285-
286401
if lint_level == LintLevel::Allow {
287402
return Ok(());
288403
}

tests/testsuite/lints_table.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1107,8 +1107,64 @@ error: use of unstable lint `im-a-teapot`
11071107
--> Cargo.toml:9:1
11081108
|
11091109
9 | im-a-teapot = \"warn\"
1110-
| ^^^^^^^^^^^
1110+
| ^^^^^^^^^^^ this is behind `test-dummy-unstable`, which is not enabled
11111111
|
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
11121168
",
11131169
)
11141170
.run();

0 commit comments

Comments
 (0)