#![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![warn(rust_2018_idioms, unused_lifetimes)]

use std::ffi::OsStr;
use std::path::PathBuf;
use std::sync::LazyLock;

use regex::RegexSet;

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

impl Message {
    fn new(path: PathBuf) -> Self {
        // we don't want the first letter after "error: ", "help: " ... to be capitalized
        // also no punctuation (except for "?" ?) at the end of a line
        // Prefer "try" over "try this".
        static REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| {
            RegexSet::new([
                "error: [A-Z]",
                "help: [A-Z]",
                "warning: [A-Z]",
                "note: [A-Z]",
                "try: [A-Z]",
                "error: .*[.!]$",
                "help: .*[.!]$",
                "warning: .*[.!]$",
                "note: .*[.!]$",
                "try: .*[.!]$",
                "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 "?"
        static EXCEPTIONS_SET: LazyLock<RegexSet> = LazyLock::new(|| {
            RegexSet::new([
                r"\.\.\.$",
                ".*C-like enum variant discriminant is not portable to 32-bit targets",
                ".*Intel x86 assembly syntax used",
                ".*AT&T x86 assembly syntax used",
                "note: Clippy version: .*",
                "the compiler unexpectedly panicked. this is a bug.",
            ])
            .unwrap()
        });

        let content: String = std::fs::read_to_string(&path).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 punctuation 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());
}
