11use crate :: clippy_project_root;
2- use std:: fs:: { File , OpenOptions } ;
3- use std:: io;
2+ use std:: fs:: { self , OpenOptions } ;
43use std:: io:: prelude:: * ;
5- use std:: io:: ErrorKind ;
6- use std:: path:: Path ;
4+ use std:: io:: { self , ErrorKind } ;
5+ use std:: path:: { Path , PathBuf } ;
6+
7+ struct LintData < ' a > {
8+ pass : & ' a str ,
9+ name : & ' a str ,
10+ category : & ' a str ,
11+ project_root : PathBuf ,
12+ }
13+
14+ trait Context {
15+ fn context < C : AsRef < str > > ( self , text : C ) -> Self ;
16+ }
717
8- /// Creates files required to implement and test a new lint and runs `update_lints`.
18+ impl < T > Context for io:: Result < T > {
19+ fn context < C : AsRef < str > > ( self , text : C ) -> Self {
20+ match self {
21+ Ok ( t) => Ok ( t) ,
22+ Err ( e) => {
23+ let message = format ! ( "{}: {}" , text. as_ref( ) , e) ;
24+ Err ( io:: Error :: new ( ErrorKind :: Other , message) )
25+ } ,
26+ }
27+ }
28+ }
29+
30+ /// Creates the files required to implement and test a new lint and runs `update_lints`.
931///
1032/// # Errors
1133///
12- /// This function errors, if the files couldn't be created
13- pub fn create ( pass : Option < & str > , lint_name : Option < & str > , category : Option < & str > ) -> Result < ( ) , io:: Error > {
14- let pass = pass. expect ( "`pass` argument is validated by clap" ) ;
15- let lint_name = lint_name. expect ( "`name` argument is validated by clap" ) ;
16- let category = category. expect ( "`category` argument is validated by clap" ) ;
17-
18- match open_files ( lint_name) {
19- Ok ( ( mut test_file, mut lint_file) ) => {
20- let ( pass_type, pass_lifetimes, pass_import, context_import) = match pass {
21- "early" => ( "EarlyLintPass" , "" , "use rustc_ast::ast::*;" , "EarlyContext" ) ,
22- "late" => ( "LateLintPass" , "<'_, '_>" , "use rustc_hir::*;" , "LateContext" ) ,
23- _ => {
24- unreachable ! ( "`pass_type` should only ever be `early` or `late`!" ) ;
25- } ,
26- } ;
27-
28- let camel_case_name = to_camel_case ( lint_name) ;
29-
30- if let Err ( e) = test_file. write_all ( get_test_file_contents ( lint_name) . as_bytes ( ) ) {
31- return Err ( io:: Error :: new (
32- ErrorKind :: Other ,
33- format ! ( "Could not write to test file: {}" , e) ,
34- ) ) ;
35- } ;
36-
37- if let Err ( e) = lint_file. write_all (
38- get_lint_file_contents (
39- pass_type,
40- pass_lifetimes,
41- lint_name,
42- & camel_case_name,
43- category,
44- pass_import,
45- context_import,
46- )
47- . as_bytes ( ) ,
48- ) {
49- return Err ( io:: Error :: new (
50- ErrorKind :: Other ,
51- format ! ( "Could not write to lint file: {}" , e) ,
52- ) ) ;
53- }
54- Ok ( ( ) )
34+ /// This function errors out if the files couldn't be created or written to.
35+ pub fn create ( pass : Option < & str > , lint_name : Option < & str > , category : Option < & str > ) -> io:: Result < ( ) > {
36+ let lint = LintData {
37+ pass : pass. expect ( "`pass` argument is validated by clap" ) ,
38+ name : lint_name. expect ( "`name` argument is validated by clap" ) ,
39+ category : category. expect ( "`category` argument is validated by clap" ) ,
40+ project_root : clippy_project_root ( ) ,
41+ } ;
42+
43+ create_lint ( & lint) . context ( "Unable to create lint implementation" ) ?;
44+ create_test ( & lint) . context ( "Unable to create a test for the new lint" )
45+ }
46+
47+ fn create_lint ( lint : & LintData ) -> io:: Result < ( ) > {
48+ let ( pass_type, pass_lifetimes, pass_import, context_import) = match lint. pass {
49+ "early" => ( "EarlyLintPass" , "" , "use rustc_ast::ast::*;" , "EarlyContext" ) ,
50+ "late" => ( "LateLintPass" , "<'_, '_>" , "use rustc_hir::*;" , "LateContext" ) ,
51+ _ => {
52+ unreachable ! ( "`pass_type` should only ever be `early` or `late`!" ) ;
5553 } ,
56- Err ( e) => Err ( io:: Error :: new (
57- ErrorKind :: Other ,
58- format ! ( "Unable to create lint: {}" , e) ,
59- ) ) ,
60- }
54+ } ;
55+
56+ let camel_case_name = to_camel_case ( lint. name ) ;
57+ let lint_contents = get_lint_file_contents (
58+ pass_type,
59+ pass_lifetimes,
60+ lint. name ,
61+ & camel_case_name,
62+ lint. category ,
63+ pass_import,
64+ context_import,
65+ ) ;
66+
67+ let lint_path = format ! ( "clippy_lints/src/{}.rs" , lint. name) ;
68+ write_file ( lint. project_root . join ( & lint_path) , lint_contents. as_bytes ( ) )
6169}
6270
63- fn open_files ( lint_name : & str ) -> Result < ( File , File ) , io:: Error > {
64- let project_root = clippy_project_root ( ) ;
71+ fn create_test ( lint : & LintData ) -> io:: Result < ( ) > {
72+ fn create_project_layout < P : Into < PathBuf > > ( lint_name : & str , location : P , case : & str , hint : & str ) -> io:: Result < ( ) > {
73+ let mut path = location. into ( ) . join ( case) ;
74+ fs:: create_dir ( & path) ?;
75+ write_file ( path. join ( "Cargo.toml" ) , get_manifest_contents ( lint_name, hint) ) ?;
6576
66- let test_file_path = project_root. join ( "tests" ) . join ( "ui" ) . join ( format ! ( "{}.rs" , lint_name) ) ;
67- let lint_file_path = project_root
68- . join ( "clippy_lints" )
69- . join ( "src" )
70- . join ( format ! ( "{}.rs" , lint_name) ) ;
77+ path. push ( "src" ) ;
78+ fs:: create_dir ( & path) ?;
79+ write_file ( path. join ( "main.rs" ) , get_test_file_contents ( lint_name) ) ?;
7180
72- if Path :: new ( & test_file_path) . exists ( ) {
73- return Err ( io:: Error :: new (
74- ErrorKind :: AlreadyExists ,
75- format ! ( "test file {:?} already exists" , test_file_path) ,
76- ) ) ;
81+ Ok ( ( ) )
7782 }
78- if Path :: new ( & lint_file_path) . exists ( ) {
79- return Err ( io:: Error :: new (
80- ErrorKind :: AlreadyExists ,
81- format ! ( "lint file {:?} already exists" , lint_file_path) ,
82- ) ) ;
83+
84+ if lint. category == "cargo" {
85+ let relative_test_dir = format ! ( "tests/ui-cargo/{}" , lint. name) ;
86+ let test_dir = lint. project_root . join ( relative_test_dir) ;
87+ fs:: create_dir ( & test_dir) ?;
88+
89+ create_project_layout ( lint. name , & test_dir, "fail" , "Content that triggers the lint goes here" ) ?;
90+ create_project_layout ( lint. name , & test_dir, "pass" , "This file should not trigger the lint" )
91+ } else {
92+ let test_path = format ! ( "tests/ui/{}.rs" , lint. name) ;
93+ let test_contents = get_test_file_contents ( lint. name ) ;
94+ write_file ( lint. project_root . join ( test_path) , test_contents)
8395 }
96+ }
8497
85- let test_file = OpenOptions :: new ( ) . write ( true ) . create_new ( true ) . open ( test_file_path) ?;
86- let lint_file = OpenOptions :: new ( ) . write ( true ) . create_new ( true ) . open ( lint_file_path) ?;
98+ fn write_file < P : AsRef < Path > , C : AsRef < [ u8 ] > > ( path : P , contents : C ) -> io:: Result < ( ) > {
99+ fn inner ( path : & Path , contents : & [ u8 ] ) -> io:: Result < ( ) > {
100+ OpenOptions :: new ( )
101+ . write ( true )
102+ . create_new ( true )
103+ . open ( path) ?
104+ . write_all ( contents)
105+ }
87106
88- Ok ( ( test_file , lint_file ) )
107+ inner ( path . as_ref ( ) , contents . as_ref ( ) ) . context ( format ! ( "writing to file: {}" , path . as_ref ( ) . display ( ) ) )
89108}
90109
91110fn to_camel_case ( name : & str ) -> String {
@@ -112,6 +131,20 @@ fn main() {{
112131 )
113132}
114133
134+ fn get_manifest_contents ( lint_name : & str , hint : & str ) -> String {
135+ format ! (
136+ r#"
137+ # {}
138+
139+ [package]
140+ name = "{}"
141+ version = "0.1.0"
142+ publish = false
143+ "# ,
144+ hint, lint_name
145+ )
146+ }
147+
115148fn get_lint_file_contents (
116149 pass_type : & str ,
117150 pass_lifetimes : & str ,
0 commit comments