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,249 @@ 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+
355+ let lint_file_path = ty_dir. join ( format ! ( "{}.rs" , lint. name) ) ;
356+ let mut lint_file_contents = String :: new ( ) ;
357+
358+ if enable_msrv {
359+ let _ = writedoc ! (
360+ lint_file_contents,
361+ r#"
362+ use clippy_utils::{{meets_msrv, msrvs}};
363+ use rustc_lint::{{{context_import}, LintContext}};
364+ use rustc_semver::RustcVersion;
365+
366+ use super::{name_upper};
367+
368+ // TODO: Adjust the parameters as necessary
369+ pub(super) fn check(cx: &{context_import}, msrv: Option<RustcVersion>) {{
370+ if !meets_msrv(msrv, todo!("Add a new entry in `clippy_utils/src/msrvs`")) {{
371+ return;
372+ }}
373+ todo!();
374+ }}
375+ "# ,
376+ context_import = context_import,
377+ name_upper = name_upper,
378+ ) ;
379+ } else {
380+ let _ = writedoc ! (
381+ lint_file_contents,
382+ r#"
383+ use rustc_lint::{{{context_import}, LintContext}};
384+
385+ use super::{name_upper};
386+
387+ // TODO: Adjust the parameters as necessary
388+ pub(super) fn check(cx: &{context_import}) {{
389+ todo!();
390+ }}
391+ "# ,
392+ context_import = context_import,
393+ name_upper = name_upper,
394+ ) ;
395+ }
396+
397+ write_file ( lint_file_path, lint_file_contents) ?;
398+
399+ Ok ( ( ) )
400+ }
401+
402+ #[ allow( clippy:: too_many_lines) ]
403+ fn setup_mod_file ( path : & Path , lint : & LintData < ' _ > ) -> io:: Result < & ' static str > {
404+ use super :: update_lints:: { match_tokens, LintDeclSearchResult } ;
405+ use rustc_lexer:: TokenKind ;
406+
407+ let lint_name_upper = lint. name . to_uppercase ( ) ;
408+
409+ let mut file_contents = fs:: read_to_string ( path) ?;
410+ assert ! (
411+ !file_contents. contains( & lint_name_upper) ,
412+ "Lint `{}` already defined in `{}`" ,
413+ lint. name,
414+ path. display( )
415+ ) ;
416+
417+ let mut offset = 0usize ;
418+ let mut last_decl_curly_offset = None ;
419+ let mut lint_context = None ;
420+
421+ let mut iter = rustc_lexer:: tokenize ( & file_contents) . map ( |t| {
422+ let range = offset..offset + t. len ;
423+ offset = range. end ;
424+
425+ LintDeclSearchResult {
426+ token_kind : t. kind ,
427+ content : & file_contents[ range. clone ( ) ] ,
428+ range,
429+ }
430+ } ) ;
431+
432+ // Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl
433+ while let Some ( LintDeclSearchResult { content, .. } ) = iter. find ( |result| result. token_kind == TokenKind :: Ident ) {
434+ let mut iter = iter
435+ . by_ref ( )
436+ . filter ( |t| !matches ! ( t. token_kind, TokenKind :: Whitespace | TokenKind :: LineComment { .. } ) ) ;
437+
438+ match content {
439+ "declare_clippy_lint" => {
440+ // matches `!{`
441+ match_tokens ! ( iter, Bang OpenBrace ) ;
442+ if let Some ( LintDeclSearchResult { range, .. } ) =
443+ iter. find ( |result| result. token_kind == TokenKind :: CloseBrace )
444+ {
445+ last_decl_curly_offset = Some ( range. end ) ;
446+ }
447+ } ,
448+ "impl" => {
449+ let mut token = iter. next ( ) ;
450+ match token {
451+ // matches <'foo>
452+ Some ( LintDeclSearchResult {
453+ token_kind : TokenKind :: Lt ,
454+ ..
455+ } ) => {
456+ match_tokens ! ( iter, Lifetime { .. } Gt ) ;
457+ token = iter. next ( ) ;
458+ } ,
459+ None => break ,
460+ _ => { } ,
461+ }
462+
463+ if let Some ( LintDeclSearchResult {
464+ token_kind : TokenKind :: Ident ,
465+ content,
466+ ..
467+ } ) = token
468+ {
469+ // Get the appropriate lint context struct
470+ lint_context = match content {
471+ "LateLintPass" => Some ( "LateContext" ) ,
472+ "EarlyLintPass" => Some ( "EarlyContext" ) ,
473+ _ => continue ,
474+ } ;
475+ }
476+ } ,
477+ _ => { } ,
478+ }
479+ }
480+
481+ drop ( iter) ;
482+
483+ let last_decl_curly_offset =
484+ last_decl_curly_offset. unwrap_or_else ( || panic ! ( "No lint declarations found in `{}`" , path. display( ) ) ) ;
485+ let lint_context =
486+ lint_context. unwrap_or_else ( || panic ! ( "No lint pass implementation found in `{}`" , path. display( ) ) ) ;
487+
488+ // Add the lint declaration to `mod.rs`
489+ file_contents. replace_range (
490+ // Remove the trailing newline, which should always be present
491+ last_decl_curly_offset..=last_decl_curly_offset,
492+ & format ! ( "\n \n {}" , get_lint_declaration( & lint_name_upper, lint. category) ) ,
493+ ) ;
494+
495+ // Add the lint to `impl_lint_pass`/`declare_lint_pass`
496+ let impl_lint_pass_start = file_contents. find ( "impl_lint_pass!" ) . unwrap_or_else ( || {
497+ file_contents
498+ . find ( "declare_lint_pass!" )
499+ . unwrap_or_else ( || panic ! ( "failed to find `impl_lint_pass`/`declare_lint_pass`" ) )
500+ } ) ;
501+
502+ let mut arr_start = file_contents[ impl_lint_pass_start..] . find ( '[' ) . unwrap_or_else ( || {
503+ panic ! ( "malformed `impl_lint_pass`/`declare_lint_pass`" ) ;
504+ } ) ;
505+
506+ arr_start += impl_lint_pass_start;
507+
508+ let mut arr_end = file_contents[ arr_start..]
509+ . find ( ']' )
510+ . expect ( "failed to find `impl_lint_pass` terminator" ) ;
511+
512+ arr_end += arr_start;
513+
514+ let mut arr_content = file_contents[ arr_start + 1 ..arr_end] . to_string ( ) ;
515+ arr_content. retain ( |c| !c. is_whitespace ( ) ) ;
516+
517+ let mut new_arr_content = String :: new ( ) ;
518+ for ident in arr_content
519+ . split ( ',' )
520+ . chain ( std:: iter:: once ( & * lint_name_upper) )
521+ . filter ( |s| !s. is_empty ( ) )
522+ {
523+ let _ = write ! ( new_arr_content, "\n {}," , ident) ;
524+ }
525+ new_arr_content. push ( '\n' ) ;
526+
527+ file_contents. replace_range ( arr_start + 1 ..arr_end, & new_arr_content) ;
528+
529+ // Just add the mod declaration at the top, it'll be fixed by rustfmt
530+ file_contents. insert_str ( 0 , & format ! ( "mod {};\n " , & lint. name) ) ;
531+
532+ let mut file = OpenOptions :: new ( )
533+ . write ( true )
534+ . truncate ( true )
535+ . open ( path)
536+ . context ( format ! ( "trying to open: `{}`" , path. display( ) ) ) ?;
537+ file. write_all ( file_contents. as_bytes ( ) )
538+ . context ( format ! ( "writing to file: `{}`" , path. display( ) ) ) ?;
539+
540+ Ok ( lint_context)
541+ }
542+
315543#[ test]
316544fn test_camel_case ( ) {
317545 let s = "a_lint" ;
0 commit comments