]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/enum_variants.rs
8905870f1d823aaf95a8158568b45ef39de82327
[rust.git] / clippy_lints / src / enum_variants.rs
1 //! lint on enum variants that are prefixed or suffixed by the same characters
2
3 use rustc::lint::*;
4 use syntax::ast::*;
5 use syntax::codemap::Span;
6 use syntax::parse::token::InternedString;
7 use utils::{span_help_and_lint, span_lint};
8 use utils::{camel_case_from, camel_case_until, in_macro};
9
10 /// **What it does:** Warns on enum variants that are prefixed or suffixed by the same characters
11 ///
12 /// **Why is this bad?** Enum variant names should specify their variant, not the enum, too.
13 ///
14 /// **Known problems:** None
15 ///
16 /// **Example:** enum Cake { BlackForestCake, HummingbirdCake }
17 declare_lint! {
18     pub ENUM_VARIANT_NAMES, Warn,
19     "finds enums where all variants share a prefix/postfix"
20 }
21
22 #[derive(Default)]
23 pub struct EnumVariantNames {
24     modules: Vec<String>,
25 }
26
27 impl LintPass for EnumVariantNames {
28     fn get_lints(&self) -> LintArray {
29         lint_array!(ENUM_VARIANT_NAMES)
30     }
31 }
32
33 fn var2str(var: &Variant) -> InternedString {
34     var.node.name.name.as_str()
35 }
36
37 /// Returns the number of chars that match from the start
38 fn partial_match(pre: &str, name: &str) -> usize {
39     let mut name_iter = name.chars();
40     let _ = name_iter.next_back(); // make sure the name is never fully matched
41     pre.chars().zip(name_iter).take_while(|&(l, r)| l == r).count()
42 }
43
44 /// Returns the number of chars that match from the end
45 fn partial_rmatch(post: &str, name: &str) -> usize {
46     let mut name_iter = name.chars();
47     let _ = name_iter.next(); // make sure the name is never fully matched
48     post.chars().rev().zip(name_iter.rev()).take_while(|&(l, r)| l == r).count()
49 }
50
51 // FIXME: #600
52 #[allow(while_let_on_iterator)]
53 fn check_variant(cx: &EarlyContext, def: &EnumDef, item_name: &str, item_name_chars: usize, span: Span) {
54     for var in &def.variants {
55         let name = var2str(var);
56         if partial_match(item_name, &name) == item_name_chars {
57             span_lint(cx, ENUM_VARIANT_NAMES, var.span, "Variant name starts with the enum's name");
58         }
59         if partial_rmatch(item_name, &name) == item_name_chars {
60             span_lint(cx, ENUM_VARIANT_NAMES, var.span, "Variant name ends with the enum's name");
61         }
62     }
63     if def.variants.len() < 2 {
64         return;
65     }
66     let first = var2str(&def.variants[0]);
67     let mut pre = &first[..camel_case_until(&*first)];
68     let mut post = &first[camel_case_from(&*first)..];
69     for var in &def.variants {
70         let name = var2str(var);
71
72         let pre_match = partial_match(pre, &name);
73         pre = &pre[..pre_match];
74         let pre_camel = camel_case_until(pre);
75         pre = &pre[..pre_camel];
76         while let Some((next, last)) = name[pre.len()..].chars().zip(pre.chars().rev()).next() {
77             if next.is_lowercase() {
78                 let last = pre.len() - last.len_utf8();
79                 let last_camel = camel_case_until(&pre[..last]);
80                 pre = &pre[..last_camel];
81             } else {
82                 break;
83             }
84         }
85
86         let post_match = partial_rmatch(post, &name);
87         let post_end = post.len() - post_match;
88         post = &post[post_end..];
89         let post_camel = camel_case_from(post);
90         post = &post[post_camel..];
91     }
92     let (what, value) = match (pre.is_empty(), post.is_empty()) {
93         (true, true) => return,
94         (false, _) => ("pre", pre),
95         (true, false) => ("post", post),
96     };
97     span_help_and_lint(cx,
98                        ENUM_VARIANT_NAMES,
99                        span,
100                        &format!("All variants have the same {}fix: `{}`", what, value),
101                        &format!("remove the {}fixes and use full paths to \
102                                  the variants instead of glob imports",
103                                 what));
104 }
105
106 fn to_camel_case(item_name: &str) -> String {
107     let mut s = String::new();
108     let mut up = true;
109     for c in item_name.chars() {
110         if c.is_uppercase() {
111             // we only turn snake case text into CamelCase
112             return item_name.to_string();
113         }
114         if c == '_' {
115             up = true;
116             continue;
117         }
118         if up {
119             up = false;
120             s.extend(c.to_uppercase());
121         } else {
122             s.push(c);
123         }
124     }
125     s
126 }
127
128 impl EarlyLintPass for EnumVariantNames {
129     fn check_item_post(&mut self, _cx: &EarlyContext, _item: &Item) {
130         let last = self.modules.pop();
131         assert!(last.is_some());
132     }
133
134     fn check_item(&mut self, cx: &EarlyContext, item: &Item) {
135         let item_name = item.ident.name.as_str();
136         let item_name_chars = item_name.chars().count();
137         let item_camel = to_camel_case(&item_name);
138         if item.vis == Visibility::Public && !in_macro(cx, item.span) {
139             if let Some(mod_camel) = self.modules.last() {
140                 // constants don't have surrounding modules
141                 if !mod_camel.is_empty() {
142                     let matching = partial_match(mod_camel, &item_camel);
143                     let rmatching = partial_rmatch(mod_camel, &item_camel);
144                     let nchars = mod_camel.chars().count();
145                     if matching == nchars {
146                         span_lint(cx, ENUM_VARIANT_NAMES, item.span, &format!("Item name ({}) starts with its containing module's name ({})", item_camel, mod_camel));
147                     }
148                     if rmatching == nchars {
149                         span_lint(cx, ENUM_VARIANT_NAMES, item.span, &format!("Item name ({}) ends with its containing module's name ({})", item_camel, mod_camel));
150                     }
151                 }
152             }
153         }
154         if let ItemKind::Enum(ref def, _) = item.node {
155             check_variant(cx, def, &item_name, item_name_chars, item.span);
156         }
157         self.modules.push(item_camel);
158     }
159 }