Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/diagnostics/decl_check/case_conv.rs')
| -rw-r--r-- | crates/hir-ty/src/diagnostics/decl_check/case_conv.rs | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs b/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs new file mode 100644 index 0000000000..88d607194f --- /dev/null +++ b/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs @@ -0,0 +1,199 @@ +//! 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<String> { + 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<String>), 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<String> { + 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<String> { + 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<F: Fn(char) -> 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<F: Fn(&str) -> Option<String>>(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"]]); + } + + #[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"]]); + } +} |