use crate::String;
use once_cell::sync::Lazy;
use phf::phf_set;
use rustc_hash::FxHashMap;
use std::sync::RwLock;
pub static RESERVED_PROPS: phf::Set<&'static str> = phf_set! {
"", "key", "ref", "ref_for", "ref_key",
"onVnodeBeforeMount", "onVnodeMounted",
"onVnodeBeforeUpdate", "onVnodeUpdated",
"onVnodeBeforeUnmount", "onVnodeUnmounted"
};
pub static BUILTIN_TAGS: phf::Set<&'static str> = phf_set! {
"slot", "component"
};
pub static BUILTIN_DIRECTIVES: phf::Set<&'static str> = phf_set! {
"bind", "cloak", "else-if", "else", "for", "html", "if",
"model", "on", "once", "pre", "show", "slot", "text", "memo"
};
#[inline]
pub fn is_reserved_prop(key: &str) -> bool {
RESERVED_PROPS.contains(key)
}
#[inline]
pub fn is_builtin_tag(tag: &str) -> bool {
BUILTIN_TAGS.contains(tag)
}
#[inline]
pub fn is_builtin_directive(name: &str) -> bool {
BUILTIN_DIRECTIVES.contains(name)
}
#[inline]
pub fn is_on(key: &str) -> bool {
let bytes = key.as_bytes();
bytes.len() > 2 && bytes[0] == b'o' && bytes[1] == b'n' && (bytes[2] > 122 || bytes[2] < 97)
}
#[inline]
pub fn is_native_on(key: &str) -> bool {
let bytes = key.as_bytes();
bytes.len() > 2 && bytes[0] == b'o' && bytes[1] == b'n' && bytes[2] > 96 && bytes[2] < 123
}
#[inline]
pub fn is_model_listener(key: &str) -> bool {
key.starts_with("onUpdate:")
}
static CAMELIZE_CACHE: Lazy<RwLock<FxHashMap<String, String>>> =
Lazy::new(|| RwLock::new(FxHashMap::default()));
static HYPHENATE_CACHE: Lazy<RwLock<FxHashMap<String, String>>> =
Lazy::new(|| RwLock::new(FxHashMap::default()));
static CAPITALIZE_CACHE: Lazy<RwLock<FxHashMap<String, String>>> =
Lazy::new(|| RwLock::new(FxHashMap::default()));
pub fn camelize(s: &str) -> String {
{
let cache = CAMELIZE_CACHE.read().unwrap();
if let Some(cached) = cache.get(s) {
return cached.clone();
}
}
let result = camelize_uncached(s);
{
let mut cache = CAMELIZE_CACHE.write().unwrap();
cache.insert(String::from(s), result.clone());
}
result
}
fn camelize_uncached(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut capitalize_next = false;
for c in s.chars() {
if c == '-' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_ascii_uppercase());
capitalize_next = false;
} else {
result.push(c);
}
}
result
}
pub fn hyphenate(s: &str) -> String {
{
let cache = HYPHENATE_CACHE.read().unwrap();
if let Some(cached) = cache.get(s) {
return cached.clone();
}
}
let result = hyphenate_uncached(s);
{
let mut cache = HYPHENATE_CACHE.write().unwrap();
cache.insert(String::from(s), result.clone());
}
result
}
fn hyphenate_uncached(s: &str) -> String {
let mut result = String::with_capacity(s.len() + 4);
for (i, c) in s.chars().enumerate() {
if c.is_ascii_uppercase() && i > 0 {
result.push('-');
result.push(c.to_ascii_lowercase());
} else {
result.push(c);
}
}
result
}
pub fn capitalize(s: &str) -> String {
if s.is_empty() {
return String::new("");
}
{
let cache = CAPITALIZE_CACHE.read().unwrap();
if let Some(cached) = cache.get(s) {
return cached.clone();
}
}
let result = capitalize_uncached(s);
{
let mut cache = CAPITALIZE_CACHE.write().unwrap();
cache.insert(String::from(s), result.clone());
}
result
}
fn capitalize_uncached(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(""),
Some(first) => {
let mut result = String::with_capacity(s.len());
result.push(first.to_ascii_uppercase());
result.extend(chars);
result
}
}
}
pub fn to_handler_key(s: &str) -> String {
if s.is_empty() {
return String::new("");
}
let mut result = String::with_capacity(s.len() + 2);
result.push_str("on");
result.push_str(&capitalize(s));
result
}
pub fn get_modifier_prop_name(name: &str) -> String {
let base = if name == "modelValue" || name == "model-value" {
"model"
} else {
name
};
let suffix = if name == "model" { "$" } else { "" };
crate::cstr!("{base}Modifiers{suffix}")
}
pub fn is_simple_identifier(s: &str) -> bool {
if s.is_empty() {
return false;
}
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_alphabetic() || c == '_' || c == '$' => {}
_ => return false,
}
chars.all(|c| c.is_alphanumeric() || c == '_' || c == '$')
}
pub fn gen_props_access_exp(name: &str) -> String {
if is_simple_identifier(name) {
crate::cstr!("__props.{name}")
} else {
let key = serde_json::to_string(name).unwrap();
crate::cstr!("__props[{key}]")
}
}
pub fn can_set_value_directly(tag_name: &str) -> bool {
tag_name != "PROGRESS" && !tag_name.contains('-')
}
#[cfg(test)]
mod tests {
use super::{camelize, capitalize, hyphenate, is_on, is_simple_identifier, to_handler_key};
#[test]
fn test_is_on() {
assert!(is_on("onClick"));
assert!(is_on("onUpdate"));
assert!(!is_on("onclick"));
assert!(!is_on("on"));
}
#[test]
fn test_camelize() {
assert_eq!(camelize("foo-bar"), "fooBar");
assert_eq!(camelize("foo-bar-baz"), "fooBarBaz");
assert_eq!(camelize("foo"), "foo");
}
#[test]
fn test_hyphenate() {
assert_eq!(hyphenate("fooBar"), "foo-bar");
assert_eq!(hyphenate("fooBarBaz"), "foo-bar-baz");
assert_eq!(hyphenate("foo"), "foo");
}
#[test]
fn test_capitalize() {
assert_eq!(capitalize("foo"), "Foo");
assert_eq!(capitalize(""), "");
assert_eq!(capitalize("Foo"), "Foo");
}
#[test]
fn test_to_handler_key() {
assert_eq!(to_handler_key("click"), "onClick");
assert_eq!(to_handler_key("update"), "onUpdate");
assert_eq!(to_handler_key(""), "");
}
#[test]
fn test_is_simple_identifier() {
assert!(is_simple_identifier("foo"));
assert!(is_simple_identifier("_foo"));
assert!(is_simple_identifier("$foo"));
assert!(is_simple_identifier("foo123"));
assert!(!is_simple_identifier("123foo"));
assert!(!is_simple_identifier("foo-bar"));
assert!(!is_simple_identifier(""));
}
}