use proc_macro2::{Ident, Span}; use quote::ToTokens; pub fn escape_test_name(input: impl AsRef) -> Ident { if input.as_ref().is_empty() { return Ident::new("_empty", Span::call_site()); } let mut last_under = false; let mut ident: String = input .as_ref() .to_ascii_lowercase() .chars() .filter_map(|c| match c { c if c.is_alphanumeric() => { last_under = false; Some(c.to_ascii_lowercase()) } _ if !last_under => { last_under = true; Some('_') } _ => None, }) .collect(); if !ident.starts_with(|c: char| c == '_' || c.is_ascii_alphabetic()) { ident = format!("_{}", ident); } Ident::new(&ident, Span::call_site()) } pub fn fmt_syn(syn: &(impl ToTokens + Clone)) -> String { syn.clone().into_token_stream().to_string() } #[cfg(test)] mod tests { use super::*; mod escape_test_name { use super::*; #[test] fn converts_arbitrary_test_names() { assert_eq!( escape_test_name("word"), Ident::new("word", Span::call_site()) ); assert_eq!( escape_test_name("a simple sentence"), Ident::new("a_simple_sentence", Span::call_site()) ); assert_eq!( escape_test_name("extra spaces inbetween"), Ident::new("extra_spaces_inbetween", Span::call_site()) ); assert_eq!( escape_test_name(" extra end and start spaces "), Ident::new("_extra_end_and_start_spaces_", Span::call_site()) ); assert_eq!( escape_test_name("abcdefghijklmnoqprstuwvxyz1234567890"), Ident::new("abcdefghijklmnoqprstuwvxyz1234567890", Span::call_site()) ); } #[test] fn converts_to_lowercase() { assert_eq!( escape_test_name("ALL UPPER"), Ident::new("all_upper", Span::call_site()) ); assert_eq!( escape_test_name("MiXeD CaSe"), Ident::new("mixed_case", Span::call_site()) ); } #[test] fn handles_numeric_first_char() { assert_eq!( escape_test_name("1test"), Ident::new("_1test", Span::call_site()) ); } #[test] fn omits_unicode() { assert_eq!( escape_test_name("fromāŸ¶to"), Ident::new("from_to", Span::call_site()) ); } #[test] fn handles_empty_input() { assert_eq!( escape_test_name(""), Ident::new("_empty", Span::call_site()) ); } } }