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.
4 use std::collections::HashMap;
6 use std::fs::read_to_string;
9 // A few of those error codes can't be tested but all the others can and *should* be tested!
10 const WHITELIST: &[&str] = &[
11 "E0183", "E0227", "E0279", "E0280", "E0311", "E0313", "E0314", "E0315", "E0377", "E0456",
12 "E0461", "E0462", "E0464", "E0465", "E0472", "E0473", "E0474", "E0475", "E0476", "E0479",
13 "E0480", "E0481", "E0482", "E0483", "E0484", "E0485", "E0486", "E0487", "E0488", "E0489",
14 "E0514", "E0519", "E0523", "E0553", "E0554", "E0570", "E0629", "E0630", "E0640", "E0717",
18 // Some error codes don't have any tests apparently...
19 const IGNORE_EXPLANATION_CHECK: &[&str] =
20 &["E0570", "E0601", "E0602", "E0639", "E0729", "E0749", "E0750", "E0751"];
22 fn check_error_code_explanation(
24 error_codes: &mut HashMap<String, bool>,
27 let mut invalid_compile_fail_format = false;
28 let mut found_error_code = false;
30 for line in f.lines() {
32 if s.starts_with("```") {
33 if s.contains("compile_fail") && s.contains('E') {
34 if !found_error_code {
35 error_codes.insert(err_code.clone(), true);
36 found_error_code = true;
38 } else if s.contains("compile-fail") {
39 invalid_compile_fail_format = true;
41 } else if s.starts_with("#### Note: this error code is no longer emitted by the compiler") {
42 if !found_error_code {
43 error_codes.get_mut(&err_code).map(|x| *x = true);
44 found_error_code = true;
48 invalid_compile_fail_format
51 fn check_if_error_code_is_test_in_explanation(f: &str, err_code: &String) -> bool {
52 let mut can_be_ignored = false;
54 for line in f.lines() {
56 if s.starts_with("#### Note: this error code is no longer emitted by the compiler") {
59 if s.starts_with("```") {
60 if s.contains("compile_fail") && s.contains(err_code) {
62 } else if s.contains("(") {
63 // It's very likely that we can't actually make it fail compilation...
64 can_be_ignored = true;
71 macro_rules! some_or_continue {
80 fn extract_error_codes(
82 error_codes: &mut HashMap<String, bool>,
84 errors: &mut Vec<String>,
86 let mut reached_no_explanation = false;
88 for line in f.lines() {
90 if !reached_no_explanation && s.starts_with('E') && s.contains("include_str!(\"") {
91 if let Some(err_code) = s.splitn(2, ':').next() {
92 let err_code = err_code.to_owned();
93 if !error_codes.contains_key(&err_code) {
94 error_codes.insert(err_code.clone(), false);
96 // Now we extract the tests from the markdown file!
97 let md = some_or_continue!(s.splitn(2, "include_str!(\"").nth(1));
98 let md_file_name = some_or_continue!(md.splitn(2, "\")").next());
99 let path = some_or_continue!(path.parent())
102 .expect("failed to canonicalize error explanation file path");
103 match read_to_string(&path) {
105 if !IGNORE_EXPLANATION_CHECK.contains(&err_code.as_str())
106 && !check_if_error_code_is_test_in_explanation(&content, &err_code)
109 "`{}` doesn't use its own error code in compile_fail example",
113 if check_error_code_explanation(&content, error_codes, err_code) {
115 "`{}` uses invalid tag `compile-fail` instead of `compile_fail`",
121 eprintln!("Couldn't read `{}`: {}", path.display(), e);
125 } else if reached_no_explanation && s.starts_with('E') {
126 if let Some(err_code) = s.splitn(2, ',').next() {
127 let err_code = err_code.to_owned();
128 if !error_codes.contains_key(&err_code) {
129 // this check should *never* fail!
130 error_codes.insert(err_code, false);
134 reached_no_explanation = true;
139 fn extract_error_codes_from_tests(f: &str, error_codes: &mut HashMap<String, bool>) {
140 for line in f.lines() {
142 if s.starts_with("error[E") || s.starts_with("warning[E") {
143 if let Some(err_code) = s.splitn(2, ']').next() {
144 if let Some(err_code) = err_code.splitn(2, '[').nth(1) {
145 let nb = error_codes.entry(err_code.to_owned()).or_insert(false);
153 pub fn check(path: &Path, bad: &mut bool) {
154 let mut errors = Vec::new();
155 println!("Checking which error codes lack tests...");
156 let mut error_codes: HashMap<String, bool> = HashMap::new();
157 super::walk(path, &mut |path| super::filter_dirs(path), &mut |entry, contents| {
158 let file_name = entry.file_name();
159 if file_name == "error_codes.rs" {
160 extract_error_codes(contents, &mut error_codes, entry.path(), &mut errors);
161 } else if entry.path().extension() == Some(OsStr::new("stderr")) {
162 extract_error_codes_from_tests(contents, &mut error_codes);
165 if errors.is_empty() {
166 println!("Found {} error codes", error_codes.len());
168 for (err_code, nb) in &error_codes {
169 if !*nb && !WHITELIST.contains(&err_code.as_str()) {
170 errors.push(format!("Error code {} needs to have at least one UI test!", err_code));
176 eprintln!("{}", err);
178 println!("Found {} error codes with no tests", errors.len());
179 if !errors.is_empty() {