Skip to content

Commit 1e4b341

Browse files
New lint: missing_workspace_lints
1 parent 4ead403 commit 1e4b341

File tree

10 files changed

+181
-1
lines changed

10 files changed

+181
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6051,6 +6051,7 @@ Released 2018-09-13
60516051
[`missing_spin_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_spin_loop
60526052
[`missing_trait_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_trait_methods
60536053
[`missing_transmute_annotations`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_transmute_annotations
6054+
[`missing_workspace_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_workspace_lints
60546055
[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
60556056
[`mixed_attributes_style`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_attributes_style
60566057
[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use super::MISSING_WORKSPACE_LINTS;
2+
use cargo_metadata::Metadata;
3+
use clippy_utils::diagnostics::span_lint;
4+
use rustc_lint::LateContext;
5+
use rustc_span::DUMMY_SP;
6+
use serde::Deserialize;
7+
use std::path::Path;
8+
9+
type Lints = toml::Table;
10+
11+
#[derive(Deserialize, Debug, Default)]
12+
struct Workspace {
13+
#[serde(default)]
14+
lints: Lints,
15+
}
16+
17+
#[derive(Deserialize, Debug)]
18+
struct CargoToml {
19+
#[serde(default)]
20+
lints: Lints,
21+
#[serde(default)]
22+
workspace: Workspace,
23+
}
24+
25+
pub fn check(cx: &LateContext<'_>, metadata: &Metadata) {
26+
if let Ok(file) = cx.tcx.sess.source_map().load_file(Path::new("Cargo.toml"))
27+
&& let Some(src) = file.src.as_deref()
28+
&& let Ok(cargo_toml) = toml::from_str::<CargoToml>(src)
29+
// if `[workspace.lints]` exists,
30+
&& !cargo_toml.workspace.lints.is_empty()
31+
{
32+
// for each project that is included in the workspace,
33+
for package in &metadata.packages {
34+
// if the project's Cargo.toml doesn't have lints.workspace = true
35+
if let Ok(file) = cx.tcx.sess.source_map().load_file(package.manifest_path.as_std_path())
36+
&& let Some(src) = file.src.as_deref()
37+
&& let Ok(cargo_toml) = toml::from_str::<CargoToml>(src)
38+
&& !cargo_toml.lints.contains_key("workspace")
39+
{
40+
// TODO: Make real span
41+
span_lint(
42+
cx,
43+
MISSING_WORKSPACE_LINTS,
44+
DUMMY_SP,
45+
format!(
46+
"Your project {} is in a workspace with lints configured, but workspace.lints is not configured.",
47+
package.name
48+
),
49+
);
50+
}
51+
}
52+
}
53+
}

clippy_lints/src/cargo/mod.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod common_metadata;
22
mod feature_name;
33
mod lint_groups_priority;
4+
mod missing_workspace_lints;
45
mod multiple_crate_versions;
56
mod wildcard_dependencies;
67

@@ -213,6 +214,43 @@ declare_clippy_lint! {
213214
"a lint group in `Cargo.toml` at the same priority as a lint"
214215
}
215216

217+
declare_clippy_lint! {
218+
/// ### What it does
219+
/// Checks for lint groups with the same priority as lints in the `Cargo.toml`
220+
/// [`[lints]` table](https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section).
221+
///
222+
/// This lint will be removed once [cargo#12918](https://github.com/rust-lang/cargo/issues/12918)
223+
/// is resolved.
224+
///
225+
/// ### Why is this bad?
226+
/// The order of lints in the `[lints]` is ignored, to have a lint override a group the
227+
/// `priority` field needs to be used, otherwise the sort order is undefined.
228+
///
229+
/// ### Known problems
230+
/// Does not check lints inherited using `lints.workspace = true`
231+
///
232+
/// ### Example
233+
/// ```toml
234+
/// # Passed as `--allow=clippy::similar_names --warn=clippy::pedantic`
235+
/// # which results in `similar_names` being `warn`
236+
/// [lints.clippy]
237+
/// pedantic = "warn"
238+
/// similar_names = "allow"
239+
/// ```
240+
/// Use instead:
241+
/// ```toml
242+
/// # Passed as `--warn=clippy::pedantic --allow=clippy::similar_names`
243+
/// # which results in `similar_names` being `allow`
244+
/// [lints.clippy]
245+
/// pedantic = { level = "warn", priority = -1 }
246+
/// similar_names = "allow"
247+
/// ```
248+
#[clippy::version = "1.88.0"]
249+
pub MISSING_WORKSPACE_LINTS,
250+
cargo,
251+
"a workspace defines a lint but workspaces.lint is not set to true"
252+
}
253+
216254
pub struct Cargo {
217255
allowed_duplicate_crates: FxHashSet<String>,
218256
ignore_publish: bool,
@@ -225,6 +263,7 @@ impl_lint_pass!(Cargo => [
225263
MULTIPLE_CRATE_VERSIONS,
226264
WILDCARD_DEPENDENCIES,
227265
LINT_GROUPS_PRIORITY,
266+
MISSING_WORKSPACE_LINTS
228267
]);
229268

230269
impl Cargo {
@@ -243,6 +282,7 @@ impl LateLintPass<'_> for Cargo {
243282
REDUNDANT_FEATURE_NAMES,
244283
NEGATIVE_FEATURE_NAMES,
245284
WILDCARD_DEPENDENCIES,
285+
MISSING_WORKSPACE_LINTS,
246286
];
247287
static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS];
248288

@@ -257,6 +297,7 @@ impl LateLintPass<'_> for Cargo {
257297
common_metadata::check(cx, &metadata, self.ignore_publish);
258298
feature_name::check(cx, &metadata);
259299
wildcard_dependencies::check(cx, &metadata);
300+
missing_workspace_lints::check(cx, &metadata);
260301
},
261302
Err(e) => {
262303
for lint in NO_DEPS_LINTS {

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
4343
crate::byte_char_slices::BYTE_CHAR_SLICES_INFO,
4444
crate::cargo::CARGO_COMMON_METADATA_INFO,
4545
crate::cargo::LINT_GROUPS_PRIORITY_INFO,
46+
crate::cargo::MISSING_WORKSPACE_LINTS_INFO,
4647
crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO,
4748
crate::cargo::NEGATIVE_FEATURE_NAMES_INFO,
4849
crate::cargo::REDUNDANT_FEATURE_NAMES_INFO,

tests/workspace.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,65 @@ fn test_module_style_with_dep_in_subdir() {
4444
assert!(output.status.success());
4545
}
4646

47+
#[test]
48+
fn test_missing_workspace_lints() {
49+
if IS_RUSTC_TEST_SUITE {
50+
return;
51+
}
52+
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
53+
let target_dir = root.join("target").join("workspace_test");
54+
let cwd = root.join("tests/workspace_test");
55+
56+
// Make sure we start with a clean state
57+
Command::new("cargo")
58+
.current_dir(&cwd)
59+
.env("CARGO_TARGET_DIR", &target_dir)
60+
.arg("clean")
61+
.args(["-p", "fail-unused-workspace-lints"])
62+
.args(["-p", "pass-unused-workspace-lints"])
63+
.output()
64+
.unwrap();
65+
66+
let output = Command::new(&*CARGO_CLIPPY_PATH)
67+
.current_dir(&cwd)
68+
.env("CARGO_INCREMENTAL", "0")
69+
.env("CARGO_TARGET_DIR", &target_dir)
70+
.arg("clippy")
71+
.args(["-p", "pass-unused-workspace-lints"])
72+
.arg("--")
73+
.arg("--no-deps")
74+
.arg("-Dclippy::unused-workspace-lints")
75+
.arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir
76+
.output()
77+
.unwrap();
78+
79+
println!("status: {}", output.status);
80+
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
81+
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
82+
assert!(output.status.success());
83+
84+
let output_fail = Command::new(&*CARGO_CLIPPY_PATH)
85+
.current_dir(&cwd)
86+
.env("CARGO_INCREMENTAL", "0")
87+
.env("CARGO_TARGET_DIR", &target_dir)
88+
.arg("clippy")
89+
.args(["-p", "fail-unused-workspace-lints"])
90+
.arg("--")
91+
.arg("--no-deps")
92+
.arg("-Dclippy::unused-workspace-lints")
93+
.arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir
94+
.output()
95+
.unwrap();
96+
97+
println!("status: {}", output_fail.status);
98+
println!("stdout: {}", String::from_utf8_lossy(&output_fail.stdout));
99+
println!("stderr: {}", String::from_utf8_lossy(&output_fail.stderr));
100+
assert!(!output_fail.status.success());
101+
assert!(String::from_utf8(output.stderr).unwrap().contains(
102+
"warning: Your project is in a workspace with lints configured, but workspace.lints is not configured."
103+
));
104+
}
105+
47106
#[test]
48107
fn test_no_deps_ignores_path_deps_in_workspaces() {
49108
if IS_RUSTC_TEST_SUITE {

tests/workspace_test/Cargo.toml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,17 @@ name = "workspace_test"
33
version = "0.1.0"
44
edition = "2018"
55

6+
[lints]
7+
workspace = true
8+
69
[workspace]
7-
members = ["subcrate", "module_style/pass_no_mod_with_dep_in_subdir", "module_style/pass_mod_with_dep_in_subdir"]
10+
members = [
11+
"missing_workspace_lints/pass_missing_workspace_lints",
12+
"missing_workspace_lints/fail_missing_workspace_lints",
13+
"subcrate",
14+
"module_style/pass_no_mod_with_dep_in_subdir",
15+
"module_style/pass_mod_with_dep_in_subdir",
16+
]
17+
18+
[workspace.lints.clippy]
19+
absolute_paths = "allow"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[package]
2+
name = "fail-unused-workspace-lints"
3+
version = "0.1.0"
4+
publish = false
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn main() {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "pass-unused-workspace-lints"
3+
version = "0.1.0"
4+
publish = false
5+
6+
[lints]
7+
workspace = true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn main() {}

0 commit comments

Comments
 (0)