@@ -6,20 +6,33 @@ use std::ffi::OsStr;
6
6
use std:: fs:: read_to_string;
7
7
use std:: path:: Path ;
8
8
9
+ use regex:: Regex ;
10
+
9
11
// A few of those error codes can't be tested but all the others can and *should* be tested!
10
12
const EXEMPTED_FROM_TEST : & [ & str ] = & [
11
- "E0227" , "E0279" , "E0280" , "E0313" , "E0314" , "E0315" , "E0377" , "E0461" , "E0462" , "E0464" ,
12
- "E0465" , "E0473" , "E0474" , "E0475" , "E0476" , "E0479" , "E0480" , "E0481" , "E0482" , "E0483" ,
13
- "E0484" , "E0485" , "E0486" , "E0487" , "E0488" , "E0489" , "E0514" , "E0519" , "E0523" , "E0553" ,
14
- "E0554" , "E0570" , "E0629" , "E0630" , "E0640" , "E0717" , "E0729" ,
13
+ "E0227" , "E0279" , "E0280" , "E0313" , "E0377" , "E0461" , "E0462" , "E0464" , "E0465" , "E0476" ,
14
+ "E0482" , "E0514" , "E0519" , "E0523" , "E0554" , "E0570" , "E0640" , "E0717" , "E0729" ,
15
15
] ;
16
16
17
17
// Some error codes don't have any tests apparently...
18
18
const IGNORE_EXPLANATION_CHECK : & [ & str ] = & [ "E0570" , "E0601" , "E0602" , "E0729" ] ;
19
19
20
+ // If the file path contains any of these, we don't want to try to extract error codes from it.
21
+ //
22
+ // We need to declare each path in the windows version (with backslash).
23
+ const PATHS_TO_IGNORE_FOR_EXTRACTION : & [ & str ] =
24
+ & [ "src/test/" , "src\\ test\\ " , "src/doc/" , "src\\ doc\\ " , "src/tools/" , "src\\ tools\\ " ] ;
25
+
26
+ #[ derive( Default , Debug ) ]
27
+ struct ErrorCodeStatus {
28
+ has_test : bool ,
29
+ has_explanation : bool ,
30
+ is_used : bool ,
31
+ }
32
+
20
33
fn check_error_code_explanation (
21
34
f : & str ,
22
- error_codes : & mut HashMap < String , bool > ,
35
+ error_codes : & mut HashMap < String , ErrorCodeStatus > ,
23
36
err_code : String ,
24
37
) -> bool {
25
38
let mut invalid_compile_fail_format = false ;
@@ -30,15 +43,15 @@ fn check_error_code_explanation(
30
43
if s. starts_with ( "```" ) {
31
44
if s. contains ( "compile_fail" ) && s. contains ( 'E' ) {
32
45
if !found_error_code {
33
- error_codes. insert ( err_code. clone ( ) , true ) ;
46
+ error_codes. get_mut ( & err_code) . map ( |x| x . has_test = true ) ;
34
47
found_error_code = true ;
35
48
}
36
49
} else if s. contains ( "compile-fail" ) {
37
50
invalid_compile_fail_format = true ;
38
51
}
39
52
} else if s. starts_with ( "#### Note: this error code is no longer emitted by the compiler" ) {
40
53
if !found_error_code {
41
- error_codes. get_mut ( & err_code) . map ( |x| * x = true ) ;
54
+ error_codes. get_mut ( & err_code) . map ( |x| x . has_test = true ) ;
42
55
found_error_code = true ;
43
56
}
44
57
}
@@ -77,7 +90,7 @@ macro_rules! some_or_continue {
77
90
78
91
fn extract_error_codes (
79
92
f : & str ,
80
- error_codes : & mut HashMap < String , bool > ,
93
+ error_codes : & mut HashMap < String , ErrorCodeStatus > ,
81
94
path : & Path ,
82
95
errors : & mut Vec < String > ,
83
96
) {
@@ -90,15 +103,16 @@ fn extract_error_codes(
90
103
. split_once ( ':' )
91
104
. expect (
92
105
format ! (
93
- "Expected a line with the format `E0xxx: include_str!(\" ..\" )`, but got {} without a `:` delimiter" ,
106
+ "Expected a line with the format `E0xxx: include_str!(\" ..\" )`, but got {} \
107
+ without a `:` delimiter",
94
108
s,
95
- ) . as_str ( )
109
+ )
110
+ . as_str ( ) ,
96
111
)
97
112
. 0
98
113
. to_owned ( ) ;
99
- if !error_codes. contains_key ( & err_code) {
100
- error_codes. insert ( err_code. clone ( ) , false ) ;
101
- }
114
+ error_codes. entry ( err_code. clone ( ) ) . or_default ( ) . has_explanation = true ;
115
+
102
116
// Now we extract the tests from the markdown file!
103
117
let md_file_name = match s. split_once ( "include_str!(\" " ) {
104
118
None => continue ,
@@ -145,15 +159,15 @@ fn extract_error_codes(
145
159
. to_string ( ) ;
146
160
if !error_codes. contains_key ( & err_code) {
147
161
// this check should *never* fail!
148
- error_codes. insert ( err_code, false ) ;
162
+ error_codes. insert ( err_code, ErrorCodeStatus :: default ( ) ) ;
149
163
}
150
164
} else if s == ";" {
151
165
reached_no_explanation = true ;
152
166
}
153
167
}
154
168
}
155
169
156
- fn extract_error_codes_from_tests ( f : & str , error_codes : & mut HashMap < String , bool > ) {
170
+ fn extract_error_codes_from_tests ( f : & str , error_codes : & mut HashMap < String , ErrorCodeStatus > ) {
157
171
for line in f. lines ( ) {
158
172
let s = line. trim ( ) ;
159
173
if s. starts_with ( "error[E" ) || s. starts_with ( "warning[E" ) {
@@ -164,8 +178,24 @@ fn extract_error_codes_from_tests(f: &str, error_codes: &mut HashMap<String, boo
164
178
Some ( ( _, err_code) ) => err_code,
165
179
} ,
166
180
} ;
167
- let nb = error_codes. entry ( err_code. to_owned ( ) ) . or_insert ( false ) ;
168
- * nb = true ;
181
+ error_codes. entry ( err_code. to_owned ( ) ) . or_default ( ) . has_test = true ;
182
+ }
183
+ }
184
+ }
185
+
186
+ fn extract_error_codes_from_source (
187
+ f : & str ,
188
+ error_codes : & mut HashMap < String , ErrorCodeStatus > ,
189
+ regex : & Regex ,
190
+ ) {
191
+ for line in f. lines ( ) {
192
+ if line. trim_start ( ) . starts_with ( "//" ) {
193
+ continue ;
194
+ }
195
+ for cap in regex. captures_iter ( line) {
196
+ if let Some ( error_code) = cap. get ( 1 ) {
197
+ error_codes. entry ( error_code. as_str ( ) . to_owned ( ) ) . or_default ( ) . is_used = true ;
198
+ }
169
199
}
170
200
}
171
201
}
@@ -174,8 +204,17 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
174
204
let mut errors = Vec :: new ( ) ;
175
205
let mut found_explanations = 0 ;
176
206
let mut found_tests = 0 ;
207
+ let mut error_codes: HashMap < String , ErrorCodeStatus > = HashMap :: new ( ) ;
208
+ // We want error codes which match the following cases:
209
+ //
210
+ // * foo(a, E0111, a)
211
+ // * foo(a, E0111)
212
+ // * foo(E0111, a)
213
+ // * #[error = "E0111"]
214
+ let regex = Regex :: new ( r#"[(,"\s](E\d{4})[,)"]"# ) . unwrap ( ) ;
215
+
177
216
println ! ( "Checking which error codes lack tests..." ) ;
178
- let mut error_codes : HashMap < String , bool > = HashMap :: new ( ) ;
217
+
179
218
for path in paths {
180
219
super :: walk ( path, & mut |path| super :: filter_dirs ( path) , & mut |entry, contents| {
181
220
let file_name = entry. file_name ( ) ;
@@ -185,6 +224,11 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
185
224
} else if entry. path ( ) . extension ( ) == Some ( OsStr :: new ( "stderr" ) ) {
186
225
extract_error_codes_from_tests ( contents, & mut error_codes) ;
187
226
found_tests += 1 ;
227
+ } else if entry. path ( ) . extension ( ) == Some ( OsStr :: new ( "rs" ) ) {
228
+ let path = entry. path ( ) . to_string_lossy ( ) ;
229
+ if PATHS_TO_IGNORE_FOR_EXTRACTION . iter ( ) . all ( |c| !path. contains ( c) ) {
230
+ extract_error_codes_from_source ( contents, & mut error_codes, & regex) ;
231
+ }
188
232
}
189
233
} ) ;
190
234
}
@@ -199,15 +243,43 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
199
243
if errors. is_empty ( ) {
200
244
println ! ( "Found {} error codes" , error_codes. len( ) ) ;
201
245
202
- for ( err_code, nb ) in & error_codes {
203
- if !* nb && !EXEMPTED_FROM_TEST . contains ( & err_code. as_str ( ) ) {
246
+ for ( err_code, error_status ) in & error_codes {
247
+ if !error_status . has_test && !EXEMPTED_FROM_TEST . contains ( & err_code. as_str ( ) ) {
204
248
errors. push ( format ! ( "Error code {} needs to have at least one UI test!" , err_code) ) ;
205
- } else if * nb && EXEMPTED_FROM_TEST . contains ( & err_code. as_str ( ) ) {
249
+ } else if error_status . has_test && EXEMPTED_FROM_TEST . contains ( & err_code. as_str ( ) ) {
206
250
errors. push ( format ! (
207
251
"Error code {} has a UI test, it shouldn't be listed into EXEMPTED_FROM_TEST!" ,
208
252
err_code
209
253
) ) ;
210
254
}
255
+ if !error_status. is_used && !error_status. has_explanation {
256
+ errors. push ( format ! (
257
+ "Error code {} isn't used and doesn't have an error explanation, it should be \
258
+ commented in error_codes.rs file",
259
+ err_code
260
+ ) ) ;
261
+ }
262
+ }
263
+ }
264
+ if errors. is_empty ( ) {
265
+ // Checking if local constants need to be cleaned.
266
+ for err_code in EXEMPTED_FROM_TEST {
267
+ match error_codes. get ( err_code. to_owned ( ) ) {
268
+ Some ( status) => {
269
+ if status. has_test {
270
+ errors. push ( format ! (
271
+ "{} error code has a test and therefore should be \
272
+ removed from the `EXEMPTED_FROM_TEST` constant",
273
+ err_code
274
+ ) ) ;
275
+ }
276
+ }
277
+ None => errors. push ( format ! (
278
+ "{} error code isn't used anymore and therefore should be removed \
279
+ from `EXEMPTED_FROM_TEST` constant",
280
+ err_code
281
+ ) ) ,
282
+ }
211
283
}
212
284
}
213
285
errors. sort ( ) ;
0 commit comments