1
1
use crate :: clippy_project_root;
2
- use std:: fs:: { File , OpenOptions } ;
3
- use std:: io;
2
+ use std:: fs:: { self , OpenOptions } ;
4
3
use 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
+ }
7
17
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`.
9
31
///
10
32
/// # Errors
11
33
///
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`!" ) ;
55
53
} ,
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 ( ) )
61
69
}
62
70
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) ) ?;
65
76
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) ) ?;
71
80
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 ( ( ) )
77
82
}
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)
83
95
}
96
+ }
84
97
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
+ }
87
106
88
- Ok ( ( test_file , lint_file ) )
107
+ inner ( path . as_ref ( ) , contents . as_ref ( ) ) . context ( format ! ( "writing to file: {}" , path . as_ref ( ) . display ( ) ) )
89
108
}
90
109
91
110
fn to_camel_case ( name : & str ) -> String {
@@ -112,6 +131,20 @@ fn main() {{
112
131
)
113
132
}
114
133
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
+
115
148
fn get_lint_file_contents (
116
149
pass_type : & str ,
117
150
pass_lifetimes : & str ,
0 commit comments