@@ -6,20 +6,33 @@ use std::ffi::OsStr;
66use std:: fs:: read_to_string;
77use std:: path:: Path ;
88
9+ use regex:: Regex ;
10+
911// A few of those error codes can't be tested but all the others can and *should* be tested!
1012const 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" ,
1515] ;
1616
1717// Some error codes don't have any tests apparently...
1818const IGNORE_EXPLANATION_CHECK : & [ & str ] = & [ "E0570" , "E0601" , "E0602" , "E0729" ] ;
1919
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+
2033fn check_error_code_explanation (
2134 f : & str ,
22- error_codes : & mut HashMap < String , bool > ,
35+ error_codes : & mut HashMap < String , ErrorCodeStatus > ,
2336 err_code : String ,
2437) -> bool {
2538 let mut invalid_compile_fail_format = false ;
@@ -30,15 +43,15 @@ fn check_error_code_explanation(
3043 if s. starts_with ( "```" ) {
3144 if s. contains ( "compile_fail" ) && s. contains ( 'E' ) {
3245 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 ) ;
3447 found_error_code = true ;
3548 }
3649 } else if s. contains ( "compile-fail" ) {
3750 invalid_compile_fail_format = true ;
3851 }
3952 } else if s. starts_with ( "#### Note: this error code is no longer emitted by the compiler" ) {
4053 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 ) ;
4255 found_error_code = true ;
4356 }
4457 }
@@ -77,7 +90,7 @@ macro_rules! some_or_continue {
7790
7891fn extract_error_codes (
7992 f : & str ,
80- error_codes : & mut HashMap < String , bool > ,
93+ error_codes : & mut HashMap < String , ErrorCodeStatus > ,
8194 path : & Path ,
8295 errors : & mut Vec < String > ,
8396) {
@@ -90,15 +103,16 @@ fn extract_error_codes(
90103 . split_once ( ':' )
91104 . expect (
92105 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",
94108 s,
95- ) . as_str ( )
109+ )
110+ . as_str ( ) ,
96111 )
97112 . 0
98113 . 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+
102116 // Now we extract the tests from the markdown file!
103117 let md_file_name = match s. split_once ( "include_str!(\" " ) {
104118 None => continue ,
@@ -145,15 +159,15 @@ fn extract_error_codes(
145159 . to_string ( ) ;
146160 if !error_codes. contains_key ( & err_code) {
147161 // this check should *never* fail!
148- error_codes. insert ( err_code, false ) ;
162+ error_codes. insert ( err_code, ErrorCodeStatus :: default ( ) ) ;
149163 }
150164 } else if s == ";" {
151165 reached_no_explanation = true ;
152166 }
153167 }
154168}
155169
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 > ) {
157171 for line in f. lines ( ) {
158172 let s = line. trim ( ) ;
159173 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
164178 Some ( ( _, err_code) ) => err_code,
165179 } ,
166180 } ;
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+ }
169199 }
170200 }
171201}
@@ -174,8 +204,17 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
174204 let mut errors = Vec :: new ( ) ;
175205 let mut found_explanations = 0 ;
176206 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+
177216 println ! ( "Checking which error codes lack tests..." ) ;
178- let mut error_codes : HashMap < String , bool > = HashMap :: new ( ) ;
217+
179218 for path in paths {
180219 super :: walk ( path, & mut |path| super :: filter_dirs ( path) , & mut |entry, contents| {
181220 let file_name = entry. file_name ( ) ;
@@ -185,6 +224,11 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
185224 } else if entry. path ( ) . extension ( ) == Some ( OsStr :: new ( "stderr" ) ) {
186225 extract_error_codes_from_tests ( contents, & mut error_codes) ;
187226 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+ }
188232 }
189233 } ) ;
190234 }
@@ -199,15 +243,43 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
199243 if errors. is_empty ( ) {
200244 println ! ( "Found {} error codes" , error_codes. len( ) ) ;
201245
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 ( ) ) {
204248 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 ( ) ) {
206250 errors. push ( format ! (
207251 "Error code {} has a UI test, it shouldn't be listed into EXEMPTED_FROM_TEST!" ,
208252 err_code
209253 ) ) ;
210254 }
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+ }
211283 }
212284 }
213285 errors. sort ( ) ;
0 commit comments