From f327f0e2b60597b0b6b171c7cbfef5832238d3a2 Mon Sep 17 00:00:00 2001 From: dswij Date: Wed, 15 Dec 2021 12:08:13 +0800 Subject: [PATCH 1/8] Refactor `enum_variants` --- clippy_lints/src/enum_variants.rs | 77 ++++++++++++++++--------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index fc3a35efaf84..75cc20a5cda1 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::source::is_present_in_source; use clippy_utils::str_utils::{self, count_match_end, count_match_start}; -use rustc_hir::{EnumDef, Item, ItemKind}; +use rustc_hir::{EnumDef, Item, ItemKind, Variant}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::Span; @@ -115,50 +115,54 @@ impl EnumVariantNames { } impl_lint_pass!(EnumVariantNames => [ - ENUM_VARIANT_NAMES, - MODULE_NAME_REPETITIONS, - MODULE_INCEPTION + ENUM_VARIANT_NAMES, + MODULE_NAME_REPETITIONS, + MODULE_INCEPTION ]); -fn check_variant( - cx: &LateContext<'_>, - threshold: u64, - def: &EnumDef<'_>, - item_name: &str, - item_name_chars: usize, - span: Span, -) { +fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) { + let name = variant.ident.name.as_str(); + let item_name_chars = item_name.chars().count(); + + if count_match_start(item_name, &name).char_count == item_name_chars + && name.chars().nth(item_name_chars).map_or(false, |c| !c.is_lowercase()) + && name.chars().nth(item_name_chars + 1).map_or(false, |c| !c.is_numeric()) + { + span_lint( + cx, + ENUM_VARIANT_NAMES, + variant.span, + "variant name starts with the enum's name", + ); + } +} + +fn check_enum_end(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) { + let name = variant.ident.name.as_str(); + let item_name_chars = item_name.chars().count(); + + if count_match_end(item_name, &name).char_count == item_name_chars { + span_lint( + cx, + ENUM_VARIANT_NAMES, + variant.span, + "variant name ends with the enum's name", + ); + } +} + +fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_name: &str, span: Span) { if (def.variants.len() as u64) < threshold { return; } - for var in def.variants { - let name = var.ident.name.as_str(); - if count_match_start(item_name, &name).char_count == item_name_chars - && name.chars().nth(item_name_chars).map_or(false, |c| !c.is_lowercase()) - && name.chars().nth(item_name_chars + 1).map_or(false, |c| !c.is_numeric()) - { - span_lint( - cx, - ENUM_VARIANT_NAMES, - var.span, - "variant name starts with the enum's name", - ); - } - if count_match_end(item_name, &name).char_count == item_name_chars { - span_lint( - cx, - ENUM_VARIANT_NAMES, - var.span, - "variant name ends with the enum's name", - ); - } - } + let first = &def.variants[0].ident.name.as_str(); let mut pre = &first[..str_utils::camel_case_until(&*first).byte_index]; let mut post = &first[str_utils::camel_case_start(&*first).byte_index..]; for var in def.variants { + check_enum_start(cx, item_name, var); + check_enum_end(cx, item_name, var); let name = var.ident.name.as_str(); - let pre_match = count_match_start(pre, &name).byte_count; pre = &pre[..pre_match]; let pre_camel = str_utils::camel_case_until(pre).byte_index; @@ -233,7 +237,6 @@ impl LateLintPass<'_> for EnumVariantNames { #[allow(clippy::similar_names)] fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { let item_name = item.ident.name.as_str(); - let item_name_chars = item_name.chars().count(); let item_camel = to_camel_case(&item_name); if !item.span.from_expansion() && is_present_in_source(cx, item.span) { if let Some(&(ref mod_name, ref mod_camel)) = self.modules.last() { @@ -283,7 +286,7 @@ impl LateLintPass<'_> for EnumVariantNames { } if let ItemKind::Enum(ref def, _) = item.kind { if !(self.avoid_breaking_exported_api && cx.access_levels.is_exported(item.def_id)) { - check_variant(cx, self.threshold, def, &item_name, item_name_chars, item.span); + check_variant(cx, self.threshold, def, &item_name, item.span); } } self.modules.push((item.ident.name, item_camel)); From d58fdfbf3c995d241677f0dff76d30b5157d0d0f Mon Sep 17 00:00:00 2001 From: dswij Date: Wed, 22 Dec 2021 23:18:11 +0800 Subject: [PATCH 2/8] Fix False Positive on `enum_variants` when prefixes are not camel-case --- clippy_lints/src/enum_variants.rs | 48 ++++++++++++++----------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index 75cc20a5cda1..9862a8c4f52f 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::source::is_present_in_source; -use clippy_utils::str_utils::{self, count_match_end, count_match_start}; +use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start}; use rustc_hir::{EnumDef, Item, ItemKind, Variant}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -157,39 +157,35 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n } let first = &def.variants[0].ident.name.as_str(); - let mut pre = &first[..str_utils::camel_case_until(&*first).byte_index]; - let mut post = &first[str_utils::camel_case_start(&*first).byte_index..]; + let mut pre = camel_case_split(first); + let mut post = pre.clone(); + post.reverse(); for var in def.variants { check_enum_start(cx, item_name, var); check_enum_end(cx, item_name, var); let name = var.ident.name.as_str(); - let pre_match = count_match_start(pre, &name).byte_count; - pre = &pre[..pre_match]; - let pre_camel = str_utils::camel_case_until(pre).byte_index; - pre = &pre[..pre_camel]; - while let Some((next, last)) = name[pre.len()..].chars().zip(pre.chars().rev()).next() { - if next.is_numeric() { - return; - } - if next.is_lowercase() { - let last = pre.len() - last.len_utf8(); - let last_camel = str_utils::camel_case_until(&pre[..last]); - pre = &pre[..last_camel.byte_index]; - } else { - break; - } - } - let post_match = count_match_end(post, &name); - let post_end = post.len() - post_match.byte_count; - post = &post[post_end..]; - let post_camel = str_utils::camel_case_start(post); - post = &post[post_camel.byte_index..]; + let variant_split = camel_case_split(&name); + + pre = pre + .iter() + .copied() + .zip(variant_split.iter().copied()) + .take_while(|(a, b)| a == b) + .map(|e| e.0) + .collect(); + post = post + .iter() + .copied() + .zip(variant_split.iter().rev().copied()) + .take_while(|(a, b)| a == b) + .map(|e| e.0) + .collect(); } let (what, value) = match (pre.is_empty(), post.is_empty()) { (true, true) => return, - (false, _) => ("pre", pre), - (true, false) => ("post", post), + (false, _) => ("pre", pre.join("")), + (true, false) => ("post", post.join("")), }; span_lint_and_help( cx, From c76e2d1e59d466c725a02de7fe43df61fd7eb56f Mon Sep 17 00:00:00 2001 From: dswij Date: Wed, 22 Dec 2021 23:18:24 +0800 Subject: [PATCH 3/8] Add str_util helpers to split camelcase strings --- clippy_utils/src/str_utils.rs | 81 +++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/clippy_utils/src/str_utils.rs b/clippy_utils/src/str_utils.rs index 6dc52bf23254..daa816d211e4 100644 --- a/clippy_utils/src/str_utils.rs +++ b/clippy_utils/src/str_utils.rs @@ -68,6 +68,16 @@ pub fn camel_case_until(s: &str) -> StrIndex { /// ``` #[must_use] pub fn camel_case_start(s: &str) -> StrIndex { + camel_case_start_from_idx(s, 0) +} + +/// Returns `StrIndex` of the last camel-case component of `s[idx..]`. +/// +/// ``` +/// assert_eq!(camel_case_start("AbcDef", 0), StrIndex::new(0, 0)); +/// assert_eq!(camel_case_start("AbcDef", 1), StrIndex::new(3, 3)); +/// ``` +pub fn camel_case_start_from_idx(s: &str, start_idx: usize) -> StrIndex { let char_count = s.chars().count(); let range = 0..char_count; let mut iter = range.rev().zip(s.char_indices().rev()); @@ -78,9 +88,13 @@ pub fn camel_case_start(s: &str) -> StrIndex { } else { return StrIndex::new(char_count, s.len()); } + let mut down = true; let mut last_index = StrIndex::new(char_count, s.len()); for (char_index, (byte_index, c)) in iter { + if byte_index < start_idx { + continue; + } if down { if c.is_uppercase() { down = false; @@ -98,9 +112,51 @@ pub fn camel_case_start(s: &str) -> StrIndex { return last_index; } } + last_index } +/// Get the indexes of camel case components of a string `s` +/// +/// ``` +/// assert_eq!(camel_case_indexes("AbcDef"), vec![StrIndex::new(0, 0), StrIndex::new(3, 3)]) +/// ``` +pub fn camel_case_indexes(s: &str) -> Vec { + let mut result = Vec::new(); + let mut str_idx = camel_case_start(s); + + while str_idx.byte_index < s.len() { + let next_idx = str_idx.byte_index + 1; + result.push(str_idx); + str_idx = camel_case_start_from_idx(s, next_idx); + } + result.push(str_idx); + + result +} + +/// Split camel case string into a vector of its components +/// +/// ``` +/// assert_eq!(camel_case_split("AbcDef"), vec!["Abc", "Def"]); +/// ``` +pub fn camel_case_split(s: &str) -> Vec<&str> { + let offsets = camel_case_indexes(s); + let mut idxs_iter = offsets.iter().map(|str_idx| str_idx.byte_index).peekable(); + let idxs: Vec = if let Some(&idx) = idxs_iter.peek() { + if idx == 0 { + idxs_iter.collect() + } else { + Vec::::from([0]).into_iter().chain(idxs_iter).collect() + } + } else { + return vec![s]; + }; + let split_points: Vec<(&usize, &usize)> = idxs[..idxs.len() - 1].iter().zip(&idxs[1..]).collect(); + + split_points.iter().map(|(&start, &stop)| &s[start..stop]).collect() +} + /// Dealing with sting comparison can be complicated, this struct ensures that both the /// character and byte count are provided for correct indexing. #[derive(Debug, Default, PartialEq, Eq)] @@ -231,4 +287,29 @@ mod test { fn until_caps() { assert_eq!(camel_case_until("ABCD"), StrIndex::new(0, 0)); } + + #[test] + fn camel_case_indexes_full() { + assert_eq!( + camel_case_indexes("AbcDef"), + vec![StrIndex::new(0, 0), StrIndex::new(3, 3)] + ); + assert_eq!( + camel_case_indexes("abcDef"), + vec![StrIndex::new(0, 0), StrIndex::new(3, 3)] + ); + assert_eq!(camel_case_indexes("Abc\u{f6}\u{f6}DD"), vec![StrIndex::new(5, 7)]); + } + + #[test] + fn camel_case_split_full() { + assert_eq!(camel_case_split("A"), vec!["A"]); + assert_eq!(camel_case_split("AbcDef"), vec!["Abc", "Def"]); + assert_eq!(camel_case_split("Abc"), vec!["Abc"]); + assert_eq!(camel_case_split("abcDef"), vec!["abc", "Def"]); + assert_eq!( + camel_case_split("\u{f6}\u{f6}AabABcd"), + vec!["\u{f6}\u{f6}", "Aab", "A", "Bcd"] + ); + } } From df2e4d17c69c92ce46fc90085a4f99aaa298233e Mon Sep 17 00:00:00 2001 From: dswij Date: Wed, 22 Dec 2021 17:04:48 +0800 Subject: [PATCH 4/8] update `enum_variants` test --- tests/ui/enum_variants.rs | 6 +++++ tests/ui/enum_variants.stderr | 42 ++++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/tests/ui/enum_variants.rs b/tests/ui/enum_variants.rs index 083f5143e6e4..d3662a0a213d 100644 --- a/tests/ui/enum_variants.rs +++ b/tests/ui/enum_variants.rs @@ -145,4 +145,10 @@ enum HIDataRequest { DeleteUnpubHIData(String), } +enum North { + Normal, + NoLeft, + NoRight, +} + fn main() {} diff --git a/tests/ui/enum_variants.stderr b/tests/ui/enum_variants.stderr index add8a91e26b8..82a2b3dccb0e 100644 --- a/tests/ui/enum_variants.stderr +++ b/tests/ui/enum_variants.stderr @@ -6,6 +6,18 @@ LL | cFoo, | = note: `-D clippy::enum-variant-names` implied by `-D warnings` +error: all variants have the same prefix: `c` + --> $DIR/enum_variants.rs:14:1 + | +LL | / enum Foo { +LL | | cFoo, +LL | | cBar, +LL | | cBaz, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + error: variant name starts with the enum's name --> $DIR/enum_variants.rs:26:5 | @@ -60,31 +72,31 @@ LL | | } | = help: remove the prefixes and use full paths to the variants instead of glob imports -error: all variants have the same prefix: `WithOut` - --> $DIR/enum_variants.rs:81:1 +error: all variants have the same prefix: `C` + --> $DIR/enum_variants.rs:59:1 | -LL | / enum Seallll { -LL | | WithOutCake, -LL | | WithOutTea, -LL | | WithOut, +LL | / enum Something { +LL | | CCall, +LL | | CCreate, +LL | | CCryogenize, LL | | } | |_^ | = help: remove the prefixes and use full paths to the variants instead of glob imports -error: all variants have the same prefix: `Prefix` - --> $DIR/enum_variants.rs:87:1 +error: all variants have the same prefix: `WithOut` + --> $DIR/enum_variants.rs:81:1 | -LL | / enum NonCaps { -LL | | Prefix的, -LL | | PrefixTea, -LL | | PrefixCake, +LL | / enum Seallll { +LL | | WithOutCake, +LL | | WithOutTea, +LL | | WithOut, LL | | } | |_^ | = help: remove the prefixes and use full paths to the variants instead of glob imports -error: all variants have the same postfix: `IData` +error: all variants have the same postfix: `DataI` --> $DIR/enum_variants.rs:136:1 | LL | / enum IDataRequest { @@ -96,7 +108,7 @@ LL | | } | = help: remove the postfixes and use full paths to the variants instead of glob imports -error: all variants have the same postfix: `HIData` +error: all variants have the same postfix: `DataIH` --> $DIR/enum_variants.rs:142:1 | LL | / enum HIDataRequest { @@ -108,5 +120,5 @@ LL | | } | = help: remove the postfixes and use full paths to the variants instead of glob imports -error: aborting due to 11 previous errors +error: aborting due to 12 previous errors From 8ed723bb1f988d0fb624817bcb7e10da192bcbd9 Mon Sep 17 00:00:00 2001 From: dswij Date: Thu, 23 Dec 2021 11:32:04 +0800 Subject: [PATCH 5/8] Update str_utils test --- clippy_utils/src/str_utils.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/clippy_utils/src/str_utils.rs b/clippy_utils/src/str_utils.rs index daa816d211e4..7200baf5b3c6 100644 --- a/clippy_utils/src/str_utils.rs +++ b/clippy_utils/src/str_utils.rs @@ -74,8 +74,9 @@ pub fn camel_case_start(s: &str) -> StrIndex { /// Returns `StrIndex` of the last camel-case component of `s[idx..]`. /// /// ``` -/// assert_eq!(camel_case_start("AbcDef", 0), StrIndex::new(0, 0)); -/// assert_eq!(camel_case_start("AbcDef", 1), StrIndex::new(3, 3)); +/// # use clippy_utils::str_utils::{camel_case_start_from_idx, StrIndex}; +/// assert_eq!(camel_case_start_from_idx("AbcDef", 0), StrIndex::new(0, 0)); +/// assert_eq!(camel_case_start_from_idx("AbcDef", 1), StrIndex::new(3, 3)); /// ``` pub fn camel_case_start_from_idx(s: &str, start_idx: usize) -> StrIndex { let char_count = s.chars().count(); @@ -119,7 +120,10 @@ pub fn camel_case_start_from_idx(s: &str, start_idx: usize) -> StrIndex { /// Get the indexes of camel case components of a string `s` /// /// ``` -/// assert_eq!(camel_case_indexes("AbcDef"), vec![StrIndex::new(0, 0), StrIndex::new(3, 3)]) +/// # use clippy_utils::str_utils::{camel_case_indexes, StrIndex}; +/// assert_eq!(camel_case_indexes("AbcDef"), vec![StrIndex::new(0, 0), StrIndex::new(3, 3), +/// StrIndex::new(6, 6)]); +/// assert_eq!(camel_case_indexes("abcDef"), vec![StrIndex::new(3, 3), StrIndex::new(6, 6)]); /// ``` pub fn camel_case_indexes(s: &str) -> Vec { let mut result = Vec::new(); @@ -138,6 +142,7 @@ pub fn camel_case_indexes(s: &str) -> Vec { /// Split camel case string into a vector of its components /// /// ``` +/// # use clippy_utils::str_utils::{camel_case_split, StrIndex}; /// assert_eq!(camel_case_split("AbcDef"), vec!["Abc", "Def"]); /// ``` pub fn camel_case_split(s: &str) -> Vec<&str> { @@ -288,17 +293,16 @@ mod test { assert_eq!(camel_case_until("ABCD"), StrIndex::new(0, 0)); } + #[test] + fn camel_case_start_from_idx_full() { + assert_eq!(camel_case_start_from_idx("AbcDef", 0), StrIndex::new(0, 0)); + assert_eq!(camel_case_start_from_idx("AbcDef", 1), StrIndex::new(3, 3)); + assert_eq!(camel_case_start_from_idx("AbcDef", 4), StrIndex::new(6, 6)); + } + #[test] fn camel_case_indexes_full() { - assert_eq!( - camel_case_indexes("AbcDef"), - vec![StrIndex::new(0, 0), StrIndex::new(3, 3)] - ); - assert_eq!( - camel_case_indexes("abcDef"), - vec![StrIndex::new(0, 0), StrIndex::new(3, 3)] - ); - assert_eq!(camel_case_indexes("Abc\u{f6}\u{f6}DD"), vec![StrIndex::new(5, 7)]); + assert_eq!(camel_case_indexes("Abc\u{f6}\u{f6}DD"), vec![StrIndex::new(7, 9)]); } #[test] From c8f016f921d8ade5bf97315d722bf24badca8519 Mon Sep 17 00:00:00 2001 From: dswij Date: Thu, 23 Dec 2021 13:55:41 +0800 Subject: [PATCH 6/8] Fix reversed suggestion on postfix --- clippy_lints/src/enum_variants.rs | 5 ++++- tests/ui/enum_variants.stderr | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index 9862a8c4f52f..fd359b4e36fe 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -185,7 +185,10 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n let (what, value) = match (pre.is_empty(), post.is_empty()) { (true, true) => return, (false, _) => ("pre", pre.join("")), - (true, false) => ("post", post.join("")), + (true, false) => { + post.reverse(); + ("post", post.join("")) + }, }; span_lint_and_help( cx, diff --git a/tests/ui/enum_variants.stderr b/tests/ui/enum_variants.stderr index 82a2b3dccb0e..8a3265086e84 100644 --- a/tests/ui/enum_variants.stderr +++ b/tests/ui/enum_variants.stderr @@ -96,7 +96,7 @@ LL | | } | = help: remove the prefixes and use full paths to the variants instead of glob imports -error: all variants have the same postfix: `DataI` +error: all variants have the same postfix: `IData` --> $DIR/enum_variants.rs:136:1 | LL | / enum IDataRequest { @@ -108,7 +108,7 @@ LL | | } | = help: remove the postfixes and use full paths to the variants instead of glob imports -error: all variants have the same postfix: `DataIH` +error: all variants have the same postfix: `HIData` --> $DIR/enum_variants.rs:142:1 | LL | / enum HIDataRequest { From 6f7e5cbe21d99b7867d8d7383fd2ab753da1d256 Mon Sep 17 00:00:00 2001 From: dswij Date: Fri, 24 Dec 2021 12:10:34 +0800 Subject: [PATCH 7/8] Some minor cleanup --- clippy_lints/src/enum_variants.rs | 16 +++++------ clippy_utils/src/str_utils.rs | 48 +++++++++++++++++-------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index fd359b4e36fe..b13fd25c6e11 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -115,9 +115,9 @@ impl EnumVariantNames { } impl_lint_pass!(EnumVariantNames => [ - ENUM_VARIANT_NAMES, - MODULE_NAME_REPETITIONS, - MODULE_INCEPTION + ENUM_VARIANT_NAMES, + MODULE_NAME_REPETITIONS, + MODULE_INCEPTION ]); fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) { @@ -169,17 +169,15 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n pre = pre .iter() - .copied() - .zip(variant_split.iter().copied()) + .zip(variant_split.iter()) .take_while(|(a, b)| a == b) - .map(|e| e.0) + .map(|e| *e.0) .collect(); post = post .iter() - .copied() - .zip(variant_split.iter().rev().copied()) + .zip(variant_split.iter().rev()) .take_while(|(a, b)| a == b) - .map(|e| e.0) + .map(|e| *e.0) .collect(); } let (what, value) = match (pre.is_empty(), post.is_empty()) { diff --git a/clippy_utils/src/str_utils.rs b/clippy_utils/src/str_utils.rs index 7200baf5b3c6..03a9d3c25fd9 100644 --- a/clippy_utils/src/str_utils.rs +++ b/clippy_utils/src/str_utils.rs @@ -77,6 +77,9 @@ pub fn camel_case_start(s: &str) -> StrIndex { /// # use clippy_utils::str_utils::{camel_case_start_from_idx, StrIndex}; /// assert_eq!(camel_case_start_from_idx("AbcDef", 0), StrIndex::new(0, 0)); /// assert_eq!(camel_case_start_from_idx("AbcDef", 1), StrIndex::new(3, 3)); +/// assert_eq!(camel_case_start_from_idx("AbcDefGhi", 0), StrIndex::new(0, 0)); +/// assert_eq!(camel_case_start_from_idx("AbcDefGhi", 1), StrIndex::new(3, 3)); +/// assert_eq!(camel_case_start_from_idx("Abcdefg", 1), StrIndex::new(7, 7)); /// ``` pub fn camel_case_start_from_idx(s: &str, start_idx: usize) -> StrIndex { let char_count = s.chars().count(); @@ -94,7 +97,7 @@ pub fn camel_case_start_from_idx(s: &str, start_idx: usize) -> StrIndex { let mut last_index = StrIndex::new(char_count, s.len()); for (char_index, (byte_index, c)) in iter { if byte_index < start_idx { - continue; + break; } if down { if c.is_uppercase() { @@ -120,12 +123,17 @@ pub fn camel_case_start_from_idx(s: &str, start_idx: usize) -> StrIndex { /// Get the indexes of camel case components of a string `s` /// /// ``` -/// # use clippy_utils::str_utils::{camel_case_indexes, StrIndex}; -/// assert_eq!(camel_case_indexes("AbcDef"), vec![StrIndex::new(0, 0), StrIndex::new(3, 3), -/// StrIndex::new(6, 6)]); -/// assert_eq!(camel_case_indexes("abcDef"), vec![StrIndex::new(3, 3), StrIndex::new(6, 6)]); +/// # use clippy_utils::str_utils::{camel_case_indices, StrIndex}; +/// assert_eq!( +/// camel_case_indices("AbcDef"), +/// vec![StrIndex::new(0, 0), StrIndex::new(3, 3), StrIndex::new(6, 6)] +/// ); +/// assert_eq!( +/// camel_case_indices("abcDef"), +/// vec![StrIndex::new(3, 3), StrIndex::new(6, 6)] +/// ); /// ``` -pub fn camel_case_indexes(s: &str) -> Vec { +pub fn camel_case_indices(s: &str) -> Vec { let mut result = Vec::new(); let mut str_idx = camel_case_start(s); @@ -146,20 +154,15 @@ pub fn camel_case_indexes(s: &str) -> Vec { /// assert_eq!(camel_case_split("AbcDef"), vec!["Abc", "Def"]); /// ``` pub fn camel_case_split(s: &str) -> Vec<&str> { - let offsets = camel_case_indexes(s); - let mut idxs_iter = offsets.iter().map(|str_idx| str_idx.byte_index).peekable(); - let idxs: Vec = if let Some(&idx) = idxs_iter.peek() { - if idx == 0 { - idxs_iter.collect() - } else { - Vec::::from([0]).into_iter().chain(idxs_iter).collect() - } - } else { - return vec![s]; - }; - let split_points: Vec<(&usize, &usize)> = idxs[..idxs.len() - 1].iter().zip(&idxs[1..]).collect(); + let mut offsets = camel_case_indices(s) + .iter() + .map(|e| e.byte_index) + .collect::>(); + if offsets[0] != 0 { + offsets.insert(0, 0); + } - split_points.iter().map(|(&start, &stop)| &s[start..stop]).collect() + offsets.windows(2).map(|w| &s[w[0]..w[1]]).collect() } /// Dealing with sting comparison can be complicated, this struct ensures that both the @@ -298,11 +301,14 @@ mod test { assert_eq!(camel_case_start_from_idx("AbcDef", 0), StrIndex::new(0, 0)); assert_eq!(camel_case_start_from_idx("AbcDef", 1), StrIndex::new(3, 3)); assert_eq!(camel_case_start_from_idx("AbcDef", 4), StrIndex::new(6, 6)); + assert_eq!(camel_case_start_from_idx("AbcDefGhi", 0), StrIndex::new(0, 0)); + assert_eq!(camel_case_start_from_idx("AbcDefGhi", 1), StrIndex::new(3, 3)); + assert_eq!(camel_case_start_from_idx("Abcdefg", 1), StrIndex::new(7, 7)); } #[test] - fn camel_case_indexes_full() { - assert_eq!(camel_case_indexes("Abc\u{f6}\u{f6}DD"), vec![StrIndex::new(7, 9)]); + fn camel_case_indices_full() { + assert_eq!(camel_case_indices("Abc\u{f6}\u{f6}DD"), vec![StrIndex::new(7, 9)]); } #[test] From b82c9ce3af0bd338ef3e532eae8a9f87d6981bf0 Mon Sep 17 00:00:00 2001 From: dswij Date: Fri, 24 Dec 2021 12:32:12 +0800 Subject: [PATCH 8/8] Add limitation description for `enum_variant_names` `enum_variant_names` will consider characters with no case to be a part of prefixes/suffixes substring that are compared. This means `Foo1` and `Foo2` has different prefixes (`Foo1` and `Foo2` prefix respeectively). This applies to all non-ascii characters with no casing. --- clippy_lints/src/enum_variants.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index b13fd25c6e11..6e1fa836b8f8 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -18,6 +18,12 @@ declare_clippy_lint! { /// Enumeration variant names should specify their variant, /// not repeat the enumeration name. /// + /// ### Limitations + /// Characters with no casing will be considered when comparing prefixes/suffixes + /// This applies to numbers and non-ascii characters without casing + /// e.g. `Foo1` and `Foo2` is considered to have different prefixes + /// (the prefixes are `Foo1` and `Foo2` respectively), as also `Bar螃`, `Bar蟹` + /// /// ### Example /// ```rust /// enum Cake {