use pretty::RcDoc;
static KEYWORDS: [&str; 48] = [
"as", "break", "const", "continue", "else", "enum", "extern", "false", "fn", "for", "if",
"impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "static",
"struct", "trait", "true", "type", "unsafe", "use", "where", "while", "async", "await", "dyn",
"abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof", "unsized",
"virtual", "yield", "try", "gen",
];
static UNUSABLE_RAW_IDENTIFIERS: [&str; 5] = ["crate", "self", "Self", "super", "_"];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum IdentifierCase {
Snake,
UpperCamel,
}
pub(super) fn to_identifier_case(id: &str, case: IdentifierCase) -> (RcDoc<'_>, bool) {
if id.is_empty()
|| id.starts_with(|c: char| !c.is_ascii_alphabetic() && c != '_')
|| id.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_')
{
(RcDoc::text(format!("_{}_", candid::idl_hash(id))), true)
} else {
let processed = match case {
IdentifierCase::Snake => to_snake_case(id),
IdentifierCase::UpperCamel => to_upper_camel_case(id),
};
let modified = processed != id;
if KEYWORDS.contains(&processed.as_str()) {
(RcDoc::text(format!("r#{}", processed)), modified)
} else if UNUSABLE_RAW_IDENTIFIERS.contains(&processed.as_str()) {
(RcDoc::text(format!("{}_", processed)), true)
} else {
(RcDoc::text(processed), modified)
}
}
}
fn to_snake_case(s: &str) -> String {
let mut processed = String::new();
let mut prev_char_was_underscore = true; for (i, c) in s.chars().enumerate() {
if c.is_ascii_uppercase() {
if i > 0 && !prev_char_was_underscore {
processed.push('_');
}
processed.push(c.to_ascii_lowercase());
prev_char_was_underscore = false; } else {
processed.push(c);
prev_char_was_underscore = c == '_';
}
}
processed
}
fn to_upper_camel_case(s: &str) -> String {
let mut processed = String::new();
let mut is_leading_underscores = true;
let mut consecutive_underscores = 0;
for (i, c) in s.chars().enumerate() {
match c {
'_' => {
if i == s.len() - 1 {
processed.push_str(&"_".repeat(consecutive_underscores + 1));
} else {
consecutive_underscores += 1;
}
}
c => {
if is_leading_underscores {
processed.push_str(&"_".repeat(consecutive_underscores));
consecutive_underscores = 0;
is_leading_underscores = false;
processed.push(c.to_ascii_uppercase());
} else if consecutive_underscores > 0 {
consecutive_underscores = 0;
processed.push(c.to_ascii_uppercase());
} else {
processed.push(c);
}
}
}
}
processed
}
#[cfg(test)]
mod tests {
use super::{to_snake_case, to_upper_camel_case};
#[test]
fn test_to_snake_case() {
assert_eq!(to_snake_case("exampleName"), "example_name");
assert_eq!(to_snake_case("ExampleName"), "example_name");
assert_eq!(to_snake_case("example_name"), "example_name");
assert_eq!(to_snake_case("_"), "_");
assert_eq!(to_snake_case("_A"), "_a");
assert_eq!(to_snake_case("A_"), "a_");
assert_eq!(to_snake_case("_A_"), "_a_");
assert_eq!(to_snake_case("__A__"), "__a__");
assert_eq!(to_snake_case("__a__"), "__a__");
assert_eq!(to_snake_case("__A_B__"), "__a_b__");
assert_eq!(to_snake_case("ABC"), "a_b_c");
assert_eq!(to_snake_case("amount_e8s"), "amount_e8s");
}
#[test]
fn test_to_upper_camel_case() {
assert_eq!(to_upper_camel_case("example_name"), "ExampleName");
assert_eq!(to_upper_camel_case("ExampleName"), "ExampleName");
assert_eq!(to_upper_camel_case("exampleName"), "ExampleName");
assert_eq!(to_upper_camel_case("_"), "_");
assert_eq!(to_upper_camel_case("_A"), "_A");
assert_eq!(to_upper_camel_case("A_"), "A_");
assert_eq!(to_upper_camel_case("_A_"), "_A_");
assert_eq!(to_upper_camel_case("__A__"), "__A__");
assert_eq!(to_upper_camel_case("__a__"), "__A__");
assert_eq!(to_upper_camel_case("__A_B__"), "__AB__");
assert_eq!(to_upper_camel_case("__Aa___B__c__"), "__AaBC__");
}
}