11//! Checks that all error codes have at least one test to prevent having error
22//! codes that are silently not thrown by the compiler anymore.
33
4+ use std:: collections:: hash_map:: Entry ;
45use std:: collections:: HashMap ;
56use std:: ffi:: OsStr ;
67use std:: fs:: read_to_string;
78use std:: path:: Path ;
89
10+ use regex:: Regex ;
11+
912// A few of those error codes can't be tested but all the others can and *should* be tested!
1013const EXEMPTED_FROM_TEST : & [ & str ] = & [
1114 "E0227" , "E0279" , "E0280" , "E0313" , "E0314" , "E0315" , "E0377" , "E0461" , "E0462" , "E0464" ,
@@ -17,9 +20,16 @@ const EXEMPTED_FROM_TEST: &[&str] = &[
1720// Some error codes don't have any tests apparently...
1821const IGNORE_EXPLANATION_CHECK : & [ & str ] = & [ "E0570" , "E0601" , "E0602" , "E0729" ] ;
1922
23+ #[ derive( Default , Debug ) ]
24+ struct ErrorCodeStatus {
25+ has_test : bool ,
26+ has_explanation : bool ,
27+ is_used : bool ,
28+ }
29+
2030fn check_error_code_explanation (
2131 f : & str ,
22- error_codes : & mut HashMap < String , bool > ,
32+ error_codes : & mut HashMap < String , ErrorCodeStatus > ,
2333 err_code : String ,
2434) -> bool {
2535 let mut invalid_compile_fail_format = false ;
@@ -30,15 +40,15 @@ fn check_error_code_explanation(
3040 if s. starts_with ( "```" ) {
3141 if s. contains ( "compile_fail" ) && s. contains ( 'E' ) {
3242 if !found_error_code {
33- error_codes. insert ( err_code. clone ( ) , true ) ;
43+ error_codes. get_mut ( & err_code) . map ( |x| x . has_test = true ) ;
3444 found_error_code = true ;
3545 }
3646 } else if s. contains ( "compile-fail" ) {
3747 invalid_compile_fail_format = true ;
3848 }
3949 } else if s. starts_with ( "#### Note: this error code is no longer emitted by the compiler" ) {
4050 if !found_error_code {
41- error_codes. get_mut ( & err_code) . map ( |x| * x = true ) ;
51+ error_codes. get_mut ( & err_code) . map ( |x| x . has_test = true ) ;
4252 found_error_code = true ;
4353 }
4454 }
@@ -77,7 +87,7 @@ macro_rules! some_or_continue {
7787
7888fn extract_error_codes (
7989 f : & str ,
80- error_codes : & mut HashMap < String , bool > ,
90+ error_codes : & mut HashMap < String , ErrorCodeStatus > ,
8191 path : & Path ,
8292 errors : & mut Vec < String > ,
8393) {
@@ -90,14 +100,26 @@ fn extract_error_codes(
90100 . split_once ( ':' )
91101 . expect (
92102 format ! (
93- "Expected a line with the format `E0xxx: include_str!(\" ..\" )`, but got {} without a `:` delimiter" ,
103+ "Expected a line with the format `E0xxx: include_str!(\" ..\" )`, but got {} \
104+ without a `:` delimiter",
94105 s,
95- ) . as_str ( )
106+ )
107+ . as_str ( ) ,
96108 )
97109 . 0
98110 . to_owned ( ) ;
99- if !error_codes. contains_key ( & err_code) {
100- error_codes. insert ( err_code. clone ( ) , false ) ;
111+ match error_codes. entry ( err_code. clone ( ) ) {
112+ Entry :: Occupied ( mut e) => {
113+ let mut entry = e. get_mut ( ) ;
114+ entry. has_explanation = true
115+ }
116+ Entry :: Vacant ( e) => {
117+ e. insert ( ErrorCodeStatus {
118+ has_test : false ,
119+ is_used : false ,
120+ has_explanation : true ,
121+ } ) ;
122+ }
101123 }
102124 // Now we extract the tests from the markdown file!
103125 let md_file_name = match s. split_once ( "include_str!(\" " ) {
@@ -145,15 +167,15 @@ fn extract_error_codes(
145167 . to_string ( ) ;
146168 if !error_codes. contains_key ( & err_code) {
147169 // this check should *never* fail!
148- error_codes. insert ( err_code, false ) ;
170+ error_codes. insert ( err_code, ErrorCodeStatus :: default ( ) ) ;
149171 }
150172 } else if s == ";" {
151173 reached_no_explanation = true ;
152174 }
153175 }
154176}
155177
156- fn extract_error_codes_from_tests ( f : & str , error_codes : & mut HashMap < String , bool > ) {
178+ fn extract_error_codes_from_tests ( f : & str , error_codes : & mut HashMap < String , ErrorCodeStatus > ) {
157179 for line in f. lines ( ) {
158180 let s = line. trim ( ) ;
159181 if s. starts_with ( "error[E" ) || s. starts_with ( "warning[E" ) {
@@ -164,8 +186,48 @@ fn extract_error_codes_from_tests(f: &str, error_codes: &mut HashMap<String, boo
164186 Some ( ( _, err_code) ) => err_code,
165187 } ,
166188 } ;
167- let nb = error_codes. entry ( err_code. to_owned ( ) ) . or_insert ( false ) ;
168- * nb = true ;
189+ match error_codes. entry ( err_code. to_owned ( ) ) {
190+ Entry :: Occupied ( mut e) => {
191+ let mut entry = e. get_mut ( ) ;
192+ entry. has_test = true
193+ }
194+ Entry :: Vacant ( e) => {
195+ e. insert ( ErrorCodeStatus {
196+ has_test : true ,
197+ is_used : false ,
198+ has_explanation : false ,
199+ } ) ;
200+ }
201+ }
202+ }
203+ }
204+ }
205+
206+ fn extract_error_codes_from_source (
207+ f : & str ,
208+ error_codes : & mut HashMap < String , ErrorCodeStatus > ,
209+ regex : & Regex ,
210+ ) {
211+ for line in f. lines ( ) {
212+ if line. trim_start ( ) . starts_with ( "//" ) {
213+ continue ;
214+ }
215+ for cap in regex. captures_iter ( line) {
216+ if let Some ( error_code) = cap. get ( 1 ) {
217+ match error_codes. entry ( error_code. as_str ( ) . to_owned ( ) ) {
218+ Entry :: Occupied ( mut e) => {
219+ let mut entry = e. get_mut ( ) ;
220+ entry. is_used = true
221+ }
222+ Entry :: Vacant ( e) => {
223+ e. insert ( ErrorCodeStatus {
224+ has_test : false ,
225+ is_used : true ,
226+ has_explanation : false ,
227+ } ) ;
228+ }
229+ }
230+ }
169231 }
170232 }
171233}
@@ -174,8 +236,17 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
174236 let mut errors = Vec :: new ( ) ;
175237 let mut found_explanations = 0 ;
176238 let mut found_tests = 0 ;
239+ let mut error_codes: HashMap < String , ErrorCodeStatus > = HashMap :: new ( ) ;
240+ // We want error codes which match the following cases:
241+ //
242+ // * foo(a, E0111, a)
243+ // * foo(a, E0111)
244+ // * foo(E0111, a)
245+ // * #[error = "E0111"]
246+ let regex = Regex :: new ( r#"[(,"\s](E\d{4})[,)"]"# ) . unwrap ( ) ;
247+
177248 println ! ( "Checking which error codes lack tests..." ) ;
178- let mut error_codes : HashMap < String , bool > = HashMap :: new ( ) ;
249+
179250 for path in paths {
180251 super :: walk ( path, & mut |path| super :: filter_dirs ( path) , & mut |entry, contents| {
181252 let file_name = entry. file_name ( ) ;
@@ -185,6 +256,11 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
185256 } else if entry. path ( ) . extension ( ) == Some ( OsStr :: new ( "stderr" ) ) {
186257 extract_error_codes_from_tests ( contents, & mut error_codes) ;
187258 found_tests += 1 ;
259+ } else if entry. path ( ) . extension ( ) == Some ( OsStr :: new ( "rs" ) ) {
260+ let path = entry. path ( ) . to_string_lossy ( ) ;
261+ if [ "src/test/" , "src/doc/" , "src/tools/" ] . iter ( ) . all ( |c| !path. contains ( c) ) {
262+ extract_error_codes_from_source ( contents, & mut error_codes, & regex) ;
263+ }
188264 }
189265 } ) ;
190266 }
@@ -199,15 +275,22 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
199275 if errors. is_empty ( ) {
200276 println ! ( "Found {} error codes" , error_codes. len( ) ) ;
201277
202- for ( err_code, nb ) in & error_codes {
203- if !* nb && !EXEMPTED_FROM_TEST . contains ( & err_code. as_str ( ) ) {
278+ for ( err_code, error_status ) in & error_codes {
279+ if !error_status . has_test && !EXEMPTED_FROM_TEST . contains ( & err_code. as_str ( ) ) {
204280 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 ( ) ) {
281+ } else if error_status . has_test && EXEMPTED_FROM_TEST . contains ( & err_code. as_str ( ) ) {
206282 errors. push ( format ! (
207283 "Error code {} has a UI test, it shouldn't be listed into EXEMPTED_FROM_TEST!" ,
208284 err_code
209285 ) ) ;
210286 }
287+ if !error_status. is_used && !error_status. has_explanation {
288+ errors. push ( format ! (
289+ "Error code {} isn't used and doesn't have an error explanation, it should be \
290+ commented in error_codes.rs file",
291+ err_code
292+ ) ) ;
293+ }
211294 }
212295 }
213296 errors. sort ( ) ;
0 commit comments