diff options
-rw-r--r-- | crates/hir_ty/src/diagnostics/decl_check/str_helpers.rs | 129 |
1 files changed, 97 insertions, 32 deletions
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 @@ | |||
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 | |||
1 | pub fn to_camel_case(ident: &str) -> Option<String> { | 47 | pub fn to_camel_case(ident: &str) -> Option<String> { |
2 | let mut output = String::new(); | 48 | let detected_case = detect_case(ident); |
3 | 49 | ||
4 | if is_camel_case(ident) { | 50 | match detected_case { |
5 | return None; | 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 | _ => {} | ||
6 | } | 68 | } |
7 | 69 | ||
70 | let mut output = String::with_capacity(ident.len()); | ||
71 | |||
8 | let mut capital_added = false; | 72 | let mut capital_added = false; |
9 | for chr in ident.chars() { | 73 | for chr in ident.chars() { |
10 | if chr.is_alphabetic() { | 74 | if chr.is_alphabetic() { |
@@ -23,47 +87,37 @@ pub fn to_camel_case(ident: &str) -> Option<String> { | |||
23 | } | 87 | } |
24 | } | 88 | } |
25 | 89 | ||
26 | if output == ident { | 90 | Some(output) |
27 | None | ||
28 | } else { | ||
29 | Some(output) | ||
30 | } | ||
31 | } | 91 | } |
32 | 92 | ||
33 | pub fn to_lower_snake_case(ident: &str) -> Option<String> { | 93 | pub fn to_lower_snake_case(ident: &str) -> Option<String> { |
34 | // First, assume that it's UPPER_SNAKE_CASE. | 94 | // First, assume that it's UPPER_SNAKE_CASE. |
35 | if let Some(normalized) = to_lower_snake_case_from_upper_snake_case(ident) { | 95 | match detect_case(ident) { |
36 | return Some(normalized); | 96 | DetectedCase::LowerSnakeCase => return None, |
97 | DetectedCase::UpperSnakeCase => { | ||
98 | return Some(ident.chars().map(|chr| chr.to_ascii_lowercase()).collect()) | ||
99 | } | ||
100 | _ => {} | ||
37 | } | 101 | } |
38 | 102 | ||
39 | // Otherwise, assume that it's CamelCase. | 103 | // Otherwise, assume that it's CamelCase. |
40 | let lower_snake_case = stdx::to_lower_snake_case(ident); | 104 | let lower_snake_case = stdx::to_lower_snake_case(ident); |
41 | 105 | Some(lower_snake_case) | |
42 | if lower_snake_case == ident { | ||
43 | None | ||
44 | } else { | ||
45 | Some(lower_snake_case) | ||
46 | } | ||
47 | } | 106 | } |
48 | 107 | ||
49 | fn to_lower_snake_case_from_upper_snake_case(ident: &str) -> Option<String> { | 108 | pub fn to_upper_snake_case(ident: &str) -> Option<String> { |
50 | if is_upper_snake_case(ident) { | 109 | match detect_case(ident) { |
51 | let string = ident.chars().map(|c| c.to_ascii_lowercase()).collect(); | 110 | DetectedCase::UpperSnakeCase => return None, |
52 | Some(string) | 111 | DetectedCase::LowerSnakeCase => { |
53 | } else { | 112 | return Some(ident.chars().map(|chr| chr.to_ascii_uppercase()).collect()) |
54 | None | 113 | } |
114 | _ => {} | ||
55 | } | 115 | } |
56 | } | ||
57 | |||
58 | fn is_upper_snake_case(ident: &str) -> bool { | ||
59 | ident.chars().all(|c| c.is_ascii_uppercase() || c == '_') | ||
60 | } | ||
61 | 116 | ||
62 | fn is_camel_case(ident: &str) -> bool { | 117 | // Normalize the string from whatever form it's in currently, and then just make it uppercase. |
63 | // We assume that the string is either snake case or camel case. | 118 | let upper_snake_case = |
64 | // `_` is allowed only at the beginning or in the end of identifier, not between characters. | 119 | stdx::to_lower_snake_case(ident).chars().map(|c| c.to_ascii_uppercase()).collect(); |
65 | ident.trim_matches('_').chars().all(|c| c != '_') | 120 | Some(upper_snake_case) |
66 | && ident.chars().find(|c| c.is_alphabetic()).map(|c| c.is_ascii_uppercase()).unwrap_or(true) | ||
67 | } | 121 | } |
68 | 122 | ||
69 | #[cfg(test)] | 123 | #[cfg(test)] |
@@ -84,6 +138,7 @@ mod tests { | |||
84 | check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]); | 138 | check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]); |
85 | check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]); | 139 | check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]); |
86 | check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]); | 140 | check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]); |
141 | check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]); | ||
87 | } | 142 | } |
88 | 143 | ||
89 | #[test] | 144 | #[test] |
@@ -91,9 +146,19 @@ mod tests { | |||
91 | check(to_camel_case, "CamelCase", expect![[""]]); | 146 | check(to_camel_case, "CamelCase", expect![[""]]); |
92 | check(to_camel_case, "CamelCase_", expect![[""]]); | 147 | check(to_camel_case, "CamelCase_", expect![[""]]); |
93 | check(to_camel_case, "_CamelCase", expect![[""]]); | 148 | check(to_camel_case, "_CamelCase", expect![[""]]); |
149 | check(to_camel_case, "lowerCamelCase", expect![["LowerCamelCase"]]); | ||
94 | check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]); | 150 | check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]); |
95 | check(to_camel_case, "UPPER_SNAKE_CASE", expect![["UpperSnakeCase"]]); | 151 | check(to_camel_case, "UPPER_SNAKE_CASE", expect![["UpperSnakeCase"]]); |
96 | check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]); | 152 | check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]); |
97 | check(to_camel_case, "name", expect![["Name"]]); | 153 | check(to_camel_case, "name", expect![["Name"]]); |
98 | } | 154 | } |
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 | } | ||
99 | } | 164 | } |