aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs
diff options
context:
space:
mode:
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.rs194
1 files changed, 194 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 000000000..3800f2a6b
--- /dev/null
+++ b/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs
@@ -0,0 +1,194 @@
1//! Functions for string case manipulation, such as detecting the identifier case,
2//! and converting it into appropriate form.
3
4#[derive(Debug)]
5enum DetectedCase {
6 LowerCamelCase,
7 UpperCamelCase,
8 LowerSnakeCase,
9 UpperSnakeCase,
10 Unknown,
11}
12
13fn detect_case(ident: &str) -> DetectedCase {
14 let trimmed_ident = ident.trim_matches('_');
15 let first_lowercase = trimmed_ident.starts_with(|chr: char| chr.is_ascii_lowercase());
16 let mut has_lowercase = first_lowercase;
17 let mut has_uppercase = false;
18 let mut has_underscore = false;
19
20 for chr in trimmed_ident.chars() {
21 if chr == '_' {
22 has_underscore = true;
23 } else if chr.is_ascii_uppercase() {
24 has_uppercase = true;
25 } else if chr.is_ascii_lowercase() {
26 has_lowercase = true;
27 }
28 }
29
30 if has_uppercase {
31 if !has_lowercase {
32 DetectedCase::UpperSnakeCase
33 } else if !has_underscore {
34 if first_lowercase {
35 DetectedCase::LowerCamelCase
36 } else {
37 DetectedCase::UpperCamelCase
38 }
39 } else {
40 // It has uppercase, it has lowercase, it has underscore.
41 // No assumptions here
42 DetectedCase::Unknown
43 }
44 } else {
45 DetectedCase::LowerSnakeCase
46 }
47}
48
49/// Converts an identifier to an UpperCamelCase form.
50/// Returns `None` if the string is already is UpperCamelCase.
51pub fn to_camel_case(ident: &str) -> Option<String> {
52 let detected_case = detect_case(ident);
53
54 match detected_case {
55 DetectedCase::UpperCamelCase => return None,
56 DetectedCase::LowerCamelCase => {
57 let mut first_capitalized = false;
58 let output = ident
59 .chars()
60 .map(|chr| {
61 if !first_capitalized && chr.is_ascii_lowercase() {
62 first_capitalized = true;
63 chr.to_ascii_uppercase()
64 } else {
65 chr
66 }
67 })
68 .collect();
69 return Some(output);
70 }
71 _ => {}
72 }
73
74 let mut output = String::with_capacity(ident.len());
75
76 let mut capital_added = false;
77 for chr in ident.chars() {
78 if chr.is_alphabetic() {
79 if !capital_added {
80 output.push(chr.to_ascii_uppercase());
81 capital_added = true;
82 } else {
83 output.push(chr.to_ascii_lowercase());
84 }
85 } else if chr == '_' {
86 // Skip this character and make the next one capital.
87 capital_added = false;
88 } else {
89 // Put the characted as-is.
90 output.push(chr);
91 }
92 }
93
94 if output == ident {
95 // While we didn't detect the correct case at the beginning, there
96 // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE.
97 None
98 } else {
99 Some(output)
100 }
101}
102
103/// Converts an identifier to a lower_snake_case form.
104/// Returns `None` if the string is already in lower_snake_case.
105pub fn to_lower_snake_case(ident: &str) -> Option<String> {
106 // First, assume that it's UPPER_SNAKE_CASE.
107 match detect_case(ident) {
108 DetectedCase::LowerSnakeCase => return None,
109 DetectedCase::UpperSnakeCase => {
110 return Some(ident.chars().map(|chr| chr.to_ascii_lowercase()).collect())
111 }
112 _ => {}
113 }
114
115 // Otherwise, assume that it's CamelCase.
116 let lower_snake_case = stdx::to_lower_snake_case(ident);
117
118 if lower_snake_case == ident {
119 // While we didn't detect the correct case at the beginning, there
120 // may be special cases: e.g. `a` is both valid camelCase and snake_case.
121 None
122 } else {
123 Some(lower_snake_case)
124 }
125}
126
127/// Converts an identifier to an UPPER_SNAKE_CASE form.
128/// Returns `None` if the string is already is UPPER_SNAKE_CASE.
129pub fn to_upper_snake_case(ident: &str) -> Option<String> {
130 match detect_case(ident) {
131 DetectedCase::UpperSnakeCase => return None,
132 DetectedCase::LowerSnakeCase => {
133 return Some(ident.chars().map(|chr| chr.to_ascii_uppercase()).collect())
134 }
135 _ => {}
136 }
137
138 // Normalize the string from whatever form it's in currently, and then just make it uppercase.
139 let upper_snake_case = stdx::to_upper_snake_case(ident);
140
141 if upper_snake_case == ident {
142 // While we didn't detect the correct case at the beginning, there
143 // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE.
144 None
145 } else {
146 Some(upper_snake_case)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use expect_test::{expect, Expect};
154
155 fn check<F: Fn(&str) -> Option<String>>(fun: F, input: &str, expect: Expect) {
156 // `None` is translated to empty string, meaning that there is nothing to fix.
157 let output = fun(input).unwrap_or_default();
158
159 expect.assert_eq(&output);
160 }
161
162 #[test]
163 fn test_to_lower_snake_case() {
164 check(to_lower_snake_case, "lower_snake_case", expect![[""]]);
165 check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]);
166 check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]);
167 check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]);
168 check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]);
169 check(to_lower_snake_case, "a", expect![[""]]);
170 }
171
172 #[test]
173 fn test_to_camel_case() {
174 check(to_camel_case, "CamelCase", expect![[""]]);
175 check(to_camel_case, "CamelCase_", expect![[""]]);
176 check(to_camel_case, "_CamelCase", expect![[""]]);
177 check(to_camel_case, "lowerCamelCase", expect![["LowerCamelCase"]]);
178 check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]);
179 check(to_camel_case, "UPPER_SNAKE_CASE", expect![["UpperSnakeCase"]]);
180 check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]);
181 check(to_camel_case, "name", expect![["Name"]]);
182 check(to_camel_case, "A", expect![[""]]);
183 }
184
185 #[test]
186 fn test_to_upper_snake_case() {
187 check(to_upper_snake_case, "UPPER_SNAKE_CASE", expect![[""]]);
188 check(to_upper_snake_case, "lower_snake_case", expect![["LOWER_SNAKE_CASE"]]);
189 check(to_upper_snake_case, "Weird_Case", expect![["WEIRD_CASE"]]);
190 check(to_upper_snake_case, "CamelCase", expect![["CAMEL_CASE"]]);
191 check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]);
192 check(to_upper_snake_case, "A", expect![[""]]);
193 }
194}