use super::surfaces::{IdentifierContext, NameError, NameSurface};
use super::symbols::sanitize_symbol_component;
use crate::core::config::Language;
pub fn escape_identifier(lang: Language, name: &str, surface: NameSurface) -> String {
let context = match surface {
NameSurface::PublicHost => IdentifierContext::PublicMember,
NameSurface::Wire => IdentifierContext::Wire,
NameSurface::InternalRust => IdentifierContext::InternalRust,
NameSurface::Abi => IdentifierContext::AbiSymbol,
};
escape_identifier_for(lang, name, context)
}
pub fn escape_identifier_for(lang: Language, name: &str, context: IdentifierContext) -> String {
match context {
IdentifierContext::Wire => name.to_string(),
IdentifierContext::InternalRust => crate::core::keywords::rust_raw_ident(name),
IdentifierContext::AbiSymbol => sanitize_symbol_component(name),
IdentifierContext::SwiftSource => crate::core::keywords::swift_case_ident(name),
IdentifierContext::SwiftRustShim => crate::core::keywords::swift_ident(name),
IdentifierContext::KotlinSource => backtick_keyword(lang, name),
IdentifierContext::KotlinRustBridge => crate::core::keywords::kotlin_ident(name),
IdentifierContext::DartType => dart_type_identifier(name, None),
IdentifierContext::DartValue => dart_value_identifier(name),
IdentifierContext::DartTupleField => dart_tuple_field_identifier(name),
IdentifierContext::PublicType
| IdentifierContext::PublicMember
| IdentifierContext::PublicParameter
| IdentifierContext::PublicEnumVariant => match lang {
Language::Swift => crate::core::keywords::swift_case_ident(name),
Language::Zig => crate::core::keywords::zig_ident(name),
Language::Python => crate::core::keywords::python_ident(name),
Language::Kotlin | Language::KotlinAndroid => crate::core::keywords::kotlin_ident(name),
Language::Dart => match context {
IdentifierContext::PublicType => dart_type_identifier(name, None),
IdentifierContext::PublicMember
| IdentifierContext::PublicParameter
| IdentifierContext::PublicEnumVariant => dart_value_identifier(name),
_ => unreachable!("matched public identifier contexts only"),
},
Language::Gleam => crate::core::keywords::gleam_ident(name),
_ if is_reserved_keyword(lang, name) => format!("{name}_"),
_ => name.to_string(),
},
}
}
pub fn is_valid_identifier(lang: Language, name: &str, surface: NameSurface) -> bool {
if matches!(surface, NameSurface::Wire) {
return !name.is_empty();
}
match lang {
Language::Rust => crate::core::keywords::is_valid_rust_ident_chars(name.trim_start_matches("r#")),
Language::Swift => {
let unescaped = name.strip_prefix('`').and_then(|s| s.strip_suffix('`')).unwrap_or(name);
is_ascii_identifier(unescaped)
}
Language::Zig => is_ascii_identifier(name) && !name.starts_with(|ch: char| ch.is_ascii_digit()),
Language::Csharp => {
let unescaped = name.strip_prefix('@').unwrap_or(name);
is_ascii_identifier(unescaped)
}
_ => is_ascii_identifier(name),
}
}
pub fn validate_identifier(lang: Language, name: &str, context: IdentifierContext) -> Result<(), NameError> {
if is_valid_identifier_for(lang, name, context) {
Ok(())
} else {
Err(NameError::InvalidIdentifier {
lang,
context,
name: name.to_string(),
})
}
}
pub fn is_valid_identifier_for(lang: Language, name: &str, context: IdentifierContext) -> bool {
match context {
IdentifierContext::Wire => !name.is_empty(),
IdentifierContext::InternalRust => {
crate::core::keywords::is_valid_rust_ident_chars(name.trim_start_matches("r#"))
}
IdentifierContext::AbiSymbol => is_ascii_identifier(name),
IdentifierContext::SwiftSource => {
let unescaped = name.strip_prefix('`').and_then(|s| s.strip_suffix('`')).unwrap_or(name);
is_ascii_identifier(unescaped)
}
IdentifierContext::DartTupleField => name.starts_with("field") && is_ascii_identifier(name),
_ => is_valid_identifier(lang, name, NameSurface::PublicHost),
}
}
pub fn dart_type_identifier(name: &str, parent: Option<&str>) -> String {
if is_dart_core_type(name) || is_reserved_keyword(Language::Dart, name) {
match parent {
Some(parent) if !parent.is_empty() => format!("{parent}{name}"),
_ => format!("{name}Node"),
}
} else {
name.to_string()
}
}
pub fn dart_value_identifier(name: &str) -> String {
if name.chars().next().is_some_and(|ch| ch.is_ascii_digit()) {
return format!("field{name}");
}
crate::core::keywords::dart_ident(name)
}
pub fn dart_tuple_field_identifier(name: &str) -> String {
if name.chars().next().is_some_and(|ch| ch.is_ascii_digit()) {
format!("field{name}")
} else {
dart_value_identifier(name)
}
}
fn is_ascii_identifier(name: &str) -> bool {
if name.is_empty() {
return false;
}
let mut chars = name.chars();
let first = chars.next().expect("non-empty string has a first char");
if !first.is_ascii_alphabetic() && first != '_' {
return false;
}
chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
}
fn backtick_keyword(lang: Language, name: &str) -> String {
if is_reserved_keyword(lang, name) {
format!("`{name}`")
} else {
name.to_string()
}
}
fn is_dart_core_type(name: &str) -> bool {
const DART_CORE_TYPES: &[&str] = &[
"bool",
"double",
"Duration",
"Error",
"Exception",
"Future",
"int",
"Invocation",
"Iterable",
"Iterator",
"List",
"Map",
"MapEntry",
"Null",
"num",
"Object",
"Pattern",
"RegExp",
"RuneIterator",
"Runes",
"Set",
"Sink",
"StackTrace",
"Stream",
"String",
"StringBuffer",
"Symbol",
"Type",
"Uri",
];
DART_CORE_TYPES.contains(&name)
}
fn is_reserved_keyword(lang: Language, name: &str) -> bool {
match lang {
Language::Python => crate::core::keywords::PYTHON_KEYWORDS.contains(&name),
Language::Node | Language::Wasm => crate::core::keywords::JS_KEYWORDS.contains(&name),
Language::Ruby => crate::core::keywords::RUBY_KEYWORDS.contains(&name),
Language::Php => crate::core::keywords::PHP_KEYWORDS.contains(&name),
Language::Elixir => crate::core::keywords::ELIXIR_KEYWORDS.contains(&name),
Language::Go => crate::core::keywords::GO_KEYWORDS.contains(&name),
Language::Java | Language::Jni => crate::core::keywords::JAVA_KEYWORDS.contains(&name),
Language::Csharp => crate::core::keywords::CSHARP_KEYWORDS.contains(&name),
Language::R => crate::core::keywords::R_KEYWORDS.contains(&name),
Language::Kotlin | Language::KotlinAndroid => crate::core::keywords::KOTLIN_KEYWORDS.contains(&name),
Language::Swift => crate::core::keywords::SWIFT_KEYWORDS.contains(&name),
Language::Dart => crate::core::keywords::DART_KEYWORDS.contains(&name),
Language::Gleam => crate::core::keywords::GLEAM_KEYWORDS.contains(&name),
Language::Zig => crate::core::keywords::ZIG_KEYWORDS.contains(&name),
Language::Rust => crate::core::keywords::RUST_KEYWORDS.contains(&name),
Language::Ffi | Language::C => false,
}
}