comperr 0.2.0

A minimal, lightweight crate for emitting span-accurate compile-time errors from procedural macros.
Documentation
#[cfg(test)]
mod tests {
    use comperr::{Error, error};
    use proc_macro2::Span;

    #[test]
    fn error_creates_valid_tokenstream() {
        let result = error(Span::call_site(), "test error");
        assert!(!result.is_empty());
    }

    #[test]
    fn error_message_is_preserved() {
        let result = error(Span::call_site(), "test error message");
        let result_str = result.to_string();
        assert!(result_str.contains("test error message"));
    }

    #[test]
    fn error_contains_compile_error() {
        let result = error(Span::call_site(), "test error");
        let result_str = result.to_string();
        assert!(result_str.contains("compile_error"));
    }

    #[test]
    fn error_struct_works() {
        let err = Error::new(Span::call_site(), "test error");
        let result = err.to_compile_error();
        assert!(!result.is_empty());
    }

    #[test]
    fn error_with_different_messages() {
        let err1 = Error::new(Span::call_site(), "message one");
        let err2 = Error::new(Span::call_site(), "message two");

        let result1 = err1.to_compile_error();
        let result2 = err2.to_compile_error();

        assert!(result1.to_string().contains("message one"));
        assert!(result2.to_string().contains("message two"));
    }

    #[test]
    fn error_string_message() {
        let msg = String::from("owned message");
        let result = error(Span::call_site(), msg);
        assert!(!result.is_empty());
    }

    #[test]
    fn error_empty_message() {
        let result = error(Span::call_site(), "");
        let result_str = result.to_string();
        assert!(result_str.contains("compile_error"));
    }

    #[test]
    fn error_multiline_message() {
        let result = error(Span::call_site(), "line 1\nline 2\nline 3");
        let result_str = result.to_string();
        assert!(result_str.contains("line 1"));
    }

    #[test]
    fn error_combine_multiple_errors() {
        let mut err = Error::new(Span::call_site(), "first");
        err.combine(Error::new(Span::call_site(), "second"));
        let result = err.to_compile_error();
        let result_str = result.to_string();
        assert!(result_str.contains("compile_error"));
        assert!(result_str.contains("first"));
        assert!(result_str.contains("second"));
    }

    #[test]
    fn error_combine_three_errors() {
        let mut err = Error::new(Span::call_site(), "one");
        err.combine(Error::new(Span::call_site(), "two"));
        err.combine(Error::new(Span::call_site(), "three"));
        let result = err.to_compile_error();
        let result_str = result.to_string();
        assert!(result_str.contains("one"));
        assert!(result_str.contains("two"));
        assert!(result_str.contains("three"));
    }

    #[test]
    fn error_combine_preserves_span_per_message() {
        let span1 = Span::call_site();
        let span2 = Span::call_site();
        let mut err = Error::new(span1, "error at span1");
        err.combine(Error::new(span2, "error at span2"));
        let result = err.to_compile_error();
        let result_str = result.to_string();
        assert!(result_str.contains("error at span1"));
        assert!(result_str.contains("error at span2"));
    }

    #[test]
    fn error_special_characters_in_message() {
        let result = error(Span::call_site(), "expected <T> but found &[i32]");
        let result_str = result.to_string();
        assert!(result_str.contains("expected <T> but found &[i32]"));
    }

    #[test]
    fn error_quotes_in_message() {
        let result = error(Span::call_site(), "expected \"foo\"");
        let result_str = result.to_string();
        assert!(result_str.contains("expected \\\"foo\\\""));
    }

    #[test]
    fn error_backticks_in_message() {
        let result = error(Span::call_site(), "use `Foo` instead");
        let result_str = result.to_string();
        assert!(result_str.contains("use `Foo` instead"));
    }

    #[test]
    fn error_unicode_message() {
        let result = error(Span::call_site(), "héllo wörld");
        let result_str = result.to_string();
        assert!(result_str.contains("héllo wörld"));
    }

    #[test]
    fn error_emoji_message() {
        let result = error(Span::call_site(), "error 💔");
        let result_str = result.to_string();
        assert!(result_str.contains("💔"));
    }

    #[test]
    fn error_very_long_message() {
        let long_msg = "x".repeat(10000);
        let result = error(Span::call_site(), long_msg);
        let result_str = result.to_string();
        assert!(result_str.contains("xxxxx"));
    }

    #[test]
    fn error_single_char_message() {
        let result = error(Span::call_site(), "a");
        let result_str = result.to_string();
        assert!(result_str.contains("a"));
        assert!(result_str.contains("compile_error"));
    }

    #[test]
    fn error_new_with_string() {
        let err = Error::new(Span::call_site(), String::from("owned"));
        let result = err.to_compile_error();
        assert!(!result.is_empty());
    }

    #[test]
    fn error_with_colon_in_message() {
        let result = error(Span::call_site(), "help: did you mean?");
        let result_str = result.to_string();
        assert!(result_str.contains("help: did you mean?"));
    }

    #[test]
    fn error_with_arrow_in_message() {
        let result = error(Span::call_site(), "expected i32 -> Result");
        let result_str = result.to_string();
        assert!(result_str.contains("->"));
    }
}