Skip to content

Commit 41c7004

Browse files
committed
Auto merge of #8427 - Jarcho:merge_cargo_passes, r=llogiq
Merge cargo lints changelog: None
2 parents 0836970 + eed7e9f commit 41c7004

13 files changed

+474
-479
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//! lint on missing cargo common metadata
2+
3+
use cargo_metadata::Metadata;
4+
use clippy_utils::diagnostics::span_lint;
5+
use rustc_lint::LateContext;
6+
use rustc_span::source_map::DUMMY_SP;
7+
8+
use super::CARGO_COMMON_METADATA;
9+
10+
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata, ignore_publish: bool) {
11+
for package in &metadata.packages {
12+
// only run the lint if publish is `None` (`publish = true` or skipped entirely)
13+
// or if the vector isn't empty (`publish = ["something"]`)
14+
if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || ignore_publish {
15+
if is_empty_str(&package.description) {
16+
missing_warning(cx, package, "package.description");
17+
}
18+
19+
if is_empty_str(&package.license) && is_empty_str(&package.license_file) {
20+
missing_warning(cx, package, "either package.license or package.license_file");
21+
}
22+
23+
if is_empty_str(&package.repository) {
24+
missing_warning(cx, package, "package.repository");
25+
}
26+
27+
if is_empty_str(&package.readme) {
28+
missing_warning(cx, package, "package.readme");
29+
}
30+
31+
if is_empty_vec(&package.keywords) {
32+
missing_warning(cx, package, "package.keywords");
33+
}
34+
35+
if is_empty_vec(&package.categories) {
36+
missing_warning(cx, package, "package.categories");
37+
}
38+
}
39+
}
40+
}
41+
42+
fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) {
43+
let message = format!("package `{}` is missing `{}` metadata", package.name, field);
44+
span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message);
45+
}
46+
47+
fn is_empty_str<T: AsRef<std::ffi::OsStr>>(value: &Option<T>) -> bool {
48+
value.as_ref().map_or(true, |s| s.as_ref().is_empty())
49+
}
50+
51+
fn is_empty_vec(value: &[String]) -> bool {
52+
// This works because empty iterators return true
53+
value.iter().all(String::is_empty)
54+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use cargo_metadata::Metadata;
2+
use clippy_utils::diagnostics::span_lint_and_help;
3+
use rustc_lint::LateContext;
4+
use rustc_span::source_map::DUMMY_SP;
5+
6+
use super::{NEGATIVE_FEATURE_NAMES, REDUNDANT_FEATURE_NAMES};
7+
8+
static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"];
9+
static SUFFIXES: [&str; 2] = ["-support", "_support"];
10+
11+
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
12+
for package in &metadata.packages {
13+
let mut features: Vec<&String> = package.features.keys().collect();
14+
features.sort();
15+
for feature in features {
16+
let prefix_opt = {
17+
let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str());
18+
if i > 0 && feature.starts_with(PREFIXES[i - 1]) {
19+
Some(PREFIXES[i - 1])
20+
} else {
21+
None
22+
}
23+
};
24+
if let Some(prefix) = prefix_opt {
25+
lint(cx, feature, prefix, true);
26+
}
27+
28+
let suffix_opt: Option<&str> = {
29+
let i = SUFFIXES.partition_point(|suffix| {
30+
suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less
31+
});
32+
if i > 0 && feature.ends_with(SUFFIXES[i - 1]) {
33+
Some(SUFFIXES[i - 1])
34+
} else {
35+
None
36+
}
37+
};
38+
if let Some(suffix) = suffix_opt {
39+
lint(cx, feature, suffix, false);
40+
}
41+
}
42+
}
43+
}
44+
45+
fn is_negative_prefix(s: &str) -> bool {
46+
s.starts_with("no")
47+
}
48+
49+
fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) {
50+
let is_negative = is_prefix && is_negative_prefix(substring);
51+
span_lint_and_help(
52+
cx,
53+
if is_negative {
54+
NEGATIVE_FEATURE_NAMES
55+
} else {
56+
REDUNDANT_FEATURE_NAMES
57+
},
58+
DUMMY_SP,
59+
&format!(
60+
"the \"{}\" {} in the feature name \"{}\" is {}",
61+
substring,
62+
if is_prefix { "prefix" } else { "suffix" },
63+
feature,
64+
if is_negative { "negative" } else { "redundant" }
65+
),
66+
None,
67+
&format!(
68+
"consider renaming the feature to \"{}\"{}",
69+
if is_prefix {
70+
feature.strip_prefix(substring)
71+
} else {
72+
feature.strip_suffix(substring)
73+
}
74+
.unwrap(),
75+
if is_negative {
76+
", but make sure the feature adds functionality"
77+
} else {
78+
""
79+
}
80+
),
81+
);
82+
}
83+
84+
#[test]
85+
fn test_prefixes_sorted() {
86+
let mut sorted_prefixes = PREFIXES;
87+
sorted_prefixes.sort_unstable();
88+
assert_eq!(PREFIXES, sorted_prefixes);
89+
let mut sorted_suffixes = SUFFIXES;
90+
sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev()));
91+
assert_eq!(SUFFIXES, sorted_suffixes);
92+
}

