hir_ty/diagnostics/decl_check/
case_conv.rs

1//! Functions for string case manipulation, such as detecting the identifier case,
2//! and converting it into appropriate form.
3
4// Code that was taken from rustc was taken at commit 89fdb30,
5// from file /compiler/rustc_lint/src/nonstandard_style.rs
6
7/// Converts an identifier to an UpperCamelCase form.
8/// Returns `None` if the string is already in UpperCamelCase.
9pub(crate) fn to_camel_case(ident: &str) -> Option<String> {
10    if is_camel_case(ident) {
11        return None;
12    }
13
14    Some(stdx::to_camel_case(ident))
15}
16
17/// Converts an identifier to a lower_snake_case form.
18/// Returns `None` if the string is already in lower_snake_case.
19pub(crate) fn to_lower_snake_case(ident: &str) -> Option<String> {
20    if is_lower_snake_case(ident) {
21        return None;
22    } else if is_upper_snake_case(ident) {
23        return Some(ident.to_lowercase());
24    }
25
26    Some(stdx::to_lower_snake_case(ident))
27}
28
29/// Converts an identifier to an UPPER_SNAKE_CASE form.
30/// Returns `None` if the string is already is UPPER_SNAKE_CASE.
31pub(crate) fn to_upper_snake_case(ident: &str) -> Option<String> {
32    if is_upper_snake_case(ident) {
33        return None;
34    } else if is_lower_snake_case(ident) {
35        return Some(ident.to_uppercase());
36    }
37
38    Some(stdx::to_upper_snake_case(ident))
39}
40
41// Taken from rustc.
42// Modified by replacing the use of unstable feature `array_windows`.
43fn is_camel_case(name: &str) -> bool {
44    let name = name.trim_matches('_');
45    if name.is_empty() {
46        return true;
47    }
48
49    let mut fst = None;
50    // start with a non-lowercase letter rather than non-uppercase
51    // ones (some scripts don't have a concept of upper/lowercase)
52    name.chars().next().is_none_or(|c| !c.is_lowercase())
53        && !name.contains("__")
54        && !name.chars().any(|snd| {
55            let ret = match fst {
56                None => false,
57                Some(fst) => {
58                    stdx::char_has_case(fst) && snd == '_' || stdx::char_has_case(snd) && fst == '_'
59                }
60            };
61            fst = Some(snd);
62
63            ret
64        })
65}
66
67fn is_lower_snake_case(ident: &str) -> bool {
68    is_snake_case(ident, char::is_uppercase)
69}
70
71fn is_upper_snake_case(ident: &str) -> bool {
72    is_snake_case(ident, char::is_lowercase)
73}
74
75// Taken from rustc.
76// Modified to allow checking for both upper and lower snake case.
77fn is_snake_case<F: Fn(char) -> bool>(ident: &str, wrong_case: F) -> bool {
78    if ident.is_empty() {
79        return true;
80    }
81    let ident = ident.trim_matches('_');
82
83    let mut allow_underscore = true;
84    ident.chars().all(|c| {
85        allow_underscore = match c {
86            '_' if !allow_underscore => return false,
87            '_' => false,
88            // It would be more obvious to check for the correct case,
89            // but some characters do not have a case.
90            c if !wrong_case(c) => true,
91            _ => return false,
92        };
93        true
94    })
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use expect_test::{Expect, expect};
101
102    fn check<F: Fn(&str) -> Option<String>>(fun: F, input: &str, expect: Expect) {
103        // `None` is translated to empty string, meaning that there is nothing to fix.
104        let output = fun(input).unwrap_or_default();
105
106        expect.assert_eq(&output);
107    }
108
109    #[test]
110    fn test_to_lower_snake_case() {
111        check(to_lower_snake_case, "lower_snake_case", expect![[""]]);
112        check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]);
113        check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]);
114        check(to_lower_snake_case, "UpperCamelCase", expect![["upper_camel_case"]]);
115        check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]);
116        check(to_lower_snake_case, "a", expect![[""]]);
117        check(to_lower_snake_case, "abc", expect![[""]]);
118        check(to_lower_snake_case, "foo__bar", expect![["foo_bar"]]);
119        check(to_lower_snake_case, "Δ", expect!["δ"]);
120    }
121
122    #[test]
123    fn test_to_camel_case() {
124        check(to_camel_case, "UpperCamelCase", expect![[""]]);
125        check(to_camel_case, "UpperCamelCase_", expect![[""]]);
126        check(to_camel_case, "_CamelCase", expect![[""]]);
127        check(to_camel_case, "lowerCamelCase", expect![["LowerCamelCase"]]);
128        check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]);
129        check(to_camel_case, "UPPER_SNAKE_CASE", expect![["UpperSnakeCase"]]);
130        check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]);
131        check(to_camel_case, "name", expect![["Name"]]);
132        check(to_camel_case, "A", expect![[""]]);
133        check(to_camel_case, "AABB", expect![[""]]);
134        // Taken from rustc: /compiler/rustc_lint/src/nonstandard_style/tests.rs
135        check(to_camel_case, "X86_64", expect![[""]]);
136        check(to_camel_case, "x86__64", expect![["X86_64"]]);
137        check(to_camel_case, "Abc_123", expect![["Abc123"]]);
138        check(to_camel_case, "A1_b2_c3", expect![["A1B2C3"]]);
139    }
140
141    #[test]
142    fn test_to_upper_snake_case() {
143        check(to_upper_snake_case, "UPPER_SNAKE_CASE", expect![[""]]);
144        check(to_upper_snake_case, "lower_snake_case", expect![["LOWER_SNAKE_CASE"]]);
145        check(to_upper_snake_case, "Weird_Case", expect![["WEIRD_CASE"]]);
146        check(to_upper_snake_case, "UpperCamelCase", expect![["UPPER_CAMEL_CASE"]]);
147        check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]);
148        check(to_upper_snake_case, "A", expect![[""]]);
149        check(to_upper_snake_case, "ABC", expect![[""]]);
150        check(to_upper_snake_case, "X86_64", expect![[""]]);
151        check(to_upper_snake_case, "FOO_BAr", expect![["FOO_BAR"]]);
152        check(to_upper_snake_case, "FOO__BAR", expect![["FOO_BAR"]]);
153        check(to_upper_snake_case, "ß", expect!["SS"]);
154    }
155}