fix enum_variant_names linting on all caps enum variants

This commit is contained in:
Oliver Schneider 2016-02-15 16:59:56 +01:00
parent 59c8f6210b
commit e809eb61d7
4 changed files with 227 additions and 27 deletions

View File

@ -6,6 +6,7 @@ use syntax::ast::*;
use syntax::parse::token::InternedString;
use utils::span_help_and_lint;
use utils::{camel_case_from, camel_case_until};
/// **What it does:** Warns on enum variants that are prefixed or suffixed by the same characters
///
@ -31,48 +32,63 @@ fn var2str(var: &Variant) -> InternedString {
var.node.name.name.as_str()
}
fn partial_match(left: &str, right: &str) -> usize {
left.chars().zip(right.chars()).take_while(|&(l, r)| l == r).count()
/*
FIXME: waiting for https://github.com/rust-lang/rust/pull/31700
fn partial_match(pre: &str, name: &str) -> usize {
// skip(1) to ensure that the prefix never takes the whole variant name
pre.chars().zip(name.chars().rev().skip(1).rev()).take_while(|&(l, r)| l == r).count()
}
fn partial_rmatch(left: &str, right: &str) -> usize {
left.chars().rev().zip(right.chars().rev()).take_while(|&(l, r)| l == r).count()
fn partial_rmatch(post: &str, name: &str) -> usize {
// skip(1) to ensure that the postfix never takes the whole variant name
post.chars().rev().zip(name.chars().skip(1).rev()).take_while(|&(l, r)| l == r).count()
}*/
fn partial_match(pre: &str, name: &str) -> usize {
let mut name_iter = name.chars();
let _ = name_iter.next_back(); // make sure the name is never fully matched
pre.chars().zip(name_iter).take_while(|&(l, r)| l == r).count()
}
fn partial_rmatch(post: &str, name: &str) -> usize {
let mut name_iter = name.chars();
let _ = name_iter.next(); // make sure the name is never fully matched
post.chars().rev().zip(name_iter.rev()).take_while(|&(l, r)| l == r).count()
}
impl EarlyLintPass for EnumVariantNames {
// FIXME: #600
#[allow(while_let_on_iterator)]
fn check_item(&mut self, cx: &EarlyContext, item: &Item) {
if let ItemKind::Enum(ref def, _) = item.node {
if def.variants.len() < 2 {
return;
}
let first = var2str(&def.variants[0]);
let mut pre = first.to_string();
let mut post = pre.clone();
for var in &def.variants[1..] {
let mut pre = &first[..camel_case_until(&*first)];
let mut post = &first[camel_case_from(&*first)..];
for var in &def.variants {
let name = var2str(var);
let pre_match = partial_match(&pre, &name);
let post_match = partial_rmatch(&post, &name);
pre.truncate(pre_match);
let post_end = post.len() - post_match;
post.drain(..post_end);
}
if let Some(c) = first[pre.len()..].chars().next() {
if !c.is_uppercase() {
// non camel case prefix
pre.clear()
}
}
if let Some(c) = first[..(first.len() - post.len())].chars().rev().next() {
if let Some(c1) = post.chars().next() {
if !c.is_lowercase() || !c1.is_uppercase() {
// non camel case postfix
post.clear()
pre = &pre[..pre_match];
let pre_camel = camel_case_until(&pre);
pre = &pre[..pre_camel];
while let Some((next, last)) = name[pre.len()..].chars().zip(pre.chars().rev()).next() {
if next.is_lowercase() {
let last = pre.len() - last.len_utf8();
let last_camel = camel_case_until(&pre[..last]);
pre = &pre[..last_camel];
} else {
break;
}
}
}
if pre == "_" {
// don't lint on underscores which are meant to allow dead code
pre.clear();
let post_match = partial_rmatch(&post, &name);
let post_end = post.len() - post_match;
post = &post[post_end..];
let post_camel = camel_case_from(&post);
post = &post[post_camel..];
}
let (what, value) = if !pre.is_empty() {
("pre", pre)

View File

@ -607,3 +607,65 @@ pub fn is_expn_of(cx: &LateContext, mut span: Span, name: &str) -> Option<Span>
}
}
}
/// Returns index of character after first CamelCase component of `s`
pub fn camel_case_until(s: &str) -> usize {
let mut iter = s.char_indices();
if let Some((_, first)) = iter.next() {
if !first.is_uppercase() {
return 0;
}
} else {
return 0;
}
let mut up = true;
let mut last_i = 0;
for (i, c) in iter {
if up {
if c.is_lowercase() {
up = false;
} else {
return last_i;
}
} else if c.is_uppercase() {
up = true;
last_i = i;
} else if !c.is_lowercase() {
return i;
}
}
if up {
last_i
} else {
s.len()
}
}
/// Returns index of last CamelCase component of `s`.
pub fn camel_case_from(s: &str) -> usize {
let mut iter = s.char_indices().rev();
if let Some((_, first)) = iter.next() {
if !first.is_lowercase() {
return s.len();
}
} else {
return s.len();
}
let mut down = true;
let mut last_i = s.len();
for (i, c) in iter {
if down {
if c.is_uppercase() {
down = false;
last_i = i;
} else if !c.is_lowercase() {
return last_i;
}
} else if c.is_lowercase() {
down = true;
} else {
return last_i;
}
}
last_i
}

51
tests/camel_case.rs Normal file
View File

@ -0,0 +1,51 @@
#[allow(plugin_as_library)]
extern crate clippy;
use clippy::utils::{camel_case_from, camel_case_until};
#[test]
fn from_full() {
assert_eq!(camel_case_from("AbcDef"), 0);
assert_eq!(camel_case_from("Abc"), 0);
}
#[test]
fn from_partial() {
assert_eq!(camel_case_from("abcDef"), 3);
assert_eq!(camel_case_from("aDbc"), 1);
}
#[test]
fn from_not() {
assert_eq!(camel_case_from("AbcDef_"), 7);
assert_eq!(camel_case_from("AbcDD"), 5);
}
#[test]
fn from_caps() {
assert_eq!(camel_case_from("ABCD"), 4);
}
#[test]
fn until_full() {
assert_eq!(camel_case_until("AbcDef"), 6);
assert_eq!(camel_case_until("Abc"), 3);
}
#[test]
fn until_not() {
assert_eq!(camel_case_until("abcDef"), 0);
assert_eq!(camel_case_until("aDbc"), 0);
}
#[test]
fn until_partial() {
assert_eq!(camel_case_until("AbcDef_"), 6);
assert_eq!(camel_case_until("CallTypeC"), 8);
assert_eq!(camel_case_until("AbcDD"), 3);
}
#[test]
fn until_caps() {
assert_eq!(camel_case_until("ABCD"), 0);
}

View File

@ -0,0 +1,71 @@
#![feature(plugin, non_ascii_idents)]
#![plugin(clippy)]
#![deny(clippy)]
enum FakeCallType {
CALL, CREATE
}
enum FakeCallType2 {
CALL, CREATELL
}
enum Foo {
cFoo, cBar,
}
enum BadCallType { //~ ERROR: All variants have the same prefix: `CallType`
CallTypeCall,
CallTypeCreate,
CallTypeDestroy,
}
enum TwoCallType { //~ ERROR: All variants have the same prefix: `CallType`
CallTypeCall,
CallTypeCreate,
}
enum Consts { //~ ERROR: All variants have the same prefix: `Constant`
ConstantInt,
ConstantCake,
ConstantLie,
}
enum Two { //~ ERROR: All variants have the same prefix: `Constant`
ConstantInt,
ConstantInfer,
}
enum Something {
CCall,
CCreate,
CCryogenize,
}
enum Seal {
With,
Without,
}
enum Seall {
With,
WithOut,
Withbroken,
}
enum Sealll {
With,
WithOut,
}
enum Seallll { //~ ERROR: All variants have the same prefix: `With`
WithOutCake,
WithOut,
}
enum NonCaps { //~ ERROR: All variants have the same prefix: `Prefix`
Prefix的,
PrefixCake,
}
fn main() {}