2
2
3
3
use clap:: { App , Arg , SubCommand } ;
4
4
use clippy_dev:: * ;
5
+ use std:: fs:: { File , OpenOptions } ;
6
+ use std:: io;
7
+ use std:: io:: prelude:: * ;
8
+ use std:: io:: ErrorKind ;
9
+ use std:: path:: PathBuf ;
5
10
6
11
mod fmt;
7
12
mod stderr_length_check;
@@ -51,6 +56,47 @@ fn main() {
51
56
. help ( "Checks that util/dev update_lints has been run. Used on CI." ) ,
52
57
) ,
53
58
)
59
+ . subcommand (
60
+ SubCommand :: with_name ( "new_lint" )
61
+ . about ( "Create new lint and run util/dev update_lints" )
62
+ . arg (
63
+ Arg :: with_name ( "pass" )
64
+ . short ( "p" )
65
+ . long ( "pass" )
66
+ . help ( "Specify whether the lint runs during the early or late pass" )
67
+ . takes_value ( true )
68
+ . possible_values ( & [ "early" , "late" ] )
69
+ . required ( true ) ,
70
+ )
71
+ . arg (
72
+ Arg :: with_name ( "name" )
73
+ . short ( "n" )
74
+ . long ( "name" )
75
+ . help ( "Name of the new lint in snake case, ex: fn_too_long" )
76
+ . takes_value ( true )
77
+ . required ( true ) ,
78
+ )
79
+ . arg (
80
+ Arg :: with_name ( "category" )
81
+ . short ( "c" )
82
+ . long ( "category" )
83
+ . help ( "What category the lint belongs to, defaults to `nursery`" )
84
+ . default_value ( "nursery" )
85
+ . possible_values ( & [
86
+ "style" ,
87
+ "correctness" ,
88
+ "complexity" ,
89
+ "perf" ,
90
+ "pedantic" ,
91
+ "restriction" ,
92
+ "cargo" ,
93
+ "nursery" ,
94
+ "internal" ,
95
+ "internal_warn" ,
96
+ ] )
97
+ . takes_value ( true ) ,
98
+ ) ,
99
+ )
54
100
. arg (
55
101
Arg :: with_name ( "limit-stderr-length" )
56
102
. long ( "limit-stderr-length" )
@@ -75,10 +121,117 @@ fn main() {
75
121
update_lints ( & UpdateMode :: Change ) ;
76
122
}
77
123
} ,
124
+ ( "new_lint" , Some ( matches) ) => {
125
+ create_new_lint (
126
+ matches. value_of ( "pass" ) ,
127
+ matches. value_of ( "name" ) ,
128
+ matches. value_of ( "category" ) ,
129
+ ) ;
130
+ } ,
78
131
_ => { } ,
79
132
}
80
133
}
81
134
135
+ fn project_root ( ) -> Result < PathBuf , io:: Error > {
136
+ let current_dir = std:: env:: current_dir ( ) ?;
137
+ for path in current_dir. ancestors ( ) {
138
+ let result = std:: fs:: read_to_string ( path. join ( "Cargo.toml" ) ) ;
139
+ if let Err ( err) = & result {
140
+ if err. kind ( ) == io:: ErrorKind :: NotFound {
141
+ continue ;
142
+ }
143
+ }
144
+
145
+ let content = result?;
146
+ if content. contains ( "[package]\n name = \" clippy\" " ) {
147
+ return Ok ( path. to_path_buf ( ) ) ;
148
+ }
149
+ }
150
+ Err ( io:: Error :: new ( ErrorKind :: Other , "Unable to find project root" ) )
151
+ }
152
+
153
+ fn open_files ( lint_name : & str ) -> Result < ( File , File ) , io:: Error > {
154
+ let project_root = project_root ( ) ?;
155
+
156
+ let test_file_path = project_root. join ( format ! ( "tests/ui/{}.rs" , lint_name) ) ;
157
+ let test_file = OpenOptions :: new ( ) . write ( true ) . create_new ( true ) . open ( test_file_path) ?;
158
+
159
+ let lint_file_path = project_root. join ( format ! ( "clippy_lints/src/{}.rs" , lint_name) ) ;
160
+ let lint_file = OpenOptions :: new ( ) . write ( true ) . create_new ( true ) . open ( lint_file_path) ?;
161
+
162
+ Ok ( ( test_file, lint_file) )
163
+ }
164
+
165
+ fn to_camel_case ( name : & str ) -> String {
166
+ name. split ( '_' )
167
+ . map ( |s| [ & s[ 0 ..1 ] . to_uppercase ( ) , & s[ 1 ..] ] . concat ( ) )
168
+ . collect :: < String > ( )
169
+ }
170
+
171
+ fn create_new_lint ( pass : Option < & str > , lint_name : Option < & str > , category : Option < & str > ) {
172
+ let pass = pass. expect ( "`pass` argument is validated by clap" ) ;
173
+ let lint_name = lint_name. expect ( "`name` argument is validated by clap" ) ;
174
+ let category = category. expect ( "`category` argument is validated by clap" ) ;
175
+
176
+ match open_files ( lint_name) {
177
+ Ok ( ( mut test_file, mut lint_file) ) => {
178
+ let pass_type = match pass {
179
+ "early" => "EarlyLintPass" ,
180
+ "late" => "LateLintPass" ,
181
+ _ => {
182
+ eprintln ! ( "`pass_type` should only ever be `early` or `late`!" ) ;
183
+ return ;
184
+ } ,
185
+ } ;
186
+
187
+ let camel_case_name = to_camel_case ( lint_name) ;
188
+
189
+ test_file
190
+ . write_all (
191
+ format ! (
192
+ "#![warn(clippy::{})]
193
+
194
+ fn main() {{
195
+ // test code goes here
196
+ }}
197
+ " ,
198
+ lint_name
199
+ )
200
+ . as_bytes ( ) ,
201
+ )
202
+ . unwrap ( ) ;
203
+
204
+ lint_file
205
+ . write_all (
206
+ format ! (
207
+ "use rustc::lint::{{LintArray, LintPass, {type}}};
208
+ use rustc::declare_lint_pass;
209
+ use rustc_session::declare_tool_lint;
210
+
211
+ declare_clippy_lint! {{
212
+ pub {name_upper},
213
+ {category},
214
+ \" default lint description\"
215
+ }}
216
+
217
+ declare_lint_pass!({name_camel} => [{name_upper}]);
218
+
219
+ impl {type} for {name_camel} {{}}
220
+ " ,
221
+ type =pass_type,
222
+ name_upper=lint_name. to_uppercase( ) ,
223
+ name_camel=camel_case_name,
224
+ category=category
225
+ )
226
+ . as_bytes ( ) ,
227
+ )
228
+ . unwrap ( ) ;
229
+ update_lints ( & UpdateMode :: Change ) ;
230
+ } ,
231
+ Err ( e) => eprintln ! ( "Unable to create lint: {}" , e) ,
232
+ }
233
+ }
234
+
82
235
fn print_lints ( ) {
83
236
let lint_list = gather_all ( ) ;
84
237
let usable_lints: Vec < Lint > = Lint :: usable_lints ( lint_list) . collect ( ) ;
@@ -232,3 +385,14 @@ fn update_lints(update_mode: &UpdateMode) {
232
385
std:: process:: exit ( 1 ) ;
233
386
}
234
387
}
388
+
389
+ #[ test]
390
+ fn test_camel_case ( ) {
391
+ let s = "a_lint" ;
392
+ let s2 = to_camel_case ( s) ;
393
+ assert_eq ! ( s2, "ALint" ) ;
394
+
395
+ let name = "a_really_long_new_lint" ;
396
+ let name2 = to_camel_case ( name) ;
397
+ assert_eq ! ( name2, "AReallyLongNewLint" )
398
+ }
0 commit comments