22
33use clippy_utils:: diagnostics:: { span_lint, span_lint_and_help} ;
44use clippy_utils:: source:: is_present_in_source;
5- use clippy_utils:: str_utils:: { self , count_match_end, count_match_start} ;
6- use rustc_hir:: { EnumDef , Item , ItemKind } ;
5+ use clippy_utils:: str_utils:: { camel_case_split , count_match_end, count_match_start} ;
6+ use rustc_hir:: { EnumDef , Item , ItemKind , Variant } ;
77use rustc_lint:: { LateContext , LateLintPass } ;
88use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
99use rustc_span:: source_map:: Span ;
@@ -18,6 +18,12 @@ declare_clippy_lint! {
1818 /// Enumeration variant names should specify their variant,
1919 /// not repeat the enumeration name.
2020 ///
21+ /// ### Limitations
22+ /// Characters with no casing will be considered when comparing prefixes/suffixes
23+ /// This applies to numbers and non-ascii characters without casing
24+ /// e.g. `Foo1` and `Foo2` is considered to have different prefixes
25+ /// (the prefixes are `Foo1` and `Foo2` respectively), as also `Bar螃`, `Bar蟹`
26+ ///
2127 /// ### Example
2228 /// ```rust
2329 /// enum Cake {
@@ -120,72 +126,73 @@ impl_lint_pass!(EnumVariantNames => [
120126 MODULE_INCEPTION
121127] ) ;
122128
123- fn check_variant (
124- cx : & LateContext < ' _ > ,
125- threshold : u64 ,
126- def : & EnumDef < ' _ > ,
127- item_name : & str ,
128- item_name_chars : usize ,
129- span : Span ,
130- ) {
129+ fn check_enum_start ( cx : & LateContext < ' _ > , item_name : & str , variant : & Variant < ' _ > ) {
130+ let name = variant. ident . name . as_str ( ) ;
131+ let item_name_chars = item_name. chars ( ) . count ( ) ;
132+
133+ if count_match_start ( item_name, & name) . char_count == item_name_chars
134+ && name. chars ( ) . nth ( item_name_chars) . map_or ( false , |c| !c. is_lowercase ( ) )
135+ && name. chars ( ) . nth ( item_name_chars + 1 ) . map_or ( false , |c| !c. is_numeric ( ) )
136+ {
137+ span_lint (
138+ cx,
139+ ENUM_VARIANT_NAMES ,
140+ variant. span ,
141+ "variant name starts with the enum's name" ,
142+ ) ;
143+ }
144+ }
145+
146+ fn check_enum_end ( cx : & LateContext < ' _ > , item_name : & str , variant : & Variant < ' _ > ) {
147+ let name = variant. ident . name . as_str ( ) ;
148+ let item_name_chars = item_name. chars ( ) . count ( ) ;
149+
150+ if count_match_end ( item_name, & name) . char_count == item_name_chars {
151+ span_lint (
152+ cx,
153+ ENUM_VARIANT_NAMES ,
154+ variant. span ,
155+ "variant name ends with the enum's name" ,
156+ ) ;
157+ }
158+ }
159+
160+ fn check_variant ( cx : & LateContext < ' _ > , threshold : u64 , def : & EnumDef < ' _ > , item_name : & str , span : Span ) {
131161 if ( def. variants . len ( ) as u64 ) < threshold {
132162 return ;
133163 }
134- for var in def. variants {
135- let name = var. ident . name . as_str ( ) ;
136- if count_match_start ( item_name, & name) . char_count == item_name_chars
137- && name. chars ( ) . nth ( item_name_chars) . map_or ( false , |c| !c. is_lowercase ( ) )
138- && name. chars ( ) . nth ( item_name_chars + 1 ) . map_or ( false , |c| !c. is_numeric ( ) )
139- {
140- span_lint (
141- cx,
142- ENUM_VARIANT_NAMES ,
143- var. span ,
144- "variant name starts with the enum's name" ,
145- ) ;
146- }
147- if count_match_end ( item_name, & name) . char_count == item_name_chars {
148- span_lint (
149- cx,
150- ENUM_VARIANT_NAMES ,
151- var. span ,
152- "variant name ends with the enum's name" ,
153- ) ;
154- }
155- }
164+
156165 let first = & def. variants [ 0 ] . ident . name . as_str ( ) ;
157- let mut pre = & first[ ..str_utils:: camel_case_until ( & * first) . byte_index ] ;
158- let mut post = & first[ str_utils:: camel_case_start ( & * first) . byte_index ..] ;
166+ let mut pre = camel_case_split ( first) ;
167+ let mut post = pre. clone ( ) ;
168+ post. reverse ( ) ;
159169 for var in def. variants {
170+ check_enum_start ( cx, item_name, var) ;
171+ check_enum_end ( cx, item_name, var) ;
160172 let name = var. ident . name . as_str ( ) ;
161173
162- let pre_match = count_match_start ( pre, & name) . byte_count ;
163- pre = & pre[ ..pre_match] ;
164- let pre_camel = str_utils:: camel_case_until ( pre) . byte_index ;
165- pre = & pre[ ..pre_camel] ;
166- while let Some ( ( next, last) ) = name[ pre. len ( ) ..] . chars ( ) . zip ( pre. chars ( ) . rev ( ) ) . next ( ) {
167- if next. is_numeric ( ) {
168- return ;
169- }
170- if next. is_lowercase ( ) {
171- let last = pre. len ( ) - last. len_utf8 ( ) ;
172- let last_camel = str_utils:: camel_case_until ( & pre[ ..last] ) ;
173- pre = & pre[ ..last_camel. byte_index ] ;
174- } else {
175- break ;
176- }
177- }
174+ let variant_split = camel_case_split ( & name) ;
178175
179- let post_match = count_match_end ( post, & name) ;
180- let post_end = post. len ( ) - post_match. byte_count ;
181- post = & post[ post_end..] ;
182- let post_camel = str_utils:: camel_case_start ( post) ;
183- post = & post[ post_camel. byte_index ..] ;
176+ pre = pre
177+ . iter ( )
178+ . zip ( variant_split. iter ( ) )
179+ . take_while ( |( a, b) | a == b)
180+ . map ( |e| * e. 0 )
181+ . collect ( ) ;
182+ post = post
183+ . iter ( )
184+ . zip ( variant_split. iter ( ) . rev ( ) )
185+ . take_while ( |( a, b) | a == b)
186+ . map ( |e| * e. 0 )
187+ . collect ( ) ;
184188 }
185189 let ( what, value) = match ( pre. is_empty ( ) , post. is_empty ( ) ) {
186190 ( true , true ) => return ,
187- ( false , _) => ( "pre" , pre) ,
188- ( true , false ) => ( "post" , post) ,
191+ ( false , _) => ( "pre" , pre. join ( "" ) ) ,
192+ ( true , false ) => {
193+ post. reverse ( ) ;
194+ ( "post" , post. join ( "" ) )
195+ } ,
189196 } ;
190197 span_lint_and_help (
191198 cx,
@@ -233,7 +240,6 @@ impl LateLintPass<'_> for EnumVariantNames {
233240 #[ allow( clippy:: similar_names) ]
234241 fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
235242 let item_name = item. ident . name . as_str ( ) ;
236- let item_name_chars = item_name. chars ( ) . count ( ) ;
237243 let item_camel = to_camel_case ( & item_name) ;
238244 if !item. span . from_expansion ( ) && is_present_in_source ( cx, item. span ) {
239245 if let Some ( & ( ref mod_name, ref mod_camel) ) = self . modules . last ( ) {
@@ -283,7 +289,7 @@ impl LateLintPass<'_> for EnumVariantNames {
283289 }
284290 if let ItemKind :: Enum ( ref def, _) = item. kind {
285291 if !( self . avoid_breaking_exported_api && cx. access_levels . is_exported ( item. def_id ) ) {
286- check_variant ( cx, self . threshold , def, & item_name, item_name_chars , item. span ) ;
292+ check_variant ( cx, self . threshold , def, & item_name, item. span ) ;
287293 }
288294 }
289295 self . modules . push ( ( item. ident . name , item_camel) ) ;
0 commit comments