mold_cli/utils/
naming.rs

1use convert_case::{Case, Casing};
2
3/// Convert a string to PascalCase (e.g., "user_name" → "UserName")
4pub fn to_pascal_case(s: &str) -> String {
5    s.to_case(Case::Pascal)
6}
7
8/// Convert a string to camelCase (e.g., "user_name" → "userName")
9pub fn to_camel_case(s: &str) -> String {
10    s.to_case(Case::Camel)
11}
12
13/// Convert a string to snake_case (e.g., "UserName" → "user_name")
14pub fn to_snake_case(s: &str) -> String {
15    s.to_case(Case::Snake)
16}
17
18/// Sanitize an identifier to be valid in most languages
19/// - Prefix with underscore if starts with a digit
20/// - Replace invalid characters with underscores
21pub fn sanitize_identifier(s: &str) -> String {
22    if s.is_empty() {
23        return "_empty".to_string();
24    }
25
26    let mut result = String::new();
27    let mut chars = s.chars().peekable();
28
29    // If starts with digit, prefix with underscore
30    if let Some(first) = chars.peek() {
31        if first.is_ascii_digit() {
32            result.push('_');
33        }
34    }
35
36    for c in chars {
37        if c.is_alphanumeric() || c == '_' {
38            result.push(c);
39        } else {
40            result.push('_');
41        }
42    }
43
44    result
45}
46
47/// Check if a string is a reserved word in TypeScript/JavaScript
48pub fn is_ts_reserved(s: &str) -> bool {
49    matches!(
50        s,
51        "break"
52            | "case"
53            | "catch"
54            | "class"
55            | "const"
56            | "continue"
57            | "debugger"
58            | "default"
59            | "delete"
60            | "do"
61            | "else"
62            | "enum"
63            | "export"
64            | "extends"
65            | "false"
66            | "finally"
67            | "for"
68            | "function"
69            | "if"
70            | "import"
71            | "in"
72            | "instanceof"
73            | "new"
74            | "null"
75            | "return"
76            | "super"
77            | "switch"
78            | "this"
79            | "throw"
80            | "true"
81            | "try"
82            | "typeof"
83            | "var"
84            | "void"
85            | "while"
86            | "with"
87            | "yield"
88            | "let"
89            | "static"
90            | "implements"
91            | "interface"
92            | "package"
93            | "private"
94            | "protected"
95            | "public"
96            | "type"
97    )
98}
99
100/// Check if a string is a reserved word in Prisma
101pub fn is_prisma_reserved(s: &str) -> bool {
102    matches!(
103        s,
104        "model" | "enum" | "type" | "datasource" | "generator" | "true" | "false" | "null"
105    )
106}
107
108/// Generate a type name from a path (e.g., ["user", "address"] → "UserAddress")
109pub fn path_to_type_name(path: &[String]) -> String {
110    path.iter()
111        .map(|s| to_pascal_case(s))
112        .collect::<Vec<_>>()
113        .join("")
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_to_pascal_case() {
122        assert_eq!(to_pascal_case("user_name"), "UserName");
123        assert_eq!(to_pascal_case("hello_world"), "HelloWorld");
124        assert_eq!(to_pascal_case("id"), "Id");
125    }
126
127    #[test]
128    fn test_to_camel_case() {
129        assert_eq!(to_camel_case("user_name"), "userName");
130        assert_eq!(to_camel_case("HelloWorld"), "helloWorld");
131    }
132
133    #[test]
134    fn test_sanitize_identifier() {
135        assert_eq!(sanitize_identifier("123key"), "_123key");
136        assert_eq!(sanitize_identifier("my-key"), "my_key");
137        assert_eq!(sanitize_identifier("valid_name"), "valid_name");
138        assert_eq!(sanitize_identifier(""), "_empty");
139    }
140
141    #[test]
142    fn test_path_to_type_name() {
143        assert_eq!(
144            path_to_type_name(&["user".to_string(), "address".to_string()]),
145            "UserAddress"
146        );
147        assert_eq!(
148            path_to_type_name(&["profile".to_string(), "contact".to_string()]),
149            "ProfileContact"
150        );
151    }
152}