Skip to content

Commit 9ec1741

Browse files
committed
Refactor string helpers for decl_check module
1 parent fb96bba commit 9ec1741

File tree

1 file changed

+97
-32
lines changed

1 file changed

+97
-32
lines changed
Lines changed: 97 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,74 @@
1+
#[derive(Debug)]
2+
enum DetectedCase {
3+
LowerCamelCase,
4+
UpperCamelCase,
5+
LowerSnakeCase,
6+
UpperSnakeCase,
7+
Unknown,
8+
}
9+
10+
fn detect_case(ident: &str) -> DetectedCase {
11+
let trimmed_ident = ident.trim_matches('_');
12+
let first_lowercase =
13+
trimmed_ident.chars().next().map(|chr| chr.is_ascii_lowercase()).unwrap_or(false);
14+
let mut has_lowercase = first_lowercase;
15+
let mut has_uppercase = false;
16+
let mut has_underscore = false;
17+
18+
for chr in trimmed_ident.chars() {
19+
if chr == '_' {
20+
has_underscore = true;
21+
} else if chr.is_ascii_uppercase() {
22+
has_uppercase = true;
23+
} else if chr.is_ascii_lowercase() {
24+
has_lowercase = true;
25+
}
26+
}
27+
28+
if has_uppercase {
29+
if !has_lowercase {
30+
DetectedCase::UpperSnakeCase
31+
} else if !has_underscore {
32+
if first_lowercase {
33+
DetectedCase::LowerCamelCase
34+
} else {
35+
DetectedCase::UpperCamelCase
36+
}
37+
} else {
38+
// It has uppercase, it has lowercase, it has underscore.
39+
// No assumptions here
40+
DetectedCase::Unknown
41+
}
42+
} else {
43+
DetectedCase::LowerSnakeCase
44+
}
45+
}
46+
147
pub fn to_camel_case(ident: &str) -> Option<String> {
2-
let mut output = String::new();
48+
let detected_case = detect_case(ident);
349

4-
if is_camel_case(ident) {
5-
return None;
50+
match detected_case {
51+
DetectedCase::UpperCamelCase => return None,
52+
DetectedCase::LowerCamelCase => {
53+
let mut first_capitalized = false;
54+
let output = ident
55+
.chars()
56+
.map(|chr| {
57+
if !first_capitalized && chr.is_ascii_lowercase() {
58+
first_capitalized = true;
59+
chr.to_ascii_uppercase()
60+
} else {
61+
chr
62+
}
63+
})
64+
.collect();
65+
return Some(output);
66+
}
67+
_ => {}
668
}
769

70+
let mut output = String::with_capacity(ident.len());
71+
872
let mut capital_added = false;
973
for chr in ident.chars() {
1074
if chr.is_alphabetic() {
@@ -23,47 +87,37 @@ pub fn to_camel_case(ident: &str) -> Option<String> {
2387
}
2488
}
2589

26-
if output == ident {
27-
None
28-
} else {
29-
Some(output)
30-
}
90+
Some(output)
3191
}
3292

3393
pub fn to_lower_snake_case(ident: &str) -> Option<String> {
3494
// First, assume that it's UPPER_SNAKE_CASE.
35-
if let Some(normalized) = to_lower_snake_case_from_upper_snake_case(ident) {
36-
return Some(normalized);
95+
match detect_case(ident) {
96+
DetectedCase::LowerSnakeCase => return None,
97+
DetectedCase::UpperSnakeCase => {
98+
return Some(ident.chars().map(|chr| chr.to_ascii_lowercase()).collect())
99+
}
100+
_ => {}
37101
}
38102

39103
// Otherwise, assume that it's CamelCase.
40104
let lower_snake_case = stdx::to_lower_snake_case(ident);
41-
42-
if lower_snake_case == ident {
43-
None
44-
} else {
45-
Some(lower_snake_case)
46-
}
105+
Some(lower_snake_case)
47106
}
48107

49-
fn to_lower_snake_case_from_upper_snake_case(ident: &str) -> Option<String> {
50-
if is_upper_snake_case(ident) {
51-
let string = ident.chars().map(|c| c.to_ascii_lowercase()).collect();
52-
Some(string)
53-
} else {
54-
None
108+
pub fn to_upper_snake_case(ident: &str) -> Option<String> {
109+
match detect_case(ident) {
110+
DetectedCase::UpperSnakeCase => return None,
111+
DetectedCase::LowerSnakeCase => {
112+
return Some(ident.chars().map(|chr| chr.to_ascii_uppercase()).collect())
113+
}
114+
_ => {}
55115
}
56-
}
57-
58-
fn is_upper_snake_case(ident: &str) -> bool {
59-
ident.chars().all(|c| c.is_ascii_uppercase() || c == '_')
60-
}
61116

62-
fn is_camel_case(ident: &str) -> bool {
63-
// We assume that the string is either snake case or camel case.
64-
// `_` is allowed only at the beginning or in the end of identifier, not between characters.
65-
ident.trim_matches('_').chars().all(|c| c != '_')
66-
&& ident.chars().find(|c| c.is_alphabetic()).map(|c| c.is_ascii_uppercase()).unwrap_or(true)
117+
// Normalize the string from whatever form it's in currently, and then just make it uppercase.
118+
let upper_snake_case =
119+
stdx::to_lower_snake_case(ident).chars().map(|c| c.to_ascii_uppercase()).collect();
120+
Some(upper_snake_case)
67121
}
68122

69123
#[cfg(test)]
@@ -84,16 +138,27 @@ mod tests {
84138
check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]);
85139
check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]);
86140
check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]);
141+
check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]);
87142
}
88143

89144
#[test]
90145
fn test_to_camel_case() {
91146
check(to_camel_case, "CamelCase", expect![[""]]);
92147
check(to_camel_case, "CamelCase_", expect![[""]]);
93148
check(to_camel_case, "_CamelCase", expect![[""]]);
149+
check(to_camel_case, "lowerCamelCase", expect![["LowerCamelCase"]]);
94150
check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]);
95151
check(to_camel_case, "UPPER_SNAKE_CASE", expect![["UpperSnakeCase"]]);
96152
check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]);
97153
check(to_camel_case, "name", expect![["Name"]]);
98154
}
155+
156+
#[test]
157+
fn test_to_upper_snake_case() {
158+
check(to_upper_snake_case, "UPPER_SNAKE_CASE", expect![[""]]);
159+
check(to_upper_snake_case, "lower_snake_case", expect![["LOWER_SNAKE_CASE"]]);
160+
check(to_upper_snake_case, "Weird_Case", expect![["WEIRD_CASE"]]);
161+
check(to_upper_snake_case, "CamelCase", expect![["CAMEL_CASE"]]);
162+
check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]);
163+
}
99164
}

0 commit comments

Comments
 (0)