clippy_lints/src/cargo/mod.rs

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
use cargo_metadata::MetadataCommand;
2+
use clippy_utils::diagnostics::span_lint;
3+
use clippy_utils::is_lint_allowed;
4+
use rustc_hir::hir_id::CRATE_HIR_ID;
5+
use rustc_lint::{LateContext, LateLintPass, Lint};
6+
use rustc_session::{declare_tool_lint, impl_lint_pass};
7+
use rustc_span::DUMMY_SP;
8+
9+
mod common_metadata;
10+
mod feature_name;
11+
mod multiple_crate_versions;
12+
mod wildcard_dependencies;
13+
14+
declare_clippy_lint! {
15+
/// ### What it does
16+
/// Checks to see if all common metadata is defined in
17+
/// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata
18+
///
19+
/// ### Why is this bad?
20+
/// It will be more difficult for users to discover the
21+
/// purpose of the crate, and key information related to it.
22+
///
23+
/// ### Example
24+
/// ```toml
25+
/// # This `Cargo.toml` is missing a description field:
26+
/// [package]
27+
/// name = "clippy"
28+
/// version = "0.0.212"
29+
/// repository = "https://github.com/rust-lang/rust-clippy"
30+
/// readme = "README.md"
31+
/// license = "MIT OR Apache-2.0"
32+
/// keywords = ["clippy", "lint", "plugin"]
33+
/// categories = ["development-tools", "development-tools::cargo-plugins"]
34+
/// ```
35+
///
36+
/// Should include a description field like:
37+
///
38+
/// ```toml
39+
/// # This `Cargo.toml` includes all common metadata
40+
/// [package]
41+
/// name = "clippy"
42+
/// version = "0.0.212"
43+
/// description = "A bunch of helpful lints to avoid common pitfalls in Rust"
44+
/// repository = "https://github.com/rust-lang/rust-clippy"
45+
/// readme = "README.md"
46+
/// license = "MIT OR Apache-2.0"
47+
/// keywords = ["clippy", "lint", "plugin"]
48+
/// categories = ["development-tools", "development-tools::cargo-plugins"]
49+
/// ```
50+
#[clippy::version = "1.32.0"]
51+
pub CARGO_COMMON_METADATA,
52+
cargo,
53+
"common metadata is defined in `Cargo.toml`"
54+
}
55+
56+
declare_clippy_lint! {
57+
/// ### What it does
58+
/// Checks for feature names with prefix `use-`, `with-` or suffix `-support`
59+
///
60+
/// ### Why is this bad?
61+
/// These prefixes and suffixes have no significant meaning.
62+
///
63+
/// ### Example
64+
/// ```toml
65+
/// # The `Cargo.toml` with feature name redundancy
66+
/// [features]
67+
/// default = ["use-abc", "with-def", "ghi-support"]
68+
/// use-abc = [] // redundant
69+
/// with-def = [] // redundant
70+
/// ghi-support = [] // redundant
71+
/// ```
72+
///
73+
/// Use instead:
74+
/// ```toml
75+
/// [features]
76+
/// default = ["abc", "def", "ghi"]
77+
/// abc = []
78+
/// def = []
79+
/// ghi = []
80+
/// ```
81+
///
82+
#[clippy::version = "1.57.0"]
83+
pub REDUNDANT_FEATURE_NAMES,
84+
cargo,
85+
"usage of a redundant feature name"
86+
}
87+
88+
declare_clippy_lint! {
89+
/// ### What it does
90+
/// Checks for negative feature names with prefix `no-` or `not-`
91+
///
92+
/// ### Why is this bad?
93+
/// Features are supposed to be additive, and negatively-named features violate it.
94+
///
95+
/// ### Example
96+
/// ```toml
97+
/// # The `Cargo.toml` with negative feature names
98+
/// [features]
99+
/// default = []
100+
/// no-abc = []
101+
/// not-def = []
102+
///
103+
/// ```
104+
/// Use instead:
105+
/// ```toml
106+
/// [features]
107+
/// default = ["abc", "def"]
108+
/// abc = []
109+
/// def = []
110+
///
111+
/// ```
112+
#[clippy::version = "1.57.0"]
113+
pub NEGATIVE_FEATURE_NAMES,
114+
cargo,
115+
"usage of a negative feature name"
116+
}
117+
118+
declare_clippy_lint! {
119+
/// ### What it does
120+
/// Checks to see if multiple versions of a crate are being
121+
/// used.
122+
///
123+
/// ### Why is this bad?
124+
/// This bloats the size of targets, and can lead to
125+
/// confusing error messages when structs or traits are used interchangeably
126+
/// between different versions of a crate.
127+
///
128+
/// ### Known problems
129+
/// Because this can be caused purely by the dependencies
130+
/// themselves, it's not always possible to fix this issue.
131+
///
132+
/// ### Example
133+
/// ```toml
134+
/// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning.
135+
/// [dependencies]
136+
/// ctrlc = "=3.1.0"
137+
/// ansi_term = "=0.11.0"
138+
/// ```
139+
#[clippy::version = "pre 1.29.0"]
140+
pub MULTIPLE_CRATE_VERSIONS,
141+
cargo,
142+
"multiple versions of the same crate being used"
143+
}
144+
145+
declare_clippy_lint! {
146+
/// ### What it does
147+
/// Checks for wildcard dependencies in the `Cargo.toml`.
148+
///
149+
/// ### Why is this bad?
150+
/// [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html),
151+
/// it is highly unlikely that you work with any possible version of your dependency,
152+
/// and wildcard dependencies would cause unnecessary breakage in the ecosystem.
153+
///
154+
/// ### Example
155+
/// ```toml
156+
/// [dependencies]
157+
/// regex = "*"
158+
/// ```
159+
#[clippy::version = "1.32.0"]
160+
pub WILDCARD_DEPENDENCIES,
161+
cargo,
162+
"wildcard dependencies being used"
163+
}
164+
165+
pub struct Cargo {
166+
pub ignore_publish: bool,
167+
}
168+
169+
impl_lint_pass!(Cargo => [
170+
CARGO_COMMON_METADATA,
171+
REDUNDANT_FEATURE_NAMES,
172+
NEGATIVE_FEATURE_NAMES,
173+
MULTIPLE_CRATE_VERSIONS,
174+
WILDCARD_DEPENDENCIES
175+
]);
176+
177+
impl LateLintPass<'_> for Cargo {
178+
fn check_crate(&mut self, cx: &LateContext<'_>) {
179+
static NO_DEPS_LINTS: &[&Lint] = &[
180+
CARGO_COMMON_METADATA,
181+
REDUNDANT_FEATURE_NAMES,
182+
NEGATIVE_FEATURE_NAMES,
183+
WILDCARD_DEPENDENCIES,
184+
];
185+
static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS];
186+
187+
if !NO_DEPS_LINTS
188+
.iter()
189+
.all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID))
190+
{
191+
match MetadataCommand::new().no_deps().exec() {
192+
Ok(metadata) => {
193+
common_metadata::check(cx, &metadata, self.ignore_publish);
194+
feature_name::check(cx, &metadata);
195+
wildcard_dependencies::check(cx, &metadata);
196+
},
197+
Err(e) => {
198+
for lint in NO_DEPS_LINTS {
199+
span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {}", e));
200+
}
201+
},
202+
}
203+
}
204+
205+
if !WITH_DEPS_LINTS
206+
.iter()
207+
.all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID))
208+
{
209+
match MetadataCommand::new().exec() {
210+
Ok(metadata) => {
211+
multiple_crate_versions::check(cx, &metadata);
212+
},
213+
Err(e) => {
214+
for lint in WITH_DEPS_LINTS {
215+
span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {}", e));
216+
}
217+
},
218+
}
219+
}
220+
}
221+
}

0 commit comments

Comments
 (0)