2
2
3
3
use rustc:: lint:: * ;
4
4
use syntax:: ast:: * ;
5
+ use syntax:: codemap:: Span ;
5
6
use syntax:: parse:: token:: InternedString ;
6
- use utils:: span_help_and_lint;
7
- use utils:: { camel_case_from, camel_case_until} ;
7
+ use utils:: { span_help_and_lint, span_lint } ;
8
+ use utils:: { camel_case_from, camel_case_until, in_macro } ;
8
9
9
10
/// **What it does:** Warns on enum variants that are prefixed or suffixed by the same characters
10
11
///
@@ -18,87 +19,153 @@ declare_lint! {
18
19
"finds enums where all variants share a prefix/postfix"
19
20
}
20
21
21
- pub struct EnumVariantNames ;
22
+ /// **What it does:** Warns on type names that are prefixed or suffixed by the containing module's name
23
+ ///
24
+ /// **Why is this bad?** It requires the user to type the module name twice
25
+ ///
26
+ /// **Known problems:** None
27
+ ///
28
+ /// **Example:** mod cake { struct BlackForestCake; }
29
+ declare_lint ! {
30
+ pub STUTTER , Allow ,
31
+ "finds type names prefixed/postfixed with their containing module's name"
32
+ }
33
+
34
+ #[ derive( Default ) ]
35
+ pub struct EnumVariantNames {
36
+ modules : Vec < String > ,
37
+ }
22
38
23
39
impl LintPass for EnumVariantNames {
24
40
fn get_lints ( & self ) -> LintArray {
25
- lint_array ! ( ENUM_VARIANT_NAMES )
41
+ lint_array ! ( ENUM_VARIANT_NAMES , STUTTER )
26
42
}
27
43
}
28
44
29
45
fn var2str ( var : & Variant ) -> InternedString {
30
46
var. node . name . name . as_str ( )
31
47
}
32
48
33
- // FIXME: waiting for https://github.com/rust-lang/rust/pull/31700
34
- // fn partial_match(pre: &str, name: &str) -> usize {
35
- // // skip(1) to ensure that the prefix never takes the whole variant name
36
- // pre.chars().zip(name.chars().rev().skip(1).rev()).take_while(|&(l, r)| l == r).count()
37
- // }
38
- //
39
- // fn partial_rmatch(post: &str, name: &str) -> usize {
40
- // // skip(1) to ensure that the postfix never takes the whole variant name
41
- // post.chars().rev().zip(name.chars().skip(1).rev()).take_while(|&(l, r)| l == r).count()
42
- // }
43
-
49
+ /// Returns the number of chars that match from the start
44
50
fn partial_match ( pre : & str , name : & str ) -> usize {
45
51
let mut name_iter = name. chars ( ) ;
46
52
let _ = name_iter. next_back ( ) ; // make sure the name is never fully matched
47
53
pre. chars ( ) . zip ( name_iter) . take_while ( |& ( l, r) | l == r) . count ( )
48
54
}
49
55
56
+ /// Returns the number of chars that match from the end
50
57
fn partial_rmatch ( post : & str , name : & str ) -> usize {
51
58
let mut name_iter = name. chars ( ) ;
52
59
let _ = name_iter. next ( ) ; // make sure the name is never fully matched
53
60
post. chars ( ) . rev ( ) . zip ( name_iter. rev ( ) ) . take_while ( |& ( l, r) | l == r) . count ( )
54
61
}
55
62
56
- impl EarlyLintPass for EnumVariantNames {
57
- // FIXME: #600
58
- #[ allow( while_let_on_iterator) ]
59
- fn check_item ( & mut self , cx : & EarlyContext , item : & Item ) {
60
- if let ItemKind :: Enum ( ref def, _) = item. node {
61
- if def. variants . len ( ) < 2 {
62
- return ;
63
+ // FIXME: #600
64
+ #[ allow( while_let_on_iterator) ]
65
+ fn check_variant ( cx : & EarlyContext , def : & EnumDef , item_name : & str , item_name_chars : usize , span : Span ) {
66
+ for var in & def. variants {
67
+ let name = var2str ( var) ;
68
+ if partial_match ( item_name, & name) == item_name_chars {
69
+ span_lint ( cx, ENUM_VARIANT_NAMES , var. span , "Variant name starts with the enum's name" ) ;
70
+ }
71
+ if partial_rmatch ( item_name, & name) == item_name_chars {
72
+ span_lint ( cx, ENUM_VARIANT_NAMES , var. span , "Variant name ends with the enum's name" ) ;
73
+ }
74
+ }
75
+ if def. variants . len ( ) < 2 {
76
+ return ;
77
+ }
78
+ let first = var2str ( & def. variants [ 0 ] ) ;
79
+ let mut pre = & first[ ..camel_case_until ( & * first) ] ;
80
+ let mut post = & first[ camel_case_from ( & * first) ..] ;
81
+ for var in & def. variants {
82
+ let name = var2str ( var) ;
83
+
84
+ let pre_match = partial_match ( pre, & name) ;
85
+ pre = & pre[ ..pre_match] ;
86
+ let pre_camel = camel_case_until ( pre) ;
87
+ pre = & pre[ ..pre_camel] ;
88
+ while let Some ( ( next, last) ) = name[ pre. len ( ) ..] . chars ( ) . zip ( pre. chars ( ) . rev ( ) ) . next ( ) {
89
+ if next. is_lowercase ( ) {
90
+ let last = pre. len ( ) - last. len_utf8 ( ) ;
91
+ let last_camel = camel_case_until ( & pre[ ..last] ) ;
92
+ pre = & pre[ ..last_camel] ;
93
+ } else {
94
+ break ;
63
95
}
64
- let first = var2str ( & def. variants [ 0 ] ) ;
65
- let mut pre = & first[ ..camel_case_until ( & * first) ] ;
66
- let mut post = & first[ camel_case_from ( & * first) ..] ;
67
- for var in & def. variants {
68
- let name = var2str ( var) ;
96
+ }
69
97
70
- let pre_match = partial_match ( pre, & name) ;
71
- pre = & pre[ ..pre_match] ;
72
- let pre_camel = camel_case_until ( pre) ;
73
- pre = & pre[ ..pre_camel] ;
74
- while let Some ( ( next, last) ) = name[ pre. len ( ) ..] . chars ( ) . zip ( pre. chars ( ) . rev ( ) ) . next ( ) {
75
- if next. is_lowercase ( ) {
76
- let last = pre. len ( ) - last. len_utf8 ( ) ;
77
- let last_camel = camel_case_until ( & pre[ ..last] ) ;
78
- pre = & pre[ ..last_camel] ;
79
- } else {
80
- break ;
98
+ let post_match = partial_rmatch ( post, & name) ;
99
+ let post_end = post. len ( ) - post_match;
100
+ post = & post[ post_end..] ;
101
+ let post_camel = camel_case_from ( post) ;
102
+ post = & post[ post_camel..] ;
103
+ }
104
+ let ( what, value) = match ( pre. is_empty ( ) , post. is_empty ( ) ) {
105
+ ( true , true ) => return ,
106
+ ( false , _) => ( "pre" , pre) ,
107
+ ( true , false ) => ( "post" , post) ,
108
+ } ;
109
+ span_help_and_lint ( cx,
110
+ ENUM_VARIANT_NAMES ,
111
+ span,
112
+ & format ! ( "All variants have the same {}fix: `{}`" , what, value) ,
113
+ & format ! ( "remove the {}fixes and use full paths to \
114
+ the variants instead of glob imports",
115
+ what) ) ;
116
+ }
117
+
118
+ fn to_camel_case ( item_name : & str ) -> String {
119
+ let mut s = String :: new ( ) ;
120
+ let mut up = true ;
121
+ for c in item_name. chars ( ) {
122
+ if c. is_uppercase ( ) {
123
+ // we only turn snake case text into CamelCase
124
+ return item_name. to_string ( ) ;
125
+ }
126
+ if c == '_' {
127
+ up = true ;
128
+ continue ;
129
+ }
130
+ if up {
131
+ up = false ;
132
+ s. extend ( c. to_uppercase ( ) ) ;
133
+ } else {
134
+ s. push ( c) ;
135
+ }
136
+ }
137
+ s
138
+ }
139
+
140
+ impl EarlyLintPass for EnumVariantNames {
141
+ fn check_item_post ( & mut self , _cx : & EarlyContext , _item : & Item ) {
142
+ let last = self . modules . pop ( ) ;
143
+ assert ! ( last. is_some( ) ) ;
144
+ }
145
+
146
+ fn check_item ( & mut self , cx : & EarlyContext , item : & Item ) {
147
+ let item_name = item. ident . name . as_str ( ) ;
148
+ let item_name_chars = item_name. chars ( ) . count ( ) ;
149
+ let item_camel = to_camel_case ( & item_name) ;
150
+ if item. vis == Visibility :: Public && !in_macro ( cx, item. span ) {
151
+ if let Some ( mod_camel) = self . modules . last ( ) {
152
+ // constants don't have surrounding modules
153
+ if !mod_camel. is_empty ( ) {
154
+ let matching = partial_match ( mod_camel, & item_camel) ;
155
+ let rmatching = partial_rmatch ( mod_camel, & item_camel) ;
156
+ let nchars = mod_camel. chars ( ) . count ( ) ;
157
+ if matching == nchars {
158
+ span_lint ( cx, STUTTER , item. span , & format ! ( "Item name ({}) starts with its containing module's name ({})" , item_camel, mod_camel) ) ;
159
+ }
160
+ if rmatching == nchars {
161
+ span_lint ( cx, STUTTER , item. span , & format ! ( "Item name ({}) ends with its containing module's name ({})" , item_camel, mod_camel) ) ;
81
162
}
82
163
}
83
-
84
- let post_match = partial_rmatch ( post, & name) ;
85
- let post_end = post. len ( ) - post_match;
86
- post = & post[ post_end..] ;
87
- let post_camel = camel_case_from ( post) ;
88
- post = & post[ post_camel..] ;
89
164
}
90
- let ( what, value) = match ( pre. is_empty ( ) , post. is_empty ( ) ) {
91
- ( true , true ) => return ,
92
- ( false , _) => ( "pre" , pre) ,
93
- ( true , false ) => ( "post" , post) ,
94
- } ;
95
- span_help_and_lint ( cx,
96
- ENUM_VARIANT_NAMES ,
97
- item. span ,
98
- & format ! ( "All variants have the same {}fix: `{}`" , what, value) ,
99
- & format ! ( "remove the {}fixes and use full paths to \
100
- the variants instead of glob imports",
101
- what) ) ;
102
165
}
166
+ if let ItemKind :: Enum ( ref def, _) = item. node {
167
+ check_variant ( cx, def, & item_name, item_name_chars, item. span ) ;
168
+ }
169
+ self . modules . push ( item_camel) ;
103
170
}
104
171
}
0 commit comments