rust/clippy_lints/src/feature_name.rs

167 lines
5.0 KiB
Rust

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);
}