]>
Commit | Line | Data |
---|---|---|
e74abb32 XL |
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; | |
60c5eb7d | 6 | use std::fs::read_to_string; |
e74abb32 XL |
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! | |
f035d41b | 10 | const EXEMPTED_FROM_TEST: &[&str] = &[ |
3dfed10e XL |
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", | |
e74abb32 XL |
16 | ]; |
17 | ||
f9f354fc | 18 | // Some error codes don't have any tests apparently... |
3dfed10e | 19 | const IGNORE_EXPLANATION_CHECK: &[&str] = &["E0570", "E0601", "E0602", "E0639", "E0729"]; |
f9f354fc | 20 | |
60c5eb7d XL |
21 | fn check_error_code_explanation( |
22 | f: &str, | |
23 | error_codes: &mut HashMap<String, bool>, | |
24 | err_code: String, | |
f9f354fc XL |
25 | ) -> bool { |
26 | let mut invalid_compile_fail_format = false; | |
27 | let mut found_error_code = false; | |
28 | ||
60c5eb7d XL |
29 | for line in f.lines() { |
30 | let s = line.trim(); | |
f9f354fc XL |
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 | } | |
60c5eb7d | 40 | } else if s.starts_with("#### Note: this error code is no longer emitted by the compiler") { |
f9f354fc XL |
41 | if !found_error_code { |
42 | error_codes.get_mut(&err_code).map(|x| *x = true); | |
43 | found_error_code = true; | |
44 | } | |
60c5eb7d XL |
45 | } |
46 | } | |
f9f354fc XL |
47 | invalid_compile_fail_format |
48 | } | |
49 | ||
3dfed10e | 50 | fn check_if_error_code_is_test_in_explanation(f: &str, err_code: &str) -> bool { |
f9f354fc XL |
51 | for line in f.lines() { |
52 | let s = line.trim(); | |
53 | if s.starts_with("#### Note: this error code is no longer emitted by the compiler") { | |
54 | return true; | |
55 | } | |
56 | if s.starts_with("```") { | |
57 | if s.contains("compile_fail") && s.contains(err_code) { | |
58 | return true; | |
3dfed10e | 59 | } else if s.contains('(') { |
f9f354fc | 60 | // It's very likely that we can't actually make it fail compilation... |
3dfed10e | 61 | return true; |
f9f354fc XL |
62 | } |
63 | } | |
64 | } | |
3dfed10e | 65 | false |
60c5eb7d XL |
66 | } |
67 | ||
68 | macro_rules! some_or_continue { | |
dfeec247 | 69 | ($e:expr) => { |
60c5eb7d XL |
70 | match $e { |
71 | Some(e) => e, | |
72 | None => continue, | |
73 | } | |
dfeec247 | 74 | }; |
60c5eb7d XL |
75 | } |
76 | ||
f9f354fc XL |
77 | fn extract_error_codes( |
78 | f: &str, | |
79 | error_codes: &mut HashMap<String, bool>, | |
80 | path: &Path, | |
81 | errors: &mut Vec<String>, | |
82 | ) { | |
e74abb32 | 83 | let mut reached_no_explanation = false; |
e74abb32 XL |
84 | |
85 | for line in f.lines() { | |
86 | let s = line.trim(); | |
60c5eb7d | 87 | if !reached_no_explanation && s.starts_with('E') && s.contains("include_str!(\"") { |
e74abb32 XL |
88 | if let Some(err_code) = s.splitn(2, ':').next() { |
89 | let err_code = err_code.to_owned(); | |
e74abb32 | 90 | if !error_codes.contains_key(&err_code) { |
60c5eb7d | 91 | error_codes.insert(err_code.clone(), false); |
e74abb32 | 92 | } |
60c5eb7d | 93 | // Now we extract the tests from the markdown file! |
dfeec247 | 94 | let md = some_or_continue!(s.splitn(2, "include_str!(\"").nth(1)); |
60c5eb7d | 95 | let md_file_name = some_or_continue!(md.splitn(2, "\")").next()); |
f9f354fc XL |
96 | let path = some_or_continue!(path.parent()) |
97 | .join(md_file_name) | |
98 | .canonicalize() | |
99 | .expect("failed to canonicalize error explanation file path"); | |
60c5eb7d XL |
100 | match read_to_string(&path) { |
101 | Ok(content) => { | |
f9f354fc XL |
102 | if !IGNORE_EXPLANATION_CHECK.contains(&err_code.as_str()) |
103 | && !check_if_error_code_is_test_in_explanation(&content, &err_code) | |
104 | { | |
105 | errors.push(format!( | |
106 | "`{}` doesn't use its own error code in compile_fail example", | |
107 | path.display(), | |
108 | )); | |
109 | } | |
110 | if check_error_code_explanation(&content, error_codes, err_code) { | |
111 | errors.push(format!( | |
112 | "`{}` uses invalid tag `compile-fail` instead of `compile_fail`", | |
113 | path.display(), | |
114 | )); | |
115 | } | |
60c5eb7d XL |
116 | } |
117 | Err(e) => { | |
118 | eprintln!("Couldn't read `{}`: {}", path.display(), e); | |
119 | } | |
e74abb32 XL |
120 | } |
121 | } | |
e74abb32 XL |
122 | } else if reached_no_explanation && s.starts_with('E') { |
123 | if let Some(err_code) = s.splitn(2, ',').next() { | |
124 | let err_code = err_code.to_owned(); | |
dfeec247 XL |
125 | if !error_codes.contains_key(&err_code) { |
126 | // this check should *never* fail! | |
e74abb32 XL |
127 | error_codes.insert(err_code, false); |
128 | } | |
129 | } | |
60c5eb7d XL |
130 | } else if s == ";" { |
131 | reached_no_explanation = true; | |
e74abb32 XL |
132 | } |
133 | } | |
134 | } | |
135 | ||
136 | fn extract_error_codes_from_tests(f: &str, error_codes: &mut HashMap<String, bool>) { | |
137 | for line in f.lines() { | |
138 | let s = line.trim(); | |
139 | if s.starts_with("error[E") || s.starts_with("warning[E") { | |
140 | if let Some(err_code) = s.splitn(2, ']').next() { | |
dfeec247 | 141 | if let Some(err_code) = err_code.splitn(2, '[').nth(1) { |
e74abb32 XL |
142 | let nb = error_codes.entry(err_code.to_owned()).or_insert(false); |
143 | *nb = true; | |
144 | } | |
145 | } | |
146 | } | |
147 | } | |
148 | } | |
149 | ||
150 | pub fn check(path: &Path, bad: &mut bool) { | |
f9f354fc | 151 | let mut errors = Vec::new(); |
e74abb32 XL |
152 | println!("Checking which error codes lack tests..."); |
153 | let mut error_codes: HashMap<String, bool> = HashMap::new(); | |
dfeec247 | 154 | super::walk(path, &mut |path| super::filter_dirs(path), &mut |entry, contents| { |
e74abb32 XL |
155 | let file_name = entry.file_name(); |
156 | if file_name == "error_codes.rs" { | |
f9f354fc | 157 | extract_error_codes(contents, &mut error_codes, entry.path(), &mut errors); |
e74abb32 XL |
158 | } else if entry.path().extension() == Some(OsStr::new("stderr")) { |
159 | extract_error_codes_from_tests(contents, &mut error_codes); | |
160 | } | |
161 | }); | |
f9f354fc XL |
162 | if errors.is_empty() { |
163 | println!("Found {} error codes", error_codes.len()); | |
e74abb32 | 164 | |
f9f354fc | 165 | for (err_code, nb) in &error_codes { |
f035d41b | 166 | if !*nb && !EXEMPTED_FROM_TEST.contains(&err_code.as_str()) { |
f9f354fc XL |
167 | errors.push(format!("Error code {} needs to have at least one UI test!", err_code)); |
168 | } | |
e74abb32 XL |
169 | } |
170 | } | |
171 | errors.sort(); | |
172 | for err in &errors { | |
173 | eprintln!("{}", err); | |
174 | } | |
175 | println!("Found {} error codes with no tests", errors.len()); | |
176 | if !errors.is_empty() { | |
177 | *bad = true; | |
178 | } | |
179 | println!("Done!"); | |
180 | } |