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