diff --git a/clippy_lints/src/cargo/common_metadata.rs b/clippy_lints/src/cargo/common_metadata.rs new file mode 100644 index 0000000000000..e0442dda479d7 --- /dev/null +++ b/clippy_lints/src/cargo/common_metadata.rs @@ -0,0 +1,54 @@ +//! lint on missing cargo common metadata + +use cargo_metadata::Metadata; +use clippy_utils::diagnostics::span_lint; +use rustc_lint::LateContext; +use rustc_span::source_map::DUMMY_SP; + +use super::CARGO_COMMON_METADATA; + +pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata, ignore_publish: bool) { + for package in &metadata.packages { + // only run the lint if publish is `None` (`publish = true` or skipped entirely) + // or if the vector isn't empty (`publish = ["something"]`) + if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || ignore_publish { + if is_empty_str(&package.description) { + missing_warning(cx, package, "package.description"); + } + + if is_empty_str(&package.license) && is_empty_str(&package.license_file) { + missing_warning(cx, package, "either package.license or package.license_file"); + } + + if is_empty_str(&package.repository) { + missing_warning(cx, package, "package.repository"); + } + + if is_empty_str(&package.readme) { + missing_warning(cx, package, "package.readme"); + } + + if is_empty_vec(&package.keywords) { + missing_warning(cx, package, "package.keywords"); + } + + if is_empty_vec(&package.categories) { + missing_warning(cx, package, "package.categories"); + } + } + } +} + +fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) { + let message = format!("package `{}` is missing `{}` metadata", package.name, field); + span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message); +} + +fn is_empty_str>(value: &Option) -> bool { + value.as_ref().map_or(true, |s| s.as_ref().is_empty()) +} + +fn is_empty_vec(value: &[String]) -> bool { + // This works because empty iterators return true + value.iter().all(String::is_empty) +} diff --git a/clippy_lints/src/cargo/feature_name.rs b/clippy_lints/src/cargo/feature_name.rs new file mode 100644 index 0000000000000..79a469a4258bb --- /dev/null +++ b/clippy_lints/src/cargo/feature_name.rs @@ -0,0 +1,92 @@ +use cargo_metadata::Metadata; +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_lint::LateContext; +use rustc_span::source_map::DUMMY_SP; + +use super::{NEGATIVE_FEATURE_NAMES, REDUNDANT_FEATURE_NAMES}; + +static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"]; +static SUFFIXES: [&str; 2] = ["-support", "_support"]; + +pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) { + for package in &metadata.packages { + let mut features: Vec<&String> = package.features.keys().collect(); + features.sort(); + for feature in features { + let prefix_opt = { + let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str()); + if i > 0 && feature.starts_with(PREFIXES[i - 1]) { + Some(PREFIXES[i - 1]) + } else { + None + } + }; + if let Some(prefix) = prefix_opt { + lint(cx, feature, prefix, true); + } + + let suffix_opt: Option<&str> = { + let i = SUFFIXES.partition_point(|suffix| { + suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less + }); + if i > 0 && feature.ends_with(SUFFIXES[i - 1]) { + Some(SUFFIXES[i - 1]) + } else { + None + } + }; + if let Some(suffix) = suffix_opt { + lint(cx, feature, suffix, false); + } + } + } +} + +fn is_negative_prefix(s: &str) -> bool { + s.starts_with("no") +} + +fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) { + let is_negative = is_prefix && is_negative_prefix(substring); + span_lint_and_help( + cx, + if is_negative { + NEGATIVE_FEATURE_NAMES + } else { + REDUNDANT_FEATURE_NAMES + }, + DUMMY_SP, + &format!( + "the \"{}\" {} in the feature name \"{}\" is {}", + substring, + if is_prefix { "prefix" } else { "suffix" }, + feature, + if is_negative { "negative" } else { "redundant" } + ), + None, + &format!( + "consider renaming the feature to \"{}\"{}", + if is_prefix { + feature.strip_prefix(substring) + } else { + feature.strip_suffix(substring) + } + .unwrap(), + if is_negative { + ", but make sure the feature adds functionality" + } else { + "" + } + ), + ); +} + +#[test] +fn test_prefixes_sorted() { + let mut sorted_prefixes = PREFIXES; + sorted_prefixes.sort_unstable(); + assert_eq!(PREFIXES, sorted_prefixes); + let mut sorted_suffixes = SUFFIXES; + sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev())); + assert_eq!(SUFFIXES, sorted_suffixes); +} diff --git a/clippy_lints/src/cargo/mod.rs b/clippy_lints/src/cargo/mod.rs new file mode 100644 index 0000000000000..abe95c6663f70 --- /dev/null +++ b/clippy_lints/src/cargo/mod.rs @@ -0,0 +1,221 @@ +use cargo_metadata::MetadataCommand; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::is_lint_allowed; +use rustc_hir::hir_id::CRATE_HIR_ID; +use rustc_lint::{LateContext, LateLintPass, Lint}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::DUMMY_SP; + +mod common_metadata; +mod feature_name; +mod multiple_crate_versions; +mod wildcard_dependencies; + +declare_clippy_lint! { + /// ### What it does + /// Checks to see if all common metadata is defined in + /// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata + /// + /// ### Why is this bad? + /// It will be more difficult for users to discover the + /// purpose of the crate, and key information related to it. + /// + /// ### Example + /// ```toml + /// # This `Cargo.toml` is missing a description field: + /// [package] + /// name = "clippy" + /// version = "0.0.212" + /// repository = "https://github.com/rust-lang/rust-clippy" + /// readme = "README.md" + /// license = "MIT OR Apache-2.0" + /// keywords = ["clippy", "lint", "plugin"] + /// categories = ["development-tools", "development-tools::cargo-plugins"] + /// ``` + /// + /// Should include a description field like: + /// + /// ```toml + /// # This `Cargo.toml` includes all common metadata + /// [package] + /// name = "clippy" + /// version = "0.0.212" + /// description = "A bunch of helpful lints to avoid common pitfalls in Rust" + /// repository = "https://github.com/rust-lang/rust-clippy" + /// readme = "README.md" + /// license = "MIT OR Apache-2.0" + /// keywords = ["clippy", "lint", "plugin"] + /// categories = ["development-tools", "development-tools::cargo-plugins"] + /// ``` + #[clippy::version = "1.32.0"] + pub CARGO_COMMON_METADATA, + cargo, + "common metadata is defined in `Cargo.toml`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for feature names with prefix `use-`, `with-` or suffix `-support` + /// + /// ### Why is this bad? + /// These prefixes and suffixes have no significant meaning. + /// + /// ### Example + /// ```toml + /// # The `Cargo.toml` with feature name redundancy + /// [features] + /// default = ["use-abc", "with-def", "ghi-support"] + /// use-abc = [] // redundant + /// with-def = [] // redundant + /// ghi-support = [] // redundant + /// ``` + /// + /// Use instead: + /// ```toml + /// [features] + /// default = ["abc", "def", "ghi"] + /// abc = [] + /// def = [] + /// ghi = [] + /// ``` + /// + #[clippy::version = "1.57.0"] + pub REDUNDANT_FEATURE_NAMES, + cargo, + "usage of a redundant feature name" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for negative feature names with prefix `no-` or `not-` + /// + /// ### Why is this bad? + /// Features are supposed to be additive, and negatively-named features violate it. + /// + /// ### Example + /// ```toml + /// # The `Cargo.toml` with negative feature names + /// [features] + /// default = [] + /// no-abc = [] + /// not-def = [] + /// + /// ``` + /// Use instead: + /// ```toml + /// [features] + /// default = ["abc", "def"] + /// abc = [] + /// def = [] + /// + /// ``` + #[clippy::version = "1.57.0"] + pub NEGATIVE_FEATURE_NAMES, + cargo, + "usage of a negative feature name" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks to see if multiple versions of a crate are being + /// used. + /// + /// ### Why is this bad? + /// This bloats the size of targets, and can lead to + /// confusing error messages when structs or traits are used interchangeably + /// between different versions of a crate. + /// + /// ### Known problems + /// Because this can be caused purely by the dependencies + /// themselves, it's not always possible to fix this issue. + /// + /// ### Example + /// ```toml + /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. + /// [dependencies] + /// ctrlc = "=3.1.0" + /// ansi_term = "=0.11.0" + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MULTIPLE_CRATE_VERSIONS, + cargo, + "multiple versions of the same crate being used" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for wildcard dependencies in the `Cargo.toml`. + /// + /// ### Why is this bad? + /// [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), + /// it is highly unlikely that you work with any possible version of your dependency, + /// and wildcard dependencies would cause unnecessary breakage in the ecosystem. + /// + /// ### Example + /// ```toml + /// [dependencies] + /// regex = "*" + /// ``` + #[clippy::version = "1.32.0"] + pub WILDCARD_DEPENDENCIES, + cargo, + "wildcard dependencies being used" +} + +pub struct Cargo { + pub ignore_publish: bool, +} + +impl_lint_pass!(Cargo => [ + CARGO_COMMON_METADATA, + REDUNDANT_FEATURE_NAMES, + NEGATIVE_FEATURE_NAMES, + MULTIPLE_CRATE_VERSIONS, + WILDCARD_DEPENDENCIES +]); + +impl LateLintPass<'_> for Cargo { + fn check_crate(&mut self, cx: &LateContext<'_>) { + static NO_DEPS_LINTS: &[&Lint] = &[ + CARGO_COMMON_METADATA, + REDUNDANT_FEATURE_NAMES, + NEGATIVE_FEATURE_NAMES, + WILDCARD_DEPENDENCIES, + ]; + static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS]; + + if !NO_DEPS_LINTS + .iter() + .all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID)) + { + match MetadataCommand::new().no_deps().exec() { + Ok(metadata) => { + common_metadata::check(cx, &metadata, self.ignore_publish); + feature_name::check(cx, &metadata); + wildcard_dependencies::check(cx, &metadata); + }, + Err(e) => { + for lint in NO_DEPS_LINTS { + span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {}", e)); + } + }, + } + } + + if !WITH_DEPS_LINTS + .iter() + .all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID)) + { + match MetadataCommand::new().exec() { + Ok(metadata) => { + multiple_crate_versions::check(cx, &metadata); + }, + Err(e) => { + for lint in WITH_DEPS_LINTS { + span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {}", e)); + } + }, + } + } + } +} diff --git a/clippy_lints/src/cargo/multiple_crate_versions.rs b/clippy_lints/src/cargo/multiple_crate_versions.rs new file mode 100644 index 0000000000000..76fd0819a39a5 --- /dev/null +++ b/clippy_lints/src/cargo/multiple_crate_versions.rs @@ -0,0 +1,63 @@ +//! lint on multiple versions of a crate being used + +use cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId}; +use clippy_utils::diagnostics::span_lint; +use if_chain::if_chain; +use itertools::Itertools; +use rustc_hir::def_id::LOCAL_CRATE; +use rustc_lint::LateContext; +use rustc_span::source_map::DUMMY_SP; + +use super::MULTIPLE_CRATE_VERSIONS; + +pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) { + let local_name = cx.tcx.crate_name(LOCAL_CRATE); + let mut packages = metadata.packages.clone(); + packages.sort_by(|a, b| a.name.cmp(&b.name)); + + if_chain! { + if let Some(resolve) = &metadata.resolve; + if let Some(local_id) = packages + .iter() + .find_map(|p| if p.name == local_name.as_str() { Some(&p.id) } else { None }); + then { + for (name, group) in &packages.iter().group_by(|p| p.name.clone()) { + let group: Vec<&Package> = group.collect(); + + if group.len() <= 1 { + continue; + } + + if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) { + let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect(); + versions.sort(); + let versions = versions.iter().join(", "); + + span_lint( + cx, + MULTIPLE_CRATE_VERSIONS, + DUMMY_SP, + &format!("multiple versions for dependency `{}`: {}", name, versions), + ); + } + } + } + } +} + +fn is_normal_dep(nodes: &[Node], local_id: &PackageId, dep_id: &PackageId) -> bool { + fn depends_on(node: &Node, dep_id: &PackageId) -> bool { + node.deps.iter().any(|dep| { + dep.pkg == *dep_id + && dep + .dep_kinds + .iter() + .any(|info| matches!(info.kind, DependencyKind::Normal)) + }) + } + + nodes + .iter() + .filter(|node| depends_on(node, dep_id)) + .any(|node| node.id == *local_id || is_normal_dep(nodes, local_id, &node.id)) +} diff --git a/clippy_lints/src/cargo/wildcard_dependencies.rs b/clippy_lints/src/cargo/wildcard_dependencies.rs new file mode 100644 index 0000000000000..7fa6acbf557b1 --- /dev/null +++ b/clippy_lints/src/cargo/wildcard_dependencies.rs @@ -0,0 +1,27 @@ +use cargo_metadata::Metadata; +use clippy_utils::diagnostics::span_lint; +use if_chain::if_chain; +use rustc_lint::LateContext; +use rustc_span::source_map::DUMMY_SP; + +use super::WILDCARD_DEPENDENCIES; + +pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) { + for dep in &metadata.packages[0].dependencies { + // VersionReq::any() does not work + if_chain! { + if let Ok(wildcard_ver) = semver::VersionReq::parse("*"); + if let Some(ref source) = dep.source; + if !source.starts_with("git"); + if dep.req == wildcard_ver; + then { + span_lint( + cx, + WILDCARD_DEPENDENCIES, + DUMMY_SP, + &format!("wildcard dependency for `{}`", dep.name), + ); + } + } + } +} diff --git a/clippy_lints/src/cargo_common_metadata.rs b/clippy_lints/src/cargo_common_metadata.rs deleted file mode 100644 index 23f79fdc68238..0000000000000 --- a/clippy_lints/src/cargo_common_metadata.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! lint on missing cargo common metadata - -use clippy_utils::{diagnostics::span_lint, is_lint_allowed}; -use rustc_hir::hir_id::CRATE_HIR_ID; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::source_map::DUMMY_SP; - -declare_clippy_lint! { - /// ### What it does - /// Checks to see if all common metadata is defined in - /// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata - /// - /// ### Why is this bad? - /// It will be more difficult for users to discover the - /// purpose of the crate, and key information related to it. - /// - /// ### Example - /// ```toml - /// # This `Cargo.toml` is missing a description field: - /// [package] - /// name = "clippy" - /// version = "0.0.212" - /// repository = "https://github.com/rust-lang/rust-clippy" - /// readme = "README.md" - /// license = "MIT OR Apache-2.0" - /// keywords = ["clippy", "lint", "plugin"] - /// categories = ["development-tools", "development-tools::cargo-plugins"] - /// ``` - /// - /// Should include a description field like: - /// - /// ```toml - /// # This `Cargo.toml` includes all common metadata - /// [package] - /// name = "clippy" - /// version = "0.0.212" - /// description = "A bunch of helpful lints to avoid common pitfalls in Rust" - /// repository = "https://github.com/rust-lang/rust-clippy" - /// readme = "README.md" - /// license = "MIT OR Apache-2.0" - /// keywords = ["clippy", "lint", "plugin"] - /// categories = ["development-tools", "development-tools::cargo-plugins"] - /// ``` - #[clippy::version = "1.32.0"] - pub CARGO_COMMON_METADATA, - cargo, - "common metadata is defined in `Cargo.toml`" -} - -#[derive(Copy, Clone, Debug)] -pub struct CargoCommonMetadata { - ignore_publish: bool, -} - -impl CargoCommonMetadata { - pub fn new(ignore_publish: bool) -> Self { - Self { ignore_publish } - } -} - -impl_lint_pass!(CargoCommonMetadata => [ - CARGO_COMMON_METADATA -]); - -fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) { - let message = format!("package `{}` is missing `{}` metadata", package.name, field); - span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message); -} - -fn is_empty_str>(value: &Option) -> bool { - value.as_ref().map_or(true, |s| s.as_ref().is_empty()) -} - -fn is_empty_vec(value: &[String]) -> bool { - // This works because empty iterators return true - value.iter().all(String::is_empty) -} - -impl LateLintPass<'_> for CargoCommonMetadata { - fn check_crate(&mut self, cx: &LateContext<'_>) { - if is_lint_allowed(cx, CARGO_COMMON_METADATA, CRATE_HIR_ID) { - return; - } - - let metadata = unwrap_cargo_metadata!(cx, CARGO_COMMON_METADATA, false); - - for package in metadata.packages { - // only run the lint if publish is `None` (`publish = true` or skipped entirely) - // or if the vector isn't empty (`publish = ["something"]`) - if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || self.ignore_publish { - if is_empty_str(&package.description) { - missing_warning(cx, &package, "package.description"); - } - - if is_empty_str(&package.license) && is_empty_str(&package.license_file) { - missing_warning(cx, &package, "either package.license or package.license_file"); - } - - if is_empty_str(&package.repository) { - missing_warning(cx, &package, "package.repository"); - } - - if is_empty_str(&package.readme) { - missing_warning(cx, &package, "package.readme"); - } - - if is_empty_vec(&package.keywords) { - missing_warning(cx, &package, "package.keywords"); - } - - if is_empty_vec(&package.categories) { - missing_warning(cx, &package, "package.categories"); - } - } - } - } -} diff --git a/clippy_lints/src/feature_name.rs b/clippy_lints/src/feature_name.rs deleted file mode 100644 index dc6bef52ddd9f..0000000000000 --- a/clippy_lints/src/feature_name.rs +++ /dev/null @@ -1,166 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{diagnostics::span_lint, is_lint_allowed}; -use rustc_hir::CRATE_HIR_ID; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::DUMMY_SP; - -declare_clippy_lint! { - /// ### What it does - /// Checks for feature names with prefix `use-`, `with-` or suffix `-support` - /// - /// ### Why is this bad? - /// These prefixes and suffixes have no significant meaning. - /// - /// ### Example - /// ```toml - /// # The `Cargo.toml` with feature name redundancy - /// [features] - /// default = ["use-abc", "with-def", "ghi-support"] - /// use-abc = [] // redundant - /// with-def = [] // redundant - /// ghi-support = [] // redundant - /// ``` - /// - /// Use instead: - /// ```toml - /// [features] - /// default = ["abc", "def", "ghi"] - /// abc = [] - /// def = [] - /// ghi = [] - /// ``` - /// - #[clippy::version = "1.57.0"] - pub REDUNDANT_FEATURE_NAMES, - cargo, - "usage of a redundant feature name" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for negative feature names with prefix `no-` or `not-` - /// - /// ### Why is this bad? - /// Features are supposed to be additive, and negatively-named features violate it. - /// - /// ### Example - /// ```toml - /// # The `Cargo.toml` with negative feature names - /// [features] - /// default = [] - /// no-abc = [] - /// not-def = [] - /// - /// ``` - /// Use instead: - /// ```toml - /// [features] - /// default = ["abc", "def"] - /// abc = [] - /// def = [] - /// - /// ``` - #[clippy::version = "1.57.0"] - pub NEGATIVE_FEATURE_NAMES, - cargo, - "usage of a negative feature name" -} - -declare_lint_pass!(FeatureName => [REDUNDANT_FEATURE_NAMES, NEGATIVE_FEATURE_NAMES]); - -static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"]; -static SUFFIXES: [&str; 2] = ["-support", "_support"]; - -fn is_negative_prefix(s: &str) -> bool { - s.starts_with("no") -} - -fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) { - let is_negative = is_prefix && is_negative_prefix(substring); - span_lint_and_help( - cx, - if is_negative { - NEGATIVE_FEATURE_NAMES - } else { - REDUNDANT_FEATURE_NAMES - }, - DUMMY_SP, - &format!( - "the \"{}\" {} in the feature name \"{}\" is {}", - substring, - if is_prefix { "prefix" } else { "suffix" }, - feature, - if is_negative { "negative" } else { "redundant" } - ), - None, - &format!( - "consider renaming the feature to \"{}\"{}", - if is_prefix { - feature.strip_prefix(substring) - } else { - feature.strip_suffix(substring) - } - .unwrap(), - if is_negative { - ", but make sure the feature adds functionality" - } else { - "" - } - ), - ); -} - -impl LateLintPass<'_> for FeatureName { - fn check_crate(&mut self, cx: &LateContext<'_>) { - if is_lint_allowed(cx, REDUNDANT_FEATURE_NAMES, CRATE_HIR_ID) - && is_lint_allowed(cx, NEGATIVE_FEATURE_NAMES, CRATE_HIR_ID) - { - return; - } - - let metadata = unwrap_cargo_metadata!(cx, REDUNDANT_FEATURE_NAMES, false); - - for package in metadata.packages { - let mut features: Vec<&String> = package.features.keys().collect(); - features.sort(); - for feature in features { - let prefix_opt = { - let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str()); - if i > 0 && feature.starts_with(PREFIXES[i - 1]) { - Some(PREFIXES[i - 1]) - } else { - None - } - }; - if let Some(prefix) = prefix_opt { - lint(cx, feature, prefix, true); - } - - let suffix_opt: Option<&str> = { - let i = SUFFIXES.partition_point(|suffix| { - suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less - }); - if i > 0 && feature.ends_with(SUFFIXES[i - 1]) { - Some(SUFFIXES[i - 1]) - } else { - None - } - }; - if let Some(suffix) = suffix_opt { - lint(cx, feature, suffix, false); - } - } - } - } -} - -#[test] -fn test_prefixes_sorted() { - let mut sorted_prefixes = PREFIXES; - sorted_prefixes.sort_unstable(); - assert_eq!(PREFIXES, sorted_prefixes); - let mut sorted_suffixes = SUFFIXES; - sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev())); - assert_eq!(SUFFIXES, sorted_suffixes); -} diff --git a/clippy_lints/src/lib.register_cargo.rs b/clippy_lints/src/lib.register_cargo.rs index 1809f2cc7d462..c890523fe5aeb 100644 --- a/clippy_lints/src/lib.register_cargo.rs +++ b/clippy_lints/src/lib.register_cargo.rs @@ -3,9 +3,9 @@ // Manual edits will be overwritten. store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![ - LintId::of(cargo_common_metadata::CARGO_COMMON_METADATA), - LintId::of(feature_name::NEGATIVE_FEATURE_NAMES), - LintId::of(feature_name::REDUNDANT_FEATURE_NAMES), - LintId::of(multiple_crate_versions::MULTIPLE_CRATE_VERSIONS), - LintId::of(wildcard_dependencies::WILDCARD_DEPENDENCIES), + LintId::of(cargo::CARGO_COMMON_METADATA), + LintId::of(cargo::MULTIPLE_CRATE_VERSIONS), + LintId::of(cargo::NEGATIVE_FEATURE_NAMES), + LintId::of(cargo::REDUNDANT_FEATURE_NAMES), + LintId::of(cargo::WILDCARD_DEPENDENCIES), ]) diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index a80320a578f0e..7a62a99f5ffb9 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -61,7 +61,11 @@ store.register_lints(&[ booleans::NONMINIMAL_BOOL, borrow_as_ptr::BORROW_AS_PTR, bytecount::NAIVE_BYTECOUNT, - cargo_common_metadata::CARGO_COMMON_METADATA, + cargo::CARGO_COMMON_METADATA, + cargo::MULTIPLE_CRATE_VERSIONS, + cargo::NEGATIVE_FEATURE_NAMES, + cargo::REDUNDANT_FEATURE_NAMES, + cargo::WILDCARD_DEPENDENCIES, case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, casts::CAST_LOSSLESS, casts::CAST_POSSIBLE_TRUNCATION, @@ -139,8 +143,6 @@ store.register_lints(&[ exit::EXIT, explicit_write::EXPLICIT_WRITE, fallible_impl_from::FALLIBLE_IMPL_FROM, - feature_name::NEGATIVE_FEATURE_NAMES, - feature_name::REDUNDANT_FEATURE_NAMES, float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS, float_literal::EXCESSIVE_PRECISION, float_literal::LOSSY_FLOAT_LITERAL, @@ -353,7 +355,6 @@ store.register_lints(&[ module_style::MOD_MODULE_FILES, module_style::SELF_NAMED_MODULE_FILES, modulo_arithmetic::MODULO_ARITHMETIC, - multiple_crate_versions::MULTIPLE_CRATE_VERSIONS, mut_key::MUTABLE_KEY_TYPE, mut_mut::MUT_MUT, mut_mutex_lock::MUT_MUTEX_LOCK, @@ -520,7 +521,6 @@ store.register_lints(&[ vec_init_then_push::VEC_INIT_THEN_PUSH, vec_resize_to_zero::VEC_RESIZE_TO_ZERO, verbose_file_reads::VERBOSE_FILE_READS, - wildcard_dependencies::WILDCARD_DEPENDENCIES, wildcard_imports::ENUM_GLOB_USE, wildcard_imports::WILDCARD_IMPORTS, write::PRINTLN_EMPTY_STRING, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 3bd7699792ad7..e78f618735930 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -178,7 +178,7 @@ mod bool_assert_comparison; mod booleans; mod borrow_as_ptr; mod bytecount; -mod cargo_common_metadata; +mod cargo; mod case_sensitive_file_extension_comparisons; mod casts; mod checked_conversions; @@ -220,7 +220,6 @@ mod exhaustive_items; mod exit; mod explicit_write; mod fallible_impl_from; -mod feature_name; mod float_equality_without_abs; mod float_literal; mod floating_point_arithmetic; @@ -290,7 +289,6 @@ mod missing_enforced_import_rename; mod missing_inline; mod module_style; mod modulo_arithmetic; -mod multiple_crate_versions; mod mut_key; mod mut_mut; mod mut_mutex_lock; @@ -399,7 +397,6 @@ mod vec; mod vec_init_then_push; mod vec_resize_to_zero; mod verbose_file_reads; -mod wildcard_dependencies; mod wildcard_imports; mod write; mod zero_div_zero; @@ -724,10 +721,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(redundant_else::RedundantElse)); store.register_late_pass(|| Box::new(create_dir::CreateDir)); store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType)); - let cargo_ignore_publish = conf.cargo_ignore_publish; - store.register_late_pass(move || Box::new(cargo_common_metadata::CargoCommonMetadata::new(cargo_ignore_publish))); - store.register_late_pass(|| Box::new(multiple_crate_versions::MultipleCrateVersions)); - store.register_late_pass(|| Box::new(wildcard_dependencies::WildcardDependencies)); let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions; store.register_early_pass(move || { Box::new(literal_representation::LiteralDigitGrouping::new( @@ -842,7 +835,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts))); store.register_late_pass(|| Box::new(strlen_on_c_strings::StrlenOnCStrings)); store.register_late_pass(move || Box::new(self_named_constructors::SelfNamedConstructors)); - store.register_late_pass(move || Box::new(feature_name::FeatureName)); store.register_late_pass(move || Box::new(iter_not_returning_iterator::IterNotReturningIterator)); store.register_late_pass(move || Box::new(manual_assert::ManualAssert)); let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send; @@ -864,6 +856,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv))); store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation)); store.register_late_pass(|| Box::new(dbg_macro::DbgMacro)); + let cargo_ignore_publish = conf.cargo_ignore_publish; + store.register_late_pass(move || { + Box::new(cargo::Cargo { + ignore_publish: cargo_ignore_publish, + }) + }); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/multiple_crate_versions.rs b/clippy_lints/src/multiple_crate_versions.rs deleted file mode 100644 index 1f9db39cf8ca6..0000000000000 --- a/clippy_lints/src/multiple_crate_versions.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! lint on multiple versions of a crate being used - -use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_lint_allowed; -use rustc_hir::def_id::LOCAL_CRATE; -use rustc_hir::CRATE_HIR_ID; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::DUMMY_SP; - -use cargo_metadata::{DependencyKind, Node, Package, PackageId}; -use if_chain::if_chain; -use itertools::Itertools; - -declare_clippy_lint! { - /// ### What it does - /// Checks to see if multiple versions of a crate are being - /// used. - /// - /// ### Why is this bad? - /// This bloats the size of targets, and can lead to - /// confusing error messages when structs or traits are used interchangeably - /// between different versions of a crate. - /// - /// ### Known problems - /// Because this can be caused purely by the dependencies - /// themselves, it's not always possible to fix this issue. - /// - /// ### Example - /// ```toml - /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. - /// [dependencies] - /// ctrlc = "=3.1.0" - /// ansi_term = "=0.11.0" - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MULTIPLE_CRATE_VERSIONS, - cargo, - "multiple versions of the same crate being used" -} - -declare_lint_pass!(MultipleCrateVersions => [MULTIPLE_CRATE_VERSIONS]); - -impl LateLintPass<'_> for MultipleCrateVersions { - fn check_crate(&mut self, cx: &LateContext<'_>) { - if is_lint_allowed(cx, MULTIPLE_CRATE_VERSIONS, CRATE_HIR_ID) { - return; - } - - let metadata = unwrap_cargo_metadata!(cx, MULTIPLE_CRATE_VERSIONS, true); - let local_name = cx.tcx.crate_name(LOCAL_CRATE); - let mut packages = metadata.packages; - packages.sort_by(|a, b| a.name.cmp(&b.name)); - - if_chain! { - if let Some(resolve) = &metadata.resolve; - if let Some(local_id) = packages - .iter() - .find_map(|p| if p.name == local_name.as_str() { Some(&p.id) } else { None }); - then { - for (name, group) in &packages.iter().group_by(|p| p.name.clone()) { - let group: Vec<&Package> = group.collect(); - - if group.len() <= 1 { - continue; - } - - if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) { - let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect(); - versions.sort(); - let versions = versions.iter().join(", "); - - span_lint( - cx, - MULTIPLE_CRATE_VERSIONS, - DUMMY_SP, - &format!("multiple versions for dependency `{}`: {}", name, versions), - ); - } - } - } - } - } -} - -fn is_normal_dep(nodes: &[Node], local_id: &PackageId, dep_id: &PackageId) -> bool { - fn depends_on(node: &Node, dep_id: &PackageId) -> bool { - node.deps.iter().any(|dep| { - dep.pkg == *dep_id - && dep - .dep_kinds - .iter() - .any(|info| matches!(info.kind, DependencyKind::Normal)) - }) - } - - nodes - .iter() - .filter(|node| depends_on(node, dep_id)) - .any(|node| node.id == *local_id || is_normal_dep(nodes, local_id, &node.id)) -} diff --git a/clippy_lints/src/wildcard_dependencies.rs b/clippy_lints/src/wildcard_dependencies.rs deleted file mode 100644 index 80d7b8a1b6df5..0000000000000 --- a/clippy_lints/src/wildcard_dependencies.rs +++ /dev/null @@ -1,57 +0,0 @@ -use clippy_utils::{diagnostics::span_lint, is_lint_allowed}; -use rustc_hir::hir_id::CRATE_HIR_ID; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::DUMMY_SP; - -use if_chain::if_chain; - -declare_clippy_lint! { - /// ### What it does - /// Checks for wildcard dependencies in the `Cargo.toml`. - /// - /// ### Why is this bad? - /// [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), - /// it is highly unlikely that you work with any possible version of your dependency, - /// and wildcard dependencies would cause unnecessary breakage in the ecosystem. - /// - /// ### Example - /// ```toml - /// [dependencies] - /// regex = "*" - /// ``` - #[clippy::version = "1.32.0"] - pub WILDCARD_DEPENDENCIES, - cargo, - "wildcard dependencies being used" -} - -declare_lint_pass!(WildcardDependencies => [WILDCARD_DEPENDENCIES]); - -impl LateLintPass<'_> for WildcardDependencies { - fn check_crate(&mut self, cx: &LateContext<'_>) { - if is_lint_allowed(cx, WILDCARD_DEPENDENCIES, CRATE_HIR_ID) { - return; - } - - let metadata = unwrap_cargo_metadata!(cx, WILDCARD_DEPENDENCIES, false); - - for dep in &metadata.packages[0].dependencies { - // VersionReq::any() does not work - if_chain! { - if let Ok(wildcard_ver) = semver::VersionReq::parse("*"); - if let Some(ref source) = dep.source; - if !source.starts_with("git"); - if dep.req == wildcard_ver; - then { - span_lint( - cx, - WILDCARD_DEPENDENCIES, - DUMMY_SP, - &format!("wildcard dependency for `{}`", dep.name), - ); - } - } - } - } -} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 42955080c966e..73d91550693de 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2042,24 +2042,6 @@ pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir> expr } -#[macro_export] -macro_rules! unwrap_cargo_metadata { - ($cx: ident, $lint: ident, $deps: expr) => {{ - let mut command = cargo_metadata::MetadataCommand::new(); - if !$deps { - command.no_deps(); - } - - match command.exec() { - Ok(metadata) => metadata, - Err(err) => { - span_lint($cx, $lint, DUMMY_SP, &format!("could not read cargo metadata: {}", err)); - return; - }, - } - }}; -} - pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind { if let Res::Def(_, def_id) = path.res {