summary refs log tree commit diff
path: root/src/tools/clippy/tests/lint_message_convention.rs
blob: 2f8989c8e114082eabd4957b8431f0a090d879a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::ffi::OsStr;
use std::path::PathBuf;

use regex::RegexSet;

#[derive(Debug)]
struct Message {
    path: PathBuf,
    bad_lines: Vec<String>,
}

impl Message {
    fn new(path: PathBuf) -> Self {
        let content: String = std::fs::read_to_string(&path).unwrap();
        // we don't want the first letter after "error: ", "help: " ... to be capitalized
        // also no puncutation (except for "?" ?) at the end of a line
        let regex_set: RegexSet = RegexSet::new(&[
            r"error: [A-Z]",
            r"help: [A-Z]",
            r"warning: [A-Z]",
            r"note: [A-Z]",
            r"try this: [A-Z]",
            r"error: .*[.!]$",
            r"help: .*[.!]$",
            r"warning: .*[.!]$",
            r"note: .*[.!]$",
            r"try this: .*[.!]$",
        ])
        .unwrap();

        // sometimes the first character is capitalized and it is legal (like in "C-like enum variants") or
        // we want to ask a question ending in "?"
        let exceptions_set: RegexSet = RegexSet::new(&[
            r".*C-like enum variant discriminant is not portable to 32-bit targets",
            r".*did you mean `unix`?",
            r".*the arguments may be inverted...",
            r".*Intel x86 assembly syntax used",
            r".*AT&T x86 assembly syntax used",
            r".*remove .*the return type...",
            r"note: Clippy version: .*",
            r"the compiler unexpectedly panicked. this is a bug.",
        ])
        .unwrap();

        let bad_lines = content
            .lines()
            .filter(|line| regex_set.matches(line).matched_any())
            // ignore exceptions
            .filter(|line| !exceptions_set.matches(line).matched_any())
            .map(ToOwned::to_owned)
            .collect::<Vec<String>>();

        Message { path, bad_lines }
    }
}

#[test]
fn lint_message_convention() {
    // disable the test inside the rustc test suite
    if option_env!("RUSTC_TEST_SUITE").is_some() {
        return;
    }

    // make sure that lint messages:
    // * are not capitalized
    // * don't have puncuation at the end of the last sentence

    // these directories have interesting tests
    let test_dirs = ["ui", "ui-cargo", "ui-internal", "ui-toml"]
        .iter()
        .map(PathBuf::from)
        .map(|p| {
            let base = PathBuf::from("tests");
            base.join(p)
        });

    // gather all .stderr files
    let tests = test_dirs
        .flat_map(|dir| {
            std::fs::read_dir(dir)
                .expect("failed to read dir")
                .map(|direntry| direntry.unwrap().path())
        })
        .filter(|file| matches!(file.extension().map(OsStr::to_str), Some(Some("stderr"))));

    // get all files that have any "bad lines" in them
    let bad_tests: Vec<Message> = tests
        .map(Message::new)
        .filter(|message| !message.bad_lines.is_empty())
        .collect();

    for message in &bad_tests {
        eprintln!(
            "error: the test '{}' contained the following nonconforming lines :",
            message.path.display()
        );
        message.bad_lines.iter().for_each(|line| eprintln!("{}", line));
        eprintln!("\n\n");
    }

    eprintln!(
        "\n\n\nLint message should not start with a capital letter and should not have punctuation at the end of the message unless multiple sentences are needed."
    );
    eprintln!("Check out the rustc-dev-guide for more information:");
    eprintln!("https://rustc-dev-guide.rust-lang.org/diagnostics.html#diagnostic-structure\n\n\n");

    assert!(bad_tests.is_empty());
}