//! Functions for string case manipulation, such as detecting the identifier case, //! and converting it into appropriate form. // Code that was taken from rustc was taken at commit 89fdb30, // from file /compiler/rustc_lint/src/nonstandard_style.rs /// Converts an identifier to an UpperCamelCase form. /// Returns `None` if the string is already in UpperCamelCase. pub(crate) fn to_camel_case(ident: &str) -> Option { if is_camel_case(ident) { return None; } // Taken from rustc. let ret = ident .trim_matches('_') .split('_') .filter(|component| !component.is_empty()) .map(|component| { let mut camel_cased_component = String::with_capacity(component.len()); let mut new_word = true; let mut prev_is_lower_case = true; for c in component.chars() { // Preserve the case if an uppercase letter follows a lowercase letter, so that // `camelCase` is converted to `CamelCase`. if prev_is_lower_case && c.is_uppercase() { new_word = true; } if new_word { camel_cased_component.extend(c.to_uppercase()); } else { camel_cased_component.extend(c.to_lowercase()); } prev_is_lower_case = c.is_lowercase(); new_word = false; } camel_cased_component }) .fold((String::new(), None), |(acc, prev): (_, Option), next| { // separate two components with an underscore if their boundary cannot // be distinguished using an uppercase/lowercase case distinction let join = prev .and_then(|prev| { let f = next.chars().next()?; let l = prev.chars().last()?; Some(!char_has_case(l) && !char_has_case(f)) }) .unwrap_or(false); (acc + if join { "_" } else { "" } + &next, Some(next)) }) .0; Some(ret) } /// Converts an identifier to a lower_snake_case form. /// Returns `None` if the string is already in lower_snake_case. pub(crate) fn to_lower_snake_case(ident: &str) -> Option { if is_lower_snake_case(ident) { return None; } else if is_upper_snake_case(ident) { return Some(ident.to_lowercase()); } Some(stdx::to_lower_snake_case(ident)) } /// Converts an identifier to an UPPER_SNAKE_CASE form. /// Returns `None` if the string is already is UPPER_SNAKE_CASE. pub(crate) fn to_upper_snake_case(ident: &str) -> Option { if is_upper_snake_case(ident) { return None; } else if is_lower_snake_case(ident) { return Some(ident.to_uppercase()); } Some(stdx::to_upper_snake_case(ident)) } // Taken from rustc. // Modified by replacing the use of unstable feature `array_windows`. fn is_camel_case(name: &str) -> bool { let name = name.trim_matches('_'); if name.is_empty() { return true; } let mut fst = None; // start with a non-lowercase letter rather than non-uppercase // ones (some scripts don't have a concept of upper/lowercase) name.chars().next().map_or(true, |c| !c.is_lowercase()) && !name.contains("__") && !name.chars().any(|snd| { let ret = match fst { None => false, Some(fst) => char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_', }; fst = Some(snd); ret }) } fn is_lower_snake_case(ident: &str) -> bool { is_snake_case(ident, char::is_uppercase) } fn is_upper_snake_case(ident: &str) -> bool { is_snake_case(ident, char::is_lowercase) } // Taken from rustc. // Modified to allow checking for both upper and lower snake case. fn is_snake_case bool>(ident: &str, wrong_case: F) -> bool { if ident.is_empty() { return true; } let ident = ident.trim_matches('_'); let mut allow_underscore = true; ident.chars().all(|c| { allow_underscore = match c { '_' if !allow_underscore => return false, '_' => false, // It would be more obvious to check for the correct case, // but some characters do not have a case. c if !wrong_case(c) => true, _ => return false, }; true }) } // Taken from rustc. fn char_has_case(c: char) -> bool { c.is_lowercase() || c.is_uppercase() } #[cfg(test)] mod tests { use super::*; use expect_test::{expect, Expect}; fn check Option>(fun: F, input: &str, expect: Expect) { // `None` is translated to empty string, meaning that there is nothing to fix. let output = fun(input).unwrap_or_default(); expect.assert_eq(&output); } #[test] fn test_to_lower_snake_case() { check(to_lower_snake_case, "lower_snake_case", expect![[""]]); check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]); check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]); check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]); check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]); check(to_lower_snake_case, "a", expect![[""]]); check(to_lower_snake_case, "abc", expect![[""]]); check(to_lower_snake_case, "foo__bar", expect![["foo_bar"]]); check(to_lower_snake_case, "Δ", expect!["δ"]); } #[test] fn test_to_camel_case() { check(to_camel_case, "CamelCase", expect![[""]]); check(to_camel_case, "CamelCase_", expect![[""]]); check(to_camel_case, "_CamelCase", expect![[""]]); check(to_camel_case, "lowerCamelCase", expect![["LowerCamelCase"]]); check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]); check(to_camel_case, "UPPER_SNAKE_CASE", expect![["UpperSnakeCase"]]); check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]); check(to_camel_case, "name", expect![["Name"]]); check(to_camel_case, "A", expect![[""]]); check(to_camel_case, "AABB", expect![[""]]); // Taken from rustc: /compiler/rustc_lint/src/nonstandard_style/tests.rs check(to_camel_case, "X86_64", expect![[""]]); check(to_camel_case, "x86__64", expect![["X86_64"]]); check(to_camel_case, "Abc_123", expect![["Abc123"]]); check(to_camel_case, "A1_b2_c3", expect![["A1B2C3"]]); } #[test] fn test_to_upper_snake_case() { check(to_upper_snake_case, "UPPER_SNAKE_CASE", expect![[""]]); check(to_upper_snake_case, "lower_snake_case", expect![["LOWER_SNAKE_CASE"]]); check(to_upper_snake_case, "Weird_Case", expect![["WEIRD_CASE"]]); check(to_upper_snake_case, "CamelCase", expect![["CAMEL_CASE"]]); check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]); check(to_upper_snake_case, "A", expect![[""]]); check(to_upper_snake_case, "ABC", expect![[""]]); check(to_upper_snake_case, "X86_64", expect![[""]]); check(to_upper_snake_case, "FOO_BAr", expect![["FOO_BAR"]]); check(to_upper_snake_case, "FOO__BAR", expect![["FOO_BAR"]]); check(to_upper_snake_case, "ß", expect!["SS"]); } }