]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/feature_name.rs
Merge commit '533f0fc81ab9ba097779fcd27c8f9ea12261fef5' into psimd
[rust.git] / src / tools / clippy / clippy_lints / src / feature_name.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use clippy_utils::{diagnostics::span_lint, is_lint_allowed};
3 use rustc_hir::CRATE_HIR_ID;
4 use rustc_lint::{LateContext, LateLintPass};
5 use rustc_session::{declare_lint_pass, declare_tool_lint};
6 use rustc_span::source_map::DUMMY_SP;
7
8 declare_clippy_lint! {
9     /// ### What it does
10     /// Checks for feature names with prefix `use-`, `with-` or suffix `-support`
11     ///
12     /// ### Why is this bad?
13     /// These prefixes and suffixes have no significant meaning.
14     ///
15     /// ### Example
16     /// ```toml
17     /// # The `Cargo.toml` with feature name redundancy
18     /// [features]
19     /// default = ["use-abc", "with-def", "ghi-support"]
20     /// use-abc = []  // redundant
21     /// with-def = []   // redundant
22     /// ghi-support = []   // redundant
23     /// ```
24     ///
25     /// Use instead:
26     /// ```toml
27     /// [features]
28     /// default = ["abc", "def", "ghi"]
29     /// abc = []
30     /// def = []
31     /// ghi = []
32     /// ```
33     ///
34     #[clippy::version = "1.57.0"]
35     pub REDUNDANT_FEATURE_NAMES,
36     cargo,
37     "usage of a redundant feature name"
38 }
39
40 declare_clippy_lint! {
41     /// ### What it does
42     /// Checks for negative feature names with prefix `no-` or `not-`
43     ///
44     /// ### Why is this bad?
45     /// Features are supposed to be additive, and negatively-named features violate it.
46     ///
47     /// ### Example
48     /// ```toml
49     /// # The `Cargo.toml` with negative feature names
50     /// [features]
51     /// default = []
52     /// no-abc = []
53     /// not-def = []
54     ///
55     /// ```
56     /// Use instead:
57     /// ```toml
58     /// [features]
59     /// default = ["abc", "def"]
60     /// abc = []
61     /// def = []
62     ///
63     /// ```
64     #[clippy::version = "1.57.0"]
65     pub NEGATIVE_FEATURE_NAMES,
66     cargo,
67     "usage of a negative feature name"
68 }
69
70 declare_lint_pass!(FeatureName => [REDUNDANT_FEATURE_NAMES, NEGATIVE_FEATURE_NAMES]);
71
72 static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"];
73 static SUFFIXES: [&str; 2] = ["-support", "_support"];
74
75 fn is_negative_prefix(s: &str) -> bool {
76     s.starts_with("no")
77 }
78
79 fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) {
80     let is_negative = is_prefix && is_negative_prefix(substring);
81     span_lint_and_help(
82         cx,
83         if is_negative {
84             NEGATIVE_FEATURE_NAMES
85         } else {
86             REDUNDANT_FEATURE_NAMES
87         },
88         DUMMY_SP,
89         &format!(
90             "the \"{}\" {} in the feature name \"{}\" is {}",
91             substring,
92             if is_prefix { "prefix" } else { "suffix" },
93             feature,
94             if is_negative { "negative" } else { "redundant" }
95         ),
96         None,
97         &format!(
98             "consider renaming the feature to \"{}\"{}",
99             if is_prefix {
100                 feature.strip_prefix(substring)
101             } else {
102                 feature.strip_suffix(substring)
103             }
104             .unwrap(),
105             if is_negative {
106                 ", but make sure the feature adds functionality"
107             } else {
108                 ""
109             }
110         ),
111     );
112 }
113
114 impl LateLintPass<'_> for FeatureName {
115     fn check_crate(&mut self, cx: &LateContext<'_>) {
116         if is_lint_allowed(cx, REDUNDANT_FEATURE_NAMES, CRATE_HIR_ID)
117             && is_lint_allowed(cx, NEGATIVE_FEATURE_NAMES, CRATE_HIR_ID)
118         {
119             return;
120         }
121
122         let metadata = unwrap_cargo_metadata!(cx, REDUNDANT_FEATURE_NAMES, false);
123
124         for package in metadata.packages {
125             let mut features: Vec<&String> = package.features.keys().collect();
126             features.sort();
127             for feature in features {
128                 let prefix_opt = {
129                     let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str());
130                     if i > 0 && feature.starts_with(PREFIXES[i - 1]) {
131                         Some(PREFIXES[i - 1])
132                     } else {
133                         None
134                     }
135                 };
136                 if let Some(prefix) = prefix_opt {
137                     lint(cx, feature, prefix, true);
138                 }
139
140                 let suffix_opt: Option<&str> = {
141                     let i = SUFFIXES.partition_point(|suffix| {
142                         suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less
143                     });
144                     if i > 0 && feature.ends_with(SUFFIXES[i - 1]) {
145                         Some(SUFFIXES[i - 1])
146                     } else {
147                         None
148                     }
149                 };
150                 if let Some(suffix) = suffix_opt {
151                     lint(cx, feature, suffix, false);
152                 }
153             }
154         }
155     }
156 }
157
158 #[test]
159 fn test_prefixes_sorted() {
160     let mut sorted_prefixes = PREFIXES;
161     sorted_prefixes.sort_unstable();
162     assert_eq!(PREFIXES, sorted_prefixes);
163     let mut sorted_suffixes = SUFFIXES;
164     sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev()));
165     assert_eq!(SUFFIXES, sorted_suffixes);
166 }