From 1773c6d154abe5da00b31bb16139addcaa443bbb Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sat, 3 Oct 2020 14:35:26 +0300 Subject: Extract helper functions into a separate module --- .../src/diagnostics/decl_check/str_helpers.rs | 92 ++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs (limited to 'crates/hir_ty/src/diagnostics/decl_check') diff --git a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs new file mode 100644 index 000000000..3d8f1b5f2 --- /dev/null +++ b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs @@ -0,0 +1,92 @@ +pub fn to_camel_case(ident: &str) -> Option { + let mut output = String::new(); + + if is_camel_case(ident) { + return None; + } + + let mut capital_added = false; + for chr in ident.chars() { + if chr.is_alphabetic() { + if !capital_added { + output.push(chr.to_ascii_uppercase()); + capital_added = true; + } else { + output.push(chr.to_ascii_lowercase()); + } + } else if chr == '_' { + // Skip this character and make the next one capital. + capital_added = false; + } else { + // Put the characted as-is. + output.push(chr); + } + } + + if output == ident { + None + } else { + Some(output) + } +} + +pub fn to_lower_snake_case(ident: &str) -> Option { + // First, assume that it's UPPER_SNAKE_CASE. + if let Some(normalized) = to_lower_snake_case_from_upper_snake_case(ident) { + return Some(normalized); + } + + // Otherwise, assume that it's CamelCase. + let lower_snake_case = stdx::to_lower_snake_case(ident); + + if lower_snake_case == ident { + None + } else { + Some(lower_snake_case) + } +} + +fn to_lower_snake_case_from_upper_snake_case(ident: &str) -> Option { + if is_upper_snake_case(ident) { + let string = ident.chars().map(|c| c.to_ascii_lowercase()).collect(); + Some(string) + } else { + None + } +} + +fn is_upper_snake_case(ident: &str) -> bool { + ident.chars().all(|c| c.is_ascii_uppercase() || c == '_') +} + +fn is_camel_case(ident: &str) -> bool { + // We assume that the string is either snake case or camel case. + ident.chars().all(|c| c != '_') +} + +#[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, "CamelCase", expect![["camel_case"]]); + } + + #[test] + fn test_to_camel_case() { + check(to_camel_case, "CamelCase", expect![[""]]); + check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]); + check(to_camel_case, "UPPER_SNAKE_CASE", expect![["UpperSnakeCase"]]); + } +} -- cgit v1.2.3 From 17f1026c46e6e3797caf3c69737f66bd612c58e1 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sat, 3 Oct 2020 16:45:16 +0300 Subject: Improve string helpers functions --- crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'crates/hir_ty/src/diagnostics/decl_check') diff --git a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs index 3d8f1b5f2..953d0276f 100644 --- a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs +++ b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs @@ -61,7 +61,9 @@ fn is_upper_snake_case(ident: &str) -> bool { fn is_camel_case(ident: &str) -> bool { // We assume that the string is either snake case or camel case. - ident.chars().all(|c| c != '_') + // `_` is allowed only at the beginning or in the end of identifier, not between characters. + ident.trim_matches('_').chars().all(|c| c != '_') + && ident.chars().find(|c| c.is_alphabetic()).map(|c| c.is_ascii_uppercase()).unwrap_or(true) } #[cfg(test)] @@ -80,13 +82,18 @@ mod tests { 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"]]); } #[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, "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"]]); } } -- cgit v1.2.3 From 9ec1741b651bd13e4e5e6224f2e2c5c503846a6b Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sun, 4 Oct 2020 07:37:43 +0300 Subject: Refactor string helpers for decl_check module --- .../src/diagnostics/decl_check/str_helpers.rs | 129 ++++++++++++++++----- 1 file changed, 97 insertions(+), 32 deletions(-) (limited to 'crates/hir_ty/src/diagnostics/decl_check') diff --git a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs index 953d0276f..e3826909b 100644 --- a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs +++ b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs @@ -1,10 +1,74 @@ +#[derive(Debug)] +enum DetectedCase { + LowerCamelCase, + UpperCamelCase, + LowerSnakeCase, + UpperSnakeCase, + Unknown, +} + +fn detect_case(ident: &str) -> DetectedCase { + let trimmed_ident = ident.trim_matches('_'); + let first_lowercase = + trimmed_ident.chars().next().map(|chr| chr.is_ascii_lowercase()).unwrap_or(false); + let mut has_lowercase = first_lowercase; + let mut has_uppercase = false; + let mut has_underscore = false; + + for chr in trimmed_ident.chars() { + if chr == '_' { + has_underscore = true; + } else if chr.is_ascii_uppercase() { + has_uppercase = true; + } else if chr.is_ascii_lowercase() { + has_lowercase = true; + } + } + + if has_uppercase { + if !has_lowercase { + DetectedCase::UpperSnakeCase + } else if !has_underscore { + if first_lowercase { + DetectedCase::LowerCamelCase + } else { + DetectedCase::UpperCamelCase + } + } else { + // It has uppercase, it has lowercase, it has underscore. + // No assumptions here + DetectedCase::Unknown + } + } else { + DetectedCase::LowerSnakeCase + } +} + pub fn to_camel_case(ident: &str) -> Option { - let mut output = String::new(); + let detected_case = detect_case(ident); - if is_camel_case(ident) { - return None; + match detected_case { + DetectedCase::UpperCamelCase => return None, + DetectedCase::LowerCamelCase => { + let mut first_capitalized = false; + let output = ident + .chars() + .map(|chr| { + if !first_capitalized && chr.is_ascii_lowercase() { + first_capitalized = true; + chr.to_ascii_uppercase() + } else { + chr + } + }) + .collect(); + return Some(output); + } + _ => {} } + let mut output = String::with_capacity(ident.len()); + let mut capital_added = false; for chr in ident.chars() { if chr.is_alphabetic() { @@ -23,47 +87,37 @@ pub fn to_camel_case(ident: &str) -> Option { } } - if output == ident { - None - } else { - Some(output) - } + Some(output) } pub fn to_lower_snake_case(ident: &str) -> Option { // First, assume that it's UPPER_SNAKE_CASE. - if let Some(normalized) = to_lower_snake_case_from_upper_snake_case(ident) { - return Some(normalized); + match detect_case(ident) { + DetectedCase::LowerSnakeCase => return None, + DetectedCase::UpperSnakeCase => { + return Some(ident.chars().map(|chr| chr.to_ascii_lowercase()).collect()) + } + _ => {} } // Otherwise, assume that it's CamelCase. let lower_snake_case = stdx::to_lower_snake_case(ident); - - if lower_snake_case == ident { - None - } else { - Some(lower_snake_case) - } + Some(lower_snake_case) } -fn to_lower_snake_case_from_upper_snake_case(ident: &str) -> Option { - if is_upper_snake_case(ident) { - let string = ident.chars().map(|c| c.to_ascii_lowercase()).collect(); - Some(string) - } else { - None +pub fn to_upper_snake_case(ident: &str) -> Option { + match detect_case(ident) { + DetectedCase::UpperSnakeCase => return None, + DetectedCase::LowerSnakeCase => { + return Some(ident.chars().map(|chr| chr.to_ascii_uppercase()).collect()) + } + _ => {} } -} - -fn is_upper_snake_case(ident: &str) -> bool { - ident.chars().all(|c| c.is_ascii_uppercase() || c == '_') -} -fn is_camel_case(ident: &str) -> bool { - // We assume that the string is either snake case or camel case. - // `_` is allowed only at the beginning or in the end of identifier, not between characters. - ident.trim_matches('_').chars().all(|c| c != '_') - && ident.chars().find(|c| c.is_alphabetic()).map(|c| c.is_ascii_uppercase()).unwrap_or(true) + // Normalize the string from whatever form it's in currently, and then just make it uppercase. + let upper_snake_case = + stdx::to_lower_snake_case(ident).chars().map(|c| c.to_ascii_uppercase()).collect(); + Some(upper_snake_case) } #[cfg(test)] @@ -84,6 +138,7 @@ mod tests { 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"]]); } #[test] @@ -91,9 +146,19 @@ mod tests { 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"]]); } + + #[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"]]); + } } -- cgit v1.2.3 From 45ac2b2edec05e417124ebfc2e61ec2a5117f4d5 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sun, 4 Oct 2020 08:53:34 +0300 Subject: Code style adjustments --- .../src/diagnostics/decl_check/str_helpers.rs | 38 ++++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) (limited to 'crates/hir_ty/src/diagnostics/decl_check') diff --git a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs index e3826909b..8f70c5e84 100644 --- a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs +++ b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs @@ -1,3 +1,6 @@ +//! Functions for string case manipulation, such as detecting the identifier case, +//! and converting it into appropriate form. + #[derive(Debug)] enum DetectedCase { LowerCamelCase, @@ -44,6 +47,8 @@ fn detect_case(ident: &str) -> DetectedCase { } } +/// Converts an identifier to an UpperCamelCase form. +/// Returns `None` if the string is already is UpperCamelCase. pub fn to_camel_case(ident: &str) -> Option { let detected_case = detect_case(ident); @@ -87,9 +92,17 @@ pub fn to_camel_case(ident: &str) -> Option { } } - Some(output) + if output == ident { + // While we didn't detect the correct case at the beginning, there + // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE. + None + } else { + Some(output) + } } +/// Converts an identifier to a lower_snake_case form. +/// Returns `None` if the string is already is lower_snake_case. pub fn to_lower_snake_case(ident: &str) -> Option { // First, assume that it's UPPER_SNAKE_CASE. match detect_case(ident) { @@ -102,9 +115,18 @@ pub fn to_lower_snake_case(ident: &str) -> Option { // Otherwise, assume that it's CamelCase. let lower_snake_case = stdx::to_lower_snake_case(ident); - Some(lower_snake_case) + + if lower_snake_case == ident { + // While we didn't detect the correct case at the beginning, there + // may be special cases: e.g. `a` is both valid camelCase and snake_case. + None + } else { + Some(lower_snake_case) + } } +/// Converts an identifier to an UPPER_SNAKE_CASE form. +/// Returns `None` if the string is already is UPPER_SNAKE_CASE. pub fn to_upper_snake_case(ident: &str) -> Option { match detect_case(ident) { DetectedCase::UpperSnakeCase => return None, @@ -117,7 +139,14 @@ pub fn to_upper_snake_case(ident: &str) -> Option { // Normalize the string from whatever form it's in currently, and then just make it uppercase. let upper_snake_case = stdx::to_lower_snake_case(ident).chars().map(|c| c.to_ascii_uppercase()).collect(); - Some(upper_snake_case) + + if upper_snake_case == ident { + // While we didn't detect the correct case at the beginning, there + // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE. + None + } else { + Some(upper_snake_case) + } } #[cfg(test)] @@ -139,6 +168,7 @@ mod tests { 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![[""]]); } #[test] @@ -151,6 +181,7 @@ mod tests { 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![[""]]); } #[test] @@ -160,5 +191,6 @@ mod tests { 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![[""]]); } } -- cgit v1.2.3 From f2c91fc5a8c22b8ac80f100b2b666f2dc9baa67c Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Mon, 5 Oct 2020 19:34:23 +0300 Subject: Apply suggestions from code review Co-authored-by: Lukas Wirth --- crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'crates/hir_ty/src/diagnostics/decl_check') diff --git a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs index 8f70c5e84..c1ab1a675 100644 --- a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs +++ b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs @@ -13,7 +13,7 @@ enum DetectedCase { fn detect_case(ident: &str) -> DetectedCase { let trimmed_ident = ident.trim_matches('_'); let first_lowercase = - trimmed_ident.chars().next().map(|chr| chr.is_ascii_lowercase()).unwrap_or(false); + trimmed_ident.starts_with(|chr| chr.is_ascii_lowercase()); let mut has_lowercase = first_lowercase; let mut has_uppercase = false; let mut has_underscore = false; @@ -102,7 +102,7 @@ pub fn to_camel_case(ident: &str) -> Option { } /// Converts an identifier to a lower_snake_case form. -/// Returns `None` if the string is already is lower_snake_case. +/// Returns `None` if the string is already in lower_snake_case. pub fn to_lower_snake_case(ident: &str) -> Option { // First, assume that it's UPPER_SNAKE_CASE. match detect_case(ident) { -- cgit v1.2.3 From ebd30033b3743fafe0a0182b5ae34ffb27fe43ff Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Mon, 5 Oct 2020 20:35:52 +0300 Subject: Fix compilation error --- crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'crates/hir_ty/src/diagnostics/decl_check') diff --git a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs index c1ab1a675..2e1468c4c 100644 --- a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs +++ b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs @@ -12,8 +12,7 @@ enum DetectedCase { fn detect_case(ident: &str) -> DetectedCase { let trimmed_ident = ident.trim_matches('_'); - let first_lowercase = - trimmed_ident.starts_with(|chr| chr.is_ascii_lowercase()); + let first_lowercase = trimmed_ident.starts_with(|chr: char| chr.is_ascii_lowercase()); let mut has_lowercase = first_lowercase; let mut has_uppercase = false; let mut has_underscore = false; -- cgit v1.2.3 From 559cc970732d80e3ec624c20da4f8aac219d6b2e Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Thu, 8 Oct 2020 08:33:35 +0300 Subject: Add to_upper_snake_case function to stdx --- .../hir_ty/src/diagnostics/decl_check/case_conv.rs | 194 ++++++++++++++++++++ .../src/diagnostics/decl_check/str_helpers.rs | 195 --------------------- 2 files changed, 194 insertions(+), 195 deletions(-) create mode 100644 crates/hir_ty/src/diagnostics/decl_check/case_conv.rs delete mode 100644 crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs (limited to 'crates/hir_ty/src/diagnostics/decl_check') 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 000000000..3800f2a6b --- /dev/null +++ b/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs @@ -0,0 +1,194 @@ +//! Functions for string case manipulation, such as detecting the identifier case, +//! and converting it into appropriate form. + +#[derive(Debug)] +enum DetectedCase { + LowerCamelCase, + UpperCamelCase, + LowerSnakeCase, + UpperSnakeCase, + Unknown, +} + +fn detect_case(ident: &str) -> DetectedCase { + let trimmed_ident = ident.trim_matches('_'); + let first_lowercase = trimmed_ident.starts_with(|chr: char| chr.is_ascii_lowercase()); + let mut has_lowercase = first_lowercase; + let mut has_uppercase = false; + let mut has_underscore = false; + + for chr in trimmed_ident.chars() { + if chr == '_' { + has_underscore = true; + } else if chr.is_ascii_uppercase() { + has_uppercase = true; + } else if chr.is_ascii_lowercase() { + has_lowercase = true; + } + } + + if has_uppercase { + if !has_lowercase { + DetectedCase::UpperSnakeCase + } else if !has_underscore { + if first_lowercase { + DetectedCase::LowerCamelCase + } else { + DetectedCase::UpperCamelCase + } + } else { + // It has uppercase, it has lowercase, it has underscore. + // No assumptions here + DetectedCase::Unknown + } + } else { + DetectedCase::LowerSnakeCase + } +} + +/// Converts an identifier to an UpperCamelCase form. +/// Returns `None` if the string is already is UpperCamelCase. +pub fn to_camel_case(ident: &str) -> Option { + let detected_case = detect_case(ident); + + match detected_case { + DetectedCase::UpperCamelCase => return None, + DetectedCase::LowerCamelCase => { + let mut first_capitalized = false; + let output = ident + .chars() + .map(|chr| { + if !first_capitalized && chr.is_ascii_lowercase() { + first_capitalized = true; + chr.to_ascii_uppercase() + } else { + chr + } + }) + .collect(); + return Some(output); + } + _ => {} + } + + let mut output = String::with_capacity(ident.len()); + + let mut capital_added = false; + for chr in ident.chars() { + if chr.is_alphabetic() { + if !capital_added { + output.push(chr.to_ascii_uppercase()); + capital_added = true; + } else { + output.push(chr.to_ascii_lowercase()); + } + } else if chr == '_' { + // Skip this character and make the next one capital. + capital_added = false; + } else { + // Put the characted as-is. + output.push(chr); + } + } + + if output == ident { + // While we didn't detect the correct case at the beginning, there + // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE. + None + } else { + Some(output) + } +} + +/// Converts an identifier to a lower_snake_case form. +/// Returns `None` if the string is already in lower_snake_case. +pub fn to_lower_snake_case(ident: &str) -> Option { + // First, assume that it's UPPER_SNAKE_CASE. + match detect_case(ident) { + DetectedCase::LowerSnakeCase => return None, + DetectedCase::UpperSnakeCase => { + return Some(ident.chars().map(|chr| chr.to_ascii_lowercase()).collect()) + } + _ => {} + } + + // Otherwise, assume that it's CamelCase. + let lower_snake_case = stdx::to_lower_snake_case(ident); + + if lower_snake_case == ident { + // While we didn't detect the correct case at the beginning, there + // may be special cases: e.g. `a` is both valid camelCase and snake_case. + None + } else { + Some(lower_snake_case) + } +} + +/// Converts an identifier to an UPPER_SNAKE_CASE form. +/// Returns `None` if the string is already is UPPER_SNAKE_CASE. +pub fn to_upper_snake_case(ident: &str) -> Option { + match detect_case(ident) { + DetectedCase::UpperSnakeCase => return None, + DetectedCase::LowerSnakeCase => { + return Some(ident.chars().map(|chr| chr.to_ascii_uppercase()).collect()) + } + _ => {} + } + + // Normalize the string from whatever form it's in currently, and then just make it uppercase. + let upper_snake_case = stdx::to_upper_snake_case(ident); + + if upper_snake_case == ident { + // While we didn't detect the correct case at the beginning, there + // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE. + None + } else { + Some(upper_snake_case) + } +} + +#[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![[""]]); + } + + #[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![[""]]); + } + + #[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![[""]]); + } +} diff --git a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs b/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs deleted file mode 100644 index 2e1468c4c..000000000 --- a/crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! Functions for string case manipulation, such as detecting the identifier case, -//! and converting it into appropriate form. - -#[derive(Debug)] -enum DetectedCase { - LowerCamelCase, - UpperCamelCase, - LowerSnakeCase, - UpperSnakeCase, - Unknown, -} - -fn detect_case(ident: &str) -> DetectedCase { - let trimmed_ident = ident.trim_matches('_'); - let first_lowercase = trimmed_ident.starts_with(|chr: char| chr.is_ascii_lowercase()); - let mut has_lowercase = first_lowercase; - let mut has_uppercase = false; - let mut has_underscore = false; - - for chr in trimmed_ident.chars() { - if chr == '_' { - has_underscore = true; - } else if chr.is_ascii_uppercase() { - has_uppercase = true; - } else if chr.is_ascii_lowercase() { - has_lowercase = true; - } - } - - if has_uppercase { - if !has_lowercase { - DetectedCase::UpperSnakeCase - } else if !has_underscore { - if first_lowercase { - DetectedCase::LowerCamelCase - } else { - DetectedCase::UpperCamelCase - } - } else { - // It has uppercase, it has lowercase, it has underscore. - // No assumptions here - DetectedCase::Unknown - } - } else { - DetectedCase::LowerSnakeCase - } -} - -/// Converts an identifier to an UpperCamelCase form. -/// Returns `None` if the string is already is UpperCamelCase. -pub fn to_camel_case(ident: &str) -> Option { - let detected_case = detect_case(ident); - - match detected_case { - DetectedCase::UpperCamelCase => return None, - DetectedCase::LowerCamelCase => { - let mut first_capitalized = false; - let output = ident - .chars() - .map(|chr| { - if !first_capitalized && chr.is_ascii_lowercase() { - first_capitalized = true; - chr.to_ascii_uppercase() - } else { - chr - } - }) - .collect(); - return Some(output); - } - _ => {} - } - - let mut output = String::with_capacity(ident.len()); - - let mut capital_added = false; - for chr in ident.chars() { - if chr.is_alphabetic() { - if !capital_added { - output.push(chr.to_ascii_uppercase()); - capital_added = true; - } else { - output.push(chr.to_ascii_lowercase()); - } - } else if chr == '_' { - // Skip this character and make the next one capital. - capital_added = false; - } else { - // Put the characted as-is. - output.push(chr); - } - } - - if output == ident { - // While we didn't detect the correct case at the beginning, there - // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE. - None - } else { - Some(output) - } -} - -/// Converts an identifier to a lower_snake_case form. -/// Returns `None` if the string is already in lower_snake_case. -pub fn to_lower_snake_case(ident: &str) -> Option { - // First, assume that it's UPPER_SNAKE_CASE. - match detect_case(ident) { - DetectedCase::LowerSnakeCase => return None, - DetectedCase::UpperSnakeCase => { - return Some(ident.chars().map(|chr| chr.to_ascii_lowercase()).collect()) - } - _ => {} - } - - // Otherwise, assume that it's CamelCase. - let lower_snake_case = stdx::to_lower_snake_case(ident); - - if lower_snake_case == ident { - // While we didn't detect the correct case at the beginning, there - // may be special cases: e.g. `a` is both valid camelCase and snake_case. - None - } else { - Some(lower_snake_case) - } -} - -/// Converts an identifier to an UPPER_SNAKE_CASE form. -/// Returns `None` if the string is already is UPPER_SNAKE_CASE. -pub fn to_upper_snake_case(ident: &str) -> Option { - match detect_case(ident) { - DetectedCase::UpperSnakeCase => return None, - DetectedCase::LowerSnakeCase => { - return Some(ident.chars().map(|chr| chr.to_ascii_uppercase()).collect()) - } - _ => {} - } - - // Normalize the string from whatever form it's in currently, and then just make it uppercase. - let upper_snake_case = - stdx::to_lower_snake_case(ident).chars().map(|c| c.to_ascii_uppercase()).collect(); - - if upper_snake_case == ident { - // While we didn't detect the correct case at the beginning, there - // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE. - None - } else { - Some(upper_snake_case) - } -} - -#[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![[""]]); - } - - #[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![[""]]); - } - - #[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![[""]]); - } -} -- cgit v1.2.3