mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 23:04:33 +00:00
Rollup merge of #106341 - Ezrashaw:refactor-error-code-tidy-check, r=mejrs,klensy,GuillaumeGomez
refactor: clean up `errors.rs` and `error_codes_check.rs` `errors.rs` is basically unused now, `error_codes_check.rs` is useful but not well commented, etc. It also doesn't check certain things which are certainly not correct. For example, `E0505` has a UI test in `src/test/ui/error-codes/` but that test actually outputs `E0504`?! Other issues like these exist. I've implemented these with "warnings" which are a bit rough around the edges but should be removed eventually. r? `@GuillaumeGomez` (again not sure if you want to review but its relevant to you)
This commit is contained in:
commit
498216e9db
@ -1,3 +1,5 @@
|
||||
#### Note: this error code is no longer emitted by the compiler
|
||||
|
||||
Support for Non-Lexical Lifetimes (NLL) has been included in the Rust compiler
|
||||
since 1.31, and has been enabled on the 2015 edition since 1.36. The new borrow
|
||||
checker for NLL uncovered some bugs in the old borrow checker, which in some
|
||||
|
381
src/tools/tidy/src/error_codes.rs
Normal file
381
src/tools/tidy/src/error_codes.rs
Normal file
@ -0,0 +1,381 @@
|
||||
//! Tidy check to ensure error codes are properly documented and tested.
|
||||
//!
|
||||
//! Overview of check:
|
||||
//!
|
||||
//! 1. We create a list of error codes used by the compiler. Error codes are extracted from `compiler/rustc_error_codes/src/error_codes.rs`.
|
||||
//!
|
||||
//! 2. We check that the error code has a long-form explanation in `compiler/rustc_error_codes/src/error_codes/`.
|
||||
//! - The explanation is expected to contain a `doctest` that fails with the correct error code. (`EXEMPT_FROM_DOCTEST` *currently* bypasses this check)
|
||||
//! - Note that other stylistic conventions for markdown files are checked in the `style.rs` tidy check.
|
||||
//!
|
||||
//! 3. We check that the error code has a UI test in `src/test/ui/error-codes/`.
|
||||
//! - We ensure that there is both a `Exxxx.rs` file and a corresponding `Exxxx.stderr` file.
|
||||
//! - We also ensure that the error code is used in the tests.
|
||||
//! - *Currently*, it is possible to opt-out of this check with the `EXEMPTED_FROM_TEST` constant.
|
||||
//!
|
||||
//! 4. We check that the error code is actually emitted by the compiler.
|
||||
//! - This is done by searching `compiler/` with a regex.
|
||||
//!
|
||||
//! This tidy check was merged and refactored from two others. See #PR_NUM for information about linting changes that occurred during this refactor.
|
||||
|
||||
use std::{ffi::OsStr, fs, path::Path};
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::walk::{filter_dirs, walk, walk_many};
|
||||
|
||||
const ERROR_CODES_PATH: &str = "compiler/rustc_error_codes/src/error_codes.rs";
|
||||
const ERROR_DOCS_PATH: &str = "compiler/rustc_error_codes/src/error_codes/";
|
||||
const ERROR_TESTS_PATH: &str = "src/test/ui/error-codes/";
|
||||
|
||||
// Error codes that (for some reason) can't have a doctest in their explanation. Error codes are still expected to provide a code example, even if untested.
|
||||
const IGNORE_DOCTEST_CHECK: &[&str] = &["E0464", "E0570", "E0601", "E0602"];
|
||||
|
||||
// Error codes that don't yet have a UI test. This list will eventually be removed.
|
||||
const IGNORE_UI_TEST_CHECK: &[&str] = &[
|
||||
"E0313", "E0461", "E0465", "E0476", "E0490", "E0514", "E0523", "E0554", "E0640", "E0717",
|
||||
"E0729", "E0789",
|
||||
];
|
||||
|
||||
macro_rules! verbose_print {
|
||||
($verbose:expr, $($fmt:tt)*) => {
|
||||
if $verbose {
|
||||
println!("{}", format_args!($($fmt)*));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn check(root_path: &Path, search_paths: &[&Path], verbose: bool, bad: &mut bool) {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
// Stage 1: create list
|
||||
let error_codes = extract_error_codes(root_path, &mut errors, verbose);
|
||||
println!("Found {} error codes", error_codes.len());
|
||||
println!("Highest error code: `{}`", error_codes.iter().max().unwrap());
|
||||
|
||||
// Stage 2: check list has docs
|
||||
let no_longer_emitted = check_error_codes_docs(root_path, &error_codes, &mut errors, verbose);
|
||||
|
||||
// Stage 3: check list has UI tests
|
||||
check_error_codes_tests(root_path, &error_codes, &mut errors, verbose);
|
||||
|
||||
// Stage 4: check list is emitted by compiler
|
||||
check_error_codes_used(search_paths, &error_codes, &mut errors, &no_longer_emitted, verbose);
|
||||
|
||||
// Print any errors.
|
||||
for error in errors {
|
||||
tidy_error!(bad, "{}", error);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stage 1: Parses a list of error codes from `error_codes.rs`.
|
||||
fn extract_error_codes(root_path: &Path, errors: &mut Vec<String>, verbose: bool) -> Vec<String> {
|
||||
let path = root_path.join(Path::new(ERROR_CODES_PATH));
|
||||
let file =
|
||||
fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read `{path:?}`: {e}"));
|
||||
|
||||
let mut error_codes = Vec::new();
|
||||
let mut reached_undocumented_codes = false;
|
||||
|
||||
for line in file.lines() {
|
||||
let line = line.trim();
|
||||
|
||||
if !reached_undocumented_codes && line.starts_with('E') {
|
||||
let split_line = line.split_once(':');
|
||||
|
||||
// Extract the error code from the line, emitting a fatal error if it is not in a correct format.
|
||||
let err_code = if let Some(err_code) = split_line {
|
||||
err_code.0.to_owned()
|
||||
} else {
|
||||
errors.push(format!(
|
||||
"Expected a line with the format `Exxxx: include_str!(\"..\")`, but got \"{}\" \
|
||||
without a `:` delimiter",
|
||||
line,
|
||||
));
|
||||
continue;
|
||||
};
|
||||
|
||||
// If this is a duplicate of another error code, emit a fatal error.
|
||||
if error_codes.contains(&err_code) {
|
||||
errors.push(format!("Found duplicate error code: `{}`", err_code));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure that the line references the correct markdown file.
|
||||
let expected_filename = format!(" include_str!(\"./error_codes/{}.md\"),", err_code);
|
||||
if expected_filename != split_line.unwrap().1 {
|
||||
errors.push(format!(
|
||||
"Error code `{}` expected to reference docs with `{}` but instead found `{}` in \
|
||||
`compiler/rustc_error_codes/src/error_codes.rs`",
|
||||
err_code,
|
||||
expected_filename,
|
||||
split_line.unwrap().1,
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
error_codes.push(err_code);
|
||||
} else if reached_undocumented_codes && line.starts_with('E') {
|
||||
let err_code = match line.split_once(',') {
|
||||
None => line,
|
||||
Some((err_code, _)) => err_code,
|
||||
}
|
||||
.to_string();
|
||||
|
||||
verbose_print!(verbose, "warning: Error code `{}` is undocumented.", err_code);
|
||||
|
||||
if error_codes.contains(&err_code) {
|
||||
errors.push(format!("Found duplicate error code: `{}`", err_code));
|
||||
}
|
||||
|
||||
error_codes.push(err_code);
|
||||
} else if line == ";" {
|
||||
// Once we reach the undocumented error codes, adapt to different syntax.
|
||||
reached_undocumented_codes = true;
|
||||
}
|
||||
}
|
||||
|
||||
error_codes
|
||||
}
|
||||
|
||||
/// Stage 2: Checks that long-form error code explanations exist and have doctests.
|
||||
fn check_error_codes_docs(
|
||||
root_path: &Path,
|
||||
error_codes: &[String],
|
||||
errors: &mut Vec<String>,
|
||||
verbose: bool,
|
||||
) -> Vec<String> {
|
||||
let docs_path = root_path.join(Path::new(ERROR_DOCS_PATH));
|
||||
|
||||
let mut no_longer_emitted_codes = Vec::new();
|
||||
|
||||
walk(&docs_path, &mut |_| false, &mut |entry, contents| {
|
||||
let path = entry.path();
|
||||
|
||||
// Error if the file isn't markdown.
|
||||
if path.extension() != Some(OsStr::new("md")) {
|
||||
errors.push(format!(
|
||||
"Found unexpected non-markdown file in error code docs directory: {}",
|
||||
path.display()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure that the file is referenced in `error_codes.rs`
|
||||
let filename = path.file_name().unwrap().to_str().unwrap().split_once('.');
|
||||
let err_code = filename.unwrap().0; // `unwrap` is ok because we know the filename is in the correct format.
|
||||
|
||||
if error_codes.iter().all(|e| e != err_code) {
|
||||
errors.push(format!(
|
||||
"Found valid file `{}` in error code docs directory without corresponding \
|
||||
entry in `error_code.rs`",
|
||||
path.display()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
let (found_code_example, found_proper_doctest, emit_ignore_warning, emit_no_longer_warning) =
|
||||
check_explanation_has_doctest(&contents, &err_code);
|
||||
if emit_ignore_warning {
|
||||
verbose_print!(
|
||||
verbose,
|
||||
"warning: Error code `{err_code}` uses the ignore header. This should not be used, add the error code to the \
|
||||
`IGNORE_DOCTEST_CHECK` constant instead."
|
||||
);
|
||||
}
|
||||
if emit_no_longer_warning {
|
||||
no_longer_emitted_codes.push(err_code.to_owned());
|
||||
verbose_print!(
|
||||
verbose,
|
||||
"warning: Error code `{err_code}` is no longer emitted and should be removed entirely."
|
||||
);
|
||||
}
|
||||
if !found_code_example {
|
||||
verbose_print!(
|
||||
verbose,
|
||||
"warning: Error code `{err_code}` doesn't have a code example, all error codes are expected to have one \
|
||||
(even if untested)."
|
||||
);
|
||||
}
|
||||
|
||||
let test_ignored = IGNORE_DOCTEST_CHECK.contains(&&err_code);
|
||||
|
||||
// Check that the explanation has a doctest, and if it shouldn't, that it doesn't
|
||||
if !found_proper_doctest && !test_ignored {
|
||||
errors.push(format!(
|
||||
"`{}` doesn't use its own error code in compile_fail example",
|
||||
path.display(),
|
||||
));
|
||||
} else if found_proper_doctest && test_ignored {
|
||||
errors.push(format!(
|
||||
"`{}` has a compile_fail doctest with its own error code, it shouldn't \
|
||||
be listed in `IGNORE_DOCTEST_CHECK`",
|
||||
path.display(),
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
no_longer_emitted_codes
|
||||
}
|
||||
|
||||
/// This function returns a tuple indicating whether the provided explanation:
|
||||
/// a) has a code example, tested or not.
|
||||
/// b) has a valid doctest
|
||||
fn check_explanation_has_doctest(explanation: &str, err_code: &str) -> (bool, bool, bool, bool) {
|
||||
let mut found_code_example = false;
|
||||
let mut found_proper_doctest = false;
|
||||
|
||||
let mut emit_ignore_warning = false;
|
||||
let mut emit_no_longer_warning = false;
|
||||
|
||||
for line in explanation.lines() {
|
||||
let line = line.trim();
|
||||
|
||||
if line.starts_with("```") {
|
||||
found_code_example = true;
|
||||
|
||||
// Check for the `rustdoc` doctest headers.
|
||||
if line.contains("compile_fail") && line.contains(err_code) {
|
||||
found_proper_doctest = true;
|
||||
}
|
||||
|
||||
if line.contains("ignore") {
|
||||
emit_ignore_warning = true;
|
||||
found_proper_doctest = true;
|
||||
}
|
||||
} else if line
|
||||
.starts_with("#### Note: this error code is no longer emitted by the compiler")
|
||||
{
|
||||
emit_no_longer_warning = true;
|
||||
found_code_example = true;
|
||||
found_proper_doctest = true;
|
||||
}
|
||||
}
|
||||
|
||||
(found_code_example, found_proper_doctest, emit_ignore_warning, emit_no_longer_warning)
|
||||
}
|
||||
|
||||
// Stage 3: Checks that each error code has a UI test in the correct directory
|
||||
fn check_error_codes_tests(
|
||||
root_path: &Path,
|
||||
error_codes: &[String],
|
||||
errors: &mut Vec<String>,
|
||||
verbose: bool,
|
||||
) {
|
||||
let tests_path = root_path.join(Path::new(ERROR_TESTS_PATH));
|
||||
|
||||
for code in error_codes {
|
||||
let test_path = tests_path.join(format!("{}.stderr", code));
|
||||
|
||||
if !test_path.exists() && !IGNORE_UI_TEST_CHECK.contains(&code.as_str()) {
|
||||
verbose_print!(
|
||||
verbose,
|
||||
"warning: Error code `{code}` needs to have at least one UI test in the `src/test/ui/error-codes/` directory`!"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if IGNORE_UI_TEST_CHECK.contains(&code.as_str()) {
|
||||
if test_path.exists() {
|
||||
errors.push(format!(
|
||||
"Error code `{code}` has a UI test in `src/test/ui/error-codes/{code}.rs`, it shouldn't be listed in `EXEMPTED_FROM_TEST`!"
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let file = match fs::read_to_string(&test_path) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
verbose_print!(
|
||||
verbose,
|
||||
"warning: Failed to read UI test file (`{}`) for `{code}` but the file exists. The test is assumed to work:\n{err}",
|
||||
test_path.display()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut found_code = false;
|
||||
|
||||
for line in file.lines() {
|
||||
let s = line.trim();
|
||||
// Assuming the line starts with `error[E`, we can substring the error code out.
|
||||
if s.starts_with("error[E") {
|
||||
if &s[6..11] == code {
|
||||
found_code = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if !found_code {
|
||||
verbose_print!(
|
||||
verbose,
|
||||
"warning: Error code {code}`` has a UI test file, but doesn't contain its own error code!"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stage 4: Search `compiler/` and ensure that every error code is actually used by the compiler and that no undocumented error codes exist.
|
||||
fn check_error_codes_used(
|
||||
search_paths: &[&Path],
|
||||
error_codes: &[String],
|
||||
errors: &mut Vec<String>,
|
||||
no_longer_emitted: &[String],
|
||||
verbose: bool,
|
||||
) {
|
||||
// We want error codes which match the following cases:
|
||||
//
|
||||
// * foo(a, E0111, a)
|
||||
// * foo(a, E0111)
|
||||
// * foo(E0111, a)
|
||||
// * #[error = "E0111"]
|
||||
let regex = Regex::new(r#"[(,"\s](E\d{4})[,)"]"#).unwrap();
|
||||
|
||||
let mut found_codes = Vec::new();
|
||||
|
||||
walk_many(search_paths, &mut filter_dirs, &mut |entry, contents| {
|
||||
let path = entry.path();
|
||||
|
||||
// Return early if we aren't looking at a source file.
|
||||
if path.extension() != Some(OsStr::new("rs")) {
|
||||
return;
|
||||
}
|
||||
|
||||
for line in contents.lines() {
|
||||
// We want to avoid parsing error codes in comments.
|
||||
if line.trim_start().starts_with("//") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for cap in regex.captures_iter(line) {
|
||||
if let Some(error_code) = cap.get(1) {
|
||||
let error_code = error_code.as_str().to_owned();
|
||||
|
||||
if !error_codes.contains(&error_code) {
|
||||
// This error code isn't properly defined, we must error.
|
||||
errors.push(format!("Error code `{}` is used in the compiler but not defined and documented in `compiler/rustc_error_codes/src/error_codes.rs`.", error_code));
|
||||
continue;
|
||||
}
|
||||
|
||||
// This error code can now be marked as used.
|
||||
found_codes.push(error_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for code in error_codes {
|
||||
if !found_codes.contains(code) && !no_longer_emitted.contains(code) {
|
||||
errors.push(format!("Error code `{code}` exists, but is not emitted by the compiler!"))
|
||||
}
|
||||
|
||||
if found_codes.contains(code) && no_longer_emitted.contains(code) {
|
||||
verbose_print!(
|
||||
verbose,
|
||||
"warning: Error code `{code}` is used when it's marked as \"no longer emitted\""
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,305 +0,0 @@
|
||||
//! Checks that all error codes have at least one test to prevent having error
|
||||
//! codes that are silently not thrown by the compiler anymore.
|
||||
|
||||
use crate::walk::{filter_dirs, walk};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::read_to_string;
|
||||
use std::path::Path;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
// A few of those error codes can't be tested but all the others can and *should* be tested!
|
||||
const EXEMPTED_FROM_TEST: &[&str] = &[
|
||||
"E0313", "E0461", "E0476", "E0490", "E0514", "E0523", "E0554", "E0640", "E0717", "E0729",
|
||||
"E0789",
|
||||
];
|
||||
|
||||
// Some error codes don't have any tests apparently...
|
||||
const IGNORE_EXPLANATION_CHECK: &[&str] = &["E0464", "E0570", "E0601", "E0602", "E0729"];
|
||||
|
||||
// If the file path contains any of these, we don't want to try to extract error codes from it.
|
||||
//
|
||||
// We need to declare each path in the windows version (with backslash).
|
||||
const PATHS_TO_IGNORE_FOR_EXTRACTION: &[&str] =
|
||||
&["src/test/", "src\\test\\", "src/doc/", "src\\doc\\", "src/tools/", "src\\tools\\"];
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct ErrorCodeStatus {
|
||||
has_test: bool,
|
||||
has_explanation: bool,
|
||||
is_used: bool,
|
||||
}
|
||||
|
||||
fn check_error_code_explanation(
|
||||
f: &str,
|
||||
error_codes: &mut HashMap<String, ErrorCodeStatus>,
|
||||
err_code: String,
|
||||
) -> bool {
|
||||
let mut invalid_compile_fail_format = false;
|
||||
let mut found_error_code = false;
|
||||
|
||||
for line in f.lines() {
|
||||
let s = line.trim();
|
||||
if s.starts_with("```") {
|
||||
if s.contains("compile_fail") && s.contains('E') {
|
||||
if !found_error_code {
|
||||
error_codes.get_mut(&err_code).map(|x| x.has_test = true);
|
||||
found_error_code = true;
|
||||
}
|
||||
} else if s.contains("compile-fail") {
|
||||
invalid_compile_fail_format = true;
|
||||
}
|
||||
} else if s.starts_with("#### Note: this error code is no longer emitted by the compiler") {
|
||||
if !found_error_code {
|
||||
error_codes.get_mut(&err_code).map(|x| x.has_test = true);
|
||||
found_error_code = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
invalid_compile_fail_format
|
||||
}
|
||||
|
||||
fn check_if_error_code_is_test_in_explanation(f: &str, err_code: &str) -> bool {
|
||||
let mut ignore_found = false;
|
||||
|
||||
for line in f.lines() {
|
||||
let s = line.trim();
|
||||
if s.starts_with("#### Note: this error code is no longer emitted by the compiler") {
|
||||
return true;
|
||||
}
|
||||
if s.starts_with("```") {
|
||||
if s.contains("compile_fail") && s.contains(err_code) {
|
||||
return true;
|
||||
} else if s.contains("ignore") {
|
||||
// It's very likely that we can't actually make it fail compilation...
|
||||
ignore_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ignore_found
|
||||
}
|
||||
|
||||
fn extract_error_codes(
|
||||
f: &str,
|
||||
error_codes: &mut HashMap<String, ErrorCodeStatus>,
|
||||
path: &Path,
|
||||
errors: &mut Vec<String>,
|
||||
) {
|
||||
let mut reached_no_explanation = false;
|
||||
|
||||
for line in f.lines() {
|
||||
let s = line.trim();
|
||||
if !reached_no_explanation && s.starts_with('E') && s.contains("include_str!(\"") {
|
||||
let err_code = s
|
||||
.split_once(':')
|
||||
.expect(
|
||||
format!(
|
||||
"Expected a line with the format `E0xxx: include_str!(\"..\")`, but got {} \
|
||||
without a `:` delimiter",
|
||||
s,
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.0
|
||||
.to_owned();
|
||||
error_codes.entry(err_code.clone()).or_default().has_explanation = true;
|
||||
|
||||
// Now we extract the tests from the markdown file!
|
||||
let md_file_name = match s.split_once("include_str!(\"") {
|
||||
None => continue,
|
||||
Some((_, md)) => match md.split_once("\")") {
|
||||
None => continue,
|
||||
Some((file_name, _)) => file_name,
|
||||
},
|
||||
};
|
||||
|
||||
let Some(parent) = path.parent() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let path = parent
|
||||
.join(md_file_name)
|
||||
.canonicalize()
|
||||
.expect("failed to canonicalize error explanation file path");
|
||||
|
||||
match read_to_string(&path) {
|
||||
Ok(content) => {
|
||||
let has_test = check_if_error_code_is_test_in_explanation(&content, &err_code);
|
||||
if !has_test && !IGNORE_EXPLANATION_CHECK.contains(&err_code.as_str()) {
|
||||
errors.push(format!(
|
||||
"`{}` doesn't use its own error code in compile_fail example",
|
||||
path.display(),
|
||||
));
|
||||
} else if has_test && IGNORE_EXPLANATION_CHECK.contains(&err_code.as_str()) {
|
||||
errors.push(format!(
|
||||
"`{}` has a compile_fail example with its own error code, it shouldn't \
|
||||
be listed in IGNORE_EXPLANATION_CHECK!",
|
||||
path.display(),
|
||||
));
|
||||
}
|
||||
if check_error_code_explanation(&content, error_codes, err_code) {
|
||||
errors.push(format!(
|
||||
"`{}` uses invalid tag `compile-fail` instead of `compile_fail`",
|
||||
path.display(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Couldn't read `{}`: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
} else if reached_no_explanation && s.starts_with('E') {
|
||||
let err_code = match s.split_once(',') {
|
||||
None => s,
|
||||
Some((err_code, _)) => err_code,
|
||||
}
|
||||
.to_string();
|
||||
if !error_codes.contains_key(&err_code) {
|
||||
// this check should *never* fail!
|
||||
error_codes.insert(err_code, ErrorCodeStatus::default());
|
||||
}
|
||||
} else if s == ";" {
|
||||
reached_no_explanation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_error_codes_from_tests(f: &str, error_codes: &mut HashMap<String, ErrorCodeStatus>) {
|
||||
for line in f.lines() {
|
||||
let s = line.trim();
|
||||
if s.starts_with("error[E") || s.starts_with("warning[E") {
|
||||
let err_code = match s.split_once(']') {
|
||||
None => continue,
|
||||
Some((err_code, _)) => match err_code.split_once('[') {
|
||||
None => continue,
|
||||
Some((_, err_code)) => err_code,
|
||||
},
|
||||
};
|
||||
error_codes.entry(err_code.to_owned()).or_default().has_test = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_error_codes_from_source(
|
||||
f: &str,
|
||||
error_codes: &mut HashMap<String, ErrorCodeStatus>,
|
||||
regex: &Regex,
|
||||
) {
|
||||
for line in f.lines() {
|
||||
if line.trim_start().starts_with("//") {
|
||||
continue;
|
||||
}
|
||||
for cap in regex.captures_iter(line) {
|
||||
if let Some(error_code) = cap.get(1) {
|
||||
error_codes.entry(error_code.as_str().to_owned()).or_default().is_used = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(paths: &[&Path], bad: &mut bool) {
|
||||
let mut errors = Vec::new();
|
||||
let mut found_explanations = 0;
|
||||
let mut found_tests = 0;
|
||||
let mut error_codes: HashMap<String, ErrorCodeStatus> = HashMap::new();
|
||||
let mut explanations: HashSet<String> = HashSet::new();
|
||||
// We want error codes which match the following cases:
|
||||
//
|
||||
// * foo(a, E0111, a)
|
||||
// * foo(a, E0111)
|
||||
// * foo(E0111, a)
|
||||
// * #[error = "E0111"]
|
||||
let regex = Regex::new(r#"[(,"\s](E\d{4})[,)"]"#).unwrap();
|
||||
|
||||
for path in paths {
|
||||
walk(path, &mut filter_dirs, &mut |entry, contents| {
|
||||
let file_name = entry.file_name();
|
||||
let entry_path = entry.path();
|
||||
|
||||
if file_name == "error_codes.rs" {
|
||||
extract_error_codes(contents, &mut error_codes, entry.path(), &mut errors);
|
||||
found_explanations += 1;
|
||||
} else if entry_path.extension() == Some(OsStr::new("stderr")) {
|
||||
extract_error_codes_from_tests(contents, &mut error_codes);
|
||||
found_tests += 1;
|
||||
} else if entry_path.extension() == Some(OsStr::new("rs")) {
|
||||
let path = entry.path().to_string_lossy();
|
||||
if PATHS_TO_IGNORE_FOR_EXTRACTION.iter().all(|c| !path.contains(c)) {
|
||||
extract_error_codes_from_source(contents, &mut error_codes, ®ex);
|
||||
}
|
||||
} else if entry_path
|
||||
.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.map(|p| p == "error_codes")
|
||||
.unwrap_or(false)
|
||||
&& entry_path.extension() == Some(OsStr::new("md"))
|
||||
{
|
||||
explanations.insert(file_name.to_str().unwrap().replace(".md", ""));
|
||||
}
|
||||
});
|
||||
}
|
||||
if found_explanations == 0 {
|
||||
tidy_error!(bad, "No error code explanation was tested!");
|
||||
}
|
||||
if found_tests == 0 {
|
||||
tidy_error!(bad, "No error code was found in compilation errors!");
|
||||
}
|
||||
if explanations.is_empty() {
|
||||
tidy_error!(bad, "No error code explanation was found!");
|
||||
}
|
||||
if errors.is_empty() {
|
||||
for (err_code, error_status) in &error_codes {
|
||||
if !error_status.has_test && !EXEMPTED_FROM_TEST.contains(&err_code.as_str()) {
|
||||
errors.push(format!("Error code {err_code} needs to have at least one UI test!"));
|
||||
} else if error_status.has_test && EXEMPTED_FROM_TEST.contains(&err_code.as_str()) {
|
||||
errors.push(format!(
|
||||
"Error code {} has a UI test, it shouldn't be listed into EXEMPTED_FROM_TEST!",
|
||||
err_code
|
||||
));
|
||||
}
|
||||
if !error_status.is_used && !error_status.has_explanation {
|
||||
errors.push(format!(
|
||||
"Error code {} isn't used and doesn't have an error explanation, it should be \
|
||||
commented in error_codes.rs file",
|
||||
err_code
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if errors.is_empty() {
|
||||
// Checking if local constants need to be cleaned.
|
||||
for err_code in EXEMPTED_FROM_TEST {
|
||||
match error_codes.get(err_code.to_owned()) {
|
||||
Some(status) => {
|
||||
if status.has_test {
|
||||
errors.push(format!(
|
||||
"{} error code has a test and therefore should be \
|
||||
removed from the `EXEMPTED_FROM_TEST` constant",
|
||||
err_code
|
||||
));
|
||||
}
|
||||
}
|
||||
None => errors.push(format!(
|
||||
"{} error code isn't used anymore and therefore should be removed \
|
||||
from `EXEMPTED_FROM_TEST` constant",
|
||||
err_code
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
if errors.is_empty() {
|
||||
for explanation in explanations {
|
||||
if !error_codes.contains_key(&explanation) {
|
||||
errors.push(format!(
|
||||
"{} error code explanation should be listed in `error_codes.rs`",
|
||||
explanation
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
errors.sort();
|
||||
for err in &errors {
|
||||
tidy_error!(bad, "{err}");
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
//! Tidy check to verify the validity of long error diagnostic codes.
|
||||
//!
|
||||
//! This ensures that error codes are used at most once and also prints out some
|
||||
//! statistics about the error codes.
|
||||
|
||||
use crate::walk::{filter_dirs, walk};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn check(path: &Path, bad: &mut bool) {
|
||||
let mut map: HashMap<_, Vec<_>> = HashMap::new();
|
||||
walk(
|
||||
path,
|
||||
&mut |path| filter_dirs(path) || path.ends_with("src/test"),
|
||||
&mut |entry, contents| {
|
||||
let file = entry.path();
|
||||
let filename = file.file_name().unwrap().to_string_lossy();
|
||||
if filename != "error_codes.rs" {
|
||||
return;
|
||||
}
|
||||
|
||||
// In the `register_long_diagnostics!` macro, entries look like this:
|
||||
//
|
||||
// ```
|
||||
// EXXXX: r##"
|
||||
// <Long diagnostic message>
|
||||
// "##,
|
||||
// ```
|
||||
//
|
||||
// and these long messages often have error codes themselves inside
|
||||
// them, but we don't want to report duplicates in these cases. This
|
||||
// variable keeps track of whether we're currently inside one of these
|
||||
// long diagnostic messages.
|
||||
let mut inside_long_diag = false;
|
||||
for (num, line) in contents.lines().enumerate() {
|
||||
if inside_long_diag {
|
||||
inside_long_diag = !line.contains("\"##");
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut search = line;
|
||||
while let Some(i) = search.find('E') {
|
||||
search = &search[i + 1..];
|
||||
let code = if search.len() > 4 { search[..4].parse::<u32>() } else { continue };
|
||||
let code = match code {
|
||||
Ok(n) => n,
|
||||
Err(..) => continue,
|
||||
};
|
||||
map.entry(code).or_default().push((file.to_owned(), num + 1, line.to_owned()));
|
||||
break;
|
||||
}
|
||||
|
||||
inside_long_diag = line.contains("r##\"");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let mut max = 0;
|
||||
for (&code, entries) in map.iter() {
|
||||
if code > max {
|
||||
max = code;
|
||||
}
|
||||
if entries.len() == 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
tidy_error!(bad, "duplicate error code: {}", code);
|
||||
for &(ref file, line_num, ref line) in entries.iter() {
|
||||
tidy_error!(bad, "{}:{}: {}", file.display(), line_num, line);
|
||||
}
|
||||
}
|
||||
|
||||
if !*bad {
|
||||
println!("* {} error codes", map.len());
|
||||
println!("* highest error code: E{:04}", max);
|
||||
}
|
||||
}
|
@ -56,8 +56,7 @@ pub mod bins;
|
||||
pub mod debug_artifacts;
|
||||
pub mod deps;
|
||||
pub mod edition;
|
||||
pub mod error_codes_check;
|
||||
pub mod errors;
|
||||
pub mod error_codes;
|
||||
pub mod extdeps;
|
||||
pub mod features;
|
||||
pub mod mir_opt_tests;
|
||||
|
@ -27,6 +27,7 @@ fn main() {
|
||||
let src_path = root_path.join("src");
|
||||
let library_path = root_path.join("library");
|
||||
let compiler_path = root_path.join("compiler");
|
||||
let librustdoc_path = src_path.join("librustdoc");
|
||||
|
||||
let args: Vec<String> = env::args().skip(1).collect();
|
||||
|
||||
@ -79,8 +80,7 @@ fn main() {
|
||||
check!(mir_opt_tests, &src_path, bless);
|
||||
|
||||
// Checks that only make sense for the compiler.
|
||||
check!(errors, &compiler_path);
|
||||
check!(error_codes_check, &[&src_path, &compiler_path]);
|
||||
check!(error_codes, &root_path, &[&compiler_path, &librustdoc_path], verbose);
|
||||
|
||||
// Checks that only make sense for the std libs.
|
||||
check!(pal, &library_path);
|
||||
|
Loading…
Reference in New Issue
Block a user