Skip to content

Commit 23e5ed1

Browse files
Check if error code is used
1 parent b6f3cb9 commit 23e5ed1

File tree

1 file changed

+99
-16
lines changed

1 file changed

+99
-16
lines changed

src/tools/tidy/src/error_codes_check.rs

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
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;
45
use std::collections::HashMap;
56
use std::ffi::OsStr;
67
use std::fs::read_to_string;
78
use 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!
1013
const 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...
1821
const 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+
2030
fn 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

7888
fn 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

Comments
 (0)