11use crate :: clippy_project_root;
2- use indoc:: indoc;
2+ use indoc:: { indoc, writedoc } ;
33use std:: fmt:: Write as _;
44use std:: fs:: { self , OpenOptions } ;
55use std:: io:: prelude:: * ;
@@ -10,6 +10,7 @@ struct LintData<'a> {
1010 pass : & ' a str ,
1111 name : & ' a str ,
1212 category : & ' a str ,
13+ ty : Option < & ' a str > ,
1314 project_root : PathBuf ,
1415}
1516
@@ -38,25 +39,35 @@ pub fn create(
3839 pass : Option < & String > ,
3940 lint_name : Option < & String > ,
4041 category : Option < & String > ,
42+ ty : Option < & String > ,
4143 msrv : bool ,
4244) -> io:: Result < ( ) > {
4345 let lint = LintData {
44- pass : pass. expect ( "`pass` argument is validated by clap" ) ,
46+ pass : pass. map_or ( "" , String :: as_str ) ,
4547 name : lint_name. expect ( "`name` argument is validated by clap" ) ,
4648 category : category. expect ( "`category` argument is validated by clap" ) ,
49+ ty : ty. map ( String :: as_str) ,
4750 project_root : clippy_project_root ( ) ,
4851 } ;
4952
5053 create_lint ( & lint, msrv) . context ( "Unable to create lint implementation" ) ?;
5154 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 ( ( ) )
5361}
5462
5563fn 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+ }
6071}
6172
6273fn create_test ( lint : & LintData < ' _ > ) -> io:: Result < ( ) > {
@@ -204,7 +215,6 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
204215 } ,
205216 } ;
206217
207- let version = get_stabilization_version ( ) ;
208218 let lint_name = lint. name ;
209219 let category = lint. category ;
210220 let name_camel = to_camel_case ( lint. name ) ;
@@ -238,32 +248,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
238248 )
239249 } ) ;
240250
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) ) ;
267252
268253 result. push_str ( & if enable_msrv {
269254 format ! (
@@ -312,6 +297,247 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
312297 result
313298}
314299
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+
315541#[ test]
316542fn test_camel_case ( ) {
317543 let s = "a_lint" ;
0 commit comments