1 //! lint on enum variants that are prefixed or suffixed by the same characters
5 use syntax
::codemap
::Span
;
6 use syntax
::symbol
::InternedString
;
7 use utils
::{span_help_and_lint, span_lint}
;
8 use utils
::{camel_case_from, camel_case_until, in_macro}
;
10 /// **What it does:** Detects enumeration variants that are prefixed or suffixed
11 /// by the same characters.
13 /// **Why is this bad?** Enumeration variant names should specify their variant,
14 /// not repeat the enumeration name.
16 /// **Known problems:** None.
26 pub ENUM_VARIANT_NAMES
,
28 "enums where all variants share a prefix/postfix"
31 /// **What it does:** Detects enumeration variants that are prefixed or suffixed
32 /// by the same characters.
34 /// **Why is this bad?** Enumeration variant names should specify their variant,
35 /// not repeat the enumeration name.
37 /// **Known problems:** None.
47 pub PUB_ENUM_VARIANT_NAMES
,
49 "enums where all variants share a prefix/postfix"
52 /// **What it does:** Detects type names that are prefixed or suffixed by the
53 /// containing module's name.
55 /// **Why is this bad?** It requires the user to type the module name twice.
57 /// **Known problems:** None.
62 /// struct BlackForestCake;
68 "type names prefixed/postfixed with their containing module's name"
71 /// **What it does:** Checks for modules that have the same name as their
74 /// **Why is this bad?** A typical beginner mistake is to have `mod foo;` and
75 /// again `mod foo { ..
77 /// The expectation is that items inside the inner `mod foo { .. }` are then
79 /// through `foo::x`, but they are only available through
81 /// If this is done on purpose, it would be better to choose a more
82 /// representative module name.
84 /// **Known problems:** None.
98 "modules that have the same name as their parent module"
101 pub struct EnumVariantNames
{
102 modules
: Vec
<(InternedString
, String
)>,
106 impl EnumVariantNames
{
107 pub fn new(threshold
: u64) -> Self {
110 threshold
: threshold
,
115 impl LintPass
for EnumVariantNames
{
116 fn get_lints(&self) -> LintArray
{
117 lint_array
!(ENUM_VARIANT_NAMES
, PUB_ENUM_VARIANT_NAMES
, STUTTER
, MODULE_INCEPTION
)
121 fn var2str(var
: &Variant
) -> InternedString
{
122 var
.node
.name
.name
.as_str()
125 /// Returns the number of chars that match from the start
126 fn partial_match(pre
: &str, name
: &str) -> usize {
127 let mut name_iter
= name
.chars();
128 let _
= name_iter
.next_back(); // make sure the name is never fully matched
131 .take_while(|&(l
, r
)| l
== r
)
135 /// Returns the number of chars that match from the end
136 fn partial_rmatch(post
: &str, name
: &str) -> usize {
137 let mut name_iter
= name
.chars();
138 let _
= name_iter
.next(); // make sure the name is never fully matched
141 .zip(name_iter
.rev())
142 .take_while(|&(l
, r
)| l
== r
)
147 #[allow(while_let_on_iterator)]
153 item_name_chars
: usize,
157 if (def
.variants
.len() as u64) < threshold
{
160 for var
in &def
.variants
{
161 let name
= var2str(var
);
162 if partial_match(item_name
, &name
) == item_name_chars
164 .nth(item_name_chars
)
165 .map_or(false, |c
| !c
.is_lowercase())
167 span_lint(cx
, lint
, var
.span
, "Variant name starts with the enum's name");
169 if partial_rmatch(item_name
, &name
) == item_name_chars
{
170 span_lint(cx
, lint
, var
.span
, "Variant name ends with the enum's name");
173 let first
= var2str(&def
.variants
[0]);
174 let mut pre
= &first
[..camel_case_until(&*first
)];
175 let mut post
= &first
[camel_case_from(&*first
)..];
176 for var
in &def
.variants
{
177 let name
= var2str(var
);
179 let pre_match
= partial_match(pre
, &name
);
180 pre
= &pre
[..pre_match
];
181 let pre_camel
= camel_case_until(pre
);
182 pre
= &pre
[..pre_camel
];
183 while let Some((next
, last
)) = name
[pre
.len()..].chars().zip(pre
.chars().rev()).next() {
184 if next
.is_lowercase() {
185 let last
= pre
.len() - last
.len_utf8();
186 let last_camel
= camel_case_until(&pre
[..last
]);
187 pre
= &pre
[..last_camel
];
193 let post_match
= partial_rmatch(post
, &name
);
194 let post_end
= post
.len() - post_match
;
195 post
= &post
[post_end
..];
196 let post_camel
= camel_case_from(post
);
197 post
= &post
[post_camel
..];
199 let (what
, value
) = match (pre
.is_empty(), post
.is_empty()) {
200 (true, true) => return,
201 (false, _
) => ("pre", pre
),
202 (true, false) => ("post", post
),
208 &format
!("All variants have the same {}fix: `{}`", what
, value
),
210 "remove the {}fixes and use full paths to \
211 the variants instead of glob imports",
217 fn to_camel_case(item_name
: &str) -> String
{
218 let mut s
= String
::new();
220 for c
in item_name
.chars() {
221 if c
.is_uppercase() {
222 // we only turn snake case text into CamelCase
223 return item_name
.to_string();
231 s
.extend(c
.to_uppercase());
239 impl EarlyLintPass
for EnumVariantNames
{
240 fn check_item_post(&mut self, _cx
: &EarlyContext
, _item
: &Item
) {
241 let last
= self.modules
.pop();
242 assert
!(last
.is_some());
245 fn check_item(&mut self, cx
: &EarlyContext
, item
: &Item
) {
246 let item_name
= item
.ident
.name
.as_str();
247 let item_name_chars
= item_name
.chars().count();
248 let item_camel
= to_camel_case(&item_name
);
249 if !in_macro(item
.span
) {
250 if let Some(&(ref mod_name
, ref mod_camel
)) = self.modules
.last() {
251 // constants don't have surrounding modules
252 if !mod_camel
.is_empty() {
253 if *mod_name
== item_name
{
254 if let ItemKind
::Mod(..) = item
.node
{
259 "module has the same name as its containing module",
263 if item
.vis
== Visibility
::Public
{
264 let matching
= partial_match(mod_camel
, &item_camel
);
265 let rmatching
= partial_rmatch(mod_camel
, &item_camel
);
266 let nchars
= mod_camel
.chars().count();
267 if matching
== nchars
{
268 span_lint(cx
, STUTTER
, item
.span
, "item name starts with its containing module's name");
270 if rmatching
== nchars
{
271 span_lint(cx
, STUTTER
, item
.span
, "item name ends with its containing module's name");
277 if let ItemKind
::Enum(ref def
, _
) = item
.node
{
278 let lint
= match item
.vis
{
279 Visibility
::Public
=> PUB_ENUM_VARIANT_NAMES
,
280 _
=> ENUM_VARIANT_NAMES
,
282 check_variant(cx
, self.threshold
, def
, &item_name
, item_name_chars
, item
.span
, lint
);
284 self.modules
.push((item_name
, item_camel
));