1
1
use crate :: clippy_project_root;
2
- use indoc:: indoc;
2
+ use indoc:: { indoc, writedoc } ;
3
3
use std:: fmt:: Write as _;
4
4
use std:: fs:: { self , OpenOptions } ;
5
5
use std:: io:: prelude:: * ;
@@ -10,6 +10,7 @@ struct LintData<'a> {
10
10
pass : & ' a str ,
11
11
name : & ' a str ,
12
12
category : & ' a str ,
13
+ ty : Option < & ' a str > ,
13
14
project_root : PathBuf ,
14
15
}
15
16
@@ -38,25 +39,35 @@ pub fn create(
38
39
pass : Option < & String > ,
39
40
lint_name : Option < & String > ,
40
41
category : Option < & String > ,
42
+ ty : Option < & String > ,
41
43
msrv : bool ,
42
44
) -> io:: Result < ( ) > {
43
45
let lint = LintData {
44
- pass : pass. expect ( "`pass` argument is validated by clap" ) ,
46
+ pass : pass. map_or ( "" , String :: as_str ) ,
45
47
name : lint_name. expect ( "`name` argument is validated by clap" ) ,
46
48
category : category. expect ( "`category` argument is validated by clap" ) ,
49
+ ty : ty. map ( String :: as_str) ,
47
50
project_root : clippy_project_root ( ) ,
48
51
} ;
49
52
50
53
create_lint ( & lint, msrv) . context ( "Unable to create lint implementation" ) ?;
51
54
create_test ( & lint) . context ( "Unable to create a test for the new lint" ) ?;
52
- add_lint ( & lint, msrv) . context ( "Unable to add lint to clippy_lints/src/lib.rs" )
55
+
56
+ if lint. ty . is_none ( ) {
57
+ add_lint ( & lint, msrv) . context ( "Unable to add lint to clippy_lints/src/lib.rs" ) ?;
58
+ }
59
+
60
+ Ok ( ( ) )
53
61
}
54
62
55
63
fn create_lint ( lint : & LintData < ' _ > , enable_msrv : bool ) -> io:: Result < ( ) > {
56
- let lint_contents = get_lint_file_contents ( lint, enable_msrv) ;
57
-
58
- let lint_path = format ! ( "clippy_lints/src/{}.rs" , lint. name) ;
59
- write_file ( lint. project_root . join ( & lint_path) , lint_contents. as_bytes ( ) )
64
+ if let Some ( ty) = lint. ty {
65
+ generate_from_ty ( lint, enable_msrv, ty)
66
+ } else {
67
+ let lint_contents = get_lint_file_contents ( lint, enable_msrv) ;
68
+ let lint_path = format ! ( "clippy_lints/src/{}.rs" , lint. name) ;
69
+ write_file ( lint. project_root . join ( & lint_path) , lint_contents. as_bytes ( ) )
70
+ }
60
71
}
61
72
62
73
fn create_test ( lint : & LintData < ' _ > ) -> io:: Result < ( ) > {
@@ -204,7 +215,6 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
204
215
} ,
205
216
} ;
206
217
207
- let version = get_stabilization_version ( ) ;
208
218
let lint_name = lint. name ;
209
219
let category = lint. category ;
210
220
let name_camel = to_camel_case ( lint. name ) ;
@@ -238,32 +248,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
238
248
)
239
249
} ) ;
240
250
241
- let _ = write ! (
242
- result,
243
- indoc! { r#"
244
- declare_clippy_lint! {{
245
- /// ### What it does
246
- ///
247
- /// ### Why is this bad?
248
- ///
249
- /// ### Example
250
- /// ```rust
251
- /// // example code where clippy issues a warning
252
- /// ```
253
- /// Use instead:
254
- /// ```rust
255
- /// // example code which does not raise clippy warning
256
- /// ```
257
- #[clippy::version = "{version}"]
258
- pub {name_upper},
259
- {category},
260
- "default lint description"
261
- }}
262
- "# } ,
263
- version = version,
264
- name_upper = name_upper,
265
- category = category,
266
- ) ;
251
+ let _ = write ! ( result, "{}" , get_lint_declaration( & name_upper, category) ) ;
267
252
268
253
result. push_str ( & if enable_msrv {
269
254
format ! (
@@ -312,6 +297,247 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
312
297
result
313
298
}
314
299
300
+ fn get_lint_declaration ( name_upper : & str , category : & str ) -> String {
301
+ format ! (
302
+ indoc! { r#"
303
+ declare_clippy_lint! {{
304
+ /// ### What it does
305
+ ///
306
+ /// ### Why is this bad?
307
+ ///
308
+ /// ### Example
309
+ /// ```rust
310
+ /// // example code where clippy issues a warning
311
+ /// ```
312
+ /// Use instead:
313
+ /// ```rust
314
+ /// // example code which does not raise clippy warning
315
+ /// ```
316
+ #[clippy::version = "{version}"]
317
+ pub {name_upper},
318
+ {category},
319
+ "default lint description"
320
+ }}
321
+ "# } ,
322
+ version = get_stabilization_version( ) ,
323
+ name_upper = name_upper,
324
+ category = category,
325
+ )
326
+ }
327
+
328
+ fn generate_from_ty ( lint : & LintData < ' _ > , enable_msrv : bool , ty : & str ) -> io:: Result < ( ) > {
329
+ if ty == "cargo" {
330
+ assert_eq ! (
331
+ lint. category, "cargo" ,
332
+ "Lints of type `cargo` must have the `cargo` category"
333
+ ) ;
334
+ }
335
+
336
+ let ty_dir = lint. project_root . join ( format ! ( "clippy_lints/src/{}" , ty) ) ;
337
+ assert ! (
338
+ ty_dir. exists( ) && ty_dir. is_dir( ) ,
339
+ "Directory `{}` does not exist!" ,
340
+ ty_dir. display( )
341
+ ) ;
342
+
343
+ let lint_file_path = ty_dir. join ( format ! ( "{}.rs" , lint. name) ) ;
344
+ assert ! (
345
+ !lint_file_path. exists( ) ,
346
+ "File `{}` already exists" ,
347
+ lint_file_path. display( )
348
+ ) ;
349
+
350
+ let mod_file_path = ty_dir. join ( "mod.rs" ) ;
351
+ let context_import = setup_mod_file ( & mod_file_path, lint) ?;
352
+
353
+ let name_upper = lint. name . to_uppercase ( ) ;
354
+ let mut lint_file_contents = String :: new ( ) ;
355
+
356
+ if enable_msrv {
357
+ let _ = writedoc ! (
358
+ lint_file_contents,
359
+ r#"
360
+ use clippy_utils::{{meets_msrv, msrvs}};
361
+ use rustc_lint::{{{context_import}, LintContext}};
362
+ use rustc_semver::RustcVersion;
363
+
364
+ use super::{name_upper};
365
+
366
+ // TODO: Adjust the parameters as necessary
367
+ pub(super) fn check(cx: &{context_import}, msrv: Option<RustcVersion>) {{
368
+ if !meets_msrv(msrv, todo!("Add a new entry in `clippy_utils/src/msrvs`")) {{
369
+ return;
370
+ }}
371
+ todo!();
372
+ }}
373
+ "# ,
374
+ context_import = context_import,
375
+ name_upper = name_upper,
376
+ ) ;
377
+ } else {
378
+ let _ = writedoc ! (
379
+ lint_file_contents,
380
+ r#"
381
+ use rustc_lint::{{{context_import}, LintContext}};
382
+
383
+ use super::{name_upper};
384
+
385
+ // TODO: Adjust the parameters as necessary
386
+ pub(super) fn check(cx: &{context_import}) {{
387
+ todo!();
388
+ }}
389
+ "# ,
390
+ context_import = context_import,
391
+ name_upper = name_upper,
392
+ ) ;
393
+ }
394
+
395
+ write_file ( lint_file_path, lint_file_contents) ?;
396
+
397
+ Ok ( ( ) )
398
+ }
399
+
400
+ #[ allow( clippy:: too_many_lines) ]
401
+ fn setup_mod_file ( path : & Path , lint : & LintData < ' _ > ) -> io:: Result < & ' static str > {
402
+ use super :: update_lints:: { match_tokens, LintDeclSearchResult } ;
403
+ use rustc_lexer:: TokenKind ;
404
+
405
+ let lint_name_upper = lint. name . to_uppercase ( ) ;
406
+
407
+ let mut file_contents = fs:: read_to_string ( path) ?;
408
+ assert ! (
409
+ !file_contents. contains( & lint_name_upper) ,
410
+ "Lint `{}` already defined in `{}`" ,
411
+ lint. name,
412
+ path. display( )
413
+ ) ;
414
+
415
+ let mut offset = 0usize ;
416
+ let mut last_decl_curly_offset = None ;
417
+ let mut lint_context = None ;
418
+
419
+ let mut iter = rustc_lexer:: tokenize ( & file_contents) . map ( |t| {
420
+ let range = offset..offset + t. len ;
421
+ offset = range. end ;
422
+
423
+ LintDeclSearchResult {
424
+ token_kind : t. kind ,
425
+ content : & file_contents[ range. clone ( ) ] ,
426
+ range,
427
+ }
428
+ } ) ;
429
+
430
+ // Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl
431
+ while let Some ( LintDeclSearchResult { content, .. } ) = iter. find ( |result| result. token_kind == TokenKind :: Ident ) {
432
+ let mut iter = iter
433
+ . by_ref ( )
434
+ . filter ( |t| !matches ! ( t. token_kind, TokenKind :: Whitespace | TokenKind :: LineComment { .. } ) ) ;
435
+
436
+ match content {
437
+ "declare_clippy_lint" => {
438
+ // matches `!{`
439
+ match_tokens ! ( iter, Bang OpenBrace ) ;
440
+ if let Some ( LintDeclSearchResult { range, .. } ) =
441
+ iter. find ( |result| result. token_kind == TokenKind :: CloseBrace )
442
+ {
443
+ last_decl_curly_offset = Some ( range. end ) ;
444
+ }
445
+ } ,
446
+ "impl" => {
447
+ let mut token = iter. next ( ) ;
448
+ match token {
449
+ // matches <'foo>
450
+ Some ( LintDeclSearchResult {
451
+ token_kind : TokenKind :: Lt ,
452
+ ..
453
+ } ) => {
454
+ match_tokens ! ( iter, Lifetime { .. } Gt ) ;
455
+ token = iter. next ( ) ;
456
+ } ,
457
+ None => break ,
458
+ _ => { } ,
459
+ }
460
+
461
+ if let Some ( LintDeclSearchResult {
462
+ token_kind : TokenKind :: Ident ,
463
+ content,
464
+ ..
465
+ } ) = token
466
+ {
467
+ // Get the appropriate lint context struct
468
+ lint_context = match content {
469
+ "LateLintPass" => Some ( "LateContext" ) ,
470
+ "EarlyLintPass" => Some ( "EarlyContext" ) ,
471
+ _ => continue ,
472
+ } ;
473
+ }
474
+ } ,
475
+ _ => { } ,
476
+ }
477
+ }
478
+
479
+ drop ( iter) ;
480
+
481
+ let last_decl_curly_offset =
482
+ last_decl_curly_offset. unwrap_or_else ( || panic ! ( "No lint declarations found in `{}`" , path. display( ) ) ) ;
483
+ let lint_context =
484
+ lint_context. unwrap_or_else ( || panic ! ( "No lint pass implementation found in `{}`" , path. display( ) ) ) ;
485
+
486
+ // Add the lint declaration to `mod.rs`
487
+ file_contents. replace_range (
488
+ // Remove the trailing newline, which should always be present
489
+ last_decl_curly_offset..=last_decl_curly_offset,
490
+ & format ! ( "\n \n {}" , get_lint_declaration( & lint_name_upper, lint. category) ) ,
491
+ ) ;
492
+
493
+ // Add the lint to `impl_lint_pass`/`declare_lint_pass`
494
+ let impl_lint_pass_start = file_contents. find ( "impl_lint_pass!" ) . unwrap_or_else ( || {
495
+ file_contents
496
+ . find ( "declare_lint_pass!" )
497
+ . unwrap_or_else ( || panic ! ( "failed to find `impl_lint_pass`/`declare_lint_pass`" ) )
498
+ } ) ;
499
+
500
+ let mut arr_start = file_contents[ impl_lint_pass_start..] . find ( '[' ) . unwrap_or_else ( || {
501
+ panic ! ( "malformed `impl_lint_pass`/`declare_lint_pass`" ) ;
502
+ } ) ;
503
+
504
+ arr_start += impl_lint_pass_start;
505
+
506
+ let mut arr_end = file_contents[ arr_start..]
507
+ . find ( ']' )
508
+ . expect ( "failed to find `impl_lint_pass` terminator" ) ;
509
+
510
+ arr_end += arr_start;
511
+
512
+ let mut arr_content = file_contents[ arr_start + 1 ..arr_end] . to_string ( ) ;
513
+ arr_content. retain ( |c| !c. is_whitespace ( ) ) ;
514
+
515
+ let mut new_arr_content = String :: new ( ) ;
516
+ for ident in arr_content
517
+ . split ( ',' )
518
+ . chain ( std:: iter:: once ( & * lint_name_upper) )
519
+ . filter ( |s| !s. is_empty ( ) )
520
+ {
521
+ let _ = write ! ( new_arr_content, "\n {}," , ident) ;
522
+ }
523
+ new_arr_content. push ( '\n' ) ;
524
+
525
+ file_contents. replace_range ( arr_start + 1 ..arr_end, & new_arr_content) ;
526
+
527
+ // Just add the mod declaration at the top, it'll be fixed by rustfmt
528
+ file_contents. insert_str ( 0 , & format ! ( "mod {};\n " , & lint. name) ) ;
529
+
530
+ let mut file = OpenOptions :: new ( )
531
+ . write ( true )
532
+ . truncate ( true )
533
+ . open ( path)
534
+ . context ( format ! ( "trying to open: `{}`" , path. display( ) ) ) ?;
535
+ file. write_all ( file_contents. as_bytes ( ) )
536
+ . context ( format ! ( "writing to file: `{}`" , path. display( ) ) ) ?;
537
+
538
+ Ok ( lint_context)
539
+ }
540
+
315
541
#[ test]
316
542
fn test_camel_case ( ) {
317
543
let s = "a_lint" ;
0 commit comments