]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/error_codes_check.rs
Remove E0749 from untested error codes
[rust.git] / src / tools / tidy / src / error_codes_check.rs
1 //! Checks that all error codes have at least one test to prevent having error
2 //! codes that are silently not thrown by the compiler anymore.
3
4 use std::collections::HashMap;
5 use std::ffi::OsStr;
6 use std::fs::read_to_string;
7 use std::path::Path;
8
9 // A few of those error codes can't be tested but all the others can and *should* be tested!
10 const EXEMPTED_FROM_TEST: &[&str] = &[
11     "E0183", "E0227", "E0279", "E0280", "E0311", "E0313", "E0314", "E0315", "E0377", "E0461",
12     "E0462", "E0464", "E0465", "E0472", "E0473", "E0474", "E0475", "E0476", "E0479", "E0480",
13     "E0481", "E0482", "E0483", "E0484", "E0485", "E0486", "E0487", "E0488", "E0489", "E0514",
14     "E0519", "E0523", "E0553", "E0554", "E0570", "E0629", "E0630", "E0640", "E0717", "E0727",
15     "E0729",
16 ];
17
18 // Some error codes don't have any tests apparently...
19 const IGNORE_EXPLANATION_CHECK: &[&str] = &["E0570", "E0601", "E0602", "E0639", "E0729"];
20
21 fn check_error_code_explanation(
22     f: &str,
23     error_codes: &mut HashMap<String, bool>,
24     err_code: String,
25 ) -> bool {
26     let mut invalid_compile_fail_format = false;
27     let mut found_error_code = false;
28
29     for line in f.lines() {
30         let s = line.trim();
31         if s.starts_with("```") {
32             if s.contains("compile_fail") && s.contains('E') {
33                 if !found_error_code {
34                     error_codes.insert(err_code.clone(), true);
35                     found_error_code = true;
36                 }
37             } else if s.contains("compile-fail") {
38                 invalid_compile_fail_format = true;
39             }
40         } else if s.starts_with("#### Note: this error code is no longer emitted by the compiler") {
41             if !found_error_code {
42                 error_codes.get_mut(&err_code).map(|x| *x = true);
43                 found_error_code = true;
44             }
45         }
46     }
47     invalid_compile_fail_format
48 }
49
50 fn check_if_error_code_is_test_in_explanation(f: &str, err_code: &String) -> bool {
51     let mut can_be_ignored = false;
52
53     for line in f.lines() {
54         let s = line.trim();
55         if s.starts_with("#### Note: this error code is no longer emitted by the compiler") {
56             return true;
57         }
58         if s.starts_with("```") {
59             if s.contains("compile_fail") && s.contains(err_code) {
60                 return true;
61             } else if s.contains("(") {
62                 // It's very likely that we can't actually make it fail compilation...
63                 can_be_ignored = true;
64             }
65         }
66     }
67     can_be_ignored
68 }
69
70 macro_rules! some_or_continue {
71     ($e:expr) => {
72         match $e {
73             Some(e) => e,
74             None => continue,
75         }
76     };
77 }
78
79 fn extract_error_codes(
80     f: &str,
81     error_codes: &mut HashMap<String, bool>,
82     path: &Path,
83     errors: &mut Vec<String>,
84 ) {
85     let mut reached_no_explanation = false;
86
87     for line in f.lines() {
88         let s = line.trim();
89         if !reached_no_explanation && s.starts_with('E') && s.contains("include_str!(\"") {
90             if let Some(err_code) = s.splitn(2, ':').next() {
91                 let err_code = err_code.to_owned();
92                 if !error_codes.contains_key(&err_code) {
93                     error_codes.insert(err_code.clone(), false);
94                 }
95                 // Now we extract the tests from the markdown file!
96                 let md = some_or_continue!(s.splitn(2, "include_str!(\"").nth(1));
97                 let md_file_name = some_or_continue!(md.splitn(2, "\")").next());
98                 let path = some_or_continue!(path.parent())
99                     .join(md_file_name)
100                     .canonicalize()
101                     .expect("failed to canonicalize error explanation file path");
102                 match read_to_string(&path) {
103                     Ok(content) => {
104                         if !IGNORE_EXPLANATION_CHECK.contains(&err_code.as_str())
105                             && !check_if_error_code_is_test_in_explanation(&content, &err_code)
106                         {
107                             errors.push(format!(
108                                 "`{}` doesn't use its own error code in compile_fail example",
109                                 path.display(),
110                             ));
111                         }
112                         if check_error_code_explanation(&content, error_codes, err_code) {
113                             errors.push(format!(
114                                 "`{}` uses invalid tag `compile-fail` instead of `compile_fail`",
115                                 path.display(),
116                             ));
117                         }
118                     }
119                     Err(e) => {
120                         eprintln!("Couldn't read `{}`: {}", path.display(), e);
121                     }
122                 }
123             }
124         } else if reached_no_explanation && s.starts_with('E') {
125             if let Some(err_code) = s.splitn(2, ',').next() {
126                 let err_code = err_code.to_owned();
127                 if !error_codes.contains_key(&err_code) {
128                     // this check should *never* fail!
129                     error_codes.insert(err_code, false);
130                 }
131             }
132         } else if s == ";" {
133             reached_no_explanation = true;
134         }
135     }
136 }
137
138 fn extract_error_codes_from_tests(f: &str, error_codes: &mut HashMap<String, bool>) {
139     for line in f.lines() {
140         let s = line.trim();
141         if s.starts_with("error[E") || s.starts_with("warning[E") {
142             if let Some(err_code) = s.splitn(2, ']').next() {
143                 if let Some(err_code) = err_code.splitn(2, '[').nth(1) {
144                     let nb = error_codes.entry(err_code.to_owned()).or_insert(false);
145                     *nb = true;
146                 }
147             }
148         }
149     }
150 }
151
152 pub fn check(path: &Path, bad: &mut bool) {
153     let mut errors = Vec::new();
154     println!("Checking which error codes lack tests...");
155     let mut error_codes: HashMap<String, bool> = HashMap::new();
156     super::walk(path, &mut |path| super::filter_dirs(path), &mut |entry, contents| {
157         let file_name = entry.file_name();
158         if file_name == "error_codes.rs" {
159             extract_error_codes(contents, &mut error_codes, entry.path(), &mut errors);
160         } else if entry.path().extension() == Some(OsStr::new("stderr")) {
161             extract_error_codes_from_tests(contents, &mut error_codes);
162         }
163     });
164     if errors.is_empty() {
165         println!("Found {} error codes", error_codes.len());
166
167         for (err_code, nb) in &error_codes {
168             if !*nb && !EXEMPTED_FROM_TEST.contains(&err_code.as_str()) {
169                 errors.push(format!("Error code {} needs to have at least one UI test!", err_code));
170             }
171         }
172     }
173     errors.sort();
174     for err in &errors {
175         eprintln!("{}", err);
176     }
177     println!("Found {} error codes with no tests", errors.len());
178     if !errors.is_empty() {
179         *bad = true;
180     }
181     println!("Done!");
182 }