diff --git a/src/consts.rs b/src/consts.rs index 7cb0683711a..37322aeffc8 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -15,16 +15,16 @@ use syntax::ptr::P; #[derive(Debug, Copy, Clone)] pub enum FloatWidth { - Fw32, - Fw64, - FwAny, + F32, + F64, + Any, } impl From for FloatWidth { fn from(ty: FloatTy) -> FloatWidth { match ty { - FloatTy::F32 => FloatWidth::Fw32, - FloatTy::F64 => FloatWidth::Fw64, + FloatTy::F32 => FloatWidth::F32, + FloatTy::F64 => FloatWidth::F64, } } } @@ -200,7 +200,7 @@ fn lit_to_constant(lit: &LitKind) -> Constant { LitKind::Char(c) => Constant::Char(c), LitKind::Int(value, ty) => Constant::Int(value, ty, Sign::Plus), LitKind::Float(ref is, ty) => Constant::Float(is.to_string(), ty.into()), - LitKind::FloatUnsuffixed(ref is) => Constant::Float(is.to_string(), FloatWidth::FwAny), + LitKind::FloatUnsuffixed(ref is) => Constant::Float(is.to_string(), FloatWidth::Any), LitKind::Bool(b) => Constant::Bool(b), } } diff --git a/src/enum_variants.rs b/src/enum_variants.rs index 1dab2c40891..8ad7adf0077 100644 --- a/src/enum_variants.rs +++ b/src/enum_variants.rs @@ -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) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9fd52ff0e98..3a7c6c90d51 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -607,3 +607,65 @@ pub fn is_expn_of(cx: &LateContext, mut span: Span, name: &str) -> Option } } } + +/// 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 +} diff --git a/src/zero_div_zero.rs b/src/zero_div_zero.rs index 1576d699a4a..dbfa2744189 100644 --- a/src/zero_div_zero.rs +++ b/src/zero_div_zero.rs @@ -47,8 +47,8 @@ impl LateLintPass for ZeroDivZeroPass { // since we're about to suggest a use of std::f32::NaN or std::f64::NaN, // match the precision of the literals that are given. let float_type = match (lhs_width, rhs_width) { - (FloatWidth::Fw64, _) - | (_, FloatWidth::Fw64) => "f64", + (FloatWidth::F64, _) + | (_, FloatWidth::F64) => "f64", _ => "f32" }; span_help_and_lint(cx, ZERO_DIVIDED_BY_ZERO, expr.span, diff --git a/tests/camel_case.rs b/tests/camel_case.rs new file mode 100644 index 00000000000..201b796af1c --- /dev/null +++ b/tests/camel_case.rs @@ -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); +} diff --git a/tests/compile-fail/enum_variants.rs b/tests/compile-fail/enum_variants.rs new file mode 100644 index 00000000000..6589bd35fd3 --- /dev/null +++ b/tests/compile-fail/enum_variants.rs @@ -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() {} diff --git a/tests/consts.rs b/tests/consts.rs index 67be4243335..5c6088d0554 100644 --- a/tests/consts.rs +++ b/tests/consts.rs @@ -77,9 +77,9 @@ fn test_ops() { check(ONE, &binop(BiMul, litone.clone(), litone.clone())); check(ONE, &binop(BiDiv, litone.clone(), litone.clone())); - let half_any = Constant::Float("0.5".into(), FloatWidth::FwAny); - let half32 = Constant::Float("0.5".into(), FloatWidth::Fw32); - let half64 = Constant::Float("0.5".into(), FloatWidth::Fw64); + let half_any = Constant::Float("0.5".into(), FloatWidth::Any); + let half32 = Constant::Float("0.5".into(), FloatWidth::F32); + let half64 = Constant::Float("0.5".into(), FloatWidth::F64); assert_eq!(half_any, half32); assert_eq!(half_any, half64);